.. _writing_files_details:

Further details on exporting raw pixel data to OME-TIFF files
============================================================= 

This document explains how to export pixel data to OME-TIFF using Bio-Formats
version 4.2 and later.

Working example code is provided in
:download:`FileExport.java <examples/FileExport.java>` - code from that class
is referenced here in part. You will need to have :file:`bioformats_package.jar` in
your Java :envvar:`CLASSPATH` in order to compile :file:`FileExport.java`.

The first thing that must happen is we must create the object that stores
OME-XML metadata.  This is done as follows:

::

      ServiceFactory factory = new ServiceFactory();
      OMEXMLService service = factory.getInstance(OMEXMLService.class);
      IMetadata omexml = service.createOMEXMLMetadata();

The 'omexml' object can now be used in our code to store OME-XML metadata, and
by the file format writer to retrieve OME-XML metadata.

Now that we have somewhere to put metadata, we need to populate as much
metadata as we can. The minimum amount of metadata required is:

- endianness of the pixel data
- the order in which dimensions are stored
- the bit depth of the pixel data
- the number of channels
- the number of timepoints
- the number of Z sections
- the width (in pixels) of an image
- the height (in pixels) of an image
- the number of samples per channel (3 for RGB images, 1 otherwise)

We populate that metadata as follows:

::

      omexml.setImageID("Image:0", 0);
      omexml.setPixelsID("Pixels:0", 0);

      // specify that the pixel data is stored in big-endian order
      // replace 'TRUE' with 'FALSE' to specify little-endian order
      omexml.setPixelsBinDataBigEndian(Boolean.TRUE, 0, 0);

      omexml.setPixelsDimensionOrder(DimensionOrder.XYCZT, 0);
      omexml.setPixelsType(PixelType.UINT16, 0);
      omexml.setPixelsSizeX(new PositiveInteger(width), 0);
      omexml.setPixelsSizeY(new PositiveInteger(height), 0);
      omexml.setPixelsSizeZ(new PositiveInteger(zSectionCount), 0);
      omexml.setPixelsSizeC(new PositiveInteger(channelCount *
      samplesPerChannel), 0);
      omexml.setPixelsSizeT(new PositiveInteger(timepointCount), 0);

      for (int channel=0; channel<channelCount; channel++) {
        omexml.setChannelID("Channel:0:" + channel, 0, channel);
        omexml.setChannelSamplesPerPixel(new PositiveInteger(samplesPerChannel),
          0, channel);
      }
      
      Unit<Length> unit = UNITS.MICROMETER;
      Length physicalSizeX = new Length(1.0, unit);
      Length physicalSizeY = new Length(1.5, unit);
      Length physicalSizeZ = new Length(2, unit);
      omexml.setPixelsPhysicalSizeX(physicalSizeX, 0);
      omexml.setPixelsPhysicalSizeY(physicalSizeY, 0);
      omexml.setPixelsPhysicalSizeZ(physicalSizeZ, 0);

There is much more metadata that can be stored; please see the Javadoc for
loci.formats.meta.MetadataStore for a complete list.

Now that we have defined all of the metadata, we need to create a file writer:

::

      ImageWriter writer = new ImageWriter();

Now we must associate the 'omexml' object with the file writer:

:: 

      writer.setMetadataRetrieve(omexml);

The writer now knows to retrieve any metadata that it needs from 'omexml'.

We now tell the writer which file it should write to:

::

      writer.setId("output-file.ome.tiff");

It is critical that the file name given to the writer ends with ".ome.tiff" or
".ome.tif", as it is the file name extension that determines which format will
be written.

Now that everything is set up, we can save the image data.  This is done
plane by plane, and we assume that the pixel data is stored in a 2D byte array
'pixelData':

::

        int sizeC = omexml.getPixelsSizeC(0).getValue();
        int sizeZ = omexml.getPixelsSizeZ(0).getValue();
        int sizeT = omexml.getPixelsSizeT(0).getValue();
        int samplesPerChannel = omexml.getChannelSamplesPerPixel(0).getValue();
        sizeC /= samplesPerChannel;

        int imageCount = sizeC * sizeZ * sizeT;

        for (int image=0; image<imageCount; image++) {
          writer.saveBytes(image, pixelData[image]);
        }
      }


Finally, we must tell the writer that we are finished, so that the output file
can be properly closed:

::

      writer.close();

There should now be a complete OME-TIFF file at whichever path was specified
above.
