GithubHelp home page GithubHelp logo

mytardisbf's Introduction

mytardisbf

Documentation Status

MyTardis Bioformats Preview Image and Metadata Extraction

MyTardisBF is an App for the [MyTardis](https://github.com/mytardis/mytardis) data management platform that provides tools for extracting preview images and metadata from common microscopy formats. The App uses the [Bioformats](http://www.openmicroscopy.org/site/products/bio-formats) library via [python-bioformats](https://github.com/CellProfiler/python-bioformats) and supports the extraction of preview images and limited metadata information for all images [supported by Bioformats](http://www.openmicroscopy.org/site/support/bio-formats5.1/supported-formats.html). Additionally, the filter supports extraction of metadata and preview images for each series in multi-series image formats like Leica LIF files.

Bioformats Metadata for ND2 file

Installation

These instructions assume that you have installed and configured MyTardis. If you haven't please follow the instructions in the latest [MyTardis documentation](https://mytardis.readthedocs.io/en/develop/admin/install.html). First install numpy into your MyTardis python environment:

pip install -U numpy

Then install the latest relesase of the mytardisbf app:

pip install -e git+https://github.com/keithschulze/[email protected]#egg=mytardisbf

or for the latest development version:

pip install -e git+https://github.com/keithschulze/mytardisbf.git#egg=mytardisbf

If you using a virtualenv, remember to activate it first.

Add the following to your MyTardis settings file eg. /path/to/mytardis/tardis/settings.py:

Add mytardisbf to your INSTALLED_APPS:

INSTALLED_APPS = INSTALLED_APPS + (
    'mytardisbf',
)

Enable the filter middleware for all actions:

MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + ('tardis.tardis_portal.filters.FilterInitMiddleware',)
FILTER_MIDDLEWARE = (("tardis.tardis_portal.filters", "FilterInitMiddleware"),)

Then add the definition for the MyTardisBF filter:

POST_SAVE_FILTERS = [
   ("mytardisbf.filters.metadata_filter.make_filter",
   ["BioformatsMetadata", "http://tardis.edu.au/schemas/bioformats/2"]),
]

The Bioformats filter can be run outside of the default celery queue. To specify a different celery queue, add the following to MyTardis's settings.py:

BIOFORMATS_QUEUE = "nameofqueue"

where nameofqueue is the name of the celery queue in which you want to run the filter.

The maximum heap space use by the JVM in each celery worker can also be configured:

MTBF_MAX_HEAP_SIZE = "1G"

Developers

Get the source:

git clone https://github.com/keithschulze/mytardisbf.git
cd mytardisbf

To test in with MyTardis:

# Activate your MyTardis virtualenv
pip install -e .

Configuration of the App and Filters in MyTardis is the same as decribed above for normal Installation.

Testing

Note that many of the original unittests are skipped because the test image files were large and therefore not included in this repository. Nosetests is required: pip install nose

In order to run unittests, you will need to enable the javabridge nosetest plugin.

Add the following to setup.cfg:

[nosetests]
with-javabridge = True
classpath = /path/to/loci_tools.jar

Note: loci_tools.jar can be downloade from the [python-bioformats](https://github.com/CellProfiler/python-bioformats/blob/master/bioformats/jars/loci_tools.jar) github page or in the python-bioformats package inside with virtualenv where you installed it (this can be tricky to locate).

mytardisbf's People

Contributors

keithschulze avatar wettenhj avatar jameswettenhall avatar

Stargazers

NOPMLLC avatar

Watchers

Josh Moore avatar  avatar James Cloos avatar  avatar  avatar NOPMLLC avatar

mytardisbf's Issues

Make JVM max heaps space configurable

The amount of heap space available to the jvm is specified at startup and javabridge allows you to specify this. Currently this hard-coded to 4G, but really it should be configurable because huge files may require more than this.

Image Preview fails for particular tifs

For one datafile ("01_MCB02_Fig1B_CowgCpX.tif"), the preview image generation failed with the following exception:

Exception in thread "Thread-0" org.mozilla.javascript.WrappedException: Wrapped java.lang.NullPointerException (<java-python-bridge>#11)
        at org.mozilla.javascript.Context.throwAsScriptRuntimeEx(Context.java:1754)
        at org.mozilla.javascript.MemberBox.invoke(MemberBox.java:148)
        at org.mozilla.javascript.NativeJavaMethod.call(NativeJavaMethod.java:225)
        at org.mozilla.javascript.optimizer.OptRuntime.call1(OptRuntime.java:32)
        at org.mozilla.javascript.gen._java_python_bridge__2._c_script_0(<java-python-bridge>:11)
        at org.mozilla.javascript.gen._java_python_bridge__2.call(<java-python-bridge>)
        at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:394)
        at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3091)
        at org.mozilla.javascript.gen._java_python_bridge__2.call(<java-python-bridge>)
        at org.mozilla.javascript.gen._java_python_bridge__2.exec(<java-python-bridge>)
        at org.mozilla.javascript.Context.evaluateString(Context.java:1079)
Caused by: java.lang.NullPointerException
        at loci.formats.in.NDPISReader.initFile(NDPISReader.java:173)
        at loci.formats.FormatReader.setId(FormatReader.java:1317)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:622)
        at org.mozilla.javascript.MemberBox.invoke(MemberBox.java:126)
        ... 9 more

Use MyTardis StorageBox interface for storing metadata

Issue #11 highlights issues with using global settings to set path for metadata storage. MyTardis now uses concepts of StorageBox to do filesystem writes and we should probably use this for writing metadata files. This issue is to outline what required to move to StorageBox based architecture.

  • Gather information about how StorageBox's could be used to store metadata outputs

Preview image written to datafile's storage box, ignoring settings.METADATA_STORE_PATH

The name "output_rel_path" here: https://github.com/keithschulze/mytardisbf/blob/master/mytardisbf/filters/filter_utils.py#L188
implies that it is intended to be a relative path, but the first path component used in os.path.join is os.path.dirname(input_file_path), which is an absolute path. This means that the next os.path.join, involving settings.METADATA_STORE_PATH doesn't work, because from the Python docs, "If a component is an absolute path, all previous components are thrown away and joining continues from the absolute path component": https://docs.python.org/2/library/os.path.html#os.path.join

There are a few reasons for having a separate METADATA_STORE_PATH base directory, separate from the datafile's storage box, for example the datafile could be in a storage location whose files get moved to tape after a while, whereas preview images always need to be available on disk. Also, when
uploading via MyData, each datafile is initially stored in a temporary storage box. The current mytardisbf code could write the preview image to a temporary storage box instead of a permanent storage box, which we don't want.

Looking at the other filters on Store.Monash, the first component of the relative path (the equivalent of your "output_rel_path") is os.path.dirname(urlparse.urlparse(dfo.uri).path), which in most cases is equivalent to os.path.dirname(dfo.uri), unless you're expecting DFO URIs like "file:///path/to/datafile".

So I think this could be fixed simply by replacing os.path.dirname(input_file_path) with os.path.dirname(dfo.uri) or os.path.dirname(urlparse.urlparse(dfo.uri).path).

This is an interim fix. Eventually, we should stop using the global settings.METADATA_STORE_PATH and use MyTardis's StorageBox interfaces every time we write to the filesystem.

bioformats schema not installed

@whettenhj reported that bioformats.json schema is not installed correctly when MyTardis starts up. Appears that the fixtures package is not found. As @whettenhj points out:
It seems that "include_package_data=True" in setup.py is not 100% reliable for installing Python modules from source, at least when using Python 2.7.3 (the system Python version on Store.Monash-Test's Ubuntu 12). Here's a StackOverflow discussion on this matter: http://stackoverflow.com/questions/7522250/how-to-include-package-data-with-setuptools-distribute

Suggest pinning javabridge dependency to 1.0.14 in setup.py

pip install javabridge==1.0.14 works fine for me,
but pip install javabridge fails with the error below.

This is after having run pip install numpy >= 1.9 (which installed numpy==1.13.3) because of the issue with installing numpy and scipy at the same time.

I have javac 1.7.0_151 installed from this package:

$ dpkg -l | grep jdk
ii  openjdk-7-jdk:amd64                  7u151-2.6.11-0ubuntu1.14.04.1

Should probably raise this as a "javabridge" issue, but pinning mytardisbf's dependency to 1.0.14 seems like a good workaround for now.

Collecting javabridge
  Using cached javabridge-1.0.15.tar.gz
Requirement already satisfied: numpy in ./virtualenvs/mytardis/lib/python2.7/site-packages (from javabridge)
Building wheels for collected packages: javabridge
  Running setup.py bdist_wheel for javabridge ... error
  Complete output from command /home/mytardis/virtualenvs/mytardis/bin/python -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-LGEeSa/javabridge/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" bdist_wheel -d /tmp/tmph0prcBpip-wheel- --python-tag cp27:
  running bdist_wheel
  running build
  running build_py
  creating build
  creating build/lib.linux-x86_64-2.7
  creating build/lib.linux-x86_64-2.7/javabridge
  copying javabridge/noseplugin.py -> build/lib.linux-x86_64-2.7/javabridge
  copying javabridge/locate.py -> build/lib.linux-x86_64-2.7/javabridge
  copying javabridge/__init__.py -> build/lib.linux-x86_64-2.7/javabridge
  copying javabridge/jutil.py -> build/lib.linux-x86_64-2.7/javabridge
  copying javabridge/wrappers.py -> build/lib.linux-x86_64-2.7/javabridge
  copying javabridge/_version.py -> build/lib.linux-x86_64-2.7/javabridge
  creating build/lib.linux-x86_64-2.7/javabridge/tests
  copying javabridge/tests/test_cpython.py -> build/lib.linux-x86_64-2.7/javabridge/tests
  copying javabridge/tests/test_javabridge.py -> build/lib.linux-x86_64-2.7/javabridge/tests
  copying javabridge/tests/__init__.py -> build/lib.linux-x86_64-2.7/javabridge/tests
  copying javabridge/tests/test_wrappers.py -> build/lib.linux-x86_64-2.7/javabridge/tests
  copying javabridge/tests/test_jutil.py -> build/lib.linux-x86_64-2.7/javabridge/tests
  creating build/lib.linux-x86_64-2.7/javabridge/jars
  copying javabridge/jars/rhino-1.7R4.jar -> build/lib.linux-x86_64-2.7/javabridge/jars
  copying javabridge/jars/test.jar -> build/lib.linux-x86_64-2.7/javabridge/jars
  copying javabridge/jars/runnablequeue.jar -> build/lib.linux-x86_64-2.7/javabridge/jars
  copying javabridge/jars/cpython.jar -> build/lib.linux-x86_64-2.7/javabridge/jars
  copying javabridge/jars/libjava2cpython.so -> build/lib.linux-x86_64-2.7/javabridge/jars
  running build_ext
  javac -source 6 -target 6 /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/runnablequeue/RunnableQueue.java
  warning: [options] bootstrap class path not set in conjunction with -source 1.6
  1 warning
  javac -source 6 -target 6 /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/javabridge/test/RealRect.java
  warning: [options] bootstrap class path not set in conjunction with -source 1.6
  1 warning
  javac -source 6 -target 6 /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/javabridge/CPython.java /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/javabridge/CPythonInvocationHandler.java
  warning: [options] bootstrap class path not set in conjunction with -source 1.6
  Note: /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/javabridge/CPythonInvocationHandler.java uses unchecked or unsafe operations.
  Note: Recompile with -Xlint:unchecked for details.
  1 warning
  building 'javabridge._javabridge' extension
  creating build/temp.linux-x86_64-2.7
  x86_64-linux-gnu-gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include -I/usr/lib/jvm/java-7-openjdk-amd64/jre/include -I/usr/lib/jvm/java-7-openjdk-amd64/jre/include/linux -I/usr/include/python2.7 -c _javabridge.c -o build/temp.linux-x86_64-2.7/_javabridge.o
  In file included from /home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1809:0,
                   from /home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:18,
                   from /home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,
                   from _javabridge.c:435:
  /home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:15:2: warning: #warning "Using deprecated NumPy API, disable it by " "#defining NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" [-Wcpp]
   #warning "Using deprecated NumPy API, disable it by " \
    ^
  _javabridge.c:437:17: fatal error: jni.h: No such file or directory
   #include "jni.h"
                   ^
  compilation terminated.
  error: command 'x86_64-linux-gnu-gcc' failed with exit status 1
  
  ----------------------------------------
  Failed building wheel for javabridge
  Running setup.py clean for javabridge
Failed to build javabridge
Installing collected packages: javabridge
  Running setup.py install for javabridge ... error
    Complete output from command /home/mytardis/virtualenvs/mytardis/bin/python -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-LGEeSa/javabridge/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record /tmp/pip-iddXPx-record/install-record.txt --single-version-externally-managed --compile --install-headers /home/mytardis/virtualenvs/mytardis/include/site/python2.7/javabridge:
    running install
    running build
    running build_py
    creating build
    creating build/lib.linux-x86_64-2.7
    creating build/lib.linux-x86_64-2.7/javabridge
    copying javabridge/noseplugin.py -> build/lib.linux-x86_64-2.7/javabridge
    copying javabridge/locate.py -> build/lib.linux-x86_64-2.7/javabridge
    copying javabridge/__init__.py -> build/lib.linux-x86_64-2.7/javabridge
    copying javabridge/jutil.py -> build/lib.linux-x86_64-2.7/javabridge
    copying javabridge/wrappers.py -> build/lib.linux-x86_64-2.7/javabridge
    copying javabridge/_version.py -> build/lib.linux-x86_64-2.7/javabridge
    creating build/lib.linux-x86_64-2.7/javabridge/tests
    copying javabridge/tests/test_cpython.py -> build/lib.linux-x86_64-2.7/javabridge/tests
    copying javabridge/tests/test_javabridge.py -> build/lib.linux-x86_64-2.7/javabridge/tests
    copying javabridge/tests/__init__.py -> build/lib.linux-x86_64-2.7/javabridge/tests
    copying javabridge/tests/test_wrappers.py -> build/lib.linux-x86_64-2.7/javabridge/tests
    copying javabridge/tests/test_jutil.py -> build/lib.linux-x86_64-2.7/javabridge/tests
    creating build/lib.linux-x86_64-2.7/javabridge/jars
    copying javabridge/jars/rhino-1.7R4.jar -> build/lib.linux-x86_64-2.7/javabridge/jars
    copying javabridge/jars/test.jar -> build/lib.linux-x86_64-2.7/javabridge/jars
    copying javabridge/jars/runnablequeue.jar -> build/lib.linux-x86_64-2.7/javabridge/jars
    copying javabridge/jars/cpython.jar -> build/lib.linux-x86_64-2.7/javabridge/jars
    copying javabridge/jars/libjava2cpython.so -> build/lib.linux-x86_64-2.7/javabridge/jars
    running build_ext
    javac -source 6 -target 6 /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/runnablequeue/RunnableQueue.java
    warning: [options] bootstrap class path not set in conjunction with -source 1.6
    1 warning
    javac -source 6 -target 6 /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/javabridge/test/RealRect.java
    warning: [options] bootstrap class path not set in conjunction with -source 1.6
    1 warning
    javac -source 6 -target 6 /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/javabridge/CPython.java /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/javabridge/CPythonInvocationHandler.java
    warning: [options] bootstrap class path not set in conjunction with -source 1.6
    Note: /tmp/pip-build-LGEeSa/javabridge/java/org/cellprofiler/javabridge/CPythonInvocationHandler.java uses unchecked or unsafe operations.
    Note: Recompile with -Xlint:unchecked for details.
    1 warning
    building 'javabridge._javabridge' extension
    creating build/temp.linux-x86_64-2.7
    x86_64-linux-gnu-gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include -I/usr/lib/jvm/java-7-openjdk-amd64/jre/include -I/usr/lib/jvm/java-7-openjdk-amd64/jre/include/linux -I/usr/include/python2.7 -c _javabridge.c -o build/temp.linux-x86_64-2.7/_javabridge.o
    In file included from /home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1809:0,
                     from /home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:18,
                     from /home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,
                     from _javabridge.c:435:
    /home/mytardis/virtualenvs/mytardis/local/lib/python2.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:15:2: warning: #warning "Using deprecated NumPy API, disable it by " "#defining NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION" [-Wcpp]
     #warning "Using deprecated NumPy API, disable it by " \
      ^
    _javabridge.c:437:17: fatal error: jni.h: No such file or directory
     #include "jni.h"
                     ^
    compilation terminated.
    error: command 'x86_64-linux-gnu-gcc' failed with exit status 1

Metadata extraction tied to specific version of OME spec

Current version of the OME metadata extraction filter does queries of the OME XML using a specific OME namespace version. The OME spec was recently updated and recent versions of the LOCI bioformats library, included in python-bioformats, extracts OME metadata that is compliant with latest OME spec (i.e., uses most recent namespace). Hence this prevents us from upgrading to latest python-bioformats because metadata filter will not longer work.

Bioformats exceptions for non OME-XML Files

The logged messages look like this:

[2016-03-08 15:40:52,075: ERROR/Worker-2] Metadata extraction failed for:

/mnt/sonas/cryoem/market/store.monash/MYTARDIS_STAGING/Yibin_0038-25549/Images-Disc1/GridSquare_1221538/Data/FoilHole_1223696_Data_1224843_1224844_20160302_1606.xml

[2016-03-08 15:40:52,529: ERROR/Worker-4] Metadata extraction failed for: /mnt/sonas/cryoem/market/store.monash/MYTARDIS_STAGING/Yibin_0038-25549/Images-Disc1/GridSquare_1221538/Data/FoilHole_1223696_Data_1224843_1224844_20160302_1606_frames.xml

[2016-03-08 15:40:55,377: ERROR/Worker-3] Metadata extraction failed for: /mnt/sonas/cryoem/market/store.monash/MYTARDIS_STAGING/Yibin_0038-25549/Images-Disc1/GridSquare_1221538/Data/FoilHole_1223696_Data_1224843_1224844_20160302_1606_frames.xml

And each traceback looks like this:

Exception in thread "Thread-0" org.mozilla.javascript.WrappedException: Wrapped java.lang.NullPointerException (#11)
at org.mozilla.javascript.Context.throwAsScriptRuntimeEx(Context.java:1754)
at org.mozilla.javascript.MemberBox.invoke(MemberBox.java:148)
at org.mozilla.javascript.NativeJavaMethod.call(NativeJavaMethod.java:225)
at org.mozilla.javascript.optimizer.OptRuntime.call1(OptRuntime.java:32)
at org.mozilla.javascript.gen._java_python_bridge__434._c_script_0(:11)
at org.mozilla.javascript.gen._java_python_bridge__434.call()
at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:394)
at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3091)
at org.mozilla.javascript.gen._java_python_bridge__434.call()
at org.mozilla.javascript.gen._java_python_bridge__434.exec()
at org.mozilla.javascript.Context.evaluateString(Context.java:1079)
Caused by: java.lang.NullPointerException
at loci.formats.in.NDPISReader.initFile(NDPISReader.java:173)
at loci.formats.FormatReader.setId(FormatReader.java:1317)
at sun.reflect.GeneratedMethodAccessor68.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.mozilla.javascript.MemberBox.invoke(MemberBox.java:126)
... 9 more

No users have reported problems relating to this, but Store.Monash's "filters" Celery queue grew very large recently, possibly due to Celery tasks aborting uncleanly, leading to stalling of task processing.

Fresh database won't migrate with app installed

@dean-taylor encountered this issue when setting up a new system in an automated environment.
When migrating the database with the app installed the loading of the app happens before the migration. However, in apps.py, there is a query, which hits a database that's not migrated yet and it fails with table not found.
You could wrap it in try: except django.db.utils.ProgrammingError: maybe?
Then it would continue the migration and set up the schema when the server starts

Numpy required for javabridge install

Automatic installation of javabridge fails if and up-to-date version of numpy is not install. Include instructions to install numpy first before attempting install mytardisbf.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.