.. _ome-files-cpp-tutorial:

Tutorial
========


Metadata
--------

OME-Files supports several different classes of metadata, from very
basic information about the image dimensions and pixel type to
detailed information about the acquisition hardware and experimental
parameters.  From simplest to most complex, these are:

Core metadata
  Basic information describing an individual 5D image (series),
  including dimension sizes, dimension order and pixel type

Original metadata
  Key-value pairs describing metadata from the original file format
  for the image.  Two forms exist: global metadata for an entire
  dataset (image collection) and series metadata for an individual 5D
  image

Metadata store
  A container for all image metadata providing interfaces to get and
  set individual metadata values.  This is a superset of the core and
  original metadata content (it can represent all values contained
  within the core and original metadata).  It is an alternative
  representation of the OME-XML data model objects, and is used by the
  OME-Files reader and writer interfaces.

OME-XML data model objects
  The abstract OME-XML data model is realized as a collection of
  *model objects*.  Classes are generated from the elements of the
  OME-XML data model schema, and a tree of the model objects acts as a
  representation of the OME data model which may be modified and
  manipulated.  The model objects may be created from an OME-XML text
  document, and vice versa.

For the simplest cases of reading and writing image data, the core
metadata interface will likely be sufficient.  If specific individual
parameters from the original file format are needed, then original
metadata may also be useful.  For more advanced processing and
rendering, the metadata store should be the next source of
information, for example to get information about the image scale,
stage position, instrument setup including light sources, light paths,
detectors etc., and access to plate/well information, regions of
interest etc.  Direct access to the OME-XML data model objects is an
alternative to the metadata store, but is more difficult to use;
certain modifications to the data model may only be made via direct
access to the model objects, otherwise the higher-level metadata store
interface should be preferred.

The header file :doxygen:`ome/files/MetadataTools.h
<MetadataTools_8h_source.html>` provides several convenience functions
to work with and manipulate the various forms of metadata, including
conversion of Core metadata to and from a metadata store.

Core metadata
^^^^^^^^^^^^^

Core metadata is accessible through the getter methods in the
:cpp:class:`FormatReader` interface.  These operate on the *current*
series, set using the :cpp:func:`setSeries` method.  The
:cpp:class:`CoreMetadata` objects are also accessible directly using
the :cpp:class:`getCoreMetadataList` method.  The
:cpp:class:`FormatReader` interface should be preferred; the objects
themselves are more of an implementation detail at present.

.. literalinclude:: examples/metadata-formatreader.cpp
   :language: cpp
   :start-after: read-example-start
   :end-before: read-example-end

If implementing a reader, it is fairly typical to set the basic image
metadata in :cpp:class:`CoreMetadata` objects, and then use the
:cpp:func:`fillMetadata` function in
:doxygen:`ome/files/MetadataTools.h
<MetadataTools_8h_source.html>` to fill the reader's metadata store
with this information, before filling the metadata store with
additional (non-core) metadata as required.  When writing an image, a
metadata store is required in order to provide the writer with all the
metadata needed to write an image.  If the metadata store was not
already obtained from a reader, :cpp:func:`fillMetadata` may also be
used in this situation to create a suitable metadata store:

.. literalinclude:: examples/metadata-formatwriter.cpp
   :language: cpp
   :start-after: write-example-start
   :end-before: write-example-end

Full example source: :download:`metadata-formatreader.cpp
<examples/metadata-formatreader.cpp>`,
:download:`metadata-formatreader.cpp
<examples/metadata-formatwriter.cpp>`

.. seealso::

  - :doxygen:`CoreMetadata <classome_1_1files_1_1CoreMetadata.html>`
  - :doxygen:`FormatReader <classome_1_1files_1_1FormatReader.html>`


Original metadata
^^^^^^^^^^^^^^^^^

Original metadata is stored in two forms: in a
:cpp:class:`MetadataMap` which is accessible through the
:cpp:class:`FormatReader` interface, which offers access to individual
keys and the whole map for both global and series metadata.  It is
also accessible using the metadata store; original metadata is stored
as an :cpp:class:`XMLAnnotation`.  The following example demonstrates
access to the global and series metadata using the
:cpp:class:`FormatReader` interface to get access to the maps:

.. literalinclude:: examples/metadata-formatreader.cpp
   :language: cpp
   :start-after: original-example-start
   :end-before: original-example-end

It would also be possible to use :cpp:func:`getMetadataValue` and
:cpp:func:`getSeriesMetadataValue` to obtain values for individual
keys.  Note that the :cpp:class:`MetadataMap` values can be scalar
values or lists of scalar values; call the :cpp:func:`flatten` method
to split the lists into separate key-value pairs with a numbered
suffix.

Full example source: :download:`metadata-formatreader.cpp <examples/metadata-formatreader.cpp>`

.. seealso::

  - :doxygen:`MetadataMap <classome_1_1files_1_1MetadataMap.html>`
  - :doxygen:`FormatReader <classome_1_1files_1_1FormatReader.html>`
  - :doxygen:`OriginalMetadataAnnotation <classome_1_1xml_1_1model_1_1OriginalMetadataAnnotation.html>`

Metadata store
^^^^^^^^^^^^^^

Access to metadata is provided via the :cpp:class:`MetadataStore` and
:cpp:class:`MetadataRetrieve` interfaces.  These provide setters and
getters, respectively, to store and retrieve metadata to and from an
underlying abstract metadata store.  The primary store is the
:cpp:class:`OMEXMLMetadata` which stores the metadata in OME-XML data
model objects (see below), and implements both interfaces.  However,
other storage classes are available, and may be used to filter the
stored metadata, combine different stores, or do nothing at all.
Additional storage backends could also be implemented, for example to
allow metadata retrieval from a relational database, or JSON/YAML.

When using :cpp:class:`OMEXMLMetadata` the convenience function
:cpp:func:`createOMEXMLMetadata` is the recommended method for
creating a new instance and then filling it with the content from an
OME-XML document.  This is overloaded to allow the OME-XML to be
obtained from various sources.  For example, from a file:

.. literalinclude:: examples/metadata-io.cpp
   :language: cpp
   :start-after: read-file-example-start
   :end-before: read-file-example-end

Alternatively from a DOM tree:

.. literalinclude:: examples/metadata-io.cpp
   :language: cpp
   :start-after: read-dom-example-start
   :end-before: read-dom-example-end

The convenience function :cpp:func:`getOMEXML` may be used to reverse
the process, i.e. obtain an OME-XML document from the store.  Note the
use of :cpp:func:`convert`.  Only the :cpp:class:`OMEXMLMetadata`
class can dump an OME-XML document, therefore if the source of the
data is another class implementing the :cpp:class:`MetadataRetrieve`
interface, the stored data will need to be copied into an
:cpp:class:`OMEXMLMetadata` instance first.

.. literalinclude:: examples/metadata-io.cpp
   :language: cpp
   :start-after: write-example-start
   :end-before: write-example-end

Conceptually, the metadata store contains lists of objects, accessed
by index (insertion order).  In the example below,
:cpp:func:`getImageCount` method is used to find the number of images.
This is then used to safely loop through each of the available images.
Each of the :cpp:func:`getPixelsSizeA` methods takes the image index
as its only argument.  Internally, this is used to find the
:cpp:class:`Image` model object for the specified index, and then call
the :cpp:func:`getSizeA` method on that object and return the result.
Since objects can contain other objects, some accessor methods require
the use of more than one index.  For example, an :cpp:class:`Image`
object can contain multiple :cpp:class:`Plane` objects.  Similar to
the above example, there is a :cpp:func:`getPlaneCount` method,
however since it is contained by an :cpp:class:`Image` it has an
additional image index argument to get the plane count for the
specified image.  Likewise its accessors such as
:cpp:func:`getPlaneTheZ` take two arguments, the image index and
the plane index.  Internally, these indices will be used to find the
:cpp:class:`Image`, then the :cpp:class:`Plane`, and then call
:cpp:func:`getTheZ`.  When using the :cpp:class:`MetadataRetrieve`
interface with an :cpp:class:`OMEXMLMetadata` store, the methods are
simply a shorthand for navigating through the tree of model objects.

.. literalinclude:: examples/metadata-io.cpp
   :language: cpp
   :start-after: query-example-start
   :end-before: query-example-end

The methods for storing data using the :cpp:class:`MetadataStore`
interface are similar.  The set methods use the same indices as the
get methods, with the value to set as an additional initial argument.
The following example demonstrates how to update dimension sizes for
images in the store:

.. literalinclude:: examples/metadata-io.cpp
   :language: cpp
   :start-after: update-example-start
   :end-before: update-example-end

When adding new objects to the store, as opposed to updating existing
ones, some additional considerations apply.  An new object is added to
the store if the object corresponding to an index does not exist and
the index is the current object count (i.e. one past the end of the
last valid index).  Note that for data model objects with a
:cpp:func:`setID` method, this method alone will trigger insertion and
must be called first, before any other methods which modify the
object.  The following example demonstrates the addition of a new
:cpp:class:`Image` to the store, plus contained :cpp:class:`Plane`
objects.

.. literalinclude:: examples/metadata-io.cpp
   :language: cpp
   :start-after: add-example-start
   :end-before: add-example-end

Full example source: :download:`metadata-io.cpp <examples/metadata-io.cpp>`

.. seealso::

  - :doxygen:`Metadata classes <namespaceome_1_1xml_1_1meta.html>`
  - :doxygen:`createID <namespaceome_1_1files.html#ab3bf80ec03bcf20b199ce2761d48fe01>`
  - :doxygen:`createOMEXMLMetadata <namespaceome_1_1files.html#ae61f12958973765e8328348874a85731>`
  - :doxygen:`getOMEXML <namespaceome_1_1files.html#a32e5424991ce09b857ddc0d5be37c4f1>`


OME-XML data model objects
^^^^^^^^^^^^^^^^^^^^^^^^^^

The data model objects are not typically used directly, but are
created, modified and queried using the :cpp:class:`Metadata`
interfaces (above), so in practice these examples should not be
needed.

To create a tree of OME-XML data model objects from OME-XML text:

.. literalinclude:: examples/model-io.cpp
   :language: cpp
   :start-after: read-example-start
   :end-before: read-example-end

In this example, the OME-XML text is read from a file into a DOM tree.
This could have been read directly from a string or stream if the
source was not a file.  The DOM tree is then processed using the
:cpp:class:`OME` root object's :cpp:func:`update` method, which uses
the data from the DOM tree elements to create a tree of corresponding
model objects contained by the root object.

To reverse the process, taking a tree of OME-XML model objects and
converting them back of OME-XML text:

.. literalinclude:: examples/model-io.cpp
   :language: cpp
   :start-after: write-example-start
   :end-before: write-example-end

Here, the :cpp:class:`OME` root object's :cpp:func:`asXMLElement`
method is used to copy the data from the OME root object and its
children into an XML DOM tree.  The DOM tree is then converted to text
for output.

Full example source: :download:`model-io.cpp <examples/model-io.cpp>`

.. seealso::

  - :doxygen:`OME model classes <namespaceome_1_1xml_1_1model.html>`
  - :doxygen:`OME <classome_1_1xml_1_1model_1_1OME.html>`


Pixel data
----------

The Bio-Formats Java implementation stores and passes pixel values in
a raw :cpp:type:`byte` array.  Due to limitations with C++ array
passing, this was not possible for the OME-Files C++ implementation.
While a vector or other container could have been used, several
problems remain.  The type and endianness of the data in the raw bytes
is not known, and the dimension ordering and dimension extents are
also unknown, which imposes a significant burden on the programmer to
correctly process the data.  The C++ implementation provides two types
to solve these problems.

The :cpp:class:`PixelBuffer` class is a container of pixel data.  It
is a template class, templated on the pixel type in use.  The class
contains the order of the dimensions, and the size of each dimension,
making it possible to process pixel data without need for
externally-provided metadata to describe its structure.  This class
may be used to contain and process pixel data of a specific pixel
type.  Internally, the pixel data is contained within a
:cpp:class:`boost::multi_array` as a 9D hyper-volume, though its usage
in this release of OME-Files is limited to 5D.  The class can either
contain its own memory allocation for pixel data, or it can reference
memory allocated or mapped externally, allowing use with memory-mapped
data, for example.

In many situations, it is desirable to work with arbitrary pixel
types, or at least the set of pixel types defined in the OME data
model in its :cpp:class:`PixelType` enumeration.  The
:cpp:class:`VariantPixelBuffer` fulfills this need, using
:cpp:class:`boost::variant` to allow it to contain a
:cpp:class:`PixelBuffer` specialized for any of the pixel types in the
OME data model.  This is used to allow transfer and processing of any
supported pixel type, for example by the :cpp:class:`FormatReader`
class' :cpp:func:`getLookupTable` and :cpp:func:`openBytes` methods,
and the corresponding :cpp:class:`FormatWriter` class'
:cpp:func:`setLookupTable` and :cpp:func:`saveBytes` methods.

An additional problem with supporting many different pixel types is
that each operation upon the pixel data, for example for display or
analysis, may require implementing separately for each pixel type.
This imposes a significant testing and maintenance burden.
:cpp:class:`VariantPixelBuffer` solves this problem through use of
:cpp:func:`boost::apply_visitor` and
:cpp:class:`boost::static_visitor`, which allow algorithms to be
defined in a template and compiled for each pixel type.  They also
allow algorithms to be specialized for different classes of pixel
type, for example signed vs. unsigned, integer vs. floating point, or
simple vs. complex, or special-cased per type e.g. for bitmasks.  When
:cpp:func:`boost::apply_visitor` is called with a specified algorithm
and :cpp:class:`VariantPixelBuffer` object, it will select the
matching algorithm for the pixel type contained within the buffer, and
then invoke it on the buffer.  This permits the programmer to support
arbitrary pixel types without creating a maintenance nightmare, and
without unnecessary code duplication.

The 9D pixel buffer makes a distinction between the logical dimension
order (used by the API) and the storage order (the layout of the pixel
data in memory).  The logical order is defined by the values in the
:doxygen:`Dimensions
<namespaceome_1_1files.html#ad9ebb405a4815c189fa788325f68a91a>`
enum.  The storage order is specified by the programmer when creating
a pixel buffer.

The following example shows creation of a pixel buffer with a defined
size, and :doxygen:`default storage order
<classome_1_1files_1_1PixelBufferBase.html#a419ad49f2ea90937a57b81a74b56380b>`:

.. literalinclude:: examples/pixeldata.cpp
   :language: cpp
   :start-after: create-example-start
   :end-before: create-example-end

The storage order may be set explicitly.  The order may be created by
hand, or with a :doxygen:`helper function
<classome_1_1files_1_1PixelBufferBase.html#ac7e922610bf561f311d13c3d7fcaeb69>`.
While the helper function is limited to supporting the ordering
defined by the data model, specifying the order by hand allows
additional flexibility.  Manual ordering may be used to allow the
indexing for individual dimensions to run backward rather than
forward, which is useful if the Y-axis requires inverting, for
example.  The following example shows creation of two pixel buffers with
defined storage order using the helper function:

.. literalinclude:: examples/pixeldata.cpp
   :language: cpp
   :start-after: create-ordered-example-start
   :end-before: create-ordered-example-end

Note that the logical order of the dimension extents is unchanged.

In practice, it is unlikely that you will need to create any
:cpp:class:`PixelBuffer` objects directly.  The
:cpp:class:`FormatReader` and :cpp:class:`FormatWriter` interfaces use
:cpp:class:`VariantPixelBuffer` objects, and in the case of the reader
interface the :cpp:func:`getLookupTable` and :cpp:func:`openBytes`
methods can be passed a default-constructed
:cpp:class:`VariantPixelBuffer` and it will be set up automatically,
changing the image dimensions, dimension order and pixel type to match
the data being fetched, if the size, order and type do not match.  For
example, to read all pixel data in an image using
:cpp:func:`openBytes`:

.. literalinclude:: examples/metadata-formatreader.cpp
   :language: cpp
   :start-after: pixel-example-start
   :end-before: pixel-example-end

To perform the reverse process, writing pixel data with
:cpp:func:`saveBytes`:

.. literalinclude:: examples/metadata-formatwriter.cpp
   :language: cpp
   :start-after: pixel-example-start
   :end-before: pixel-example-end

Both buffer classes provide access to the pixel data so that it may be
accessed, manipulated and passed elsewhere.  The
:cpp:class:`PixelBuffer` class provides an :cpp:class:`at` method.
This allows access to individual pixel values using a 9D coordinate:

.. literalinclude:: examples/pixeldata.cpp
   :language: cpp
   :start-after: at-example-start
   :end-before: at-example-end

Conceptually, this is the same as using an index for a normal 1D
array, but extended to use an array of nine indices for each of the
nine dimensions, in the logical storage order.  The
:cpp:class:`VariantPixelBuffer` does not provide an :cpp:class:`at`
method for efficiency reasons.  Instead, visitors should be used for
the processing of bulk pixel data.  For example, this is one way the
minimum and maximum pixel values could be obtained:

.. literalinclude:: examples/pixeldata.cpp
   :language: cpp
   :start-after: visitor-example-start
   :end-before: visitor-example-end

This example demonstrates several features:

- The visitor operators can return values to the caller (for more
  complex algorithms, the visitor class could use member variables and
  additional methods)
- The operator is expanded once for each pixel type
- The operators can be special-cased for individual pixel types; here
  we use the `SFINAE rule
  <http://en.cppreference.com/w/language/sfinae>`_ to implement a
  specialization for an entire category of pixel types (complex
  numbers), but standard function overloading and templates will also
  work for more common cases
- Pixel data can be assigned to the buffer with a single
  :cpp:func:`assign` call.

The OME-Files source uses pixel buffer visitors for several purposes,
for example to load pixel data into OpenGL textures, which
automatically handles pixel format conversion and repacking of pixel
data as needed.

While the pixel buffers may appear complex, they do permit the OME
Files library to support all pixel types with relative ease, and it
will allow your applications to also handle multiple pixel types by
writing your own visitors.  Assignment of one buffer to another will
also repack the pixel data if they use different storage ordering
(i.e. the logical ordering is used for the copy), which can be useful
if you need the pixel data in a defined ordering.

If all you want is access to the raw data, as in the Java API, you are
not required to use the above features.  Simply use the
:cpp:func:`data` method on the buffer to get a pointer to the raw
data.  Note that you will need to multiply the buffer size obtained
with :cpp:func:`num_elements` by the size of the pixel type (use
:cpp:func:`bytesPerPixel` or :cpp:func:`sizeof` on the buffer
:cpp:type:`value_type`).

Alternatively, it is also possible to access the underlying
:cpp:class:`boost::multi_array` using the :cpp:func:`array` method, if
you need access to functionality not wrapped by
:cpp:class:`PixelBuffer`.

Full example source: :download:`pixeldata.cpp <examples/pixeldata.cpp>`

.. seealso::

  - :doxygen:`PixelType <classome_1_1xml_1_1model_1_1enums_1_1PixelType.html>`
  - :doxygen:`PixelBuffer <classome_1_1files_1_1PixelBuffer.html>`
  - :doxygen:`VariantPixelBuffer <classome_1_1files_1_1VariantPixelBuffer.html>`
  - :doxygen:`FormatReader::getLookupTable <classome_1_1files_1_1FormatReader.html#a9b69e3612f0ad4c945d1c0f111242cc2>`
  - :doxygen:`FormatReader::openBytes <classome_1_1files_1_1FormatReader.html#a5bfa86b4b68b03b63d76bb050cbe7101>`
  - :doxygen:`FormatWriter::setLookupTable <classome_1_1files_1_1FormatWriter.html#a00ae3dc46c205e64f782c7b6f47bd5ab>`
  - :doxygen:`FormatWriter::saveBytes <classome_1_1files_1_1FormatWriter.html#ad1e8b427214f7cfd19ce2251d38e24f5>`

Reading images
--------------

Image reading is performed using the :cpp:class:`FormatReader`
interface.  This is an abstract reader interface implemented by
file-format-specific reader classes.  Examples of readers include
:cpp:class:`TIFFReader`, which implements reading of Baseline TIFF
(optionally with additional ImageJ metadata), and
:cpp:class:`OMETIFFReader` which implements reading of OME-TIFF (TIFF
with OME-XML metadata).

Using a reader involves these steps:

#. Create a reader instance.
#. Set options to control reader behavior.
#. Call :cpp:func:`setId` to specify the image file to read.
#. Retrieve desired metadata and pixel data.
#. Close the reader.

These steps are illustrated in this example:

.. literalinclude:: examples/metadata-formatreader.cpp
   :language: cpp
   :start-after: reader-example-start
   :end-before: reader-example-end

Here we create a reader to read TIFF files, set two options (metadata
filtering and file grouping), and then call :cpp:func:`setId`.  At
this point the reader has been set up and initialized, and we can then
read metadata and pixel data, which we covered in the preceding
sections.  You might like to combine this example with the
:cpp:class:`MinMaxVisitor` example to make it display the minimum and
maximum values for each plane in an image; if you try running the
example with TIFF images of different pixel types, it will
transparently adapt to any supported pixel type.

.. note::

  Reader option-setting methods may only be called *before*
  :cpp:func:`setId`.  Reader state changing and querying methods such
  as :cpp:func:`setSeries` and :cpp:func:`getSeries`, metadata
  retrieval and pixel data retrieval methods may only be called
  *after* :cpp:func:`setId`.  If these constraints are violated, a
  :cpp:class:`FormatException` will be thrown.

Full example source: :download:`metadata-formatreader.cpp <examples/metadata-formatreader.cpp>`

.. seealso::

  - :doxygen:`FormatReader <classome_1_1files_1_1FormatReader.html>`
  - :doxygen:`TIFFReader <classome_1_1files_1_1in_1_1TIFFReader.html>`
  - :doxygen:`OMETIFFReader <classome_1_1files_1_1in_1_1OMETIFFReader.html>`

Writing images
--------------

Image writing is performed using the :cpp:class:`FormatWriter`
interface.  This is an abstract writer interface implemented by
file-format-specific writer classes.  Examples of writers include
:cpp:class:`MinimalTIFFWriter`, which implements writing of Baseline
TIFF and :cpp:class:`OMETIFFWriter` which implements writing of
OME-TIFF (TIFF with OME-XML metadata).

Using a writer involves these steps:

#. Create a writer instance.
#. Set metadata store to use.
#. Set options to control writer behavior.
#. Call :cpp:func:`setId` to specify the image file to write.
#. Store pixel data for each plane of each image in the specified
   dimension order.
#. Close the writer.

These steps are illustrated in this example:

.. literalinclude:: examples/metadata-formatwriter.cpp
   :language: cpp
   :start-after: writer-example-start
   :end-before: writer-example-end

Here we create a writer to write OME-TIFF files, set the metadata
store using metadata we create, then set a writer option (sample
interleaving), and then call :cpp:func:`setId`.  At this point the
writer has been set up and initialized, and we can then write the
pixel data, which we covered in the preceding sections.  Finally we
call :cpp:func:`close` to flush all data.

.. note::

  Metadata store setting and writer option-setting methods may only be
  called *before* :cpp:func:`setId`.  Writer state changing and
  querying methods such as :cpp:func:`setSeries` and
  :cpp:func:`getSeries`, and pixel data storage methods may only be
  called *after* :cpp:func:`setId`.  If these constraints are
  violated, a :cpp:class:`FormatException` will be thrown.

.. note::

  :cpp:func:`close` should be called explicitly to catch any errors.
  While this will be called by the destructor, the destructor can't
  throw exceptions and any errors will be silently ignored.

Full example source: :download:`metadata-formatwriter.cpp <examples/metadata-formatwriter.cpp>`

.. seealso::

  - :doxygen:`FormatWriter <classome_1_1files_1_1FormatWriter.html>`
  - :doxygen:`TIFFWriter <classome_1_1files_1_1out_1_1MinimalTIFFWriter.html>`
  - :doxygen:`OMETIFFWriter <classome_1_1files_1_1out_1_1OMETIFFWriter.html>`
