// // ZeissZVIReader.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.*; import loci.formats.codec.JPEGCodec; /** * ZeissZVIReader is the file format reader for Zeiss ZVI files. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="https://skyking.microscopy.wisc.edu/trac/java/browser/trunk/loci/formats/in/ZeissZVIReader.java">Trac</a>, * <a href="https://skyking.microscopy.wisc.edu/svn/java/trunk/loci/formats/in/ZeissZVIReader.java">SVN</a></dd></dl> * * @author Melissa Linkert linkert at wisc.edu */ public class ZeissZVIReader extends FormatReader { // -- Constants -- private static final String NO_POI_MSG = "Jakarta POI is required to read ZVI files. Please " + "obtain poi-loci.jar from http://loci.wisc.edu/ome/formats.html"; // -- Static fields -- private LegacyZVIReader legacy = new LegacyZVIReader(); 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; if (debug) LogTools.trace(exc); } return r; } // -- Fields -- /** Flag set to true if we need to use the legacy reader. */ private boolean needLegacy = false; /** Number of bytes per pixel. */ private int bpp; /** Hashtable containing the directory entry for each plane. */ private Hashtable pixels; /** * Hashtable containing the document name for each plane, * indexed by the plane number. */ private Hashtable names; /** Vector containing Z indices. */ private Vector zIndices; /** Vector containing C indices. */ private Vector cIndices; /** Vector containing T indices. */ private Vector tIndices; private Hashtable offsets; private int zIndex = -1, cIndex = -1, tIndex = -1; private boolean isTiled; private int tileRows, tileColumns; private boolean isJPEG; // -- Constructor -- /** Constructs a new ZeissZVI reader. */ public ZeissZVIReader() { super("Zeiss Vision Image (ZVI)", "zvi"); } // -- 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#setMetadataStore(MetadataStore) */ public void setMetadataStore(MetadataStore store) { FormatTools.assertId(currentId, false, 1); super.setMetadataStore(store); if (noPOI || needLegacy) legacy.setMetadataStore(store); } /* @see loci.formats.IFormatReader#openBytes(int, byte[]) */ public byte[] openBytes(int no, byte[] buf) throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); if (noPOI || needLegacy) return legacy.openBytes(no, buf); FormatTools.checkPlaneNumber(this, no); FormatTools.checkBufferSize(this, buf.length); try { int tiles = tileRows * tileColumns; if (tiles == 0) { tiles = 1; tileRows = 1; tileColumns = 1; } int start = no * tiles; int bytes = FormatTools.getBytesPerPixel(core.pixelType[0]) * getRGBChannelCount(); int ex = core.sizeX[0] / tileColumns; int ey = core.sizeY[0] / tileRows; int row = ex * bytes; int[] tileOrder = new int[tiles]; if (tiles == 1) tileOrder[0] = no; else { int p = 0; for (int r=0; r<tileRows; r++) { for (int c=0; c<tileColumns; c++) { int n = (no % core.sizeC[0]) + (tiles * core.sizeC[0] * (no / core.sizeC[0])); if (r % 2 == 0) { tileOrder[p] = p*core.sizeC[0] + n; } else { tileOrder[p] = (tileColumns - c)*core.sizeC[0]; if (p > 0 && c == 0) tileOrder[p] += tileOrder[p - 1]; else if (c > 0) tileOrder[p] = tileOrder[p - 1] - core.sizeC[0]; } p++; } } } for (int i=start; i<start+tiles; i++) { Integer ii = new Integer(tileOrder[i - start]); Object directory = pixels.get(ii); String name = (String) names.get(ii); r.setVar("dir", directory); r.setVar("entryName", name); r.exec("document = dir.getEntry(entryName)"); r.exec("dis = new DocumentInputStream(document)"); r.exec("numBytes = dis.available()"); r.setVar("skipBytes", ((Integer) offsets.get(ii)).longValue()); r.exec("blah = dis.skip(skipBytes)"); r.setVar("data", buf); int xf = ((i - start) % tileColumns) * ex * bytes; int yf = ((i - start) / tileRows) * ey; int offset = yf*core.sizeX[0]*bytes + xf; for (int y=0; y<ey; y++) { r.setVar("offset", offset); r.setVar("len", row); try { r.exec("dis.read(data, offset, len)"); } catch (ReflectException e) { } offset += core.sizeX[0]*bytes; } if (isJPEG) { JPEGCodec codec = new JPEGCodec(); buf = codec.decompress(buf); } } if (bpp > 6) bpp = 1; if (bpp == 3) { // reverse bytes in groups of 3 to account for BGR storage for (int i=0; i<buf.length; i+=3) { byte b = buf[i + 2]; buf[i + 2] = buf[i]; buf[i] = b; } } return buf; } catch (ReflectException e) { needLegacy = true; return openBytes(no, buf); } } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { if (fileOnly) { if (in != null) in.close(); if (legacy != null) legacy.close(fileOnly); } else close(); } // -- IFormatHandler API methods -- /* @see loci.formats.IFormatHandler#close() */ public void close() throws IOException { super.close(); needLegacy = false; if (legacy != null) legacy.close(); pixels = null; names = null; offsets = 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 FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { if (debug) debug("ZeissZVIReader.initFile(" + id + ")"); if (noPOI || needLegacy) { legacy.setId(id); core = legacy.getCoreMetadata(); return; } super.initFile(id); pixels = new Hashtable(); names = new Hashtable(); offsets = new Hashtable(); zIndices = new Vector(); cIndices = new Vector(); tIndices = new Vector(); try { in = new RandomAccessStream(id); // Don't uncomment this block. Even though OIBReader has something // like this, it's really a bad idea here. Every ZVI file we have *will* // break if you uncomment it. // //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")); status("Populating metadata"); core.rgb[0] = core.sizeC[0] > 1 && (core.sizeZ[0] * core.sizeC[0] * core.sizeT[0] != core.imageCount[0]); core.littleEndian[0] = true; core.interleaved[0] = !isJPEG; core.indexed[0] = false; core.falseColor[0] = false; core.metadataComplete[0] = true; core.sizeZ[0] = zIndices.size(); core.sizeT[0] = tIndices.size(); if (core.sizeC[0] != cIndices.size()) core.sizeC[0] *= cIndices.size(); core.imageCount[0] = core.sizeZ[0] * core.sizeT[0] * (core.rgb[0] ? core.sizeC[0] / 3 : core.sizeC[0]); if (isTiled) { String zeroIndex = (String) getMeta("ImageTile Index 0"); String oneIndex = (String) getMeta("ImageTile Index 1"); if (zeroIndex == null || zeroIndex.equals("")) zeroIndex = null; if (oneIndex == null || oneIndex.equals("")) oneIndex = null; if (zeroIndex == null || oneIndex == null) { isTiled = false; } else { int lowerLeft = Integer.parseInt(zeroIndex); int middle = Integer.parseInt(oneIndex); tileColumns = lowerLeft - middle - 1; tileRows = (lowerLeft / tileColumns) + 1; if (tileColumns < 0) tileColumns = 1; if (tileRows < 0) tileRows = 1; core.sizeX[0] *= tileColumns; core.sizeY[0] *= tileRows; if (tileColumns == 1 && tileRows == 1) isTiled = false; } } if (cIndex != -1) { int[] dims = {core.sizeZ[0], core.sizeC[0], core.sizeT[0]}; int max = 0, min = Integer.MAX_VALUE, maxNdx = 0, minNdx = 0; String[] axes = {"Z", "C", "T"}; for (int i=0; i<dims.length; i++) { if (dims[i] > max) { max = dims[i]; maxNdx = i; } if (dims[i] < min) { min = dims[i]; minNdx = i; } } int medNdx = 0; for (int i=0; i<3; i++) { if (i != maxNdx && i != minNdx) medNdx = i; } core.currentOrder[0] = "XY" + axes[maxNdx] + axes[medNdx] + axes[minNdx]; int num = core.sizeZ[0] * core.sizeT[0] - core.sizeC[0]; if ((zIndex != -1 && tIndex != -1) && (zIndex != num && tIndex != num)) { if (zIndex != core.sizeZ[0]) { if (core.sizeZ[0] != 1) { core.currentOrder[0] = core.currentOrder[0].replaceAll("Z", "") + "Z"; } else { core.currentOrder[0] = core.currentOrder[0].replaceAll("T", "") + "T"; } } } if (core.sizeZ[0] == core.sizeC[0] && core.sizeC[0] == core.sizeT[0]) { legacy.setId(id); core.currentOrder[0] = legacy.getDimensionOrder(); } } else if (core.rgb[0]) { core.currentOrder[0] = (core.sizeZ[0] > core.sizeT[0]) ? "XYCZT" : "XYCTZ"; } else { if (metadata.get("MultiChannelEnabled") != null || metadata.get("MultiChannelEnabled 0") != null) { core.currentOrder[0] = (core.sizeZ[0] > core.sizeT[0]) ? "XYCZT" : "XYCTZ"; } else { core.currentOrder[0] = (core.sizeZ[0] > core.sizeT[0]) ? "XYZTC" : "XYTZC"; } } } catch (ReflectException exc) { needLegacy = true; if (debug) trace(exc); initFile(id); } // rearrange axis sizes, if necessary int lastZ = zIndices.size() == 0 ? Integer.MAX_VALUE : ((Integer) zIndices.get(zIndices.size() - 1)).intValue(); int lastT = tIndices.size() == 0 ? Integer.MAX_VALUE : ((Integer) tIndices.get(tIndices.size() - 1)).intValue(); if ((zIndex > lastZ || tIndex > lastT) && (zIndex == core.sizeC[0] - 1 || tIndex == core.sizeC[0] - 1 || (zIndex != 0 && zIndex % core.sizeC[0] == 0) || (tIndex != 0 && tIndex % core.sizeC[0] == 0)) && zIndex != lastT) { if (zIndex >= core.sizeZ[0] || tIndex >= core.sizeT[0]) { int tmp = core.sizeZ[0]; core.sizeZ[0] = core.sizeT[0]; core.sizeT[0] = tmp; } } // correct emission/excitation wavelengths, if necessary if (metadata.size() > 0) { // HACK String lastEM = (String) getMeta("Emission Wavelength " + (core.sizeC[0] - 1)); String nextToLastEM = (String) getMeta("Emission Wavelength " + (core.sizeC[0] - 2)); if (lastEM == null || nextToLastEM == null || lastEM.equals(nextToLastEM)) { String lastDye = (String) getMeta("Reflector " + (core.sizeC[0] - 1)); String nextToLastDye = (String) getMeta("Reflector " + (core.sizeC[0] - 2)); if (lastDye == null) lastDye = ""; if (nextToLastDye == null) nextToLastDye = ""; lastDye = DataTools.stripString(lastDye); nextToLastDye = DataTools.stripString(nextToLastDye); if (nextToLastDye.indexOf("Rhodamine") != -1) { addMeta("Emission Wavelength " + (core.sizeC[0] - 2), "580"); addMeta("Excitation Wavelength " + (core.sizeC[0] - 2), "540"); } else if (nextToLastDye.indexOf("DAPI") != -1) { addMeta("Emission Wavelength " + (core.sizeC[0] - 2), "461"); addMeta("Excitation Wavelength " + (core.sizeC[0] - 2), "359"); } else if (nextToLastDye.startsWith("Alexa Fluor")) { addMeta("Emission Wavelength " + (core.sizeC[0] - 2), "519"); addMeta("Excitation Wavelength " + (core.sizeC[0] - 2), "495"); } else if (nextToLastDye.indexOf("Alexa Fluor") != -1) { addMeta("Emission Wavelength " + (core.sizeC[0] - 2), "668"); addMeta("Excitation Wavelength " + (core.sizeC[0] - 2), "650"); } if (lastDye.indexOf("Rhodamine") != -1) { addMeta("Emission Wavelength " + (core.sizeC[0] - 1), "580"); addMeta("Excitation Wavelength " + (core.sizeC[0] - 1), "540"); } else if (lastDye.indexOf("DAPI") != -1) { addMeta("Emission Wavelength " + (core.sizeC[0] - 1), "461"); addMeta("Excitation Wavelength " + (core.sizeC[0] - 1), "359"); } else if (lastDye.startsWith("Alexa Fluor")) { addMeta("Emission Wavelength " + (core.sizeC[0] - 1), "519"); addMeta("Excitation Wavelength " + (core.sizeC[0] - 1), "495"); } else if (lastDye.indexOf("Alexa Fluor") != -1) { addMeta("Emission Wavelength " + (core.sizeC[0] - 1), "668"); addMeta("Excitation Wavelength " + (core.sizeC[0] - 1), "650"); } } } try { initMetadata(); } catch (FormatException exc) { if (debug) trace(exc); } catch (IOException exc) { if (debug) trace(exc); } // remove extra (invalid) metadata String[] keys = (String[]) metadata.keySet().toArray(new String[0]); for (int i=0; i<keys.length; i++) { String n = keys[i]; if (n.indexOf(" ") != -1) { n = n.substring(n.lastIndexOf(" ") + 1); try { int ndx = Integer.parseInt(n); if (ndx >= core.sizeC[0]) metadata.remove(keys[i]); } catch (NumberFormatException e) { } } } } // -- Helper methods -- /** Initialize metadata hashtable and OME-XML structure. */ private void initMetadata() throws FormatException, IOException { MetadataStore store = getMetadataStore(); String fname = (String) getMeta("Title"); if (fname == null) fname = currentId; store.setImage(fname, null, null, null); if (bpp == 1 || bpp == 3) core.pixelType[0] = FormatTools.UINT8; else if (bpp == 2 || bpp == 6) core.pixelType[0] = FormatTools.UINT16; FormatTools.populatePixels(store, this); String pixX = (String) getMeta("Scale Factor for X"); String pixY = (String) getMeta("Scale Factor for Y"); String pixZ = (String) getMeta("Scale Factor for Z"); store.setDimensions( pixX == null ? null : new Float(pixX), pixY == null ? null : new Float(pixY), pixZ == null ? null : new Float(pixZ), null, null, null); String scopeName = (String) getMeta("Microscope Name"); if (scopeName == null) scopeName = (String) getMeta("Microscope Name 0"); store.setInstrument(null, scopeName, null, null, null); for (int i=0; i<core.sizeC[0]; i++) { int idx = FormatTools.getIndex(this, 0, i % getEffectiveSizeC(), 0); String emWave = (String) getMeta("Emission Wavelength " + idx); String exWave = (String) getMeta("Excitation Wavelength " + idx); if (emWave != null && emWave.indexOf(".") != -1) { emWave = emWave.substring(0, emWave.indexOf(".")); } if (exWave != null && exWave.indexOf(".") != -1) { exWave = exWave.substring(0, exWave.indexOf(".")); } store.setLogicalChannel(i, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, emWave == null ? null : new Integer(emWave), exWave == null ? null : new Integer(exWave), null, null, null); String black = (String) getMeta("BlackValue " + idx); String white = (String) getMeta("WhiteValue " + idx); String gamma = (String) getMeta("GammaValue " + idx); Double blackValue = null, whiteValue = null; Float gammaValue = null; try { blackValue = new Double(black); } catch (NumberFormatException e) { } catch (NullPointerException e) { } try { whiteValue = new Double(white); } catch (NumberFormatException e) { } catch (NullPointerException e) { } try { gammaValue = new Float(gamma); } catch (NumberFormatException e) { } catch (NullPointerException e) { } store.setDisplayChannel(new Integer(i), blackValue, whiteValue, gammaValue, null); } for (int i=0; i<core.imageCount[0]; i++) { int[] zct = FormatTools.getZCTCoords(this, i); String exposure = (String) getMeta("Exposure Time [ms] " + i); Float exp = new Float(0.0); try { exp = new Float(exposure); } catch (NumberFormatException e) { } catch (NullPointerException e) { } store.setPlaneInfo(zct[0], zct[1], zct[2], new Float(0.0), exp, null); } String objectiveName = (String) getMeta("Objective Name 0"); if (objectiveName != null) { objectiveName = DataTools.stripString(objectiveName); } String objectiveMag = (String) getMeta("Objective Magnification 0"); if (objectiveMag != null) { objectiveMag = DataTools.stripString(objectiveMag); } else objectiveMag = "1.0"; String objectiveNA = (String) getMeta("Objective N.A. 0"); if (objectiveNA != null) objectiveNA = DataTools.stripString(objectiveNA); else objectiveNA = "1.0"; store.setObjective(null, objectiveName, null, new Float(objectiveNA), new Float(objectiveMag), null, null); } 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); // Suppressing an exception here looks like poor style. // However, this at least gives us a chance at reading files with // corrupt blocks. try { r.exec("dis.read(data)"); } catch (ReflectException exc) { if (debug) trace(exc); } String entryName = (String) r.getVar("entryName"); String dirName = (String) r.getVar("dirName"); boolean isContents = entryName.toUpperCase().equals("CONTENTS"); Object directory = r.getVar("dir"); RandomAccessStream s = new RandomAccessStream(data); s.order(true); if (dirName.toUpperCase().equals("ROOT ENTRY") || dirName.toUpperCase().equals("ROOTENTRY")) { if (entryName.equals("Tags")) { try { parseTags(s); } catch (EOFException e) { } } } else if (dirName.equals("Tags") && isContents) { try { parseTags(s); } catch (EOFException e) { } } else if (isContents && (dirName.equals("Image") || dirName.toUpperCase().indexOf("ITEM") != -1) && (data.length > core.sizeX[0]*core.sizeY[0])) { s.skipBytes(6); int vt = s.readShort(); if (vt == 3) { s.skipBytes(6); } else if (vt == 8) { int l = s.readShort(); s.skipBytes(l + 2); } int len = s.readShort(); if (s.readShort() != 0) s.seek(s.getFilePointer() - 2); if (s.getFilePointer() + len <= s.length()) { s.skipBytes(len); } else break; vt = s.readShort(); if (vt == 8) { len = s.readInt(); s.skipBytes(len + 2); } int tw = s.readInt(); if (core.sizeX[0] == 0 || (tw < core.sizeX[0] && tw > 0)) { core.sizeX[0] = tw; } s.skipBytes(2); int th = s.readInt(); if (core.sizeY[0] == 0 || (th < core.sizeY[0] && th > 0)) { core.sizeY[0] = th; } s.skipBytes(14); int numImageContainers = s.readInt(); s.skipBytes(6); // VT_CLSID - PluginCLSID while (s.readShort() != 65); // VT_BLOB - Others len = s.readInt(); s.skipBytes(len); // VT_STORED_OBJECT - Layers s.skipBytes(2); long old = s.getFilePointer(); len = s.readInt(); s.skipBytes(8); int tidx = s.readInt(); int cidx = s.readInt(); int zidx = s.readInt(); Integer zndx = new Integer(zidx); Integer cndx = new Integer(cidx); Integer tndx = new Integer(tidx); if (!zIndices.contains(zndx)) zIndices.add(zndx); if (!cIndices.contains(cndx)) cIndices.add(cndx); if (!tIndices.contains(tndx)) tIndices.add(tndx); s.seek(old + len + 4); boolean foundWidth = s.readInt() == core.sizeX[0]; boolean foundHeight = s.readInt() == core.sizeY[0]; boolean findFailed = false; while ((!foundWidth || !foundHeight) && s.getFilePointer() + 1 < s.length()) { s.seek(s.getFilePointer() - 7); foundWidth = s.readInt() == core.sizeX[0]; foundHeight = s.readInt() == core.sizeY[0]; } s.seek(s.getFilePointer() - 16); findFailed = !foundWidth && !foundHeight; // image header and data if (dirName.toUpperCase().indexOf("ITEM") != -1 || (dirName.equals("Image") && numImageContainers == 0)) { if (findFailed) s.seek(old + len + 92); long fp = s.getFilePointer(); byte[] o = new byte[(int) (s.length() - fp)]; s.read(o); int imageNum = 0; if (dirName.toUpperCase().indexOf("ITEM") != -1) { String num = dirName.substring(5); num = num.substring(0, num.length() - 1); imageNum = Integer.parseInt(num); } offsets.put(new Integer(imageNum), new Integer((int) fp + 32)); parsePlane(o, imageNum, directory, entryName); } } else { try { parseTags(s); } catch (IOException e) { } } s.close(); 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()); } /** Parse a plane of data. */ private void parsePlane(byte[] data, int num, Object directory, String entry) throws IOException { RandomAccessStream s = new RandomAccessStream(data); s.order(true); if (s.readInt() == 0) s.skipBytes(4); core.sizeX[0] = s.readInt(); core.sizeY[0] = s.readInt(); s.skipBytes(4); bpp = s.readInt(); //s.skipBytes(8); s.skipBytes(4); int valid = s.readInt(); isJPEG = valid == 0 || valid == 1; pixels.put(new Integer(num), directory); names.put(new Integer(num), entry); core.imageCount[0]++; if (bpp % 3 == 0) core.sizeC[0] = 3; else core.sizeC[0] = 1; } /** Parse all of the tags in a stream. */ private void parseTags(RandomAccessStream s) throws IOException { s.skipBytes(24); int count = s.readInt(); // limit count to 4096 if (count > 4096) count = 4096; for (int i=0; i<count; i++) { if (s.getFilePointer() + 2 >= s.length()) break; int type = s.readShort(); String value = ""; switch (type) { case 0: break; case 1: break; case 2: value = "" + s.readShort(); break; case 3: case 22: case 23: value = "" + s.readInt(); break; case 4: value = "" + s.readFloat(); break; case 5: value = "" + s.readDouble(); break; case 7: case 20: case 21: value = "" + s.readLong(); break; case 69: case 8: int len = s.readInt(); if (s.getFilePointer() + len < s.length()) { value = s.readString(len); } else return; break; case 66: int l = s.readShort(); s.seek(s.getFilePointer() - 2); value = s.readString(l + 2); break; default: long old = s.getFilePointer(); while (s.readShort() != 3 && s.getFilePointer() + 2 < s.length()); long fp = s.getFilePointer() - 2; s.seek(old - 2); value = s.readString((int) (fp - old + 2)); } s.skipBytes(2); int tagID = 0; try { tagID = s.readInt(); } catch (IOException e) { } s.skipBytes(6); String key = getKey(tagID); if (key.equals("Image Index Z")) { try { zIndex = Integer.parseInt(DataTools.stripString(value)); } catch (NumberFormatException f) { } } else if (key.equals("Image Index T")) { try { tIndex = Integer.parseInt(DataTools.stripString(value)); } catch (NumberFormatException f) { } } else if (key.equals("Image Channel Index")) { try { cIndex = Integer.parseInt(DataTools.stripString(value)); } catch (NumberFormatException f) { } } else if (key.equals("ImageWidth")) { try { if (core.sizeX[0] == 0) core.sizeX[0] = Integer.parseInt(value); } catch (NumberFormatException f) { } } else if (key.equals("ImageHeight")) { try { if (core.sizeY[0] == 0) core.sizeY[0] = Integer.parseInt(value); } catch (NumberFormatException f) { } } if (metadata.get(key) != null || metadata.get(key + " 0") != null) { if (metadata.get(key) != null) { Object v = metadata.remove(key); metadata.put(key + " 0", v); } int ndx = 0; while (metadata.get(key + " " + ndx) != null) ndx++; key += " " + ndx; } if (key.indexOf("ImageTile") != -1) isTiled = true; addMeta(key, value); } } /** Return the string corresponding to the given ID. */ private String getKey(int tagID) { switch (tagID) { case 222: return "Compression"; case 258: return "BlackValue"; case 259: return "WhiteValue"; case 260: return "ImageDataMappingAutoRange"; case 261: return "Thumbnail"; case 262: return "GammaValue"; case 264: return "ImageOverExposure"; case 265: return "ImageRelativeTime1"; case 266: return "ImageRelativeTime2"; case 267: return "ImageRelativeTime3"; case 268: return "ImageRelativeTime4"; case 333: return "RelFocusPosition1"; case 334: return "RelFocusPosition2"; case 513: return "ObjectType"; case 515: return "ImageWidth"; case 516: return "ImageHeight"; case 517: return "Number Raw Count"; case 518: return "PixelType"; case 519: return "NumberOfRawImages"; case 520: return "ImageSize"; case 523: return "Acquisition pause annotation"; case 530: return "Document Subtype"; case 531: return "Acquisition Bit Depth"; case 532: return "Image Memory Usage (RAM)"; case 534: return "Z-Stack single representative"; case 769: return "Scale Factor for X"; case 770: return "Scale Unit for X"; case 771: return "Scale Width"; case 772: return "Scale Factor for Y"; case 773: return "Scale Unit for Y"; case 774: return "Scale Height"; case 775: return "Scale Factor for Z"; case 776: return "Scale Unit for Z"; case 777: return "Scale Depth"; case 778: return "Scaling Parent"; case 1001: return "Date"; case 1002: return "code"; case 1003: return "Source"; case 1004: return "Message"; case 1025: return "Acquisition Date"; case 1026: return "8-bit acquisition"; case 1027: return "Camera Bit Depth"; case 1029: return "MonoReferenceLow"; case 1030: return "MonoReferenceHigh"; case 1031: return "RedReferenceLow"; case 1032: return "RedReferenceHigh"; case 1033: return "GreenReferenceLow"; case 1034: return "GreenReferenceHigh"; case 1035: return "BlueReferenceLow"; case 1036: return "BlueReferenceHigh"; case 1041: return "FrameGrabber Name"; case 1042: return "Camera"; case 1044: return "CameraTriggerSignalType"; case 1045: return "CameraTriggerEnable"; case 1046: return "GrabberTimeout"; case 1281: return "MultiChannelEnabled"; case 1282: return "MultiChannel Color"; case 1283: return "MultiChannel Weight"; case 1284: return "Channel Name"; case 1536: return "DocumentInformationGroup"; case 1537: return "Title"; case 1538: return "Author"; case 1539: return "Keywords"; case 1540: return "Comments"; case 1541: return "SampleID"; case 1542: return "Subject"; case 1543: return "RevisionNumber"; case 1544: return "Save Folder"; case 1545: return "FileLink"; case 1546: return "Document Type"; case 1547: return "Storage Media"; case 1548: return "File ID"; case 1549: return "Reference"; case 1550: return "File Date"; case 1551: return "File Size"; case 1553: return "Filename"; case 1792: return "ProjectGroup"; case 1793: return "Acquisition Date"; case 1794: return "Last modified by"; case 1795: return "User company"; case 1796: return "User company logo"; case 1797: return "Image"; case 1800: return "User ID"; case 1801: return "User Name"; case 1802: return "User City"; case 1803: return "User Address"; case 1804: return "User Country"; case 1805: return "User Phone"; case 1806: return "User Fax"; case 2049: return "Objective Name"; case 2050: return "Optovar"; case 2051: return "Reflector"; case 2052: return "Condenser Contrast"; case 2053: return "Transmitted Light Filter 1"; case 2054: return "Transmitted Light Filter 2"; case 2055: return "Reflected Light Shutter"; case 2056: return "Condenser Front Lens"; case 2057: return "Excitation Filter Name"; case 2060: return "Transmitted Light Fieldstop Aperture"; case 2061: return "Reflected Light Aperture"; case 2062: return "Condenser N.A."; case 2063: return "Light Path"; case 2064: return "HalogenLampOn"; case 2065: return "Halogen Lamp Mode"; case 2066: return "Halogen Lamp Voltage"; case 2068: return "Fluorescence Lamp Level"; case 2069: return "Fluorescence Lamp Intensity"; case 2070: return "LightManagerEnabled"; case 2071: return "tag_ID_2071"; case 2072: return "Focus Position"; case 2073: return "Stage Position X"; case 2074: return "Stage Position Y"; case 2075: return "Microscope Name"; case 2076: return "Objective Magnification"; case 2077: return "Objective N.A."; case 2078: return "MicroscopeIllumination"; case 2079: return "External Shutter 1"; case 2080: return "External Shutter 2"; case 2081: return "External Shutter 3"; case 2082: return "External Filter Wheel 1 Name"; case 2083: return "External Filter Wheel 2 Name"; case 2084: return "Parfocal Correction"; case 2086: return "External Shutter 4"; case 2087: return "External Shutter 5"; case 2088: return "External Shutter 6"; case 2089: return "External Filter Wheel 3 Name"; case 2090: return "External Filter Wheel 4 Name"; case 2103: return "Objective Turret Position"; case 2104: return "Objective Contrast Method"; case 2105: return "Objective Immersion Type"; case 2107: return "Reflector Position"; case 2109: return "Transmitted Light Filter 1 Position"; case 2110: return "Transmitted Light Filter 2 Position"; case 2112: return "Excitation Filter Position"; case 2113: return "Lamp Mirror Position"; case 2114: return "External Filter Wheel 1 Position"; case 2115: return "External Filter Wheel 2 Position"; case 2116: return "External Filter Wheel 3 Position"; case 2117: return "External Filter Wheel 4 Position"; case 2118: return "Lightmanager Mode"; case 2119: return "Halogen Lamp Calibration"; case 2120: return "CondenserNAGoSpeed"; case 2121: return "TransmittedLightFieldstopGoSpeed"; case 2122: return "OptovarGoSpeed"; case 2123: return "Focus calibrated"; case 2124: return "FocusBasicPosition"; case 2125: return "FocusPower"; case 2126: return "FocusBacklash"; case 2127: return "FocusMeasurementOrigin"; case 2128: return "FocusMeasurementDistance"; case 2129: return "FocusSpeed"; case 2130: return "FocusGoSpeed"; case 2131: return "FocusDistance"; case 2132: return "FocusInitPosition"; case 2133: return "Stage calibrated"; case 2134: return "StagePower"; case 2135: return "StageXBacklash"; case 2136: return "StageYBacklash"; case 2137: return "StageSpeedX"; case 2138: return "StageSpeedY"; case 2139: return "StageSpeed"; case 2140: return "StageGoSpeedX"; case 2141: return "StageGoSpeedY"; case 2142: return "StageStepDistanceX"; case 2143: return "StageStepDistanceY"; case 2144: return "StageInitialisationPositionX"; case 2145: return "StageInitialisationPositionY"; case 2146: return "MicroscopeMagnification"; case 2147: return "ReflectorMagnification"; case 2148: return "LampMirrorPosition"; case 2149: return "FocusDepth"; case 2150: return "MicroscopeType"; case 2151: return "Objective Working Distance"; case 2152: return "ReflectedLightApertureGoSpeed"; case 2153: return "External Shutter"; case 2154: return "ObjectiveImmersionStop"; case 2155: return "Focus Start Speed"; case 2156: return "Focus Acceleration"; case 2157: return "ReflectedLightFieldstop"; case 2158: return "ReflectedLightFieldstopGoSpeed"; case 2159: return "ReflectedLightFilter 1"; case 2160: return "ReflectedLightFilter 2"; case 2161: return "ReflectedLightFilter1Position"; case 2162: return "ReflectedLightFilter2Position"; case 2163: return "TransmittedLightAttenuator"; case 2164: return "ReflectedLightAttenuator"; case 2165: return "Transmitted Light Shutter"; case 2166: return "TransmittedLightAttenuatorGoSpeed"; case 2167: return "ReflectedLightAttenuatorGoSpeed"; case 2176: return "TransmittedLightVirtualFilterPosition"; case 2177: return "TransmittedLightVirtualFilter"; case 2178: return "ReflectedLightVirtualFilterPosition"; case 2179: return "ReflectedLightVirtualFilter"; case 2180: return "ReflectedLightHalogenLampMode"; case 2181: return "ReflectedLightHalogenLampVoltage"; case 2182: return "ReflectedLightHalogenLampColorTemperature"; case 2183: return "ContrastManagerMode"; case 2184: return "Dazzle Protection Active"; case 2195: return "Zoom"; case 2196: return "ZoomGoSpeed"; case 2197: return "LightZoom"; case 2198: return "LightZoomGoSpeed"; case 2199: return "LightZoomCoupled"; case 2200: return "TransmittedLightHalogenLampMode"; case 2201: return "TransmittedLightHalogenLampVoltage"; case 2202: return "TransmittedLightHalogenLampColorTemperature"; case 2203: return "Reflected Coldlight Mode"; case 2204: return "Reflected Coldlight Intensity"; case 2205: return "Reflected Coldlight Color Temperature"; case 2206: return "Transmitted Coldlight Mode"; case 2207: return "Transmitted Coldlight Intensity"; case 2208: return "Transmitted Coldlight Color Temperature"; case 2209: return "Infinityspace Portchanger Position"; case 2210: return "Beamsplitter Infinity Space"; case 2211: return "TwoTv VisCamChanger Position"; case 2212: return "Beamsplitter Ocular"; case 2213: return "TwoTv CamerasChanger Position"; case 2214: return "Beamsplitter Cameras"; case 2215: return "Ocular Shutter"; case 2216: return "TwoTv CamerasChangerCube"; case 2218: return "Ocular Magnification"; case 2219: return "Camera Adapter Magnification"; case 2220: return "Microscope Port"; case 2221: return "Ocular Total Magnification"; case 2222: return "Field of View"; case 2223: return "Ocular"; case 2224: return "CameraAdapter"; case 2225: return "StageJoystickEnabled"; case 2226: return "ContrastManager Contrast Method"; case 2229: return "CamerasChanger Beamsplitter Type"; case 2235: return "Rearport Slider Position"; case 2236: return "Rearport Source"; case 2237: return "Beamsplitter Type Infinity Space"; case 2238: return "Fluorescence Attenuator"; case 2239: return "Fluorescence Attenuator Position"; case 2261: return "Objective ID"; case 2262: return "Reflector ID"; case 2307: return "Camera Framestart Left"; case 2308: return "Camera Framestart Top"; case 2309: return "Camera Frame Width"; case 2310: return "Camera Frame Height"; case 2311: return "Camera Binning"; case 2312: return "CameraFrameFull"; case 2313: return "CameraFramePixelDistance"; case 2318: return "DataFormatUseScaling"; case 2319: return "CameraFrameImageOrientation"; case 2320: return "VideoMonochromeSignalType"; case 2321: return "VideoColorSignalType"; case 2322: return "MeteorChannelInput"; case 2323: return "MeteorChannelSync"; case 2324: return "WhiteBalanceEnabled"; case 2325: return "CameraWhiteBalanceRed"; case 2326: return "CameraWhiteBalanceGreen"; case 2327: return "CameraWhiteBalanceBlue"; case 2331: return "CameraFrameScalingFactor"; case 2562: return "Meteor Camera Type"; case 2564: return "Exposure Time [ms]"; case 2568: return "CameraExposureTimeAutoCalculate"; case 2569: return "Meteor Gain Value"; case 2571: return "Meteor Gain Automatic"; case 2572: return "MeteorAdjustHue"; case 2573: return "MeteorAdjustSaturation"; case 2574: return "MeteorAdjustRedLow"; case 2575: return "MeteorAdjustGreenLow"; case 2576: return "Meteor Blue Low"; case 2577: return "MeteorAdjustRedHigh"; case 2578: return "MeteorAdjustGreenHigh"; case 2579: return "MeteorBlue High"; case 2582: return "CameraExposureTimeCalculationControl"; case 2585: return "AxioCamFadingCorrectionEnable"; case 2587: return "CameraLiveImage"; case 2588: return "CameraLiveEnabled"; case 2589: return "LiveImageSyncObjectName"; case 2590: return "CameraLiveSpeed"; case 2591: return "CameraImage"; case 2592: return "CameraImageWidth"; case 2593: return "CameraImageHeight"; case 2594: return "CameraImagePixelType"; case 2595: return "CameraImageShMemoryName"; case 2596: return "CameraLiveImageWidth"; case 2597: return "CameraLiveImageHeight"; case 2598: return "CameraLiveImagePixelType"; case 2599: return "CameraLiveImageShMemoryName"; case 2600: return "CameraLiveMaximumSpeed"; case 2601: return "CameraLiveBinning"; case 2602: return "CameraLiveGainValue"; case 2603: return "CameraLiveExposureTimeValue"; case 2604: return "CameraLiveScalingFactor"; case 2819: return "Image Index Z"; case 2820: return "Image Channel Index"; case 2821: return "Image Index T"; case 2822: return "ImageTile Index"; case 2823: return "Image acquisition Index"; case 2827: return "Image IndexS"; case 2841: return "Original Stage Position X"; case 2842: return "Original Stage Position Y"; case 3088: return "LayerDrawFlags"; case 3334: return "RemainingTime"; case 3585: return "User Field 1"; case 3586: return "User Field 2"; case 3587: return "User Field 3"; case 3588: return "User Field 4"; case 3589: return "User Field 5"; case 3590: return "User Field 6"; case 3591: return "User Field 7"; case 3592: return "User Field 8"; case 3593: return "User Field 9"; case 3594: return "User Field 10"; case 3840: return "ID"; case 3841: return "Name"; case 3842: return "Value"; case 5501: return "PvCamClockingMode"; case 8193: return "Autofocus Status Report"; case 8194: return "Autofocus Position"; case 8195: return "Autofocus Position Offset"; case 8196: return "Autofocus Empty Field Threshold"; case 8197: return "Autofocus Calibration Name"; case 8198: return "Autofocus Current Calibration Item"; case 20478: return "tag_ID_20478"; case 65537: return "CameraFrameFullWidth"; case 65538: return "CameraFrameFullHeight"; case 65541: return "AxioCam Shutter Signal"; case 65542: return "AxioCam Delay Time"; case 65543: return "AxioCam Shutter Control"; case 65544: return "AxioCam BlackRefIsCalculated"; case 65545: return "AxioCam Black Reference"; case 65547: return "Camera Shading Correction"; case 65550: return "AxioCam Enhance Color"; case 65551: return "AxioCam NIR Mode"; case 65552: return "CameraShutterCloseDelay"; case 65553: return "CameraWhiteBalanceAutoCalculate"; case 65556: return "AxioCam NIR Mode Available"; case 65557: return "AxioCam Fading Correction Available"; case 65559: return "AxioCam Enhance Color Available"; case 65565: return "MeteorVideoNorm"; case 65566: return "MeteorAdjustWhiteReference"; case 65567: return "MeteorBlackReference"; case 65568: return "MeteorChannelInputCountMono"; case 65570: return "MeteorChannelInputCountRGB"; case 65571: return "MeteorEnableVCR"; case 65572: return "Meteor Brightness"; case 65573: return "Meteor Contrast"; case 65575: return "AxioCam Selector"; case 65576: return "AxioCam Type"; case 65577: return "AxioCam Info"; case 65580: return "AxioCam Resolution"; case 65581: return "AxioCam Color Model"; case 65582: return "AxioCam MicroScanning"; case 65585: return "Amplification Index"; case 65586: return "Device Command"; case 65587: return "BeamLocation"; case 65588: return "ComponentType"; case 65589: return "ControllerType"; case 65590: return "CameraWhiteBalanceCalculationRedPaint"; case 65591: return "CameraWhiteBalanceCalculationBluePaint"; case 65592: return "CameraWhiteBalanceSetRed"; case 65593: return "CameraWhiteBalanceSetGreen"; case 65594: return "CameraWhiteBalanceSetBlue"; case 65595: return "CameraWhiteBalanceSetTargetRed"; case 65596: return "CameraWhiteBalanceSetTargetGreen"; case 65597: return "CameraWhiteBalanceSetTargetBlue"; case 65598: return "ApotomeCamCalibrationMode"; case 65599: return "ApoTome Grid Position"; case 65600: return "ApotomeCamScannerPosition"; case 65601: return "ApoTome Full Phase Shift"; case 65602: return "ApoTome Grid Name"; case 65603: return "ApoTome Staining"; case 65604: return "ApoTome Processing Mode"; case 65605: return "ApotmeCamLiveCombineMode"; case 65606: return "ApoTome Filter Name"; case 65607: return "Apotome Filter Strength"; case 65608: return "ApotomeCamFilterHarmonics"; case 65609: return "ApoTome Grating Period"; case 65610: return "ApoTome Auto Shutter Used"; case 65611: return "Apotome Cam Status"; case 65612: return "ApotomeCamNormalize"; case 65613: return "ApotomeCamSettingsManager"; case 65614: return "DeepviewCamSupervisorMode"; case 65615: return "DeepView Processing"; case 65616: return "DeepviewCamFilterName"; case 65617: return "DeepviewCamStatus"; case 65618: return "DeepviewCamSettingsManager"; case 65619: return "DeviceScalingName"; case 65620: return "CameraShadingIsCalculated"; case 65621: return "CameraShadingCalculationName"; case 65622: return "CameraShadingAutoCalculate"; case 65623: return "CameraTriggerAvailable"; case 65626: return "CameraShutterAvailable"; case 65627: return "AxioCam ShutterMicroScanningEnable"; case 65628: return "ApotomeCamLiveFocus"; case 65629: return "DeviceInitStatus"; case 65630: return "DeviceErrorStatus"; case 65631: return "ApotomeCamSliderInGridPosition"; case 65632: return "Orca NIR Mode Used"; case 65633: return "Orca Analog Gain"; case 65634: return "Orca Analog Offset"; case 65635: return "Orca Binning"; case 65636: return "Orca Bit Depth"; case 65637: return "ApoTome Averaging Count"; case 65638: return "DeepView DoF"; case 65639: return "DeepView EDoF"; case 65643: return "DeepView Slider Name"; case 65655: return "DeepView Slider Name"; case 5439491: return "Acquisition Sofware"; case 16777488: return "Excitation Wavelength"; case 16777489: return "Emission Wavelength"; case 101515267: return "File Name"; case 101253123: case 101777411: return "Image Name"; default: return "" + tagID; } } }