// // ND2Handler.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.util.ArrayList; import java.util.Hashtable; import loci.common.DataTools; import loci.common.DateTools; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.meta.MetadataStore; import ome.xml.model.primitives.PositiveFloat; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PositiveInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; /** * DefaultHandler implementation for handling XML produced by Nikon Elements. * * <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/ND2Handler.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/ND2Handler.java;hb=HEAD">Gitweb</a></dd></dl> */ public class ND2Handler extends DefaultHandler { // -- Constants -- private static final Logger LOGGER = LoggerFactory.getLogger(ND2Handler.class); private static final String DATE_FORMAT = "dd/MM/yyyy HH:mm:ss"; // -- Fields -- private String prefix = null; private String prevRuntype = null; private String prevElement = null; private Hashtable<String, Object> metadata = new Hashtable<String, Object>(); private CoreMetadata[] core; private boolean isLossless; private ArrayList<Long> zs = new ArrayList<Long>(); private ArrayList<Long> ts = new ArrayList<Long>(); private int numSeries = 0; private double pixelSizeX, pixelSizeY, pixelSizeZ; private Double pinholeSize, voltage, mag, na; private String objectiveModel, immersion, correction; private Double refractiveIndex; private ArrayList<String> channelNames = new ArrayList<String>(); private ArrayList<String> modality = new ArrayList<String>(); private ArrayList<String> binning = new ArrayList<String>(); private ArrayList<Double> speed = new ArrayList<Double>(); private ArrayList<Double> gain = new ArrayList<Double>(); private ArrayList<Double> temperature = new ArrayList<Double>(); private ArrayList<Double> exposureTime = new ArrayList<Double>(); private ArrayList<Integer> exWave = new ArrayList<Integer>(); private ArrayList<Integer> emWave = new ArrayList<Integer>(); private ArrayList<Integer> power = new ArrayList<Integer>(); private ArrayList<Hashtable<String, String>> rois = new ArrayList<Hashtable<String, String>>(); private ArrayList<Double> posX = new ArrayList<Double>(); private ArrayList<Double> posY = new ArrayList<Double>(); private ArrayList<Double> posZ = new ArrayList<Double>(); private ArrayList<String> posNames = new ArrayList<String>(); private String cameraModel; private int fieldIndex = 0; private String date; private Hashtable<String, Integer> colors = new Hashtable<String, Integer>(); private Hashtable<String, String> dyes = new Hashtable<String, String>(); private Hashtable<String, Integer> realColors = new Hashtable<String, Integer>(); // -- Constructor -- public ND2Handler(CoreMetadata[] core) { super(); this.core = core; } // -- ND2Handler API methods -- public CoreMetadata[] getCoreMetadata() { return core; } public void populateROIs(MetadataStore store) { for (int r=0; r<rois.size(); r++) { Hashtable<String, String> roi = rois.get(r); String type = roi.get("ROIType"); if (type.equals("Text")) { String roiID = MetadataTools.createLSID("ROI", r); for (int i=0; i<core.length; i++) { store.setImageROIRef(roiID, i, r); } store.setROIID(roiID, r); store.setTextID(MetadataTools.createLSID("Shape", r, 0), r, 0); store.setTextFontSize( NonNegativeInteger.valueOf(roi.get("fHeight")), r, 0); store.setTextValue(roi.get("eval-text"), r, 0); store.setTextStrokeWidth(new Double(roi.get("line-width")), r, 0); String rectangle = roi.get("rectangle"); String[] p = rectangle.split(","); double[] points = new double[p.length]; for (int i=0; i<p.length; i++) { points[i] = Double.parseDouble(p[i]); } store.setRectangleID(MetadataTools.createLSID("Shape", r, 1), r, 1); store.setRectangleX(points[0], r, 1); store.setRectangleY(points[1], r, 1); store.setRectangleWidth(points[2] - points[0], r, 1); store.setRectangleHeight(points[3] - points[1], r, 1); } else if (type.equals("HorizontalLine") || type.equals("VerticalLine")) { String roiID = MetadataTools.createLSID("ROI", r); for (int i=0; i<core.length; i++) { store.setImageROIRef(roiID, i, r); } store.setROIID(roiID, r); String segments = roi.get("segments"); segments = segments.replaceAll("\\[\\]", ""); String[] points = segments.split("\\)"); StringBuffer sb = new StringBuffer(); for (int i=0; i<points.length; i++) { points[i] = points[i].substring(points[i].indexOf(":") + 1); sb.append(points[i]); if (i < points.length - 1) sb.append(" "); } store.setPolylineID(MetadataTools.createLSID("Shape", r, 0), r, 0); store.setPolylinePoints(sb.toString(), r, 0); } } } public String getDate() { return date; } public Hashtable<String, Object> getMetadata() { return metadata; } public int getSeriesCount() { return numSeries; } public boolean isLossless() { return isLossless; } public ArrayList<Long> getZSections() { return zs; } public ArrayList<Long> getTimepoints() { return ts; } public double getPixelSizeX() { return pixelSizeX; } public double getPixelSizeY() { return pixelSizeY; } public double getPixelSizeZ() { return pixelSizeZ; } public Double getPinholeSize() { return pinholeSize; } public Double getVoltage() { return voltage; } public Double getMagnification() { return mag; } public Double getNumericalAperture() { return na; } public String getObjectiveModel() { return objectiveModel; } public String getImmersion() { return immersion; } public String getCorrection() { return correction; } public Double getRefractiveIndex() { return refractiveIndex; } public ArrayList<String> getChannelNames() { return channelNames; } public ArrayList<String> getModalities() { return modality; } public ArrayList<String> getBinnings() { return binning; } public ArrayList<Double> getSpeeds() { return speed; } public ArrayList<Double> getGains() { return gain; } public ArrayList<Double> getTemperatures() { return temperature; } public ArrayList<Double> getExposureTimes() { return exposureTime; } public ArrayList<Integer> getExcitationWavelengths() { return exWave; } public ArrayList<Integer> getEmissionWavelengths() { return emWave; } public ArrayList<Integer> getPowers() { return power; } public ArrayList<Hashtable<String, String>> getROIs() { return rois; } public ArrayList<Double> getXPositions() { return posX; } public ArrayList<Double> getYPositions() { return posY; } public ArrayList<Double> getZPositions() { return posZ; } public ArrayList<String> getPositionNames() { return posNames; } public String getCameraModel() { return cameraModel; } public int getFieldIndex() { return fieldIndex; } public Hashtable<String, Integer> getChannelColors() { return realColors; } // -- DefaultHandler API methods -- public void endElement(String uri, String localName, String qName, Attributes attributes) { if (qName.equals("CalibrationSeq") || qName.equals("MetadataSeq")) { prefix = null; } if (qName.equals(prevElement)) { prevElement = null; } } public void startElement(String uri, String localName, String qName, Attributes attributes) { if ("CLxListVariant".equals(attributes.getValue("runtype"))) { prevElement = qName; } String value = attributes.getValue("value"); if (qName.equals("uiWidth")) { core[0].sizeX = Integer.parseInt(value); } else if ("rectSensorUser".equals(prevElement)) { if (qName.equals("left") && core[0].sizeX == 0) { core[0].sizeX = -1 * Integer.parseInt(value); } else if (qName.equals("top") && core[0].sizeY == 0) { core[0].sizeY = -1 * Integer.parseInt(value); } else if (qName.equals("right") && core[0].sizeX <= 0) { core[0].sizeX += Integer.parseInt(value); } else if (qName.equals("bottom") && core[0].sizeY <= 0) { core[0].sizeY += Integer.parseInt(value); } } else if ("LoopSize".equals(prevElement) && value != null) { if (core[0].sizeT == 0) { core[0].sizeT = Integer.parseInt(value); } else if (core[0].sizeZ == 0) { core[0].sizeZ = Integer.parseInt(value); } core[0].dimensionOrder = "CTZ"; } else if ("pPosName".equals(prevElement) && value != null) { posNames.add(value); } else if (qName.equals("FramesBefore")) { if (core[0].sizeZ == 0) { core[0].sizeZ = 1; } core[0].sizeZ *= Integer.parseInt(value); } else if (qName.equals("FramesAfter")) { core[0].sizeZ *= Integer.parseInt(value); } else if (qName.equals("TimeBefore")) { if (core[0].sizeT == 0) { core[0].sizeT = 1; } core[0].sizeT *= Integer.parseInt(value); } else if (qName.equals("TimeAfter")) { core[0].sizeT *= Integer.parseInt(value); } else if (qName.equals("uiMaxDst")) { int maxPixelValue = Integer.parseInt(value) + 1; int bits = 0; while (maxPixelValue > 0) { maxPixelValue /= 2; bits++; } try { if (core[0].pixelType == 0) { core[0].pixelType = FormatTools.pixelTypeFromBytes(bits / 8, false, false); } } catch (FormatException e) { LOGGER.warn("Could not set the pixel type", e); } } else if (qName.equals("uiWidthBytes") || qName.equals("uiBpcInMemory")) { int div = qName.equals("uiWidthBytes") ? core[0].sizeX : 8; int bytes = Integer.parseInt(value) / div; try { core[0].pixelType = FormatTools.pixelTypeFromBytes(bytes, false, false); } catch (FormatException e) { } parseKeyAndValue(qName, value, prevRuntype); } else if ("dPosX".equals(prevElement) && qName.startsWith("item_")) { posX.add(new Double(DataTools.sanitizeDouble(value))); metadata.put("X position for position #" + posX.size(), value); } else if ("dPosY".equals(prevElement) && qName.startsWith("item_")) { posY.add(new Double(DataTools.sanitizeDouble(value))); metadata.put("Y position for position #" + posY.size(), value); } else if ("dPosZ".equals(prevElement) && qName.startsWith("item_")) { posZ.add(new Double(DataTools.sanitizeDouble(value))); metadata.put("Z position for position #" + posZ.size(), value); } else if (qName.startsWith("item_")) { int v = Integer.parseInt(qName.substring(qName.indexOf("_") + 1)); if (v == numSeries) { fieldIndex = core[0].dimensionOrder.length(); numSeries++; } else if (v < numSeries && fieldIndex < core[0].dimensionOrder.length()) { fieldIndex = core[0].dimensionOrder.length(); } } else if (qName.equals("uiCompCount")) { int v = Integer.parseInt(value); core[0].sizeC = (int) Math.max(core[0].sizeC, v); } else if (qName.equals("uiHeight")) { core[0].sizeY = Integer.parseInt(value); } else if (qName.startsWith("TextInfo")) { parseKeyAndValue(qName, attributes.getValue("Text"), prevRuntype); parseKeyAndValue(qName, value, prevRuntype); } else if (qName.equals("dCompressionParam")) { isLossless = Integer.parseInt(value) > 0; parseKeyAndValue(qName, value, prevRuntype); } else if (qName.equals("CalibrationSeq") || qName.equals("MetadataSeq")) { prefix = qName + " " + attributes.getValue("_SEQUENCE_INDEX"); } else if (qName.equals("HorizontalLine") || qName.equals("VerticalLine") || qName.equals("Text")) { Hashtable<String, String> roi = new Hashtable<String, String>(); roi.put("ROIType", qName); for (int q=0; q<attributes.getLength(); q++) { roi.put(attributes.getQName(q), attributes.getValue(q)); } rois.add(roi); } else if (qName.equals("dPinholeRadius")) { pinholeSize = new Double(DataTools.sanitizeDouble(value)); metadata.put("Pinhole size", value); } else if (qName.endsWith("ChannelColor")) { String name = qName.substring(0, qName.indexOf("Channel")); colors.put(name, new Integer(value)); } else if (qName.endsWith("DyeName")) { int channelIndex = qName.indexOf("Channel"); if (channelIndex < 0) channelIndex = 0; dyes.put(qName.substring(0, channelIndex), value); } else if (qName.equals("uiSequenceCount")) { int imageCount = Integer.parseInt(value); if (core.length > 0) { int newCount = imageCount / core.length; if (newCount * core.length == imageCount) { imageCount = newCount; } } if (core[0].sizeZ * core[0].sizeT != imageCount && core[0].sizeZ * core[0].sizeC * core[0].sizeT != imageCount) { if (core[0].sizeZ > 1 && core[0].sizeT <= 1) { core[0].sizeZ = imageCount; core[0].sizeT = 1; core[0].imageCount = imageCount; } else if (core[0].sizeT > 1 && core[0].sizeZ <= 1) { core[0].sizeT = imageCount; core[0].sizeZ = 1; core[0].imageCount = imageCount; } else if (imageCount == 0) { core[0].sizeT = 0; core[0].sizeZ = 0; core[0].imageCount = 0; } } metadata.put(qName, value); } else { StringBuffer sb = new StringBuffer(); if (prefix != null) { sb.append(prefix); sb.append(" "); } sb.append(qName); parseKeyAndValue(sb.toString(), value, prevRuntype); } prevRuntype = attributes.getValue("runtype"); } public void endDocument() { for (String name : colors.keySet()) { String chName = dyes.get(name); if (chName == null) chName = name; realColors.put(chName, colors.get(name)); } } // -- Helper methods -- private void parseKeyAndValue(String key, String value, String runtype) { if (key == null || value == null) return; metadata.put(key, value); if (key.endsWith("dCalibration")) { pixelSizeX = Double.parseDouble(DataTools.sanitizeDouble(value)); pixelSizeY = pixelSizeX; } else if (key.endsWith("dZStep")) { pixelSizeZ = Double.parseDouble(DataTools.sanitizeDouble(value)); } else if (key.endsWith("Gain")) { value = DataTools.sanitizeDouble(value); if (!value.equals("")) { gain.add(new Double(value)); } } else if (key.endsWith("dLampVoltage")) { voltage = new Double(DataTools.sanitizeDouble(value)); } else if (key.endsWith("dObjectiveMag") && mag == null) { mag = new Double(DataTools.sanitizeDouble(value)); } else if (key.endsWith("dObjectiveNA")) { na = new Double(DataTools.sanitizeDouble(value)); } else if (key.endsWith("dRefractIndex1")) { refractiveIndex = new Double(DataTools.sanitizeDouble(value)); } else if (key.equals("sObjective") || key.equals("wsObjectiveName") || key.equals("sOptics")) { String[] tokens = value.split(" "); int magIndex = -1; for (int i=0; i<tokens.length; i++) { if (tokens[i].indexOf("x") != -1) { magIndex = i; break; } } StringBuffer s = new StringBuffer(); for (int i=0; i<magIndex; i++) { s.append(tokens[i]); } correction = s.toString(); if (magIndex >= 0) { String m = tokens[magIndex].substring(0, tokens[magIndex].indexOf("x")); m = DataTools.sanitizeDouble(m); if (m.length() > 0) { mag = new Double(m); } } if (magIndex + 1 < tokens.length) immersion = tokens[magIndex + 1]; } else if (key.endsWith("dTimeMSec")) { long v = (long) Double.parseDouble(DataTools.sanitizeDouble(value)); if (!ts.contains(new Long(v))) { ts.add(new Long(v)); metadata.put("number of timepoints", ts.size()); } } else if (key.endsWith("dZPos")) { long v = (long) Double.parseDouble(DataTools.sanitizeDouble(value)); if (!zs.contains(new Long(v))) { zs.add(new Long(v)); } } else if (key.endsWith("uiCount")) { if (runtype != null) { if (runtype.endsWith("ZStackLoop")) { if (core[0].sizeZ == 0) { core[0].sizeZ = Integer.parseInt(value); if (core[0].dimensionOrder.indexOf("Z") == -1) { core[0].dimensionOrder = "Z" + core[0].dimensionOrder; } } } else if (runtype.endsWith("TimeLoop")) { if (core[0].sizeT == 0) { core[0].sizeT = Integer.parseInt(value); if (core[0].dimensionOrder.indexOf("T") == -1) { core[0].dimensionOrder = "T" + core[0].dimensionOrder; } } } else if (runtype.endsWith("XYPosLoop") && core.length == 1) { CoreMetadata oldCore = core[0]; core = new CoreMetadata[Integer.parseInt(value)]; for (int i=0; i<core.length; i++) { core[i] = oldCore; } } } } else if (key.endsWith("uiBpcSignificant")) { core[0].bitsPerPixel = Integer.parseInt(value); } else if (key.equals("VirtualComponents")) { if (core[0].sizeC == 0) { core[0].sizeC = Integer.parseInt(value); if (core[0].dimensionOrder.indexOf("C") == -1) { core[0].dimensionOrder += "C" + core[0].dimensionOrder; } } } else if (key.startsWith("TextInfoItem") || key.endsWith("TextInfoItem")) { metadata.remove(key); value = value.replaceAll(" ", ""); value = value.replaceAll("#x000d;", ""); value = value.replaceAll(" ", "\n"); value = value.replaceAll("#x000a;", "\n"); String[] tokens = value.split("\n"); for (String t : tokens) { t = t.trim(); if (t.startsWith("Dimensions:")) { t = t.substring(11); String[] dims = t.split(" x "); if (core[0].sizeZ == 0) core[0].sizeZ = 1; if (core[0].sizeT == 0) core[0].sizeT = 1; if (core[0].sizeC == 0) core[0].sizeC = 1; for (String dim : dims) { dim = dim.trim(); int v = Integer.parseInt(dim.replaceAll("\\D", "")); v = (int) Math.max(v, 1); if (dim.startsWith("XY")) { numSeries = v; if (numSeries > 1) { int x = core[0].sizeX; int y = core[0].sizeY; int z = core[0].sizeZ; int tSize = core[0].sizeT; int c = core[0].sizeC; String order = core[0].dimensionOrder; core = new CoreMetadata[numSeries]; for (int i=0; i<numSeries; i++) { core[i] = new CoreMetadata(); core[i].sizeX = x; core[i].sizeY = y; core[i].sizeZ = z == 0 ? 1 : z; core[i].sizeC = c == 0 ? 1 : c; core[i].sizeT = tSize == 0 ? 1 : tSize; core[i].dimensionOrder = order; } } } else if (dim.startsWith("T")) { if (core[0].sizeT <= 1 || v < core[0].sizeT) { core[0].sizeT = v; } } else if (dim.startsWith("Z")) { if (core[0].sizeZ <= 1) { core[0].sizeZ = v; } } else if (core[0].sizeC <= 1) { core[0].sizeC = v; } } core[0].imageCount = core[0].sizeZ * core[0].sizeC * core[0].sizeT; } else if (t.startsWith("Number of Picture Planes")) { core[0].sizeC = Integer.parseInt(t.replaceAll("\\D", "")); } else { String[] v = t.split(":"); if (v.length == 2) { v[1] = v[1].trim(); if (v[0].equals("Name")) { channelNames.add(v[1]); } else if (v[0].equals("Modality")) { modality.add(v[1]); } else if (v[0].equals("Camera Type")) { cameraModel = v[1]; } else if (v[0].equals("Binning")) { binning.add(v[1]); } else if (v[0].equals("Readout Speed")) { int last = v[1].lastIndexOf(" "); if (last != -1) v[1] = v[1].substring(0, last); speed.add(new Double(DataTools.sanitizeDouble(v[1]))); } else if (v[0].equals("Temperature")) { String temp = v[1].replaceAll("[\\D&&[^-.]]", ""); temperature.add(new Double(DataTools.sanitizeDouble(temp))); } else if (v[0].equals("Exposure")) { String[] s = v[1].trim().split(" "); try { double time = Double.parseDouble(DataTools.sanitizeDouble(s[0])); // TODO: check for other units if (s[1].equals("ms")) time /= 1000; exposureTime.add(new Double(time)); } catch (NumberFormatException e) { } } else if (v[0].equals("{Pinhole Size}")) { pinholeSize = new Double(DataTools.sanitizeDouble(v[1])); metadata.put("Pinhole size", v[1]); } } else if (v[0].startsWith("- Step")) { int space = v[0].indexOf(" ", v[0].indexOf("Step") + 1); int last = v[0].indexOf(" ", space + 1); if (last == -1) last = v[0].length(); pixelSizeZ = Double.parseDouble( DataTools.sanitizeDouble(v[0].substring(space, last))); } else if (v[0].equals("Line")) { String[] values = t.split(";"); for (int q=0; q<values.length; q++) { int colon = values[q].indexOf(":"); if (colon < 0) continue; String nextKey = values[q].substring(0, colon).trim(); String nextValue = values[q].substring(colon + 1).trim(); if (nextKey.equals("Emission wavelength")) { emWave.add(new Integer(nextValue)); } else if (nextKey.equals("Excitation wavelength")) { exWave.add(new Integer(nextValue)); } else if (nextKey.equals("Power")) { nextValue = DataTools.sanitizeDouble(nextValue); power.add(new Integer((int) Double.parseDouble(nextValue))); } } } else if (v.length > 1) { v[0] = v[0].replace('{', ' '); v[0] = v[0].replace('}', ' '); metadata.put(v[0].trim(), v[1]); } } } } else if (key.equals("CameraUniqueName")) { cameraModel = value; } else if (key.equals("ExposureTime")) { exposureTime.add(new Double(value) / 1000d); } else if (key.equals("sDate")) { date = DateTools.formatDate(value, DATE_FORMAT); } } }