//
// OMETiffReader.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 javax.xml.parsers.*;
import loci.formats.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
/**
* OMETiffReader is the file format reader for
* <a href="http://www.loci.wisc.edu/ome/ome-tiff-spec.html">OME-TIFF</a>
* files.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OMETiffReader.java">Trac</a>,
* <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OMETiffReader.java">SVN</a></dd></dl>
*/
public class OMETiffReader extends BaseTiffReader {
// -- Constants --
/** Factory for generating SAX parsers. */
public static final SAXParserFactory SAX_FACTORY =
SAXParserFactory.newInstance();
// -- Fields --
/** List of used files. */
private String[] usedFiles;
/** List of Image IDs. */
private Vector imageIDs;
/** List of Pixels IDs. */
private Vector pixelsIDs;
private Vector tempIfdMap, tempFileMap, tempIfdCount;
private int currentFile, currentSeries, seriesCount;
private int[] numIFDs;
private int[][] ifdMap, fileMap;
private boolean lsids, isWiscScan;
private Hashtable[][] usedIFDs;
// -- Constructor --
public OMETiffReader() {
super("OME-TIFF", new String[] {"tif", "tiff"});
}
// -- Internal BaseTiffReader API methods --
/* @see BaseTiffReader#initStandardMetadata() */
protected void initStandardMetadata() throws FormatException, IOException {
super.initStandardMetadata();
OMETiffHandler handler = new OMETiffHandler();
String comment = (String) getMeta("Comment");
currentSeries = -1;
seriesCount = 0;
imageIDs = null;
pixelsIDs = null;
lsids = true;
tempIfdMap = new Vector();
tempFileMap = new Vector();
tempIfdCount = new Vector();
try {
SAXParser parser = SAX_FACTORY.newSAXParser();
parser.parse(new ByteArrayInputStream(comment.getBytes()), handler);
}
catch (ParserConfigurationException exc) {
throw new FormatException(exc);
}
catch (SAXException exc) {
throw new FormatException(exc);
}
// MAJOR HACK : check for OME-XML in the comment of the second IFD
// There is a version of WiscScan which writes OME-XML to every IFD,
// but with SizeZ and SizeT equal to 1.
String s = null;
if (ifds.length > 1) {
s = (String) TiffTools.getIFDValue(ifds[1], TiffTools.IMAGE_DESCRIPTION);
}
isWiscScan = s != null && s.indexOf("ome.xsd") != -1;
// look for additional files in the dataset
Vector files = new Vector();
Location l = new Location(currentId);
l = l.getAbsoluteFile().getParentFile();
String[] fileList = l.list();
if (!lsids) {
fileList = new String[] {currentId};
LogTools.println("Not searching for other files - " +
"Image LSID not present");
}
for (int i=0; i<fileList.length; i++) {
String check = fileList[i].toLowerCase();
if (check.endsWith(".tif") || check.endsWith(".tiff")) {
status("Checking " + fileList[i]);
String iid = l.getAbsolutePath() + File.separator + fileList[i];
String icomment = TiffTools.getComment(iid);
boolean addToList = true;
if (imageIDs != null) {
for (int k=0; k<imageIDs.size(); k++) {
if (icomment.indexOf((String) imageIDs.get(k)) == -1) {
addToList = false;
break;
}
}
if (addToList) {
for (int k=0; k<pixelsIDs.size(); k++) {
if (icomment.indexOf((String) pixelsIDs.get(k)) == -1) {
addToList = false;
break;
}
}
}
}
if (addToList) files.add(iid);
}
}
// parse grouped files
ifdMap = new int[seriesCount][];
fileMap = new int[seriesCount][];
numIFDs = new int[seriesCount];
for (int i=0; i<seriesCount; i++) {
int ii = ((Integer) tempIfdCount.get(i)).intValue();
ifdMap[i] = new int[ii];
fileMap[i] = new int[ii];
}
// copy temp IFD/file maps
for (int i=0; i<tempIfdMap.size(); i++) {
Vector v = (Vector) tempIfdMap.get(i);
for (int j=0; j<v.size(); j++) {
ifdMap[i][j] = ((Integer) v.get(j)).intValue();
}
numIFDs[i] = v.size();
}
for (int i=0; i<tempFileMap.size(); i++) {
Vector v = (Vector) tempFileMap.get(i);
for (int j=0; j<v.size(); j++) {
fileMap[i][j] = ((Integer) v.get(j)).intValue();
}
}
usedFiles = (String[]) files.toArray(new String[0]);
usedIFDs = new Hashtable[usedFiles.length][];
for (int i=0; i<usedFiles.length; i++) {
if (usedFiles[i].endsWith(currentId)) {
usedIFDs[i] = ifds;
continue;
}
status("Parsing " + usedFiles[i]);
currentSeries = -1;
tempIfdMap = null;
tempFileMap = null;
tempIfdCount = null;
currentFile = i;
usedIFDs[i] = TiffTools.getIFDs(new RandomAccessStream(usedFiles[i]));
String c = (String)
TiffTools.getIFDValue(usedIFDs[i][0], TiffTools.IMAGE_DESCRIPTION);
try {
SAXParser parser = SAX_FACTORY.newSAXParser();
parser.parse(new ByteArrayInputStream(c.getBytes()), handler);
}
catch (ParserConfigurationException exc) {
throw new FormatException(exc);
}
catch (SAXException exc) {
throw new FormatException(exc);
}
}
for (int i=0; i<getSeriesCount(); i++) {
if (numIFDs != null && lsids) {
if (numIFDs[i] < core.imageCount[i]) {
LogTools.println("Too few IFDs; got " + numIFDs[i] +
", expected " + core.imageCount[i]);
}
else if (numIFDs[i] > core.imageCount[i]) {
LogTools.println("Too many IFDs; got " + numIFDs[i] +
", expected " + core.imageCount[i]);
}
}
else if (core.imageCount[i] > ifds.length) {
core.imageCount[i] = ifds.length;
if (core.sizeZ[i] > ifds.length) {
core.sizeZ[i] = ifds.length / (core.rgb[i] ? core.sizeC[i] : 1);
core.sizeT[i] = 1;
if (!core.rgb[i]) core.sizeC[i] = 1;
}
else if (core.sizeT[i] > ifds.length) {
core.sizeT[i] = ifds.length / (core.rgb[i] ? core.sizeC[i] : 1);
core.sizeZ[i] = 1;
if (!core.rgb[i]) core.sizeC[i] = 1;
}
}
}
}
/* @see BaseTiffReader#initMetadataStore() */
protected void initMetadataStore() {
String comment = (String) getMeta("Comment");
metadata.remove("Comment");
MetadataStore store = getMetadataStore();
MetadataTools.convertMetadata(comment, store);
}
// -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#getUsedFiles() */
public String[] getUsedFiles() {
FormatTools.assertId(currentId, true, 1);
return usedFiles;
}
/* @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);
int ifd = ifdMap[series][no];
int fileIndex = fileMap[series][no];
in = new RandomAccessStream(usedFiles[fileIndex]);
TiffTools.getSamples(usedIFDs[fileIndex][ifd], in, buf);
in.close();
return swapIfRequired(buf);
}
// -- IFormatHandler API methods --
/* @see loci.formats.IFormatHandler#isThisType(String, boolean) */
public boolean isThisType(String name, boolean open) {
if (!super.isThisType(name, open)) return false; // check extension
if (!open) return true; // not allowed to check the file contents
// just checking the filename isn't enough to differentiate between
// OME-TIFF and regular TIFF; open the file and check more thoroughly
try {
RandomAccessStream ras = new RandomAccessStream(name);
Hashtable ifd = TiffTools.getFirstIFD(ras);
ras.close();
if (ifd == null) return false;
String comment = (String)
ifd.get(new Integer(TiffTools.IMAGE_DESCRIPTION));
if (comment == null) return false;
return comment.indexOf("ome.xsd") >= 0;
}
catch (IOException e) { return false; }
}
// -- Helper class --
/** SAX handler for parsing XML. */
private class OMETiffHandler extends DefaultHandler {
private String order;
private int sizeZ, sizeC, sizeT;
public void startElement(String uri, String localName, String qName,
Attributes attributes)
{
if (qName.equals("Image")) {
String id = attributes.getValue("ID");
if (id.startsWith("urn:lsid:")) {
if (imageIDs == null) imageIDs = new Vector();
imageIDs.add(id);
}
else lsids = false;
}
else if (qName.equals("Pixels")) {
currentSeries++;
String id = attributes.getValue("ID");
if (id.startsWith("urn:lsid:")) {
if (pixelsIDs == null) pixelsIDs = new Vector();
pixelsIDs.add(id);
}
else lsids = false;
order = attributes.getValue("DimensionOrder");
sizeZ = Integer.parseInt(attributes.getValue("SizeZ"));
sizeC = Integer.parseInt(attributes.getValue("SizeC"));
sizeT = Integer.parseInt(attributes.getValue("SizeT"));
if (tempIfdCount != null) {
tempIfdCount.add(new Integer(sizeZ * sizeC * sizeT));
}
if (sizeZ < 1) sizeZ = 1;
if (sizeC < 1) sizeC = 1;
if (sizeT < 1) sizeT = 1;
if (core.sizeZ.length <= currentSeries) {
CoreMetadata tempCore = new CoreMetadata(currentSeries + 1);
int ss = core.sizeX.length;
System.arraycopy(core.sizeX, 0, tempCore.sizeX, 0, ss);
System.arraycopy(core.sizeY, 0, tempCore.sizeY, 0, ss);
System.arraycopy(core.sizeZ, 0, tempCore.sizeZ, 0, ss);
System.arraycopy(core.sizeC, 0, tempCore.sizeC, 0, ss);
System.arraycopy(core.sizeT, 0, tempCore.sizeT, 0, ss);
System.arraycopy(core.thumbSizeX, 0, tempCore.thumbSizeX, 0, ss);
System.arraycopy(core.thumbSizeY, 0, tempCore.thumbSizeY, 0, ss);
System.arraycopy(core.pixelType, 0, tempCore.pixelType, 0, ss);
System.arraycopy(core.imageCount, 0, tempCore.imageCount, 0, ss);
System.arraycopy(core.currentOrder, 0, tempCore.currentOrder, 0, ss);
System.arraycopy(core.orderCertain, 0, tempCore.orderCertain, 0, ss);
System.arraycopy(core.rgb, 0, tempCore.rgb, 0, ss);
System.arraycopy(core.littleEndian, 0, tempCore.littleEndian, 0, ss);
System.arraycopy(core.interleaved, 0, tempCore.interleaved, 0, ss);
System.arraycopy(core.indexed, 0, tempCore.indexed, 0, ss);
System.arraycopy(core.falseColor, 0, tempCore.falseColor, 0, ss);
System.arraycopy(core.metadataComplete, 0,
tempCore.metadataComplete, 0, ss);
core = tempCore;
}
core.sizeX[currentSeries] =
Integer.parseInt(attributes.getValue("SizeX"));
core.sizeY[currentSeries] =
Integer.parseInt(attributes.getValue("SizeY"));
core.sizeZ[currentSeries] = sizeZ;
core.sizeC[currentSeries] = sizeC;
core.sizeT[currentSeries] = sizeT;
core.currentOrder[currentSeries] = order;
core.rgb[currentSeries] = isRGB();
core.indexed[currentSeries] = isIndexed();
core.falseColor[currentSeries] = isFalseColor();
int sc = core.sizeC[currentSeries];
if (core.rgb[currentSeries] && !core.indexed[currentSeries]) sc /= 3;
if (core.indexed[currentSeries]) core.sizeC[currentSeries] *= 3;
core.imageCount[currentSeries] =
core.sizeZ[currentSeries] * sc * core.sizeT[currentSeries];
core.pixelType[currentSeries] =
FormatTools.pixelTypeFromString(attributes.getValue("PixelType"));
if (core.pixelType[currentSeries] == FormatTools.INT8 ||
core.pixelType[currentSeries] == FormatTools.INT16 ||
core.pixelType[currentSeries] == FormatTools.INT32)
{
core.pixelType[currentSeries]++;
}
if (isWiscScan) core.sizeT[currentSeries] = core.imageCount[0];
core.orderCertain[currentSeries] = true;
seriesCount++;
}
else if (qName.equals("TiffData")) {
String ifd = attributes.getValue("IFD");
String numPlanes = attributes.getValue("NumPlanes");
String z = attributes.getValue("FirstZ");
String c = attributes.getValue("FirstC");
String t = attributes.getValue("FirstT");
if (ifd == null || ifd.equals("")) ifd = "0";
if (numPlanes == null || numPlanes.equals("")) {
if (usedIFDs != null) numPlanes = "" + usedIFDs[currentSeries].length;
else numPlanes = "" + ifds.length;
}
if (z == null || z.equals("")) z = "0";
if (c == null || c.equals("")) c = "0";
if (t == null || t.equals("")) t = "0";
try {
if (usedIFDs != null && usedIFDs[currentFile] != null) {
int f = Integer.parseInt(ifd);
int x = (int) TiffTools.getImageWidth(usedIFDs[currentFile][f]);
int y = (int) TiffTools.getImageLength(usedIFDs[currentFile][f]);
if (x != core.sizeX[currentSeries]) {
LogTools.println("Mismatched width: got " +
core.sizeX[currentSeries] + ", expected " + x);
core.sizeX[currentSeries] = x;
}
if (y != core.sizeY[currentSeries]) {
LogTools.println("Mismatched height: got " +
core.sizeY[currentSeries] + ", expected " + y);
core.sizeY[currentSeries] = y;
}
}
}
catch (FormatException e) { }
int idx = FormatTools.getIndex(order, sizeZ, sizeC, sizeT,
sizeZ * sizeC * sizeT, Integer.parseInt(z), Integer.parseInt(c),
Integer.parseInt(t));
if (tempIfdMap != null) {
Vector v = new Vector(sizeZ * sizeC * sizeT);
Vector y = new Vector(sizeZ * sizeC * sizeT);
if (tempIfdMap.size() >= seriesCount && tempIfdMap.size() > 0) {
v = (Vector) tempIfdMap.get(seriesCount - 1);
y = (Vector) tempFileMap.get(seriesCount - 1);
}
else {
for (int i=0; i<sizeZ*sizeC*sizeT; i++) {
v.add(new Integer(-1));
y.add(new Integer(-1));
}
}
v.setElementAt(new Integer(ifd), idx);
y.setElementAt(new Integer(0), idx);
for (int i=1; i<Integer.parseInt(numPlanes); i++) {
v.setElementAt(new Integer(Integer.parseInt(ifd) + i), idx + i);
y.setElementAt(new Integer(0), idx + i);
}
if (tempIfdMap.size() >= seriesCount) {
tempIfdMap.setElementAt(v, seriesCount - 1);
tempFileMap.setElementAt(y, seriesCount - 1);
}
else {
tempIfdMap.add(v);
tempFileMap.add(y);
}
}
else {
ifdMap[currentSeries][idx] = Integer.parseInt(ifd);
fileMap[currentSeries][idx] = currentFile;
for (int i=1; i<Integer.parseInt(numPlanes); i++) {
ifdMap[currentSeries][idx + i] = ifdMap[currentSeries][idx] + i;
fileMap[currentSeries][idx + i] = currentFile;
}
}
}
}
}
}