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, byte[], int, int, int, int) throws FormatException, IOException : Returns a byte array containing the pixel data for a subimage specified image from the given file. The dimensions of the subimage (upper left X coordinate, upper left Y coordinate, width, and height) are specified in the final four int parameters. This 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.assertId(currentId, true, 1)" - this ensures that the file name is non-null. BufferedImage openImage(int, int, int, int, 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 RandomAccessInputStream 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); RandomAccessInputStream in = new RandomAccessInputStream(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.common.RandomAccessInputStream 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 RandomAccessInputStream (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 JPEG, 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), please see metadata-guide.txt further information. - Utility methods for reading and writing individual bits from a byte array can be found in loci.formats.codec.BitBuffer and loci.formats.codec.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.tools.ImageInfo ". 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 several 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 Melissa Linkert