// // IPWReader.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 loci.formats.*; /** * IPWReader is the file format reader for Image-Pro Workspace (IPW) files. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/IPWReader.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/IPWReader.java">SVN</a></dd></dl> * * @author Melissa Linkert linkert at wisc.edu */ public class IPWReader extends BaseTiffReader { // -- Constants -- private static final String NO_POI_MSG = "Jakarta POI is required to read IPW 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 (Throwable t) { noPOI = true; if (debug) LogTools.trace(t); } return r; } // -- Fields -- private Hashtable pixels; private Hashtable names; private byte[] header; // general image header data private byte[] tags; // tags data // -- Constructor -- /** Constructs a new IPW reader. */ public IPWReader() { super("Image-Pro Workspace", "ipw"); } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isThisType(byte[]) */ public boolean isThisType(byte[] block) { // all of our samples begin with 0xd0cf11e0 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); FormatTools.checkBufferSize(this, buf.length); RandomAccessStream stream = getStream(no); ifds = TiffTools.getIFDs(stream); core.littleEndian[0] = TiffTools.isLittleEndian(ifds[0]); TiffTools.getSamples(ifds[0], stream, buf); stream.close(); return buf; } // -- IFormatHandler API methods -- /* @see loci.formats.IFormatHandler#close() */ public void close() throws IOException { super.close(); pixels = null; names = null; header = null; tags = null; 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 BaseTiffReader API methods -- /* @see BaseTiffReader#initMetadata() */ public void initMetadata() throws FormatException, IOException { String directory = (String) pixels.get(new Integer(0)); String name = (String) names.get(new Integer(0)); try { r.setVar("dirName", directory); r.exec("root = fs.getRoot()"); if (!directory.equals("Root Entry")) { r.exec("dir = root.getEntry(dirName)"); r.setVar("entryName", name); r.exec("document = dir.getEntry(entryName)"); } else { r.setVar("entryName", name); r.exec("document = root.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); ifds = TiffTools.getIFDs(stream); stream.close(); } catch (ReflectException e) { } core.rgb[0] = (TiffTools.getIFDIntValue(ifds[0], TiffTools.SAMPLES_PER_PIXEL, false, 1) > 1); if (!core.rgb[0]) { core.rgb[0] = TiffTools.getIFDIntValue(ifds[0], TiffTools.PHOTOMETRIC_INTERPRETATION, false, 1) == TiffTools.RGB_PALETTE; } core.littleEndian[0] = TiffTools.isLittleEndian(ifds[0]); // parse the image description String description = new String(tags, 22, tags.length-22); addMeta("Image Description", description); // default values core.sizeZ[0] = 1; core.sizeC[0] = 1; core.sizeT[0] = getImageCount(); addMeta("slices", "1"); addMeta("channels", "1"); addMeta("frames", new Integer(getImageCount())); // parse the description to get channels/slices/times where applicable // basically the same as in SEQReader if (description != null) { StringTokenizer tokenizer = new StringTokenizer(description, "\n"); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); String label = "Timestamp"; String data; if (token.indexOf("=") != -1) { label = token.substring(0, token.indexOf("=")); data = token.substring(token.indexOf("=")+1); } else { data = token.trim(); } addMeta(label, data); if (label.equals("frames")) core.sizeZ[0] = Integer.parseInt(data); else if (label.equals("slices")) core.sizeT[0] = Integer.parseInt(data); else if (label.equals("channels")) { core.sizeC[0] = Integer.parseInt(data); } } } addMeta("Version", new String(header).trim()); Hashtable h = ifds[0]; core.sizeX[0] = TiffTools.getIFDIntValue(h, TiffTools.IMAGE_WIDTH); core.sizeY[0] = TiffTools.getIFDIntValue(h, TiffTools.IMAGE_LENGTH); core.currentOrder[0] = "XY"; if (core.sizeZ[0] == 0) core.sizeZ[0] = 1; if (core.sizeC[0] == 0) core.sizeC[0] = 1; if (core.sizeT[0] == 0) core.sizeT[0] = 1; if (core.rgb[0]) core.sizeC[0] *= 3; int maxNdx = 0, max = 0; int[] dims = {core.sizeZ[0], core.sizeC[0], core.sizeT[0]}; String[] axes = {"Z", "C", "T"}; for (int i=0; i<dims.length; i++) { if (dims[i] > max) { max = dims[i]; maxNdx = i; } } core.currentOrder[0] += axes[maxNdx]; if (maxNdx != 1) { if (core.sizeC[0] > 1) { core.currentOrder[0] += "C"; core.currentOrder[0] += (maxNdx == 0 ? axes[2] : axes[0]); } else core.currentOrder[0] += (maxNdx == 0 ? axes[2] : axes[0]) + "C"; } else { if (core.sizeZ[0] > core.sizeT[0]) core.currentOrder[0] += "ZT"; else core.currentOrder[0] += "TZ"; } int bitsPerSample = TiffTools.getIFDIntValue(ifds[0], TiffTools.BITS_PER_SAMPLE); int bitFormat = TiffTools.getIFDIntValue(ifds[0], TiffTools.SAMPLE_FORMAT); while (bitsPerSample % 8 != 0) bitsPerSample++; if (bitsPerSample == 24 || bitsPerSample == 48) bitsPerSample /= 3; core.pixelType[0] = FormatTools.UINT8; if (bitFormat == 3) core.pixelType[0] = FormatTools.FLOAT; else if (bitFormat == 2) { switch (bitsPerSample) { case 8: core.pixelType[0] = FormatTools.INT8; break; case 16: core.pixelType[0] = FormatTools.INT16; break; case 32: core.pixelType[0] = FormatTools.INT32; break; } } else { switch (bitsPerSample) { case 8: core.pixelType[0] = FormatTools.UINT8; break; case 16: core.pixelType[0] = FormatTools.UINT16; break; case 32: core.pixelType[0] = FormatTools.UINT32; break; } } // The metadata store we're working with. MetadataStore store = getMetadataStore(); FormatTools.populatePixels(store, this); store.setImage(currentId, null, (String) getMeta("Version"), null); for (int i=0; i<core.sizeC[0]; i++) { store.setLogicalChannel(i, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { if (debug) debug("IPWReader.initFile(" + id + ")"); if (noPOI) throw new FormatException(NO_POI_MSG); currentId = id; metadata = new Hashtable(); core = new CoreMetadata(1); Arrays.fill(core.orderCertain, true); getMetadataStore().createRoot(); in = new RandomAccessStream(id); pixels = new Hashtable(); names = new Hashtable(); try { r.setVar("fis", in); r.exec("fs = new POIFSFileSystem(fis)"); r.exec("dir = fs.getRoot()"); parseDir(0, r.getVar("dir")); status("Populating metadata"); initMetadata(); } catch (Throwable t) { noPOI = true; if (debug) trace(t); } } // -- Helper methods -- 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()"); 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)"); RandomAccessStream ds = new RandomAccessStream(data); ds.order(true); String entryName = (String) r.getVar("entryName"); String dirName = (String) r.getVar("dirName"); boolean isContents = entryName.equals("CONTENTS"); if (isContents) { // software version header = data; } else if (entryName.equals("FrameRate")) { // should always be exactly 4 bytes // only exists if the file has more than one image addMeta("Frame Rate", new Long(ds.readInt())); } else if (entryName.equals("FrameInfo")) { // should always be 16 bytes (if present) for(int i=0; i<data.length/2; i++) { addMeta("FrameInfo "+i, new Short(ds.readShort())); } } else if (entryName.equals("ImageInfo")) { // acquisition data tags = data; } else if (entryName.equals("ImageResponse")) { // skip this entry } else if (entryName.equals("ImageTIFF")) { // pixel data String name; if (!dirName.equals("Root Entry")) { name = dirName.substring(11, dirName.length()); } else name = "0"; Integer imageNum = Integer.valueOf(name); pixels.put(imageNum, dirName); names.put(imageNum, entryName); core.imageCount[0]++; } ds.close(); r.exec("dis.close()"); if (debug) { print(depth + 1, data.length + " bytes read."); } } } } /** Retrieve the file corresponding to the given image number. */ private RandomAccessStream getStream(int no) throws IOException { try { String directory = (String) pixels.get(new Integer(no)); String name = (String) names.get(new Integer(no)); r.setVar("dirName", directory); r.exec("root = fs.getRoot()"); if (!directory.equals("Root Entry")) { r.exec("dir = root.getEntry(dirName)"); r.setVar("entryName", name); r.exec("document = dir.getEntry(entryName)"); } else { r.setVar("entryName", name); r.exec("document = root.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]; r.setVar("data", b); r.exec("dis.read(data)"); return new RandomAccessStream(b); } catch (ReflectException e) { noPOI = true; return null; } } /** 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()); } }