//
// OMEXMLReader.java
//
/*
LOCI Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ Melissa Linkert, Curtis Rueden, Chris Allan,
Eric Kjellman and Brian Loranger.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.formats.in;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import loci.formats.*;
import loci.formats.codec.Base64Codec;
import loci.formats.codec.CBZip2InputStream;
/**
* OMEXMLReader is the file format reader for OME-XML files.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OMEXMLReader.java">Trac</a>,
* <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OMEXMLReader.java">SVN</a></dd></dl>
*
* @author Melissa Linkert linkert at wisc.edu
*/
public class OMEXMLReader extends FormatReader {
// -- Constants --
private static final String NO_OME_JAVA_MSG =
"The Java OME-XML library is required to read OME-XML files. Please " +
"obtain ome-java.jar from http://loci.wisc.edu/ome/formats.html";
// -- Static fields --
private static boolean noOME = false;
static {
try {
Class.forName("org.openmicroscopy.xml.OMENode");
}
catch (Throwable t) {
noOME = true;
if (debug) LogTools.trace(t);
}
}
// -- Fields --
/** Number of bits per pixel. */
protected int[] bpp;
/** Offset to each plane's data. */
protected Vector[] offsets;
/** String indicating the compression type. */
protected String[] compression;
// -- Constructor --
/** Constructs a new OME-XML reader. */
public OMEXMLReader() { super("OME-XML", "ome"); }
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(byte[]) */
public boolean isThisType(byte[] block) {
return new String(block, 0, 5).equals("<?xml");
}
/* @see loci.formats.IFormatReader#openBytes(int, byte[]) */
public byte[] openBytes(int no, byte[] buf)
throws FormatException, IOException
{
FormatTools.assertId(currentId, true, 1);
FormatTools.checkPlaneNumber(this, no);
FormatTools.checkBufferSize(this, buf.length);
in.seek(((Integer) offsets[series].get(no)).intValue());
byte[] b;
if (no < getImageCount() - 1) {
b = new byte[((Integer) offsets[series].get(no + 1)).intValue() -
((Integer) offsets[series].get(no)).intValue()];
}
else {
b = new byte[(int) (in.length() -
((Integer) offsets[series].get(no)).intValue())];
}
in.read(b);
String data = new String(b);
b = null;
// retrieve the compressed pixel data
int dataStart = data.indexOf(">") + 1;
String pix = data.substring(dataStart);
if (pix.indexOf("<") > 0) {
pix = pix.substring(0, pix.indexOf("<"));
}
data = null;
Base64Codec e = new Base64Codec();
byte[] pixels = e.base64Decode(pix);
pix = null;
if (compression[series].equals("bzip2")) {
byte[] tempPixels = pixels;
pixels = new byte[tempPixels.length - 2];
System.arraycopy(tempPixels, 2, pixels, 0, pixels.length);
ByteArrayInputStream bais = new ByteArrayInputStream(pixels);
CBZip2InputStream bzip = new CBZip2InputStream(bais);
pixels = new byte[core.sizeX[series] * core.sizeY[series] * bpp[series]];
for (int i=0; i<pixels.length; i++) {
pixels[i] = (byte) bzip.read();
}
tempPixels = null;
bais.close();
bais = null;
bzip = null;
}
else if (compression[series].equals("zlib")) {
try {
Inflater decompressor = new Inflater();
decompressor.setInput(pixels, 0, pixels.length);
pixels = new byte[core.sizeX[series]*core.sizeY[series]*bpp[series]];
decompressor.inflate(pixels);
decompressor.end();
}
catch (DataFormatException dfe) {
throw new FormatException("Error uncompressing zlib data.");
}
}
buf = pixels;
return buf;
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
protected void initFile(String id) throws FormatException, IOException {
if (debug) debug("OMEXMLReader.initFile(" + id + ")");
if (noOME) throw new FormatException(NO_OME_JAVA_MSG);
super.initFile(id);
in = new RandomAccessStream(id);
ReflectedUniverse r = new ReflectedUniverse();
try {
r.exec("import loci.formats.ome.OMEXMLMetadata");
r.exec("import org.openmicroscopy.xml.OMENode");
r.exec("omexmlMeta = new OMEXMLMetadata()");
}
catch (ReflectException exc) {
throw new FormatException(exc);
}
r.setVar("ome", null);
try {
File f = new File(Location.getMappedId(id));
f = f.getAbsoluteFile();
String path = f.getPath().toLowerCase();
if (f.exists() && path.endsWith(".ome")) {
r.setVar("f", f);
r.exec("ome = new OMENode(f)");
}
else {
byte[] b = new byte[(int) in.length()];
long oldFp = in.getFilePointer();
in.seek(0);
in.read(b);
in.seek(oldFp);
r.setVar("s", new String(b));
r.exec("ome = new OMENode(s)");
b = null;
}
}
catch (ReflectException exc) {
throw new FormatException(exc);
}
try {
r.exec("omexmlMeta.setRoot(ome)");
}
catch (ReflectException exc) {
throw new FormatException(exc);
}
status("Determining endianness");
in.skipBytes(200);
int numDatasets = 0;
Vector endianness = new Vector();
Vector bigEndianPos = new Vector();
byte[] buf = new byte[1];
while (in.getFilePointer() < in.length()) {
// read a block of 8192 characters, looking for the "BigEndian" pattern
buf = new byte[8192];
boolean found = false;
while (!found) {
if (in.getFilePointer() < in.length()) {
int read = in.read(buf, 9, 8183);
String test = new String(buf);
int ndx = test.indexOf("BigEndian");
if (ndx != -1) {
found = true;
String endian = test.substring(ndx + 11).trim();
if (endian.startsWith("\"")) endian = endian.substring(1);
endianness.add(new Boolean(!endian.toLowerCase().startsWith("t")));
bigEndianPos.add(new Long(in.getFilePointer() - read - 9 + ndx));
numDatasets++;
}
}
else if (numDatasets == 0) {
throw new FormatException("Pixel data not found.");
}
else found = true;
}
}
offsets = new Vector[numDatasets];
for (int i=0; i<numDatasets; i++) {
offsets[i] = new Vector();
}
status("Finding image offsets");
// look for the first BinData element in each series
for (int i=0; i<numDatasets; i++) {
in.seek(((Long) bigEndianPos.get(i)).longValue());
boolean found = false;
buf = new byte[8192];
in.read(buf, 0, 14);
while (!found) {
if (in.getFilePointer() < in.length()) {
int numRead = in.read(buf, 14, 8192-14);
String test = new String(buf);
int ndx = test.indexOf("<Bin");
if (ndx == -1) {
byte[] b = buf;
System.arraycopy(b, 8192 - 15, buf, 0, 14);
}
else {
while (!((ndx != -1) && (ndx != test.indexOf("<Bin:External")) &&
(ndx != test.indexOf("<Bin:BinaryFile"))))
{
ndx = test.indexOf("<Bin", ndx+1);
}
found = true;
numRead += 14;
offsets[i].add(new Integer(
(int) in.getFilePointer() - (numRead - ndx)));
}
test = null;
}
else {
throw new FormatException("Pixel data not found");
}
}
}
in.seek(0);
for (int i=0; i<numDatasets; i++) {
if (i == 0) {
buf = new byte[((Integer) offsets[i].get(0)).intValue()];
}
else {
// look for the next Image element
boolean found = false;
buf = new byte[8192];
in.read(buf, 0, 14);
while (!found) {
if (in.getFilePointer() < in.length()) {
in.read(buf, 14, 8192-14);
String test = new String(buf);
int ndx = test.indexOf("<Image ");
if (ndx == -1) {
byte[] b = buf;
System.arraycopy(b, 8192 - 15, buf, 0, 14);
b = null;
}
else {
found = true;
in.seek(in.getFilePointer() - (8192 - ndx));
}
test = null;
}
else {
throw new FormatException("Pixel data not found");
}
}
int bufSize = (int) (((Long) offsets[i].get(0)).longValue() -
in.getFilePointer());
buf = new byte[bufSize];
}
in.read(buf);
}
buf = null;
status("Populating metadata");
core = new CoreMetadata(numDatasets);
bpp = new int[numDatasets];
compression = new String[numDatasets];
int oldSeries = getSeries();
try {
r.exec("omexmlMeta.setRoot(ome)");
}
catch (ReflectException exc) {
throw new FormatException(exc);
}
for (int i=0; i<numDatasets; i++) {
setSeries(i);
core.littleEndian[i] = ((Boolean) endianness.get(i)).booleanValue();
Integer w = null, h = null, t = null, z = null, c = null;
String pixType = null;
try {
r.setVar("ndx", i);
w = (Integer) r.exec("omexmlMeta.getSizeX(ndx)");
h = (Integer) r.exec("omexmlMeta.getSizeY(ndx)");
t = (Integer) r.exec("omexmlMeta.getSizeT(ndx)");
z = (Integer) r.exec("omexmlMeta.getSizeZ(ndx)");
c = (Integer) r.exec("omexmlMeta.getSizeC(ndx)");
pixType = (String) r.exec("omexmlMeta.getPixelType(ndx)");
core.currentOrder[i] =
(String) r.exec("omexmlMeta.getDimensionOrder(ndx)");
}
catch (ReflectException exc) {
throw new FormatException(exc);
}
core.sizeX[i] = w.intValue();
core.sizeY[i] = h.intValue();
core.sizeT[i] = t.intValue();
core.sizeZ[i] = z.intValue();
core.sizeC[i] = c.intValue();
core.rgb[i] = false;
core.interleaved[i] = false;
core.indexed[i] = false;
core.falseColor[i] = false;
String type = pixType.toLowerCase();
if (type.endsWith("16")) {
bpp[i] = 2;
core.pixelType[i] = FormatTools.UINT16;
}
else if (type.endsWith("32")) {
bpp[i] = 4;
core.pixelType[i] = FormatTools.UINT32;
}
else if (type.equals("float")) {
bpp[i] = 4;
core.pixelType[i] = FormatTools.FLOAT;
}
else {
bpp[i] = 1;
core.pixelType[i] = FormatTools.UINT8;
}
// calculate the number of raw bytes of pixel data that we are expecting
int expected = core.sizeX[i] * core.sizeY[i] * bpp[i];
// find the compression type and adjust 'expected' accordingly
in.seek(((Integer) offsets[i].get(0)).intValue());
buf = new byte[256];
in.read(buf);
String data = new String(buf);
int compressionStart = data.indexOf("Compression") + 13;
int compressionEnd = data.indexOf("\"", compressionStart);
if (compressionStart != -1 && compressionEnd != -1) {
compression[i] = data.substring(compressionStart, compressionEnd);
}
else compression[i] = "none";
expected /= 2;
in.seek(((Integer) offsets[i].get(0)).intValue());
int planes = core.sizeZ[i] * core.sizeC[i] * core.sizeT[i];
searchForData(expected, planes);
core.imageCount[i] = offsets[i].size();
if (core.imageCount[i] < planes) {
// hope this doesn't happen too often
in.seek(((Integer) offsets[i].get(0)).intValue());
searchForData(0, planes);
core.imageCount[i] = offsets[i].size();
}
buf = null;
}
setSeries(oldSeries);
Arrays.fill(core.orderCertain, true);
// populate assigned metadata store with the
// contents of the internal OME-XML metadata object
MetadataStore store = getMetadataStore();
MetadataRetrieve omexmlMeta = null;
try {
omexmlMeta = (MetadataRetrieve) r.getVar("omexmlMeta");
}
catch (ReflectException e) {
if (debug) LogTools.trace(e);
}
String xml = MetadataTools.getOMEXML(omexmlMeta);
MetadataTools.convertMetadata(xml, store);
}
// -- Helper methods --
/** Searches for BinData elements, skipping 'safe' bytes in between. */
private void searchForData(int safe, int numPlanes) throws IOException {
int iteration = 0;
boolean found = false;
if (offsets[series].size() > 1) {
Object zeroth = offsets[series].get(0);
offsets[series].clear();
offsets[series].add(zeroth);
}
in.skipBytes(1);
while (((in.getFilePointer() + safe) < in.length()) &&
(offsets[series].size() < numPlanes))
{
in.skipBytes(safe);
// look for next BinData element
found = false;
byte[] buf = new byte[8192];
while (!found) {
if (in.getFilePointer() < in.length()) {
int numRead = in.read(buf, 20, buf.length - 20);
String test = new String(buf);
// datasets with small planes could have multiple sets of pixel data
// in this block
int ndx = test.indexOf("<Bin");
while (ndx != -1) {
found = true;
if (numRead == buf.length - 20) numRead = buf.length;
offsets[series].add(new Integer(
(int) in.getFilePointer() - (numRead - ndx)));
ndx = test.indexOf("<Bin", ndx+1);
}
test = null;
}
else {
found = true;
}
}
buf = null;
iteration++;
}
}
}