            Bio-Formats File Format Reader Guide by Melissa Linkert

This document is a brief guide to writing new Bio-Formats file format readers.

All format readers should extend either loci.formats.FormatReader or a reader
in loci.formats.in.

                              Methods to Override
                             ---------------------

boolean isThisType(byte[]) :
  Check the first few bytes of a file to determine if the file can be read by
  this reader.  You can assume that the first byte of the array corresponds to
  the first byte of the file.  Return true if the file can be read; false if
  not (or if there is no way of checking).

String[] getUsedFiles() throws FormatException, IOException :
  You only need to override this if your format uses multiple files in a single
  dataset.  This method should return a list of all the files associated with
  the given file name (i.e. every file needed to display the current dataset).
  For an example of how this works, see loci.formats.in.PerkinElmerReader.
  It is recommended that the first line of this method be
  "FormatTools.assertId(currentId, true, 1)" - this ensures that the file name
  is non-null.

byte[] openBytes(int) throws FormatException, IOException :
  Returns a byte array containing the pixel data for the specified image from
  the given file.  Should throw a FormatException if the image number is invalid
  (less than 0 or >= the number of images).  The ordering of the array
  returned by openBytes should correspond to the values returned by
  isLittleEndian() and isInterleaved().  Also, the length of the
  byte array should be [image width * image height * bytes per pixel].  Extra
  bytes will generally be truncated. It is recommended that the first line of 
  this method be "FormatTools.asserId(currentId, true, 1)" - this ensures that 
  the file name is non-null.

BufferedImage openImage(int) throws FormatException, IOException :
  Basically the same as openBytes, but returns a java.awt.image.BufferedImage
  instead of a byte array.  In general, the easiest way to implement this is
  by getting the corresponding byte array from openBytes, then returning
  loci.formats.ImageTools.makeImage(byte[], int width, int height, int
  numberOfChannels, boolean isInterleaved, int bytesPerPixel, boolean
  isLittleEndian). It is recommended that the first line of this method be
  "FormatTools.assertId(currentId, true, 1)" - this ensures that the file name
  is non-null.

protected void initFile(String) throws FormatException, IOException :
  The majority of the file parsing logic should be placed in this method.  The
  idea is to call this method once (and only once!) when the file is first
  opened.  Generally, you will want to start by calling
  super.initFile(String).  You will also need to set up the stream for reading
  the file, as well as initializing any dimension information and metadata.
  Most of this logic is up to you; however, you should populate the 'core'
  variable (see loci.formats.CoreMetadata).

  Note that each variable is initialized to 0 or null when
  super.initFile(String) is called.
  Also, super.initFile(String) constructs a Hashtable called "metadata" where
  you should store any relevant metadata.

Note that if the new format is a variant of a format currently supported by
Bio-Formats, it is more efficient to make the new reader a subclass of the
existing reader (rather than subclassing FormatReader).  In this case, it is
usually sufficient to override initFile(String) and isThisType(byte[]).

Every reader also has an instance of loci.formats.CoreMetadata.  All readers
should populate the fields in CoreMetadata, which are essential to reading
image planes.

If you read from a file using something other than RandomAccessStream or
Location, you *must* use the file name returned by Location.getMappedId(String),
not the file name passed to the reader.
Thus, a stub for initFile(String) might look like this:

  protected void initFile(String id) throws FormatException, IOException {
    super.initFile(id);

    RandomAccessStream in = new RandomAccessStream(id);
    // alternatively,
    //FileInputStream in = new FileInputStream(Location.getMappedId(id));

    // read basic file structure and metadata from stream
  }

For more details, see the javadoc for Location.mapId(String, String)
and Location.getMappedId(String).

                              Other Useful Things
                             ---------------------

- loci.formats.RandomAccessStream is a hybrid RandomAccessFile/InputStream
  class that is generally more efficient than either RandomAccessFile or
  InputStream, and implements the DataInput interface.  It also keeps track
  of open files, and will automatically close and re-open files as needed
  to ensure that there aren't too many files open at one time.
  It is recommended that you use this for reading binary files.

- loci.formats.Location provides an API similar to java.io.File, and supports
  File-like operations on URLs.  It is highly recommended that you use this
  instead of File.  See the javadoc for additional information.

- loci.formats.DataTools provides a number of methods for converting bytes to
  shorts, ints, longs, etc.  It also supports reading most primitive types
  directly from a RandomAccessStream (or other DataInput implementation).

- loci.formats.ImageTools provides several methods for manipulating
  java.awt.image.BufferedImage objects.  In particular, it can create
  BufferedImages from primitive type arrays, resize images, split RGB images
  into 3 grayscale images, and so on.  Consult the source or javadoc for more
  information.

- If your reader relies on third-party code which may not be available to all
  users, it is strongly suggested that you access this code only through a
  loci.formats.ReflectedUniverse object.  For an example of how this works,
  see loci.formats.in.ZeissZVIReader.

- Several common image compression types are supported through subclasses of
  loci.formats.BaseCompressor.  These include LZW, LZO, Base64, ZIP and
  RLE (PackBits).

- Debugging statements can be added using FormatHandler.debug(String).

- If you wish to convert a file's metadata to OME-XML (strongly encouraged),
  loci.formats.ome.OMEXMLMetadataStore provides methods for creating OME-XML
  from metadata values.  Note that OMEXMLMetadataStore is a subclass of
  loci.formats.MetadataStore (every subclass of FormatReader keeps an instance
  of MetadataStore by default); so to add OME-XML support is as simple as
  calling getMetadataStore() on a format reader instance, and then
  calling the appropriate "set" methods as documented in OMEXMLMetadataStore.

- Utility methods for reading and writing individual bits from a byte array
  can be found in loci.formats.BitBuffer and loci.formats.BitWriter

- Once you have written your file format reader, add a line to the readers.txt
  file with the fully qualified name of the reader, followed by a '#' and the
  file extensions associated with the file format. Note that ImageReader,
  the master file format reader, tries to identify which format reader to use
  according to the order given in readers.txt, so be sure to place your reader
  in an appropriate position within the list.

- The easiest way to test your new reader is by calling "java
  loci.formats.ImageReader <file name>".  If all goes well, you should see
  all of the metadata and dimension information, along with a window showing
  the images in the file.  ImageReader can take additional parameters; a
  brief listing is provided for reference, but it is recommended that you
  take a look at the contents of ConsoleTools.testRead to see exactly what
  each one does.

    Argument            Action
    --------------------------
    -nopix              Read metadata only; don't display images.
    -nometa             Output only core metadata (dimension information).
    -thumbs             Read thumbnails instead of regular images.
    -merge              Combine separate channels into a set of RGB images.
    -stitch             Open all files with a similar name.
    -separate           Force RGB images to be split into separate channels.
    -omexml             Output the OME-XML for the file.
    -normalize          Normalize floating point images.
    -fast               Display RGB images as quickly as possible.
    -debug              Turn on debugging output.
    -range              Specify a range of images to open.
    -series             Set the series number (for container file formats).
    -map                Specify file on disk to which name should be mapped.

- If you wish to test using TestNG, loci.formats.test.ReaderTest provides 15 
  basic tests that work with all Bio-Formats readers.  See the
  ReaderTest source code for additional information.

- For more details, please look at the source code and javadocs.  Studying
  existing readers is probably the best way to get a feel for the API; I would
  recommend first looking at loci.formats.in.ImarisReader (this is the most
  straightforward one).  loci.formats.in.LIFReader and ZeissZVIReader are also
  good references that show off some of the nicer features of Bio-Formats.

If you have questions about Bio-Formats, please contact:
  Curtis Rueden <ctrueden@wisc.edu>
  Melissa Linkert <linkert@wisc.edu>
