// // OIBReader.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.text.*; import java.util.*; import loci.formats.*; /** * OIBReader is the file format reader for Fluoview FV1000 OIB files. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/OIBReader.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/OIBReader.java">SVN</a></dd></dl> * * @author Melissa Linkert linkert at wisc.edu */ public class OIBReader extends FormatReader { // -- Constants -- private static final String NO_POI_MSG = "Jakarta POI is required to read OIB files. Please " + "obtain poi-loci.jar from http://loci.wisc.edu/ome/formats.html"; // -- Static fields -- private static boolean noPOI = false; private static ReflectedUniverse r = createReflectedUniverse(); private static ReflectedUniverse createReflectedUniverse() { r = null; try { r = new ReflectedUniverse(); r.exec("import org.apache.poi.poifs.filesystem.POIFSFileSystem"); r.exec("import org.apache.poi.poifs.filesystem.DirectoryEntry"); r.exec("import org.apache.poi.poifs.filesystem.DocumentEntry"); r.exec("import org.apache.poi.poifs.filesystem.DocumentInputStream"); r.exec("import java.util.Iterator"); } catch (ReflectException exc) { noPOI = true; } return r; } // -- Fields -- /** Number of images. */ private Vector nImages; /** Image width. */ private Vector width; /** Image height. */ private Vector height; /** Number of channels. */ private Vector nChannels; /** Number of timepoints. */ private Vector tSize; /** Number of Z slices. */ private Vector zSize; /** Number of bytes per pixel. */ private Vector bpp; /** Hashtable containing the directory entry for each plane. */ private Vector pixels; /** * Hashtable containing the document name for each plane, * indexed by the plane number. */ private Vector names; private Vector rgb; /** Axis data. */ private String[] labels = new String[9]; private String[] dims = new String[9]; private String[] starts = new String[9]; private String[] stops = new String[9]; // -- Constructor -- /** Constructs a new OIB reader. */ public OIBReader() { super("Fluoview FV1000 OIB", "oib"); } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isThisType(byte[]) */ public boolean isThisType(byte[] block) { return (block[0] == 0xd0 && block[1] == 0xcf && block[2] == 0x11 && block[3] == 0xe0); } /* @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); try { Integer ii = new Integer(no); String directory = (String) ((Hashtable) pixels.get(series)).get(ii); String name = (String) ((Hashtable) names.get(series)).get(ii); r.setVar("dirName", directory); r.exec("root = fs.getRoot()"); r.exec("dir = root.getEntry(dirName)"); r.setVar("entryName", name); r.exec("document = dir.getEntry(entryName)"); r.exec("dis = new DocumentInputStream(document)"); r.exec("numBytes = dis.available()"); int numBytes = ((Integer) r.getVar("numBytes")).intValue(); byte[] b = new byte[numBytes + 4]; // append 0 for final offset r.setVar("data", b); r.exec("dis.read(data)"); RandomAccessStream stream = new RandomAccessStream(b); Hashtable[] ifds = TiffTools.getIFDs(stream); TiffTools.getSamples(ifds[0], stream, buf); stream.close(); return buf; } catch (ReflectException e) { throw new FormatException(e); } } // -- IFormatHandler API methods -- /* @see loci.formats.IFormatHandler#close() */ public void close() throws IOException { super.close(); String[] vars = {"dirName", "root", "dir", "document", "dis", "numBytes", "data", "fis", "fs", "iter", "isInstance", "isDocument", "entry", "documentName", "entryName"}; for (int i=0; i<vars.length; i++) r.setVar(vars[i], null); } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { if (debug) debug("OIBReader.initFile(" + id + ")"); if (noPOI) throw new FormatException(NO_POI_MSG); super.initFile(id); width = new Vector(); height = new Vector(); nChannels = new Vector(); zSize = new Vector(); tSize = new Vector(); pixels = new Vector(); names = new Vector(); nImages = new Vector(); bpp = new Vector(); bpp.add(new Integer(0)); rgb = new Vector(); try { in = new RandomAccessStream(id); if (in.length() % 4096 != 0) { in.setExtend(4096 - (int) (in.length() % 4096)); } r.setVar("fis", in); r.exec("fs = new POIFSFileSystem(fis)"); r.exec("dir = fs.getRoot()"); parseDir(0, r.getVar("dir")); int numSeries = width.size(); status("Sorting images"); // sort names for (int i=0; i<numSeries; i++) { Vector newKeys = new Vector(); Vector comps = new Vector(); Enumeration keys = ((Hashtable) names.get(i)).keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); String value = (String) ((Hashtable) names.get(i)).get(key); int comp = Integer.parseInt(value.substring(value.indexOf("0"))); int size = newKeys.size(); for (int j=0; j<size; j++) { if (comp < ((Integer) comps.get(j)).intValue()) { newKeys.add(j, value); comps.add(j, new Integer(comp)); j = size; } else if (j == size - 1) { newKeys.add(value); comps.add(new Integer(comp)); j = size; } } if (newKeys.size() == 0) { newKeys.add(value); comps.add(new Integer(comp)); } } Hashtable newNames = new Hashtable(); for (int j=0; j<newKeys.size(); j++) { newNames.put(new Integer(j), newKeys.get(j)); } names.setElementAt(newNames, i); } status("Populating metadata"); for (int i=0; i<labels.length; i++) { if (labels[i] == null) labels[i] = ""; if (dims[i] == null) dims[i] = "0"; if (starts[i] == null) starts[i] = "0"; if (stops[i] == null) stops[i] = "0"; } for (int i=0; i<labels.length; i++) { if (labels[i].equals("\"X\"") || labels[i].equals("\"Y\"")) { } else if (labels[i].equals("\"C\"")) { if (!starts[i].equals(stops[i])) nChannels.add(new Integer(dims[i])); else nChannels.add(new Integer(1)); } else if (labels[i].equals("\"Z\"")) { if (!starts[i].equals(stops[i])) zSize.add(new Integer(dims[i])); else zSize.add(new Integer(1)); } else if (labels[i].equals("\"T\"")) { if (!starts[i].equals(stops[i])) tSize.add(new Integer(dims[i])); else tSize.add(new Integer(1)); } else if (!dims[i].equals("0")) { if (nChannels.size() > 0) { int ch = ((Integer) nChannels.get(nChannels.size() - 1)).intValue(); ch *= Integer.parseInt(dims[i]); nChannels.setElementAt(new Integer(ch), nChannels.size() - 1); } else nChannels.add(new Integer(dims[i])); } } core = new CoreMetadata(numSeries); for (int i=0; i<numSeries; i++) { core.indexed[i] = false; core.falseColor[i] = false; core.sizeX[i] = ((Integer) width.get(i)).intValue(); core.sizeY[i] = ((Integer) height.get(i)).intValue(); if (i < zSize.size()) { core.sizeZ[i] = ((Integer) zSize.get(i)).intValue(); } else core.sizeZ[i] = 1; if (i < nChannels.size()) { core.sizeC[i] = ((Integer) nChannels.get(i)).intValue(); } else core.sizeC[i] = 1; if (i < tSize.size()) { core.sizeT[i] = ((Integer) tSize.get(i)).intValue(); } else core.sizeT[i] = 1; if (core.sizeZ[i] == 0) core.sizeZ[i]++; if (core.sizeT[i] == 0) core.sizeT[i]++; core.currentOrder[i] = (core.sizeZ[i] > core.sizeT[i]) ? "XYCZT" : "XYCTZ"; core.imageCount[i] = ((Integer) nImages.get(i)).intValue(); if (core.imageCount[i] > core.sizeZ[i] * core.sizeT[i] * core.sizeC[i]) { int diff = core.imageCount[i] - (core.sizeZ[i] * core.sizeT[i] * core.sizeC[i]); if (diff % core.sizeZ[i] == 0 && core.sizeZ[i] > 1) { while (core.imageCount[i] > core.sizeZ[i] * core.sizeT[i] * core.sizeC[i]) { core.sizeT[i]++; } } else if (diff % core.sizeT[i] == 0 && core.sizeT[i] > 1) { while (core.imageCount[i] > core.sizeZ[i] * core.sizeT[i] * core.sizeC[i]) { core.sizeZ[i]++; } } else if (diff % core.sizeC[i] == 0) { if (core.sizeZ[i] > core.sizeT[i]) { while (core.imageCount[i] > core.sizeZ[i] * core.sizeC[i] * core.sizeT[i]) { core.sizeZ[i]++; } } else { while (core.imageCount[i] > core.sizeZ[i] * core.sizeC[i] * core.sizeT[i]) { core.sizeT[i]++; } } } } int oldSeries = getSeries(); setSeries(i); while (core.imageCount[i] < core.sizeZ[i] * core.sizeT[i] * getEffectiveSizeC()) { core.imageCount[i]++; } nImages.setElementAt(new Integer(core.imageCount[i]), i); setSeries(oldSeries); core.rgb[i] = ((Boolean) rgb.get(i)).booleanValue(); core.interleaved[i] = false; core.metadataComplete[i] = true; } Integer ii = new Integer(0); String directory = (String) ((Hashtable) pixels.get(series)).get(ii); String name = (String) ((Hashtable) names.get(series)).get(ii); r.setVar("dirName", directory); r.exec("root = fs.getRoot()"); r.exec("dir = root.getEntry(dirName)"); r.setVar("entryName", name); r.exec("document = dir.getEntry(entryName)"); r.exec("dis = new DocumentInputStream(document)"); r.exec("numBytes = dis.available()"); int numBytes = ((Integer) r.getVar("numBytes")).intValue(); byte[] b = new byte[numBytes + 4]; // append 0 for final offset r.setVar("data", b); r.exec("dis.read(data)"); RandomAccessStream stream = new RandomAccessStream(b); Hashtable[] ifds = TiffTools.getIFDs(stream); Arrays.fill(core.littleEndian, !TiffTools.isLittleEndian(ifds[0])); } catch (ReflectException e) { throw new FormatException(e); } try { initMetadata(); } catch (FormatException exc) { if (debug) trace(exc); } catch (IOException exc) { if (debug) trace(exc); } } // -- Helper methods -- /** Initialize metadata hashtable and OME-XML structure. */ private void initMetadata() throws FormatException, IOException { for (int i=0; i<width.size(); i++) { switch (((Integer) bpp.get(0)).intValue() % 3) { case 2: core.pixelType[i] = FormatTools.UINT16; break; default: core.pixelType[i] = FormatTools.UINT8; } } MetadataStore store = getMetadataStore(); String name = (String) getMeta("[File Info] - DataName"); if (name == null) name = currentId; FormatTools.populatePixels(store, this); for (int i=0; i<width.size(); i++) { String acquisition = "[Acquisition Parameters Common] - "; String stamp = (String) getMeta(acquisition + "ImageCaputreDate"); if (stamp != null) { stamp = stamp.substring(1, stamp.length() - 1); SimpleDateFormat parse = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = parse.parse(stamp, new ParsePosition(0)); SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); stamp = fmt.format(date); } store.setImage(name, stamp, null, new Integer(i)); String pre = "[Reference Image Parameter] - "; String x = (String) getMeta(pre + "WidthConvertValue"); String y = (String) getMeta(pre + "HeightConvertValue"); store.setDimensions(x == null ? null : new Float(x), y == null ? null : new Float(y), null, null, null, new Integer(i)); for (int j=0; j<core.sizeC[i]; j++) { String prefix = "[Channel " + (j + 1) + " Parameters] - "; String gain = (String) getMeta(prefix + "AnalogPMTGain"); String offset = (String) getMeta(prefix + "AnalogPMTOffset"); String voltage = (String) getMeta(prefix + "AnalogPMTVoltage"); if (gain != null) gain = gain.replaceAll("\"", ""); if (offset != null) offset = offset.replaceAll("\"", ""); if (voltage != null) voltage = voltage.replaceAll("\"", ""); store.setDetector(null, null, null, null, null, voltage == null ? null : new Float(voltage), null, null, new Integer(j)); store.setLogicalChannel(j, null, null, null, null, null, null, null, null, offset == null ? null : new Float(offset), gain == null ? null : new Float(gain), null, null, null, null, null, null, null, null, null, null, null, null, null, new Integer(i)); } String laserCount = (String) getMeta(acquisition + "Number of use Laser"); int numLasers = laserCount == null ? 0 : Integer.parseInt(laserCount); for (int j=0; j<numLasers; j++) { String wave = (String) getMeta(acquisition + "LaserWavelength0" + (j + 1)); if (wave == null) wave = "0"; store.setLaser(null, null, new Integer(wave), null, null, null, null, null, null, null, new Integer(j)); } } } protected void parseDir(int depth, Object dir) throws IOException, FormatException, ReflectException { r.setVar("dir", dir); r.exec("dirName = dir.getName()"); r.setVar("depth", depth); r.exec("iter = dir.getEntries()"); Iterator iter = (Iterator) r.getVar("iter"); while (iter.hasNext()) { r.setVar("entry", iter.next()); r.exec("isInstance = entry.isDirectoryEntry()"); r.exec("isDocument = entry.isDocumentEntry()"); boolean isInstance = ((Boolean) r.getVar("isInstance")).booleanValue(); boolean isDocument = ((Boolean) r.getVar("isDocument")).booleanValue(); r.setVar("dir", dir); r.exec("dirName = dir.getName()"); if (isInstance) { status("Parsing embedded folder (" + (depth + 1) + ")"); parseDir(depth + 1, r.getVar("entry")); } else if (isDocument) { status("Parsing embedded file (" + depth + ")"); r.exec("entryName = entry.getName()"); if (debug) { print(depth + 1, "Found document: " + r.getVar("entryName")); } r.exec("dis = new DocumentInputStream(entry)"); r.exec("numBytes = dis.available()"); int numbytes = ((Integer) r.getVar("numBytes")).intValue(); byte[] data = new byte[numbytes + 4]; // append 0 for final offset r.setVar("data", data); r.exec("dis.read(data)"); String entryName = (String) r.getVar("entryName"); String dirName = (String) r.getVar("dirName"); // check the first 2 bytes of the stream byte[] b = {data[0], data[1], data[2], data[3]}; if (data[0] == 0x42 && data[1] == 0x4d) { // this is the thumbnail } else if (TiffTools.checkHeader(b) != null) { // this is an actual image plane RandomAccessStream ras = new RandomAccessStream(data); Hashtable ifd = TiffTools.getIFDs(ras)[0]; ras.close(); int w = (int) TiffTools.getImageWidth(ifd); int h = (int) TiffTools.getImageLength(ifd); boolean isRGB = TiffTools.getSamplesPerPixel(ifd) > 1; if (!isRGB) { int p = TiffTools.getPhotometricInterpretation(ifd); isRGB = p == TiffTools.RGB_PALETTE || p == TiffTools.CFA_ARRAY || p == TiffTools.RGB; } boolean added = false; for (int i=0; i<width.size(); i++) { if (((Integer) width.get(i)).intValue() == w && ((Integer) height.get(i)).intValue() == h) { int num = ((Integer) nImages.get(i)).intValue(); ((Hashtable) pixels.get(i)).put(new Integer(num), dirName); ((Hashtable) names.get(i)).put(new Integer(num), entryName); num++; nImages.setElementAt(new Integer(num), i); added = true; } } if (!added) { Hashtable ht = new Hashtable(); ht.put(new Integer(0), dirName); pixels.add(ht); ht = new Hashtable(); ht.put(new Integer(0), entryName); names.add(ht); nImages.add(new Integer(1)); width.add(new Integer(w)); height.add(new Integer(h)); rgb.add(new Boolean(isRGB)); } } else if (entryName.equals("OibInfo.txt")) { } else { //else if (data[0] == (byte) 0xff && data[1] == (byte) 0xfe) { String ini = DataTools.stripString(new String(data)); StringTokenizer st = new StringTokenizer(ini, "\n"); String prefix = ""; while (st.hasMoreTokens()) { String line = st.nextToken().trim(); if (!line.startsWith("[") && (line.indexOf("=") > 0)) { String key = line.substring(0, line.indexOf("=")).trim(); String value = line.substring(line.indexOf("=") + 1).trim(); if (prefix.equals("[FileInformation] - ") && key.equals("Resolution")) { int max = Integer.parseInt(value); int bytes = ((Integer) bpp.get(0)).intValue(); while (Math.pow(2, bytes) < max) bytes++; bytes /= 8; for (int i=0; i<bpp.size(); i++) { bpp.setElementAt(new Integer(bytes), i); } } if (prefix.indexOf("Red") == -1 && prefix.indexOf("Green") == -1 && prefix.indexOf("Blue") == -1) { addMeta(prefix + key, value); if (prefix.startsWith("[Axis ") && prefix.endsWith("Parameters Common] - ")) { int ndx = Integer.parseInt( prefix.substring(6, prefix.indexOf("P")).trim()); if (key.equals("AxisCode")) labels[ndx] = value; else if (key.equals("MaxSize")) dims[ndx] = value; else if (key.equals("StartPosition")) starts[ndx] = value; else if (key.equals("EndPosition")) stops[ndx] = value; } } } else { if (line.indexOf("[") == 2) { line = line.substring(2, line.length()); } prefix = line + " - "; } } data = null; } r.exec("dis.close()"); } } } /** Debugging helper method. */ protected void print(int depth, String s) { StringBuffer sb = new StringBuffer(); for (int i=0; i<depth; i++) sb.append(" "); sb.append(s); debug(sb.toString()); } }