//
// OMEXMLReader.java
//
/*
OME Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU 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.ByteArrayInputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Vector;
import loci.common.CBZip2InputStream;
import loci.common.RandomAccessInputStream;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.common.services.ServiceFactory;
import loci.common.xml.XMLTools;
import loci.formats.CoreMetadata;
import loci.formats.FormatException;
import loci.formats.FormatReader;
import loci.formats.FormatTools;
import loci.formats.MetadataTools;
import loci.formats.MissingLibraryException;
import loci.formats.codec.Base64Codec;
import loci.formats.codec.CodecOptions;
import loci.formats.codec.JPEG2000Codec;
import loci.formats.codec.JPEGCodec;
import loci.formats.codec.ZlibCodec;
import loci.formats.meta.MetadataStore;
import loci.formats.ome.OMEXMLMetadata;
import loci.formats.services.OMEXMLService;
import loci.formats.services.OMEXMLServiceImpl;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.helpers.DefaultHandler;
/**
* OMEXMLReader is the file format reader for OME-XML files.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/in/OMEXMLReader.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/OMEXMLReader.java;hb=HEAD">Gitweb</a></dd></dl>
*
* @author Melissa Linkert melissa at glencoesoftware.com
*/
public class OMEXMLReader extends FormatReader {
// -- Static fields --
private static boolean noOME = false;
static {
try {
Class.forName("ome.xml.OMEXMLNode");
}
catch (Throwable t) {
noOME = true;
LOGGER.debug(OMEXMLServiceImpl.NO_OME_XML_MSG, t);
}
}
// -- Fields --
// compression value and offset for each BinData element
private Vector<BinData> binData;
private Vector<Long> binDataOffsets;
private Vector<String> compression;
private String omexml;
private boolean hasSPW = false;
// -- Constructor --
/** Constructs a new OME-XML reader. */
public OMEXMLReader() {
super("OME-XML", "ome");
domains = FormatTools.NON_GRAPHICS_DOMAINS;
suffixNecessary = false;
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
public boolean isThisType(RandomAccessInputStream stream) throws IOException {
final int blockLen = 64;
String xml = stream.readString(blockLen);
return xml.startsWith("<?xml") && xml.indexOf("<OME") >= 0;
}
/* @see loci.formats.IFormatReader#getDomains() */
public String[] getDomains() {
FormatTools.assertId(currentId, true, 1);
return hasSPW ? new String[] {FormatTools.HCS_DOMAIN} :
FormatTools.NON_SPECIAL_DOMAINS;
}
/**
* @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
*/
public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
int index = no;
for (int i=0; i<series; i++) {
index += core[i].imageCount;
}
if (index >= binDataOffsets.size()) {
index = binDataOffsets.size() - 1;
}
long offset = binDataOffsets.get(index).longValue();
String compress = compression.get(index);
in.seek(offset);
int depth = FormatTools.getBytesPerPixel(getPixelType());
int planeSize = getSizeX() * getSizeY() * depth;
CodecOptions options = new CodecOptions();
options.width = getSizeX();
options.height = getSizeY();
options.bitsPerSample = depth * 8;
options.channels = getRGBChannelCount();
options.maxBytes = planeSize;
options.littleEndian = isLittleEndian();
options.interleaved = isInterleaved();
byte[] pixels = new Base64Codec().decompress(in, options);
// return a blank plane if no pixel data was stored
if (pixels.length == 0) {
LOGGER.debug("No pixel data for plane #{}", no);
return buf;
}
// TODO: Create a method uncompress to handle all compression methods
if (compress.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[planeSize];
bzip.read(pixels, 0, pixels.length);
tempPixels = null;
bais.close();
bais = null;
bzip = null;
}
else if (compress.equals("zlib")) {
pixels = new ZlibCodec().decompress(pixels, options);
}
else if (compress.equals("J2K")) {
pixels = new JPEG2000Codec().decompress(pixels, options);
}
else if (compress.equals("JPEG")) {
pixels = new JPEGCodec().decompress(pixels, options);
}
for (int row=0; row<h; row++) {
int off = (row + y) * getSizeX() * depth + x * depth;
System.arraycopy(pixels, off, buf, row * w * depth, w * depth);
}
pixels = null;
return buf;
}
/* @see loci.formats.IFormatReader#close(boolean) */
public void close(boolean fileOnly) throws IOException {
super.close(fileOnly);
if (!fileOnly) {
compression = null;
binDataOffsets = null;
binData = null;
omexml = null;
hasSPW = false;
}
}
// -- Internal FormatReader API methods --
/* @see loci.formats.FormatReader#initFile(String) */
protected void initFile(String id) throws FormatException, IOException {
if (noOME) {
throw new MissingLibraryException(OMEXMLServiceImpl.NO_OME_XML_MSG);
}
super.initFile(id);
in = new RandomAccessInputStream(id);
binData = new Vector<BinData>();
binDataOffsets = new Vector<Long>();
compression = new Vector<String>();
DefaultHandler handler = new OMEXMLHandler();
try {
RandomAccessInputStream s = new RandomAccessInputStream(id);
XMLTools.parseXML(s, handler);
s.close();
}
catch (IOException e) {
throw new FormatException("Malformed OME-XML", e);
}
int lineNumber = 1;
for (BinData bin : binData) {
int line = bin.getRow();
int col = bin.getColumn();
while (lineNumber < line) {
in.readLine();
lineNumber++;
}
binDataOffsets.add(in.getFilePointer() + col - 1);
}
if (binDataOffsets.size() == 0) {
throw new FormatException("Pixel data not found");
}
LOGGER.info("Populating metadata");
OMEXMLMetadata omexmlMeta;
OMEXMLService service;
try {
ServiceFactory factory = new ServiceFactory();
service = factory.getInstance(OMEXMLService.class);
omexmlMeta = service.createOMEXMLMetadata(omexml);
}
catch (DependencyException de) {
throw new MissingLibraryException(OMEXMLServiceImpl.NO_OME_XML_MSG, de);
}
catch (ServiceException se) {
throw new FormatException(se);
}
hasSPW = omexmlMeta.getPlateCount() > 0;
addGlobalMeta("Is SPW file", hasSPW);
// TODO
//Hashtable originalMetadata = omexmlMeta.getOriginalMetadata();
//if (originalMetadata != null) metadata = originalMetadata;
int numDatasets = omexmlMeta.getImageCount();
core = new CoreMetadata[numDatasets];
int oldSeries = getSeries();
for (int i=0; i<numDatasets; i++) {
setSeries(i);
core[i] = new CoreMetadata();
Integer w = omexmlMeta.getPixelsSizeX(i).getValue();
Integer h = omexmlMeta.getPixelsSizeY(i).getValue();
Integer t = omexmlMeta.getPixelsSizeT(i).getValue();
Integer z = omexmlMeta.getPixelsSizeZ(i).getValue();
Integer c = omexmlMeta.getPixelsSizeC(i).getValue();
if (w == null || h == null || t == null || z == null | c == null) {
throw new FormatException("Image dimensions not found");
}
Boolean endian = omexmlMeta.getPixelsBinDataBigEndian(i, 0);
String pixType = omexmlMeta.getPixelsType(i).toString();
core[i].dimensionOrder = omexmlMeta.getPixelsDimensionOrder(i).toString();
core[i].sizeX = w.intValue();
core[i].sizeY = h.intValue();
core[i].sizeT = t.intValue();
core[i].sizeZ = z.intValue();
core[i].sizeC = c.intValue();
core[i].imageCount = getSizeZ() * getSizeC() * getSizeT();
core[i].littleEndian = endian == null ? false : !endian.booleanValue();
core[i].rgb = false;
core[i].interleaved = false;
core[i].indexed = false;
core[i].falseColor = true;
core[i].pixelType = FormatTools.pixelTypeFromString(pixType);
core[i].orderCertain = true;
}
setSeries(oldSeries);
// populate assigned metadata store with the
// contents of the internal OME-XML metadata object
MetadataStore store = getMetadataStore();
MetadataTools.populatePixels(store, this);
service.convertMetadata(omexmlMeta, store);
}
// -- Helper class --
class OMEXMLHandler extends DefaultHandler {
private StringBuffer xmlBuffer;
private String currentQName;
private Locator locator;
public OMEXMLHandler() {
xmlBuffer = new StringBuffer();
}
public void characters(char[] ch, int start, int length) {
if (currentQName.indexOf("BinData") < 0) {
xmlBuffer.append(new String(ch, start, length));
}
}
public void endElement(String uri, String localName, String qName) {
xmlBuffer.append("</");
xmlBuffer.append(qName);
xmlBuffer.append(">");
}
public void startElement(String ur, String localName, String qName,
Attributes attributes)
{
currentQName = qName;
if (qName.indexOf("BinData") == -1) {
xmlBuffer.append("<");
xmlBuffer.append(qName);
for (int i=0; i<attributes.getLength(); i++) {
String key = attributes.getQName(i);
String value = attributes.getValue(i);
if (key.equals("BigEndian")) {
String endian = value.toLowerCase();
if (!endian.equals("true") && !endian.equals("false")) {
// hack for files that specify 't' or 'f' instead of
// 'true' or 'false'
if (endian.startsWith("t")) endian = "true";
else if (endian.startsWith("f")) endian = "false";
}
value = endian;
}
xmlBuffer.append(" ");
xmlBuffer.append(key);
xmlBuffer.append("=\"");
xmlBuffer.append(value);
xmlBuffer.append("\"");
}
xmlBuffer.append(">");
}
else {
binData.add(
new BinData(locator.getLineNumber(), locator.getColumnNumber()));
String compress = attributes.getValue("Compression");
compression.add(compress == null ? "" : compress);
xmlBuffer.append("<");
xmlBuffer.append(qName);
for (int i=0; i<attributes.getLength(); i++) {
String key = attributes.getQName(i);
String value = attributes.getValue(i);
if (key.equals("Length")) value = "0";
xmlBuffer.append(" ");
xmlBuffer.append(key);
xmlBuffer.append("=\"");
xmlBuffer.append(value);
xmlBuffer.append("\"");
}
xmlBuffer.append(">");
}
}
public void endDocument() {
omexml = xmlBuffer.toString();
}
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
}
class BinData {
private int row;
private int column;
public BinData(int row, int column) {
this.row = row;
this.column = column;
}
public int getRow() { return row; }
public int getColumn() { return column; }
}
}