/* * #%L * OME Bio-Formats package for reading and converting biological file formats. * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * 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, see * <http://www.gnu.org/licenses/gpl-2.0.html>. * #L% */ package loci.formats.in; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import javax.xml.parsers.DocumentBuilder; import loci.common.ByteArrayHandle; import loci.common.Constants; import loci.common.DateTools; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.common.Region; import loci.common.xml.XMLTools; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatReader; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.UnsupportedCompressionException; import loci.formats.codec.CodecOptions; import loci.formats.codec.JPEGCodec; import loci.formats.codec.LZWCodec; import loci.formats.meta.MetadataStore; import ome.xml.model.enums.AcquisitionMode; import ome.xml.model.enums.Binning; import ome.xml.model.enums.IlluminationType; import ome.xml.model.primitives.Color; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PercentFraction; import ome.xml.model.primitives.PositiveFloat; import ome.xml.model.primitives.PositiveInteger; import ome.xml.model.primitives.Timestamp; import ome.units.quantity.Length; import ome.units.quantity.Power; import ome.units.quantity.Pressure; import ome.units.quantity.Temperature; import ome.units.quantity.Time; import ome.units.UNITS; import org.xml.sax.SAXException; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * ZeissCZIReader is the file format reader for Zeiss .czi files. */ public class ZeissCZIReader extends FormatReader { // -- Constants -- private static final int ALIGNMENT = 32; private static final int HEADER_SIZE = 32; private static final String CZI_MAGIC_STRING = "ZISRAWFILE"; private static final int BUFFER_SIZE = 512; /** Compression constants. */ private static final int UNCOMPRESSED = 0; private static final int JPEG = 1; private static final int LZW = 2; private static final int JPEGXR = 4; /** Pixel type constants. */ private static final int GRAY8 = 0; private static final int GRAY16 = 1; private static final int GRAY_FLOAT = 2; private static final int BGR_24 = 3; private static final int BGR_48 = 4; private static final int BGR_FLOAT = 8; private static final int BGRA_8 = 9; private static final int COMPLEX = 10; private static final int COMPLEX_FLOAT = 11; private static final int GRAY32 = 12; private static final int GRAY_DOUBLE = 13; // -- Fields -- private MetadataStore store; private HashMap<Integer, String> pixels; private ArrayList<Segment> segments; private ArrayList<SubBlock> planes; private HashMap<Coordinate, ArrayList<Integer>> indexIntoPlanes = new HashMap<Coordinate, ArrayList<Integer>>(); private int rotations = 1; private int positions = 1; private int illuminations = 1; private int acquisitions = 1; private int mosaics = 1; private int phases = 1; private int angles = 1; private String imageName; private String acquiredDate; private String description; private String userDisplayName, userName; private String userFirstName, userLastName, userMiddleName; private String userEmail; private String userInstitution; private String temperature, airPressure, humidity, co2Percent; private String correctionCollar, medium, refractiveIndex; private String zoom; private String gain; private ArrayList<Channel> channels = new ArrayList<Channel>(); private ArrayList<String> binnings = new ArrayList<String>(); private ArrayList<String> detectorRefs = new ArrayList<String>(); private ArrayList<Double> timestamps = new ArrayList<Double>(); private Length[] positionsX; private Length[] positionsY; private Length[] positionsZ; private int previousChannel = 0; private Boolean prestitched = null; private String objectiveSettingsID; private boolean hasDetectorSettings = false; private int scanDim = 1; private String[] rotationLabels, phaseLabels, illuminationLabels; private transient DocumentBuilder parser; // -- Constructor -- /** Constructs a new Zeiss .czi reader. */ public ZeissCZIReader() { super("Zeiss CZI", "czi"); domains = new String[] {FormatTools.LM_DOMAIN}; suffixSufficient = true; suffixNecessary = false; } // -- IFormatReader API methods -- /** * @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ @Override public boolean isThisType(RandomAccessInputStream stream) throws IOException { final int blockLen = 10; if (!FormatTools.validStream(stream, blockLen, true)) return false; String check = stream.readString(blockLen); return check.equals(CZI_MAGIC_STRING); } /** * @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ @Override public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); if (pixels == null || pixels.size() == 0 && noPixels) { return null; } else if (noPixels) { return null; } String[] files = new String[pixels.size() + 1]; files[0] = currentId; Integer[] keys = pixels.keySet().toArray(new Integer[pixels.size()]); Arrays.sort(keys); for (int i=0; i<keys.length; i++) { files[i + 1] = pixels.get(keys[i]); } return files; } /* @see loci.formats.IFormatReader#get8BitLookupTable() */ @Override public byte[][] get8BitLookupTable() throws FormatException, IOException { if ((getPixelType() != FormatTools.INT8 && getPixelType() != FormatTools.UINT8) || previousChannel == -1 || previousChannel >= channels.size()) { return null; } byte[][] lut = new byte[3][256]; String color = channels.get(previousChannel).color; if (color != null) { color = color.replaceAll("#", ""); try { int colorValue = Integer.parseInt(color, 16); int redMax = (colorValue & 0xff0000) >> 16; int greenMax = (colorValue & 0xff00) >> 8; int blueMax = colorValue & 0xff; for (int i=0; i<lut[0].length; i++) { lut[0][i] = (byte) (redMax * (i / 255.0)); lut[1][i] = (byte) (greenMax * (i / 255.0)); lut[2][i] = (byte) (blueMax * (i / 255.0)); } return lut; } catch (NumberFormatException e) { return null; } } else return null; } /* @see loci.formats.IFormatReader#get16BitLookupTable() */ @Override public short[][] get16BitLookupTable() throws FormatException, IOException { if ((getPixelType() != FormatTools.INT16 && getPixelType() != FormatTools.UINT16) || previousChannel == -1 || previousChannel >= channels.size()) { return null; } short[][] lut = new short[3][65536]; String color = channels.get(previousChannel).color; if (color != null) { color = color.replaceAll("#", ""); try { int colorValue = Integer.parseInt(color, 16); int redMax = (colorValue & 0xff0000) >> 16; int greenMax = (colorValue & 0xff00) >> 8; int blueMax = colorValue & 0xff; redMax = (int) (65535 * (redMax / 255.0)); greenMax = (int) (65535 * (greenMax / 255.0)); blueMax = (int) (65535 * (blueMax / 255.0)); for (int i=0; i<lut[0].length; i++) { lut[0][i] = (short) ((int) (redMax * (i / 65535.0)) & 0xffff); lut[1][i] = (short) ((int) (greenMax * (i / 65535.0)) & 0xffff); lut[2][i] = (short) ((int) (blueMax * (i / 65535.0)) & 0xffff); } return lut; } catch (NumberFormatException e) { return null; } } else return null; } /** * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ @Override public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); previousChannel = getZCTCoords(no)[1]; int currentSeries = getSeries(); Region image = new Region(x, y, w, h); int bpp = FormatTools.getBytesPerPixel(getPixelType()); int pixel = getRGBChannelCount() * bpp; int outputRowLen = w * pixel; int outputRow = 0, outputCol = 0; boolean validScanDim = scanDim == (getImageCount() / getSizeC()) && scanDim > 1; if (planes.size() == getImageCount()) { validScanDim = false; } Arrays.fill(buf, (byte) 0); RandomAccessInputStream stream = new RandomAccessInputStream(currentId); try { for (SubBlock plane : planes) { if ((plane.seriesIndex == currentSeries && plane.planeIndex == no) || (plane.planeIndex == previousChannel && validScanDim)) { if ((prestitched != null && prestitched) || validScanDim) { int realX = plane.x; int realY = plane.y; Region tile = new Region(plane.col, plane.row, realX, realY); if (validScanDim) { tile.y += (no / getSizeC()); image.height = scanDim; } if (prestitched != null && prestitched && realX == getSizeX() && realY == getSizeY()) { tile.x = 0; tile.y = 0; } if (tile.intersects(image)) { byte[] rawData = new SubBlock(plane).readPixelData(); Region intersection = tile.intersection(image); int intersectionX = 0; if (tile.x < image.x) { intersectionX = image.x - tile.x; } outputCol = (intersection.x - x) * pixel; outputRow = intersection.y - y; if (validScanDim) { outputRow -= tile.y; } int rowLen = pixel * (int) Math.min(intersection.width, realX); int outputOffset = outputRow * outputRowLen + outputCol; for (int trow=0; trow<intersection.height; trow++) { int realRow = trow + intersection.y - tile.y; if (validScanDim) { realRow += tile.y; } int inputOffset = pixel * (realRow * realX + intersectionX); System.arraycopy( rawData, inputOffset, buf, outputOffset, rowLen); outputOffset += outputRowLen; } } } else { byte[] rawData = new SubBlock(plane).readPixelData(); RandomAccessInputStream s = new RandomAccessInputStream(rawData); try { readPlane(s, x, y, w, h, buf); } finally { s.close(); } break; } } } } finally { stream.close(); } if (isRGB()) { // channels are stored in BGR order; red and blue channels need switching for (int i=0; i<buf.length/(getRGBChannelCount()*bpp); i++) { for (int b=0; b<bpp; b++) { int blueIndex = i * getRGBChannelCount() * bpp + b; int redIndex = i * getRGBChannelCount() * bpp + bpp * 2 + b; byte red = buf[redIndex]; buf[redIndex] = buf[blueIndex]; buf[blueIndex] = red; } } } return buf; } /* @see loci.formats.IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { pixels = null; segments = null; planes = null; rotations = 1; positions = 1; illuminations = 1; acquisitions = 1; mosaics = 1; phases = 1; angles = 1; store = null; acquiredDate = null; userDisplayName = null; userName = null; userFirstName = null; userLastName = null; userMiddleName = null; userEmail = null; userInstitution = null; temperature = null; airPressure = null; humidity = null; co2Percent = null; correctionCollar = null; medium = null; refractiveIndex = null; positionsX = null; positionsY = null; positionsZ = null; zoom = null; gain = null; channels.clear(); binnings.clear(); detectorRefs.clear(); timestamps.clear(); previousChannel = 0; prestitched = null; objectiveSettingsID = null; imageName = null; hasDetectorSettings = false; scanDim = 1; rotationLabels = null; illuminationLabels = null; phaseLabels = null; indexIntoPlanes.clear(); parser = null; } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ @Override protected void initFile(String id) throws FormatException, IOException { super.initFile(id); parser = XMLTools.createBuilder(); // switch to the master file if this is part of a multi-file dataset String base = id.substring(0, id.lastIndexOf(".")); if (base.endsWith(")") && isGroupFiles()) { LOGGER.info("Checking for master file"); int lastFileSeparator = base.lastIndexOf(File.separator); int end = base.lastIndexOf(" ("); if (end < 0 || end < lastFileSeparator) { end = base.lastIndexOf("("); } if (end > 0 && end > lastFileSeparator) { base = base.substring(0, end) + ".czi"; if (new Location(base).exists()) { LOGGER.info("Initializing master file {}", base); initFile(base); return; } } } CoreMetadata ms0 = core.get(0); ms0.littleEndian = true; pixels = new HashMap<Integer, String>(); segments = new ArrayList<Segment>(); planes = new ArrayList<SubBlock>(); readSegments(id); // check if we have the master file in a multi-file dataset // file names are not stored in the files; we have to rely on a // specific naming convention: // // master_file.czi // master_file (1).czi // master_file (2).czi // ... // // the number of files is also not stored, so we have to manually check // for all files with a matching name Location file = new Location(id).getAbsoluteFile(); base = file.getName(); base = base.substring(0, base.lastIndexOf(".")); Location parent = file.getParentFile(); String[] list = parent.list(true); for (String f : list) { if (f.startsWith(base + "(") || f.startsWith(base + " (")) { String part = f.substring(f.lastIndexOf("(") + 1, f.lastIndexOf(")")); try { pixels.put(Integer.parseInt(part), new Location(parent, f).getAbsolutePath()); } catch (NumberFormatException e) { LOGGER.debug("{} not included in multi-file dataset", f); } } } Integer[] keys = pixels.keySet().toArray(new Integer[pixels.size()]); Arrays.sort(keys); for (Integer key : keys) { readSegments(pixels.get(key)); } calculateDimensions(); if (getSizeC() == 0) { ms0.sizeC = 1; } if (getSizeZ() == 0) { ms0.sizeZ = 1; } if (getSizeT() == 0) { ms0.sizeT = 1; } if (getImageCount() == 0) { ms0.imageCount = ms0.sizeZ * ms0.sizeT; } convertPixelType(planes.get(0).directoryEntry.pixelType); // remove any invalid SubBlocks int bpp = FormatTools.getBytesPerPixel(getPixelType()); for (int i=0; i<planes.size(); i++) { long planeSize = (long) planes.get(i).x * planes.get(i).y * bpp; if (planes.get(i).directoryEntry.compression == UNCOMPRESSED) { long size = planes.get(i).dataSize; if (size < planeSize || planeSize >= Integer.MAX_VALUE || size < 0) { LOGGER.trace( "removing block #{}; calculated size = {}, recorded size = {}", i, planeSize, size); planes.remove(i); i--; } else { scanDim = (int) (size / planeSize); } } else { byte[] pixels = planes.get(i).readPixelData(); if (pixels.length < planeSize || planeSize >= Integer.MAX_VALUE) { LOGGER.trace( "removing block #{}; calculated size = {}, decoded size = {}", i, planeSize, pixels.length); planes.remove(i); i--; } else { scanDim = (int) (pixels.length / planeSize); } } } if (getSizeZ() == 0) { ms0.sizeZ = 1; } if (getSizeC() == 0) { ms0.sizeC = 1; } if (getSizeT() == 0) { ms0.sizeT = 1; } // set modulo annotations // rotations -> modulo Z // illuminations -> modulo C // phases -> modulo T LOGGER.trace("rotations = {}", rotations); LOGGER.trace("illuminations = {}", illuminations); LOGGER.trace("phases = {}", phases); LOGGER.trace("positions = {}", positions); LOGGER.trace("acquisitions = {}", acquisitions); LOGGER.trace("mosaics = {}", mosaics); LOGGER.trace("angles = {}", angles); ms0.moduloZ.step = ms0.sizeZ; ms0.moduloZ.end = ms0.sizeZ * (rotations - 1); ms0.moduloZ.type = FormatTools.ROTATION; ms0.sizeZ *= rotations; ms0.moduloC.step = ms0.sizeC; ms0.moduloC.end = ms0.sizeC * (illuminations - 1); ms0.moduloC.type = FormatTools.ILLUMINATION; ms0.moduloC.parentType = FormatTools.CHANNEL; ms0.sizeC *= illuminations; ms0.moduloT.step = ms0.sizeT; ms0.moduloT.end = ms0.sizeT * (phases - 1); ms0.moduloT.type = FormatTools.PHASE; ms0.sizeT *= phases; // finish populating the core metadata int seriesCount = positions * acquisitions * mosaics * angles; ms0.imageCount = getSizeZ() * (isRGB() ? 1 : getSizeC()) * getSizeT(); LOGGER.trace("Size Z = {}", getSizeZ()); LOGGER.trace("Size C = {}", getSizeC()); LOGGER.trace("Size T = {}", getSizeT()); LOGGER.trace("is RGB = {}", isRGB()); LOGGER.trace("calculated image count = {}", ms0.imageCount); LOGGER.trace("number of available planes = {}", planes.size()); LOGGER.trace("prestitched = {}", prestitched); LOGGER.trace("scanDim = {}", scanDim); if (mosaics == seriesCount && seriesCount == (planes.size() / getImageCount()) && prestitched != null && prestitched) { boolean equalTiles = true; for (SubBlock plane : planes) { if (plane.x != planes.get(0).x || plane.y != planes.get(0).y) { equalTiles = false; break; } } if (getSizeX() > planes.get(0).x && !equalTiles) { // image was fused; treat the mosaics as a single image seriesCount = 1; positions = 1; acquisitions = 1; mosaics = 1; angles = 1; } else { prestitched = false; ms0.sizeX = planes.get(planes.size() - 1).x; ms0.sizeY = planes.get(planes.size() - 1).y; } } if (ms0.imageCount * seriesCount > planes.size() * scanDim && planes.size() > 0) { if (planes.size() != ms0.imageCount && planes.size() != ms0.sizeT && (planes.size() % (seriesCount * getSizeZ())) == 0) { if (!isGroupFiles() && planes.size() == (ms0.imageCount * seriesCount) / positions) { seriesCount /= positions; positions = 1; } } else if (planes.size() == ms0.sizeT || planes.size() == ms0.imageCount || (!isGroupFiles() && positions > 1)) { positions = 1; acquisitions = 1; mosaics = 1; angles = 1; seriesCount = 1; } } if (seriesCount > 1) { core.clear(); for (int i=0; i<seriesCount; i++) { core.add(ms0); } } ms0.dimensionOrder = "XYCZT"; // populate the OME metadata store = makeFilterMetadata(); MetadataTools.populatePixels(store, this, true); String firstXML = null; boolean canSkipXML = true; String currentPath = new Location(currentId).getAbsolutePath(); for (Segment segment : segments) { String path = new Location(segment.filename).getAbsolutePath(); if (currentPath.equals(path) && segment instanceof Metadata) { segment.fillInData(); String xml = ((Metadata) segment).xml; xml = XMLTools.sanitizeXML(xml); if (firstXML == null && canSkipXML) { firstXML = xml; } if (canSkipXML && firstXML.equals(xml)) { translateMetadata(xml); } else if (!firstXML.equals(xml)) { canSkipXML = false; } ((Metadata) segment).clearXML(); } else if (segment instanceof Attachment) { AttachmentEntry entry = ((Attachment) segment).attachment; if (entry.name.trim().equals("TimeStamps")) { segment.fillInData(); RandomAccessInputStream s = new RandomAccessInputStream(((Attachment) segment).attachmentData); try { s.order(isLittleEndian()); s.seek(8); while (s.getFilePointer() + 8 <= s.length()) { timestamps.add(s.readDouble()); } } finally { s.close(); } } } segment.close(); } if (rotationLabels != null) { ms0.moduloZ.labels = rotationLabels; ms0.moduloZ.end = ms0.moduloZ.start; } if (illuminationLabels != null) { ms0.moduloC.labels = illuminationLabels; ms0.moduloC.end = ms0.moduloC.start; } if (phaseLabels != null) { ms0.moduloT.labels = phaseLabels; ms0.moduloT.end = ms0.moduloT.start; } assignPlaneIndices(); for (int i=0; i<planes.size(); i++) { SubBlock p = planes.get(i); Coordinate c = new Coordinate(p.seriesIndex, p.planeIndex, getImageCount()); ArrayList<Integer> indices = new ArrayList<Integer>(); if (indexIntoPlanes.containsKey(c)) { indices = indexIntoPlanes.get(c); } indices.add(i); indexIntoPlanes.put(c, indices); } if (channels.size() > 0 && channels.get(0).color != null && !isRGB()) { for (int i=0; i<seriesCount; i++) { core.get(i).indexed = true; } } String experimenterID = MetadataTools.createLSID("Experimenter", 0); store.setExperimenterID(experimenterID, 0); store.setExperimenterEmail(userEmail, 0); store.setExperimenterFirstName(userFirstName, 0); store.setExperimenterInstitution(userInstitution, 0); store.setExperimenterLastName(userLastName, 0); store.setExperimenterMiddleName(userMiddleName, 0); store.setExperimenterUserName(userName, 0); String name = new Location(getCurrentFile()).getName(); if (imageName != null && imageName.trim().length() > 0) { name = imageName; } int indexLength = String.valueOf(getSeriesCount()).length(); for (int i=0; i<getSeriesCount(); i++) { store.setImageInstrumentRef(MetadataTools.createLSID("Instrument", 0), i); if (acquiredDate != null) { store.setImageAcquisitionDate(new Timestamp(acquiredDate), i); } else if (planes.get(0).timestamp != null) { long timestamp = (long) (planes.get(0).timestamp * 1000); String date = DateTools.convertDate(timestamp, DateTools.UNIX); store.setImageAcquisitionDate(new Timestamp(date), i); } if (experimenterID != null) { store.setImageExperimenterRef(experimenterID, i); } String imageIndex = String.valueOf(i + 1); while (imageIndex.length() < indexLength) { imageIndex = "0" + imageIndex; } store.setImageName(name + " #" + imageIndex, i); if (description != null && description.length() > 0) { store.setImageDescription(description, i); } if (airPressure != null) { store.setImagingEnvironmentAirPressure( new Pressure(new Double(airPressure), UNITS.MBAR), i); } if (co2Percent != null) { store.setImagingEnvironmentCO2Percent( PercentFraction.valueOf(co2Percent), i); } if (humidity != null) { store.setImagingEnvironmentHumidity( PercentFraction.valueOf(humidity), i); } if (temperature != null) { store.setImagingEnvironmentTemperature(new Temperature( new Double(temperature), UNITS.DEGREEC), i); } if (objectiveSettingsID != null) { store.setObjectiveSettingsID(objectiveSettingsID, i); if (correctionCollar != null) { store.setObjectiveSettingsCorrectionCollar( new Double(correctionCollar), i); } if (medium != null) { store.setObjectiveSettingsMedium(getMedium(medium), i); } if (refractiveIndex != null) { store.setObjectiveSettingsRefractiveIndex( new Double(refractiveIndex), i); } } Double startTime = null; if (acquiredDate != null) { Timestamp t = Timestamp.valueOf(acquiredDate); if (t != null) startTime = t.asInstant().getMillis() / 1000d; } for (int plane=0; plane<getImageCount(); plane++) { Coordinate coordinate = new Coordinate(i, plane, getImageCount()); ArrayList<Integer> index = indexIntoPlanes.get(coordinate); if (index == null) { continue; } SubBlock p = planes.get(index.get(0)); if (startTime == null) { startTime = p.timestamp; } if (p.stageX != null) { store.setPlanePositionX(p.stageX, i, plane); } else if (positionsX != null && i < positionsX.length && positionsX[i] != null) { store.setPlanePositionX(positionsX[i], i, plane); } else { store.setPlanePositionX(new Length(p.col, UNITS.REFERENCEFRAME), i, plane); } if (p.stageY != null) { store.setPlanePositionY(p.stageY, i, plane); } else if (positionsY != null && i < positionsY.length && positionsY[i] != null) { store.setPlanePositionY(positionsY[i], i, plane); } else { store.setPlanePositionY(new Length(p.row, UNITS.REFERENCEFRAME), i, plane); } if (p.stageZ != null) { store.setPlanePositionZ(p.stageZ, i, plane); } else if (positionsZ != null && i < positionsZ.length) { store.setPlanePositionZ(positionsZ[i], i, plane); } if (p.timestamp != null) { store.setPlaneDeltaT(new Time(p.timestamp - startTime, UNITS.S), i, plane); } else if (plane < timestamps.size()) { if (timestamps.get(plane) != null) { store.setPlaneDeltaT(new Time(timestamps.get(plane), UNITS.S), i, plane); } } if (p.exposureTime != null) { store.setPlaneExposureTime(new Time(p.exposureTime, UNITS.S), i, plane); } else { int channel = getZCTCoords(plane)[1]; if (channel < channels.size() && channels.get(channel).exposure != null) { store.setPlaneExposureTime( new Time(channels.get(channel).exposure, UNITS.S), i, plane); } } } for (int c=0; c<getEffectiveSizeC(); c++) { if (c < channels.size()) { store.setChannelName(channels.get(c).name, i, c); store.setChannelFluor(channels.get(c).fluor, i, c); if (channels.get(c).filterSetRef != null) { store.setChannelFilterSetRef(channels.get(c).filterSetRef, i, c); } String color = channels.get(c).color; if (color != null) { color = color.replaceAll("#", ""); if (color.length() > 6) { color = color.substring(2, color.length()); } try { // shift by 8 to allow alpha in the final byte store.setChannelColor( new Color((Integer.parseInt(color, 16) << 8) | 0xff), i, c); } catch (NumberFormatException e) { LOGGER.warn("", e); } } String emWave = channels.get(c).emission; if (emWave != null) { Double wave = new Double(emWave); Length em = FormatTools.getEmissionWavelength(wave); if (em != null) { store.setChannelEmissionWavelength(em, i, c); } } String exWave = channels.get(c).excitation; if (exWave != null) { Double wave = new Double(exWave); Length ex = FormatTools.getExcitationWavelength(wave); if (ex != null) { store.setChannelExcitationWavelength(ex, i, c); } } if (channels.get(c).illumination != null) { store.setChannelIlluminationType( channels.get(c).illumination, i, c); } if (channels.get(c).pinhole != null) { store.setChannelPinholeSize( new Length(new Double(channels.get(c).pinhole), UNITS.MICROM), i, c); } if (channels.get(c).acquisitionMode != null) { store.setChannelAcquisitionMode( channels.get(c).acquisitionMode, i, c); } } if (c < detectorRefs.size()) { String detector = detectorRefs.get(c); store.setDetectorSettingsID(detector, i, c); if (c < binnings.size()) { store.setDetectorSettingsBinning(getBinning(binnings.get(c)), i, c); } if (c < channels.size()) { store.setDetectorSettingsGain(channels.get(c).gain, i, c); } } if (c < channels.size()) { if (hasDetectorSettings) { store.setDetectorSettingsGain(channels.get(c).gain, i, c); } } } } // not needed by further calls on the reader segments = null; } // -- Helper methods -- private void readSegments(String id) throws IOException { if (in != null) { in.close(); } in = new RandomAccessInputStream(id, BUFFER_SIZE); in.order(isLittleEndian()); while (in.getFilePointer() < in.length()) { Segment segment = readSegment(id); if (segment == null) { break; } segments.add(segment); if (segment instanceof SubBlock) { planes.add((SubBlock) segment); } segment.close(); } } private void calculateDimensions() { // calculate the dimensions CoreMetadata ms0 = core.get(0); ArrayList<Integer> uniqueT = new ArrayList<Integer>(); for (SubBlock plane : planes) { for (DimensionEntry dimension : plane.directoryEntry.dimensionEntries) { if (dimension == null) { continue; } switch (dimension.dimension.charAt(0)) { case 'X': plane.x = dimension.size; plane.col = dimension.start; if ((prestitched == null || prestitched) && getSizeX() > 0 && dimension.size != getSizeX()) { prestitched = true; continue; } ms0.sizeX = dimension.size; break; case 'Y': plane.y = dimension.size; plane.row = dimension.start; if ((prestitched == null || prestitched) && getSizeY() > 0 && dimension.size != getSizeY()) { prestitched = true; continue; } ms0.sizeY = dimension.size; break; case 'C': if (dimension.start >= getSizeC()) { ms0.sizeC = dimension.start + 1; } break; case 'Z': if (dimension.start >= getSizeZ()) { ms0.sizeZ = dimension.start + 1; } else if (dimension.size > getSizeZ()) { ms0.sizeZ = dimension.size; } break; case 'T': if (!uniqueT.contains(dimension.start)) { uniqueT.add(dimension.start); ms0.sizeT = uniqueT.size(); } if (dimension.size > getSizeT()) { ms0.sizeT = dimension.size; } break; case 'R': if (dimension.start >= rotations) { rotations = dimension.start + 1; } break; case 'S': if (dimension.start >= positions) { positions = dimension.start + 1; } break; case 'I': if (dimension.start >= illuminations) { illuminations = dimension.start + 1; } break; case 'B': if (dimension.start >= acquisitions) { acquisitions = dimension.start + 1; } break; case 'M': if (dimension.start >= mosaics) { mosaics = dimension.start + 1; } break; case 'H': if (dimension.start >= phases) { phases = dimension.start + 1; } break; case 'V': if (dimension.start >= angles) { angles = dimension.start + 1; } break; default: LOGGER.warn("Unknown dimension '{}'", dimension.dimension); } } } } private void assignPlaneIndices() { LOGGER.trace("assignPlaneIndices:"); // assign plane and series indices to each SubBlock if (getSeriesCount() == mosaics) { LOGGER.trace(" reset position, acquisition, and angle count"); positions = 1; acquisitions = 1; angles = 1; } // use the natural ordering of the extra dimensions, // instead of always using SBMV ArrayList<Character> extraDimOrder = new ArrayList<Character>(); int[] extraLengths = new int[4]; int prevS = 0, prevB = 0, prevM = 0, prevV = 0; for (int p=0; p<planes.size(); p++) { SubBlock plane = planes.get(p); for (DimensionEntry dimension : plane.directoryEntry.dimensionEntries) { if (dimension == null) { continue; } switch (dimension.dimension.charAt(0)) { case 'S': if (dimension.start > prevS) { if (!extraDimOrder.contains('S')) { extraLengths[extraDimOrder.size()] = positions; extraDimOrder.add('S'); } } prevS = dimension.start; break; case 'B': if (dimension.start > prevB) { if (!extraDimOrder.contains('B')) { extraLengths[extraDimOrder.size()] = acquisitions; extraDimOrder.add('B'); } } prevB = dimension.start; break; case 'M': if (dimension.start > prevM) { if (!extraDimOrder.contains('M')) { extraLengths[extraDimOrder.size()] = mosaics; extraDimOrder.add('M'); } } prevM = dimension.start; break; case 'V': if (dimension.start > prevV) { if (!extraDimOrder.contains('V')) { extraLengths[extraDimOrder.size()] = angles; extraDimOrder.add('V'); } } prevV = dimension.start; break; } } } for (int p=0; p<planes.size(); p++) { LOGGER.trace(" processing plane #{} of {}", p, planes.size()); SubBlock plane = planes.get(p); int z = 0; int c = 0; int t = 0; int r = 0; int i = 0; int phase = 0; int[] extra = new int[4]; boolean noAngle = true; for (DimensionEntry dimension : plane.directoryEntry.dimensionEntries) { LOGGER.trace(" procession dimension '{}'", dimension.dimension); LOGGER.trace(" dimension size = {}", dimension.size); LOGGER.trace(" dimension start = {}", dimension.start); if (dimension == null) { continue; } int extraIndex = extraDimOrder.indexOf(dimension.dimension.charAt(0)); switch (dimension.dimension.charAt(0)) { case 'C': c = dimension.start; break; case 'Z': z = dimension.start; if (z >= getSizeZ()) { z = getSizeZ() - 1; } break; case 'T': t = dimension.start; if (t >= getSizeT()) { t = getSizeT() - 1; } break; case 'R': r = dimension.start; break; case 'S': if (extraIndex >= 0) { extra[extraIndex] = dimension.start; if (extra[extraIndex] >= extraLengths[extraIndex]) { extra[extraIndex] = 0; } } break; case 'I': i = dimension.start; break; case 'B': if (extraIndex >= 0) { extra[extraIndex] = dimension.start; if (extra[extraIndex] >= extraLengths[extraIndex]) { extra[extraIndex] = 0; } } break; case 'M': if (extraIndex >= 0) { extra[extraIndex] = dimension.start; if (extra[extraIndex] >= extraLengths[extraIndex]) { extra[extraIndex] = 0; } } break; case 'H': phase = dimension.start; break; case 'V': if (extraIndex >= 0) { extra[extraIndex] = dimension.start; if (extra[extraIndex] >= extraLengths[extraIndex]) { extra[extraIndex] = 0; } } noAngle = false; break; } } if (angles > 1 && noAngle) { extra[extraDimOrder.indexOf('V')] = p / (getImageCount() * (getSeriesCount() / angles)); } if (rotations > 0) { z = r * (getSizeZ() / rotations) + z; } if (illuminations > 0) { c = i * (getSizeC() / illuminations) + c; } if (phases > 0) { t = phase * (getSizeT() / phases) + t; } plane.planeIndex = getIndex(z, c, t); plane.seriesIndex = FormatTools.positionToRaster(extraLengths, extra); LOGGER.trace(" assigned plane index = {}; series index = {}", plane.planeIndex, plane.seriesIndex); } } private void translateMetadata(String xml) throws FormatException, IOException { Element root = null; try { ByteArrayInputStream s = new ByteArrayInputStream(xml.getBytes(Constants.ENCODING)); root = parser.parse(s).getDocumentElement(); s.close(); } catch (SAXException e) { throw new FormatException(e); } if (root == null) { throw new FormatException("Could not parse the XML metadata."); } NodeList children = root.getChildNodes(); Element realRoot = null; for (int i=0; i<children.getLength(); i++) { if (children.item(i) instanceof Element) { realRoot = (Element) children.item(i); break; } } translateExperiment(realRoot); translateInformation(realRoot); translateScaling(realRoot); translateDisplaySettings(realRoot); translateLayers(realRoot); translateHardwareSettings(realRoot); final Deque<String> nameStack = new ArrayDeque<String>(); populateOriginalMetadata(realRoot, nameStack); } private void translateInformation(Element root) throws FormatException { NodeList informations = root.getElementsByTagName("Information"); if (informations == null || informations.getLength() == 0) { return; } Element information = (Element) informations.item(0); Element image = getFirstNode(information, "Image"); Element user = getFirstNode(information, "User"); Element environment = getFirstNode(information, "Environment"); Element instrument = getFirstNode(information, "Instrument"); Element document = getFirstNode(information, "Document"); if (image != null) { String bitCount = getFirstNodeValue(image, "ComponentBitCount"); if (bitCount != null) { core.get(0).bitsPerPixel = Integer.parseInt(bitCount); } acquiredDate = getFirstNodeValue(image, "AcquisitionDateAndTime"); Element objectiveSettings = getFirstNode(image, "ObjectiveSettings"); correctionCollar = getFirstNodeValue(objectiveSettings, "CorrectionCollar"); medium = getFirstNodeValue(objectiveSettings, "Medium"); refractiveIndex = getFirstNodeValue(objectiveSettings, "RefractiveIndex"); String sizeV = getFirstNodeValue(image, "SizeV"); if (sizeV != null && angles == 1) { angles = Integer.parseInt(sizeV); } Element dimensions = getFirstNode(image, "Dimensions"); NodeList channelNodes = getGrandchildren(dimensions, "Channel"); if (channelNodes == null) { channelNodes = image.getElementsByTagName("Channel"); } if (channelNodes != null) { for (int i=0; i<channelNodes.getLength(); i++) { Element channel = (Element) channelNodes.item(i); while (channels.size() <= i) { channels.add(new Channel()); } channels.get(i).emission = getFirstNodeValue(channel, "EmissionWavelength"); channels.get(i).excitation = getFirstNodeValue(channel, "ExcitationWavelength"); channels.get(i).pinhole = getFirstNodeValue(channel, "PinholeSize"); channels.get(i).name = channel.getAttribute("Name"); String illumination = getFirstNodeValue(channel, "IlluminationType"); if (illumination != null) { channels.get(i).illumination = getIlluminationType(illumination); } String acquisition = getFirstNodeValue(channel, "AcquisitionMode"); if (acquisition != null) { channels.get(i).acquisitionMode = getAcquisitionMode(acquisition); } Element detectorSettings = getFirstNode(channel, "DetectorSettings"); String binning = getFirstNodeValue(detectorSettings, "Binning"); if (binning != null) { binning = binning.replaceAll(",", "x"); binnings.add(binning); } Element scanInfo = getFirstNode(channel, "LaserScanInfo"); if (scanInfo != null) { zoom = getFirstNodeValue(scanInfo, "ZoomX"); } Element detector = getFirstNode(detectorSettings, "Detector"); if (detector != null) { String detectorID = detector.getAttribute("Id"); if (detectorID.indexOf(" ") != -1) { detectorID = detectorID.substring(detectorID.lastIndexOf(" ") + 1); } if (!detectorID.startsWith("Detector:")) { detectorID = "Detector:" + detectorID; } detectorRefs.add(detectorID); } Element filterSet = getFirstNode(channel, "FilterSetRef"); if (filterSet != null) { channels.get(i).filterSetRef = filterSet.getAttribute("Id"); } } } } if (user != null) { userDisplayName = getFirstNodeValue(user, "DisplayName"); userFirstName = getFirstNodeValue(user, "FirstName"); userLastName = getFirstNodeValue(user, "LastName"); userMiddleName = getFirstNodeValue(user, "MiddleName"); userEmail = getFirstNodeValue(user, "Email"); userInstitution = getFirstNodeValue(user, "Institution"); userName = getFirstNodeValue(user, "UserName"); } if (environment != null) { temperature = getFirstNodeValue(environment, "Temperature"); airPressure = getFirstNodeValue(environment, "AirPressure"); humidity = getFirstNodeValue(environment, "Humidity"); co2Percent = getFirstNodeValue(environment, "CO2Percent"); } if (instrument != null) { NodeList microscopes = getGrandchildren(instrument, "Microscope"); Element manufacturerNode = null; store.setInstrumentID(MetadataTools.createLSID("Instrument", 0), 0); if (microscopes != null) { Element microscope = (Element) microscopes.item(0); manufacturerNode = getFirstNode(microscope, "Manufacturer"); store.setMicroscopeManufacturer( getFirstNodeValue(manufacturerNode, "Manufacturer"), 0); store.setMicroscopeModel( getFirstNodeValue(manufacturerNode, "Model"), 0); store.setMicroscopeSerialNumber( getFirstNodeValue(manufacturerNode, "SerialNumber"), 0); store.setMicroscopeLotNumber( getFirstNodeValue(manufacturerNode, "LotNumber"), 0); String microscopeType = getFirstNodeValue(microscope, "Type"); if (microscopeType != null) { store.setMicroscopeType(getMicroscopeType(microscopeType), 0); } } NodeList lightSources = getGrandchildren(instrument, "LightSource"); if (lightSources != null) { for (int i=0; i<lightSources.getLength(); i++) { Element lightSource = (Element) lightSources.item(i); manufacturerNode = getFirstNode(lightSource, "Manufacturer"); String manufacturer = getFirstNodeValue(manufacturerNode, "Manufacturer"); String model = getFirstNodeValue(manufacturerNode, "Model"); String serialNumber = getFirstNodeValue(manufacturerNode, "SerialNumber"); String lotNumber = getFirstNodeValue(manufacturerNode, "LotNumber"); String type = getFirstNodeValue(lightSource, "LightSourceType"); String power = getFirstNodeValue(lightSource, "Power"); if ("Laser".equals(type)) { if (power != null) { store.setLaserPower(new Power(new Double(power), UNITS.MW), 0, i); } store.setLaserLotNumber(lotNumber, 0, i); store.setLaserManufacturer(manufacturer, 0, i); store.setLaserModel(model, 0, i); store.setLaserSerialNumber(serialNumber, 0, i); } else if ("Arc".equals(type)) { if (power != null) { store.setArcPower(new Power(new Double(power), UNITS.MW), 0, i); } store.setArcLotNumber(lotNumber, 0, i); store.setArcManufacturer(manufacturer, 0, i); store.setArcModel(model, 0, i); store.setArcSerialNumber(serialNumber, 0, i); } else if ("LightEmittingDiode".equals(type)) { if (power != null) { store.setLightEmittingDiodePower(new Power(new Double(power), UNITS.MW), 0, i); } store.setLightEmittingDiodeLotNumber(lotNumber, 0, i); store.setLightEmittingDiodeManufacturer(manufacturer, 0, i); store.setLightEmittingDiodeModel(model, 0, i); store.setLightEmittingDiodeSerialNumber(serialNumber, 0, i); } else if ("Filament".equals(type)) { if (power != null) { store.setFilamentPower(new Power(new Double(power), UNITS.MW), 0, i); } store.setFilamentLotNumber(lotNumber, 0, i); store.setFilamentManufacturer(manufacturer, 0, i); store.setFilamentModel(model, 0, i); store.setFilamentSerialNumber(serialNumber, 0, i); } } } NodeList detectors = getGrandchildren(instrument, "Detector"); if (detectors != null) { for (int i=0; i<detectors.getLength(); i++) { Element detector = (Element) detectors.item(i); manufacturerNode = getFirstNode(detector, "Manufacturer"); String manufacturer = getFirstNodeValue(manufacturerNode, "Manufacturer"); String model = getFirstNodeValue(manufacturerNode, "Model"); String serialNumber = getFirstNodeValue(manufacturerNode, "SerialNumber"); String lotNumber = getFirstNodeValue(manufacturerNode, "LotNumber"); String detectorID = detector.getAttribute("Id"); if (detectorID.indexOf(" ") != -1) { detectorID = detectorID.substring(detectorID.lastIndexOf(" ") + 1); } if (!detectorID.startsWith("Detector:")) { detectorID = "Detector:" + detectorID; } store.setDetectorID(detectorID, 0, i); store.setDetectorManufacturer(manufacturer, 0, i); store.setDetectorModel(model, 0, i); store.setDetectorSerialNumber(serialNumber, 0, i); store.setDetectorLotNumber(lotNumber, 0, i); if (gain == null) { gain = getFirstNodeValue(detector, "Gain"); } if (gain != null && !gain.equals("")) { store.setDetectorGain(new Double(gain), 0, i); } String offset = getFirstNodeValue(detector, "Offset"); if (offset != null && !offset.equals("")) { store.setDetectorOffset(new Double(offset), 0, i); } if (zoom == null) { zoom = getFirstNodeValue(detector, "Zoom"); } if (zoom != null && !zoom.equals("")) { store.setDetectorZoom(new Double(zoom), 0, i); } String ampGain = getFirstNodeValue(detector, "AmplificationGain"); if (ampGain != null && !ampGain.equals("")) { store.setDetectorAmplificationGain(new Double(ampGain), 0, i); } String detectorType = getFirstNodeValue(detector, "Type"); if (detectorType != null && !detectorType.equals("")) { store.setDetectorType(getDetectorType(detectorType), 0, i); } } } NodeList objectives = getGrandchildren(instrument, "Objective"); parseObjectives(objectives); NodeList filterSets = getGrandchildren(instrument, "FilterSet"); if (filterSets != null) { for (int i=0; i<filterSets.getLength(); i++) { Element filterSet = (Element) filterSets.item(i); manufacturerNode = getFirstNode(filterSet, "Manufacturer"); String manufacturer = getFirstNodeValue(manufacturerNode, "Manufacturer"); String model = getFirstNodeValue(manufacturerNode, "Model"); String serialNumber = getFirstNodeValue(manufacturerNode, "SerialNumber"); String lotNumber = getFirstNodeValue(manufacturerNode, "LotNumber"); String dichroicRef = getFirstNodeValue(filterSet, "DichroicRef"); NodeList excitations = getGrandchildren( filterSet, "ExcitationFilters", "ExcitationFilterRef"); NodeList emissions = getGrandchildren(filterSet, "EmissionFilters", "EmissionFilterRef"); if (dichroicRef == null || dichroicRef.length() <= 0) { Element ref = getFirstNode(filterSet, "DichroicRef"); if (ref != null) { dichroicRef = ref.getAttribute("Id"); } } if (excitations == null) { excitations = filterSet.getElementsByTagName("ExcitationFilterRef"); } if (emissions == null) { emissions = filterSet.getElementsByTagName("EmissionFilterRef"); } if (dichroicRef != null || excitations != null || emissions != null) { store.setFilterSetID(filterSet.getAttribute("Id"), 0, i); store.setFilterSetManufacturer(manufacturer, 0, i); store.setFilterSetModel(model, 0, i); store.setFilterSetSerialNumber(serialNumber, 0, i); store.setFilterSetLotNumber(lotNumber, 0, i); } if (dichroicRef != null && dichroicRef.length() > 0) { store.setFilterSetDichroicRef(dichroicRef, 0, i); } if (excitations != null) { for (int ex=0; ex<excitations.getLength(); ex++) { Element excitation = (Element) excitations.item(ex); String ref = excitation.getTextContent(); if (ref == null || ref.length() <= 0) { ref = excitation.getAttribute("Id"); } if (ref != null && ref.length() > 0) { store.setFilterSetExcitationFilterRef(ref, 0, i, ex); } } } if (emissions != null) { for (int em=0; em<emissions.getLength(); em++) { Element emission = (Element) emissions.item(em); String ref = emission.getTextContent(); if (ref == null || ref.length() <= 0) { ref = emission.getAttribute("Id"); } if (ref != null && ref.length() > 0) { store.setFilterSetEmissionFilterRef(ref, 0, i, em); } } } } } NodeList filters = getGrandchildren(instrument, "Filter"); if (filters != null) { for (int i=0; i<filters.getLength(); i++) { Element filter = (Element) filters.item(i); manufacturerNode = getFirstNode(filter, "Manufacturer"); String manufacturer = getFirstNodeValue(manufacturerNode, "Manufacturer"); String model = getFirstNodeValue(manufacturerNode, "Model"); String serialNumber = getFirstNodeValue(manufacturerNode, "SerialNumber"); String lotNumber = getFirstNodeValue(manufacturerNode, "LotNumber"); store.setFilterID(filter.getAttribute("Id"), 0, i); store.setFilterManufacturer(manufacturer, 0, i); store.setFilterModel(model, 0, i); store.setFilterSerialNumber(serialNumber, 0, i); store.setFilterLotNumber(lotNumber, 0, i); String filterType = getFirstNodeValue(filter, "Type"); if (filterType != null) { store.setFilterType(getFilterType(filterType), 0, i); } store.setFilterFilterWheel( getFirstNodeValue(filter, "FilterWheel"), 0, i); Element transmittance = getFirstNode(filter, "TransmittanceRange"); String cutIn = getFirstNodeValue(transmittance, "CutIn"); String cutOut = getFirstNodeValue(transmittance, "CutOut"); Double inWave = cutIn == null ? 0 : new Double(cutIn); Double outWave = cutOut == null ? 0 : new Double(cutOut); Length in = FormatTools.getCutIn(inWave); Length out = FormatTools.getCutOut(outWave); if (in != null) { store.setTransmittanceRangeCutIn(in, 0, i); } if (out != null) { store.setTransmittanceRangeCutOut(out, 0, i); } String inTolerance = getFirstNodeValue(transmittance, "CutInTolerance"); String outTolerance = getFirstNodeValue(transmittance, "CutOutTolerance"); if (inTolerance != null) { Double cutInTolerance = new Double(inTolerance); store.setTransmittanceRangeCutInTolerance( new Length(cutInTolerance, UNITS.NM), 0, i); } if (outTolerance != null) { Double cutOutTolerance = new Double(outTolerance); store.setTransmittanceRangeCutOutTolerance( new Length(cutOutTolerance, UNITS.NM), 0, i); } String transmittancePercent = getFirstNodeValue(transmittance, "Transmittance"); if (transmittancePercent != null) { store.setTransmittanceRangeTransmittance( PercentFraction.valueOf(transmittancePercent), 0, i); } } } NodeList dichroics = getGrandchildren(instrument, "Dichroic"); if (dichroics != null) { for (int i=0; i<dichroics.getLength(); i++) { Element dichroic = (Element) dichroics.item(i); manufacturerNode = getFirstNode(dichroic, "Manufacturer"); String manufacturer = getFirstNodeValue(manufacturerNode, "Manufacturer"); String model = getFirstNodeValue(manufacturerNode, "Model"); String serialNumber = getFirstNodeValue(manufacturerNode, "SerialNumber"); String lotNumber = getFirstNodeValue(manufacturerNode, "LotNumber"); store.setDichroicID(dichroic.getAttribute("Id"), 0, i); store.setDichroicManufacturer(manufacturer, 0, i); store.setDichroicModel(model, 0, i); store.setDichroicSerialNumber(serialNumber, 0, i); store.setDichroicLotNumber(lotNumber, 0, i); } } } if (document != null) { description = getFirstNodeValue(document, "Description"); if (userName == null) { userName = getFirstNodeValue(document, "UserName"); } imageName = getFirstNodeValue(document, "Name"); } } private void translateScaling(Element root) { NodeList scalings = root.getElementsByTagName("Scaling"); if (scalings == null || scalings.getLength() == 0) { return; } Element scaling = (Element) scalings.item(0); NodeList distances = getGrandchildren(scaling, "Items", "Distance"); if (distances != null) { for (int i=0; i<distances.getLength(); i++) { Element distance = (Element) distances.item(i); String id = distance.getAttribute("Id"); String originalValue = getFirstNodeValue(distance, "Value"); if (originalValue == null) { continue; } Double value = new Double(originalValue) * 1000000; if (value > 0) { PositiveFloat size = new PositiveFloat(value); if (id.equals("X")) { for (int series=0; series<getSeriesCount(); series++) { store.setPixelsPhysicalSizeX(FormatTools.createLength(size, UNITS.MICROM), series); } } else if (id.equals("Y")) { for (int series=0; series<getSeriesCount(); series++) { store.setPixelsPhysicalSizeY(FormatTools.createLength(size, UNITS.MICROM), series); } } else if (id.equals("Z")) { for (int series=0; series<getSeriesCount(); series++) { store.setPixelsPhysicalSizeZ(FormatTools.createLength(size, UNITS.MICROM), series); } } } else { LOGGER.debug( "Expected positive value for PhysicalSize; got {}", value); } } } } private void translateDisplaySettings(Element root) throws FormatException { NodeList displaySettings = root.getElementsByTagName("DisplaySetting"); if (displaySettings == null || displaySettings.getLength() == 0) { return; } Element displaySetting = (Element) displaySettings.item(0); NodeList channelNodes = getGrandchildren(displaySetting, "Channel"); if (channelNodes != null) { for (int i=0; i<channelNodes.getLength(); i++) { Element channel = (Element) channelNodes.item(i); String color = getFirstNodeValue(channel, "Color"); if (color == null) { color = getFirstNodeValue(channel, "OriginalColor"); } while (channels.size() <= i) { channels.add(new Channel()); } channels.get(i).color = color; String fluor = getFirstNodeValue(channel, "DyeName"); if (fluor != null) { channels.get(i).fluor = fluor; } String name = channel.getAttribute("Name"); if (name != null) { channels.get(i).name = name; } String emission = getFirstNodeValue(channel, "DyeMaxEmission"); if (emission != null) { channels.get(i).emission = emission; } String excitation = getFirstNodeValue(channel, "DyeMaxExcitation"); if (excitation != null) { channels.get(i).excitation = excitation; } String illumination = getFirstNodeValue(channel, "IlluminationType"); if (illumination != null) { channels.get(i).illumination = getIlluminationType(illumination); } } } } private void translateLayers(Element root) { NodeList layerses = root.getElementsByTagName("Layers"); if (layerses == null || layerses.getLength() == 0) { return; } Element layersNode = (Element) layerses.item(0); NodeList layers = layersNode.getElementsByTagName("Layer"); if (layers != null) { for (int i=0; i<layers.getLength(); i++) { Element layer = (Element) layers.item(i); NodeList elementses = layer.getElementsByTagName("Elements"); if (elementses.getLength() == 0) { continue; } NodeList allGrandchildren = elementses.item(0).getChildNodes(); int shape = 0; NodeList lines = getGrandchildren(layer, "Elements", "Line"); shape = populateLines(lines, i, shape); NodeList arrows = getGrandchildren(layer, "Elements", "OpenArrow"); shape = populateLines(arrows, i, shape); NodeList crosses = getGrandchildren(layer, "Elements", "Cross"); for (int s=0; s<crosses.getLength(); s++, shape+=2) { Element cross = (Element) crosses.item(s); Element geometry = getFirstNode(cross, "Geometry"); Element textElements = getFirstNode(cross, "TextElements"); Element attributes = getFirstNode(cross, "Attributes"); store.setLineID( MetadataTools.createLSID("Shape", i, shape), i, shape); store.setLineID( MetadataTools.createLSID("Shape", i, shape + 1), i, shape + 1); String length = getFirstNodeValue(geometry, "Length"); String centerX = getFirstNodeValue(geometry, "CenterX"); String centerY = getFirstNodeValue(geometry, "CenterY"); if (length != null) { Double halfLen = new Double(length) / 2; if (centerX != null) { store.setLineX1(new Double(centerX) - halfLen, i, shape); store.setLineX2(new Double(centerX) + halfLen, i, shape); store.setLineX1(new Double(centerX), i, shape + 1); store.setLineX2(new Double(centerX), i, shape + 1); } if (centerY != null) { store.setLineY1(new Double(centerY), i, shape); store.setLineY2(new Double(centerY), i, shape); store.setLineY1(new Double(centerY) - halfLen, i, shape + 1); store.setLineY2(new Double(centerY) + halfLen, i, shape + 1); } } store.setLineText(getFirstNodeValue(textElements, "Text"), i, shape); store.setLineText(getFirstNodeValue(textElements, "Text"), i, shape + 1); } NodeList rectangles = getGrandchildren(layer, "Elements", "Rectangle"); if (rectangles != null) { shape = populateRectangles(rectangles, i, shape); } NodeList ellipses = getGrandchildren(layer, "Elements", "Ellipse"); if (ellipses != null) { for (int s=0; s<ellipses.getLength(); s++, shape++) { Element ellipse = (Element) ellipses.item(s); Element geometry = getFirstNode(ellipse, "Geometry"); Element textElements = getFirstNode(ellipse, "TextElements"); Element attributes = getFirstNode(ellipse, "Attributes"); store.setEllipseID( MetadataTools.createLSID("Shape", i, shape), i, shape); String radiusX = getFirstNodeValue(geometry, "RadiusX"); String radiusY = getFirstNodeValue(geometry, "RadiusY"); String centerX = getFirstNodeValue(geometry, "CenterX"); String centerY = getFirstNodeValue(geometry, "CenterY"); if (radiusX != null) { store.setEllipseRadiusX(new Double(radiusX), i, shape); } if (radiusY != null) { store.setEllipseRadiusY(new Double(radiusY), i, shape); } if (centerX != null) { store.setEllipseX(new Double(centerX), i, shape); } if (centerY != null) { store.setEllipseY(new Double(centerY), i, shape); } store.setEllipseText( getFirstNodeValue(textElements, "Text"), i, shape); } } // translate all of the circle ROIs NodeList circles = getGrandchildren(layer, "Elements", "Circle"); if (circles != null) { shape = populateCircles(circles, i, shape); } NodeList inOutCircles = getGrandchildren(layer, "Elements", "InOutCircle"); if (inOutCircles != null) { shape = populateCircles(inOutCircles, i, shape); } NodeList outInCircles = getGrandchildren(layer, "Elements", "OutInCircle"); if (outInCircles != null) { shape = populateCircles(outInCircles, i, shape); } NodeList pointsCircles = getGrandchildren(layer, "Elements", "PointsCircle"); if (pointsCircles != null) { shape = populateCircles(pointsCircles, i, shape); } NodeList polygons = getGrandchildren(layer, "Elements", "Polygon"); if (polygons != null) { shape = populatePolylines(polygons, i, shape, true); } NodeList polylines = getGrandchildren(layer, "Elements", "Polyline"); if (polylines != null) { shape = populatePolylines(polylines, i, shape, false); } NodeList openPolylines = getGrandchildren(layer, "Elements", "OpenPolyline"); if (openPolylines != null) { shape = populatePolylines(openPolylines, i, shape, false); } NodeList closedPolylines = getGrandchildren(layer, "Elements", "ClosedPolyline"); if (closedPolylines != null) { shape = populatePolylines(closedPolylines, i, shape, true); } NodeList rectRoi = getGrandchildren(layer, "Elements", "RectRoi"); if (rectRoi != null) { shape = populateRectangles(rectRoi, i, shape); } NodeList textBoxes = getGrandchildren(layer, "Elements", "TextBox"); if (textBoxes != null) { shape = populateRectangles(textBoxes, i, shape); } NodeList text = getGrandchildren(layer, "Elements", "Text"); if (text != null) { shape = populateRectangles(text, i, shape); } if (shape > 0) { String roiID = MetadataTools.createLSID("ROI", i); store.setROIID(roiID, i); store.setROIName(layer.getAttribute("Name"), i); store.setROIDescription(getFirstNodeValue(layer, "Usage"), i); for (int series=0; series<getSeriesCount(); series++) { store.setImageROIRef(roiID, series, i); } } } } } private void translateHardwareSettings(Element root) throws FormatException { NodeList hardwareSettings = root.getElementsByTagName("HardwareSetting"); if (hardwareSettings == null || hardwareSettings.getLength() == 0) { return; } Element hardware = (Element) hardwareSettings.item(0); store.setInstrumentID(MetadataTools.createLSID("Instrument", 0), 0); Element microscope = getFirstNode(hardware, "Microscope"); if (microscope != null) { String model = microscope.getAttribute("Name"); store.setMicroscopeModel(model, 0); } Element objectiveChanger = getFirstNode(hardware, "ObjectiveChanger"); if (objectiveChanger != null) { String position = getFirstNodeValue(objectiveChanger, "Position"); int positionIndex = -1; if (position != null) { try { positionIndex = Integer.parseInt(position) - 1; } catch (NumberFormatException e) { LOGGER.debug("Could not parse ObjectiveSettings", e); } } NodeList objectives = objectiveChanger.getElementsByTagName("Objective"); if (objectives != null) { for (int i=0; i<objectives.getLength(); i++) { Element objective = (Element) objectives.item(i); String objectiveID = MetadataTools.createLSID("Objective", 0, i); if (i == positionIndex || (objectives.getLength() == 1 && objectiveSettingsID != null)) { objectiveSettingsID = objectiveID; } store.setObjectiveID(objectiveID, 0, i); store.setObjectiveModel(objective.getAttribute("Model"), 0, i); store.setObjectiveSerialNumber( objective.getAttribute("UniqueName"), 0, i); String immersion = getFirstNodeValue(objective, "Immersions"); store.setObjectiveImmersion(getImmersion(immersion), 0, i); store.setObjectiveCorrection(getCorrection("Other"), 0, i); String magnification = getFirstNodeValue(objective, "Magnification"); String na = getFirstNodeValue(objective, "NumericalAperture"); String wd = getFirstNodeValue(objective, "WorkingDistance"); if (magnification != null) { try { store.setObjectiveNominalMagnification( new Double(magnification), 0, i); } catch (NumberFormatException e) { LOGGER.debug("Could not parse magnification", e); } } if (na != null) { try { store.setObjectiveLensNA(new Double(na), 0, i); } catch (NumberFormatException e) { LOGGER.debug("Could not parse numerical aperture", e); } } if (wd != null) { try { store.setObjectiveWorkingDistance(new Length(new Double(wd), UNITS.MICROM), 0, i); } catch (NumberFormatException e) { LOGGER.debug("Could not parse working distance", e); } } } } } } private int populateRectangles(NodeList rectangles, int roi, int shape) { for (int s=0; s<rectangles.getLength(); s++) { Element rectangle = (Element) rectangles.item(s); Element geometry = getFirstNode(rectangle, "Geometry"); Element textElements = getFirstNode(rectangle, "TextElements"); Element attributes = getFirstNode(rectangle, "Attributes"); String left = getFirstNodeValue(geometry, "Left"); String top = getFirstNodeValue(geometry, "Top"); String width = getFirstNodeValue(geometry, "Width"); String height = getFirstNodeValue(geometry, "Height"); if (left != null && top != null && width != null && height != null) { store.setRectangleID( MetadataTools.createLSID("Shape", roi, shape), roi, shape); store.setRectangleX(new Double(left), roi, shape); store.setRectangleY(new Double(top), roi, shape); store.setRectangleWidth(new Double(width), roi, shape); store.setRectangleHeight(new Double(height), roi, shape); String name = getFirstNodeValue(attributes, "Name"); String label = getFirstNodeValue(textElements, "Text"); if (label != null) { store.setRectangleText(label, roi, shape); } shape++; } } return shape; } private int populatePolylines(NodeList polylines, int roi, int shape, boolean closed) { for (int s=0; s<polylines.getLength(); s++, shape++) { Element polyline = (Element) polylines.item(s); Element geometry = getFirstNode(polyline, "Geometry"); Element textElements = getFirstNode(polyline, "TextElements"); Element attributes = getFirstNode(polyline, "Attributes"); String shapeID = MetadataTools.createLSID("Shape", roi, shape); if (closed) { store.setPolygonID(shapeID, roi, shape); store.setPolygonPoints( getFirstNodeValue(geometry, "Points"), roi, shape); store.setPolygonText( getFirstNodeValue(textElements, "Text"), roi, shape); } else { store.setPolylineID(shapeID, roi, shape); store.setPolylinePoints( getFirstNodeValue(geometry, "Points"), roi, shape); store.setPolylineText( getFirstNodeValue(textElements, "Text"), roi, shape); } } return shape; } private int populateLines(NodeList lines, int roi, int shape) { for (int s=0; s<lines.getLength(); s++, shape++) { Element line = (Element) lines.item(s); Element geometry = getFirstNode(line, "Geometry"); Element textElements = getFirstNode(line, "TextElements"); Element attributes = getFirstNode(line, "Attributes"); String x1 = getFirstNodeValue(geometry, "X1"); String x2 = getFirstNodeValue(geometry, "X2"); String y1 = getFirstNodeValue(geometry, "Y1"); String y2 = getFirstNodeValue(geometry, "Y2"); store.setLineID( MetadataTools.createLSID("Shape", roi, shape), roi, shape); if (x1 != null) { store.setLineX1(new Double(x1), roi, shape); } if (x2 != null) { store.setLineX2(new Double(x2), roi, shape); } if (y1 != null) { store.setLineY1(new Double(y1), roi, shape); } if (y2 != null) { store.setLineY2(new Double(y2), roi, shape); } store.setLineText(getFirstNodeValue(textElements, "Text"), roi, shape); } return shape; } private int populateCircles(NodeList circles, int roi, int shape) { for (int s=0; s<circles.getLength(); s++, shape++) { Element circle = (Element) circles.item(s); Element geometry = getFirstNode(circle, "Geometry"); Element textElements = getFirstNode(circle, "TextElements"); Element attributes = getFirstNode(circle, "Attributes"); store.setEllipseID( MetadataTools.createLSID("Shape", roi, shape), roi, shape); String radius = getFirstNodeValue(geometry, "Radius"); String centerX = getFirstNodeValue(geometry, "CenterX"); String centerY = getFirstNodeValue(geometry, "CenterY"); if (radius != null) { store.setEllipseRadiusX(new Double(radius), roi, shape); store.setEllipseRadiusY(new Double(radius), roi, shape); } if (centerX != null) { store.setEllipseX(new Double(centerX), roi, shape); } if (centerY != null) { store.setEllipseY(new Double(centerY), roi, shape); } store.setEllipseText(getFirstNodeValue(textElements, "Text"), roi, shape); } return shape; } private void translateExperiment(Element root) throws FormatException { NodeList experiments = root.getElementsByTagName("Experiment"); if (experiments == null || experiments.getLength() == 0) { return; } Element experimentBlock = getFirstNode((Element) experiments.item(0), "ExperimentBlocks"); Element acquisition = getFirstNode(experimentBlock, "AcquisitionBlock"); Element tilesSetup = getFirstNode(acquisition, "TilesSetup"); NodeList groups = getGrandchildren(tilesSetup, "PositionGroup"); positionsX = new Length[core.size()]; positionsY = new Length[core.size()]; positionsZ = new Length[core.size()]; if (groups != null) { for (int i=0; i<groups.getLength(); i++) { Element group = (Element) groups.item(i); Element position = getFirstNode(group, "Position"); if (position != null) { int tilesX = Integer.parseInt(getFirstNodeValue(group, "TilesX")); int tilesY = Integer.parseInt(getFirstNodeValue(group, "TilesY")); String x = position.getAttribute("X"); String y = position.getAttribute("Y"); String z = position.getAttribute("Z"); Length xPos = null; try { xPos = new Length(Double.valueOf(x), UNITS.REFERENCEFRAME); } catch (NumberFormatException e) { } Length yPos = null; try { yPos = new Length(Double.valueOf(y), UNITS.REFERENCEFRAME); } catch (NumberFormatException e) { } Length zPos = null; try { zPos = new Length(Double.valueOf(z), UNITS.REFERENCEFRAME); } catch (NumberFormatException e) { } for (int tile=0; tile<tilesX * tilesY; tile++) { int index = i * tilesX * tilesY + tile; if (index < positionsX.length) { positionsX[index] = xPos; positionsY[index] = yPos; positionsZ[index] = zPos; } } } } } else { Element regionsSetup = getFirstNode(acquisition, "RegionsSetup"); if (regionsSetup != null) { Element sampleHolder = getFirstNode(regionsSetup, "SampleHolder"); if (sampleHolder != null) { NodeList regions = getGrandchildren(sampleHolder, "SingleTileRegionArray", "SingleTileRegion"); if (regions != null) { for (int i=0; i<regions.getLength(); i++) { Element region = (Element) regions.item(i); String x = getFirstNode(region, "X").getTextContent(); String y = getFirstNode(region, "Y").getTextContent(); String z = getFirstNode(region, "Z").getTextContent(); // safe to assume all 3 arrays have the same length if (i < positionsX.length) { if (x == null) { positionsX[i] = null; } else { final Double number = Double.valueOf(x); positionsX[i] = new Length(number, UNITS.REFERENCEFRAME); } if (y == null) { positionsY[i] = null; } else { final Double number = Double.valueOf(y); positionsY[i] = new Length(number, UNITS.REFERENCEFRAME); } if (z == null) { positionsZ[i] = null; } else { final Double number = Double.valueOf(z); positionsZ[i] = new Length(number, UNITS.REFERENCEFRAME); } } } } } } } NodeList detectors = getGrandchildren(acquisition, "Detector"); Element setup = getFirstNode(acquisition, "AcquisitionModeSetup"); String cameraModel = getFirstNodeValue(setup, "SelectedCamera"); if (detectors != null) { for (int i=0; i<detectors.getLength(); i++) { Element detector = (Element) detectors.item(i); String id = MetadataTools.createLSID("Detector", 0, i); store.setDetectorID(id, 0, i); String model = detector.getAttribute("Id"); store.setDetectorModel(model, 0, i); String bin = getFirstNodeValue(detector, "Binning"); if (bin != null) { bin = bin.replaceAll(",", "x"); Binning binning = getBinning(bin); if (model != null && model.equals(cameraModel)) { for (int image=0; image<getSeriesCount(); image++) { for (int c=0; c<getEffectiveSizeC(); c++) { store.setDetectorSettingsID(id, image, c); store.setDetectorSettingsBinning(binning, image, c); } } hasDetectorSettings = true; } } } } Element multiTrack = getFirstNode(acquisition, "MultiTrackSetup"); if (multiTrack == null) { return; } detectors = getGrandchildren(multiTrack, "Detector"); if (detectors != null && detectors.getLength() > 0) { Element detector = (Element) detectors.item(0); gain = getFirstNodeValue(detector, "Voltage"); } NodeList tracks = multiTrack.getElementsByTagName("Track"); if (tracks != null && tracks.getLength() > 0) { for (int i=0; i<tracks.getLength(); i++) { Element track = (Element) tracks.item(i); Element channel = getFirstNode(track, "Channel"); String exposure = getFirstNodeValue(channel, "ExposureTime"); String gain = getFirstNodeValue(channel, "EMGain"); while (channels.size() <= i) { channels.add(new Channel()); } try { if (exposure != null) { channels.get(i).exposure = new Double(exposure); } } catch (NumberFormatException e) { LOGGER.debug("Could not parse exposure time", e); } try { if (gain != null) { channels.get(i).gain = new Double(gain); } } catch (NumberFormatException e) { LOGGER.debug("Could not parse gain", e); } } } } private Element getFirstNode(Element root, String name) { if (root == null) { return null; } NodeList list = root.getElementsByTagName(name); if (list == null) { return null; } return (Element) list.item(0); } private NodeList getGrandchildren(Element root, String name) { return getGrandchildren(root, name + "s", name); } private NodeList getGrandchildren(Element root, String child, String name) { if (root == null) { return null; } NodeList children = root.getElementsByTagName(child); if (children != null && children.getLength() > 0) { Element childNode = (Element) children.item(0); return childNode.getElementsByTagName(name); } return null; } private String getFirstNodeValue(Element root, String name) { if (root == null) { return null; } NodeList nodes = root.getElementsByTagName(name); if (nodes != null && nodes.getLength() > 0) { return nodes.item(0).getTextContent(); } return null; } private void populateOriginalMetadata(Element root, Deque<String> nameStack) { String name = root.getNodeName(); if (name.equals("DisplaySetting")) { return; } nameStack.push(name); StringBuffer key = new StringBuffer(); String k = null; Iterator<String> keys = nameStack.descendingIterator(); while (keys.hasNext()) { k = keys.next(); if (!k.equals("Metadata") && (!k.endsWith("s") || k.equals(name))) { key.append(k); key.append("|"); } } if (root.getChildNodes().getLength() == 1) { String value = root.getTextContent(); if (value != null && key.length() > 0) { String s = key.toString(); if (s.endsWith("|")){ s = s.substring(0, s.length() - 1); } addGlobalMetaList(s, value); if (key.toString().endsWith("|Rotations|")) { rotationLabels = value.split(" "); } else if (key.toString().endsWith("|Phases|")) { phaseLabels = value.split(" "); } else if (key.toString().endsWith("|Illuminations|")) { illuminationLabels = value.split(" "); } } } NamedNodeMap attributes = root.getAttributes(); for (int i=0; i<attributes.getLength(); i++) { Node attr = attributes.item(i); String attrName = attr.getNodeName(); String attrValue = attr.getNodeValue(); String keyString = key.toString(); if (attrName.endsWith("|")){ attrName = attrName.substring(0, attrName.length() - 1); } else if(attrName.length() == 0 && keyString.endsWith("|")) { keyString = keyString.substring(0, keyString.length() - 1); } addGlobalMetaList(keyString + attrName, attrValue); } NodeList children = root.getChildNodes(); if (children != null) { for (int i=0; i<children.getLength(); i++) { Object child = children.item(i); if (child instanceof Element) { populateOriginalMetadata((Element) child, nameStack); } } } nameStack.pop(); } private Segment readSegment(String filename) throws IOException { // align the stream to a multiple of 32 bytes int skip = (ALIGNMENT - (int) (in.getFilePointer() % ALIGNMENT)) % ALIGNMENT; in.skipBytes(skip); long startingPosition = in.getFilePointer(); // instantiate a Segment subclass based upon the segment ID String segmentID = in.readString(16).trim(); Segment segment = null; boolean skipData = false; if (segmentID.equals("ZISRAWFILE")) { segment = new FileHeader(); } else if (segmentID.equals("ZISRAWMETADATA")) { segment = new Metadata(); } else if (segmentID.equals("ZISRAWSUBBLOCK")) { segment = new SubBlock(); } else if (segmentID.equals("ZISRAWATTACH")) { segment = new Attachment(filename, startingPosition); // attachments can be large, so only read binary data on demand skipData = true; } else if (segmentID.equals("ZISRAWDIRECTORY")) { segment = new Directory(); } else if (segmentID.equals("ZISRAWATTDIR")) { segment = new AttachmentDirectory(); } else if (segmentID.equals("DELETED")) { segment = new Segment(); } else if (segmentID.length() == 0) { segment = new Segment(); skipData = true; } else { LOGGER.info("Unknown segment type: {}", segmentID); segment = new Segment(); } segment.startingPosition = startingPosition; segment.id = segmentID; segment.filename = filename; segment.stream = in; if (!(segment instanceof Metadata)) { if (!skipData) { segment.fillInData(); } } else { ((Metadata) segment).skipData(); } long pos = segment.startingPosition + segment.allocatedSize + HEADER_SIZE; if (pos < in.length()) { in.seek(pos); } else { in.seek(in.length()); } if (skipData && !(segment instanceof Attachment)) { segment.close(); return null; } return segment; } private void convertPixelType(int pixelType) throws FormatException { CoreMetadata ms0 = core.get(0); switch (pixelType) { case GRAY8: ms0.pixelType = FormatTools.UINT8; break; case GRAY16: ms0.pixelType = FormatTools.UINT16; break; case GRAY32: ms0.pixelType = FormatTools.UINT32; break; case GRAY_FLOAT: ms0.pixelType = FormatTools.FLOAT; break; case GRAY_DOUBLE: ms0.pixelType = FormatTools.DOUBLE; break; case BGR_24: ms0.pixelType = FormatTools.UINT8; ms0.sizeC *= 3; ms0.rgb = true; ms0.interleaved = true; break; case BGR_48: ms0.pixelType = FormatTools.UINT16; ms0.sizeC *= 3; ms0.rgb = true; ms0.interleaved = true; break; case BGRA_8: ms0.pixelType = FormatTools.UINT8; ms0.sizeC *= 4; ms0.rgb = true; ms0.interleaved = true; break; case BGR_FLOAT: ms0.pixelType = FormatTools.FLOAT; ms0.sizeC *= 3; ms0.rgb = true; ms0.interleaved = true; break; case COMPLEX: case COMPLEX_FLOAT: throw new FormatException("Sorry, complex pixel data not supported."); default: throw new FormatException("Unknown pixel type: " + pixelType); } ms0.interleaved = ms0.rgb; } private void parseObjectives(NodeList objectives) throws FormatException { if (objectives != null) { for (int i=0; i<objectives.getLength(); i++) { Element objective = (Element) objectives.item(i); Element manufacturerNode = getFirstNode(objective, "Manufacturer"); String manufacturer = getFirstNodeValue(manufacturerNode, "Manufacturer"); String model = getFirstNodeValue(manufacturerNode, "Model"); String serialNumber = getFirstNodeValue(manufacturerNode, "SerialNumber"); String lotNumber = getFirstNodeValue(manufacturerNode, "LotNumber"); if (objectiveSettingsID == null) { objectiveSettingsID = objective.getAttribute("Id"); } store.setObjectiveID(objective.getAttribute("Id"), 0, i); store.setObjectiveManufacturer(manufacturer, 0, i); store.setObjectiveModel(model, 0, i); store.setObjectiveSerialNumber(serialNumber, 0, i); store.setObjectiveLotNumber(lotNumber, 0, i); String correction = getFirstNodeValue(objective, "Correction"); if (correction != null) { store.setObjectiveCorrection(getCorrection(correction), 0, i); } store.setObjectiveImmersion( getImmersion(getFirstNodeValue(objective, "Immersion")), 0, i); String lensNA = getFirstNodeValue(objective, "LensNA"); if (lensNA != null) { store.setObjectiveLensNA(new Double(lensNA), 0, i); } String magnification = getFirstNodeValue(objective, "NominalMagnification"); if (magnification == null) { magnification = getFirstNodeValue(objective, "Magnification"); } Double mag = magnification == null ? null : new Double(magnification); if (mag != null) { store.setObjectiveNominalMagnification(mag, 0, i); } String calibratedMag = getFirstNodeValue(objective, "CalibratedMagnification"); if (calibratedMag != null) { store.setObjectiveCalibratedMagnification( new Double(calibratedMag), 0, i); } String wd = getFirstNodeValue(objective, "WorkingDistance"); if (wd != null) { store.setObjectiveWorkingDistance(new Length(new Double(wd), UNITS.MICROM), 0, i); } String iris = getFirstNodeValue(objective, "Iris"); if (iris != null) { store.setObjectiveIris(new Boolean(iris), 0, i); } } } } // -- Helper classes -- /** Top-level class that implements logic common to all types of Segment. */ class Segment { public String filename; public long startingPosition; public String id; public long allocatedSize; public long usedSize; public RandomAccessInputStream stream; public Segment() { filename = null; startingPosition = 0; id = null; allocatedSize = 0; usedSize = 0; stream = null; } public Segment(String filename) { this(); this.filename = filename; } public Segment(Segment model) { super(); this.filename = model.filename; this.startingPosition = model.startingPosition; this.id = model.id; this.allocatedSize = model.allocatedSize; this.usedSize = model.usedSize; } public void fillInData() throws IOException { RandomAccessInputStream s = getStream(); try { s.order(isLittleEndian()); s.seek(startingPosition + 16); // read the segment header allocatedSize = s.readLong(); usedSize = s.readLong(); if (usedSize == 0) { usedSize = allocatedSize; } } finally { if (stream == null) { s.close(); } } } public void close() throws IOException { // whatever created the Segment is responsible for closing the stream // we just need to remove the reference stream = null; } public RandomAccessInputStream getStream() throws IOException { if (stream != null) { return stream; } return new RandomAccessInputStream(filename, BUFFER_SIZE); } } /** Segment with ID "ZISRAWFILE". */ class FileHeader extends Segment { public int majorVersion; public int minorVersion; public long primaryFileGUID; public long fileGUID; public int filePart; public long directoryPosition; public long metadataPosition; public boolean updatePending; public long attachmentDirectoryPosition; @Override public void fillInData() throws IOException { super.fillInData(); RandomAccessInputStream s = getStream(); try { s.order(isLittleEndian()); s.seek(startingPosition + HEADER_SIZE); majorVersion = s.readInt(); minorVersion = s.readInt(); s.skipBytes(4); // reserved 1 s.skipBytes(4); // reserved 2 primaryFileGUID = s.readLong(); // 16 fileGUID = s.readLong(); // 16 filePart = s.readInt(); directoryPosition = s.readLong(); metadataPosition = s.readLong(); updatePending = s.readInt() != 0; attachmentDirectoryPosition = s.readLong(); } finally { if (stream == null) { s.close(); } } } } /** Segment with ID "ZISRAWMETADATA". */ class Metadata extends Segment { public String xml; public byte[] attachment; public void skipData() throws IOException { super.fillInData(); } @Override public void fillInData() throws IOException { super.fillInData(); RandomAccessInputStream s = getStream(); try { s.order(true); s.seek(startingPosition + HEADER_SIZE); int xmlSize = s.readInt(); int attachmentSize = s.readInt(); s.skipBytes(248); xml = s.readString(xmlSize); attachment = new byte[attachmentSize]; s.read(attachment); } finally { if (stream == null) { s.close(); } } } public void clearXML() { xml = null; } } /** Segment with ID "ZISRAWSUBBLOCK". */ class SubBlock extends Segment { public int metadataSize; public int attachmentSize; public long dataSize; public DirectoryEntry directoryEntry; public String metadata; public int seriesIndex; public int planeIndex; private long dataOffset; private Length stageX, stageY, stageZ; private Double timestamp, exposureTime; public int x, y; public int row, col; public SubBlock() { super(); } public SubBlock(SubBlock model) { super(model); this.metadataSize = model.metadataSize; this.attachmentSize = model.attachmentSize; this.dataSize = model.dataSize; this.directoryEntry = model.directoryEntry; this.metadata = model.metadata; this.seriesIndex = model.seriesIndex; this.planeIndex = model.planeIndex; this.dataOffset = model.dataOffset; this.stageX = model.stageX; this.stageY = model.stageY; this.timestamp = model.timestamp; this.exposureTime = model.exposureTime; this.stageZ = model.stageZ; this.x = model.x; this.y = model.y; } @Override public void fillInData() throws IOException { super.fillInData(); RandomAccessInputStream s = getStream(); try { s.order(isLittleEndian()); s.seek(startingPosition + HEADER_SIZE); long fp = s.getFilePointer(); metadataSize = s.readInt(); attachmentSize = s.readInt(); dataSize = s.readLong(); directoryEntry = new DirectoryEntry(s); s.skipBytes((int) Math.max(256 - (s.getFilePointer() - fp), 0)); metadata = s.readString(metadataSize).trim(); dataOffset = s.getFilePointer(); if (s.getFilePointer() + dataSize + attachmentSize < s.length()) { s.seek(s.getFilePointer() + dataSize + attachmentSize); parseMetadata(); } } finally { if (stream == null) { s.close(); } } } // -- SubBlock API methods -- public byte[] readPixelData() throws FormatException, IOException { RandomAccessInputStream s = new RandomAccessInputStream(filename); try { return readPixelData(s); } finally { s.close(); } } public byte[] readPixelData(RandomAccessInputStream s) throws FormatException, IOException { byte[] data = new byte[(int) dataSize]; s.order(isLittleEndian()); s.seek(dataOffset); s.read(data); CodecOptions options = new CodecOptions(); options.interleaved = isInterleaved(); options.littleEndian = isLittleEndian(); options.maxBytes = getSizeX() * getSizeY() * getRGBChannelCount() * FormatTools.getBytesPerPixel(getPixelType()); switch (directoryEntry.compression) { case UNCOMPRESSED: break; case JPEG: data = new JPEGCodec().decompress(data, options); break; case LZW: data = new LZWCodec().decompress(data, options); break; case JPEGXR: throw new UnsupportedCompressionException( "JPEG-XR not yet supported"); case 104: // camera-specific packed pixels data = decode12BitCamera(data, options.maxBytes); // reverse column ordering for (int row=0; row<getSizeY(); row++) { for (int col=0; col<getSizeX()/2; col++) { int left = row * getSizeX() * 2 + col * 2; int right = row * getSizeX() * 2 + (getSizeX() - col - 1) * 2; byte left1 = data[left]; byte left2 = data[left + 1]; data[left] = data[right]; data[left + 1] = data[right + 1]; data[right] = left1; data[right + 1] = left2; } } break; case 504: // camera-specific packed pixels data = decode12BitCamera(data, options.maxBytes); break; } return data; } // -- Helper methods -- private void parseMetadata() throws IOException { if (metadata.length() <= 16) { return; } Element root = null; try { ByteArrayInputStream s = new ByteArrayInputStream(metadata.getBytes(Constants.ENCODING)); root = parser.parse(s).getDocumentElement(); s.close(); } catch (SAXException e) { metadata = null; return; } if (root == null) { metadata = null; return; } NodeList children = root.getChildNodes(); if (children == null) { metadata = null; return; } for (int i=0; i<children.getLength(); i++) { if (!(children.item(i) instanceof Element)) { continue; } Element child = (Element) children.item(i); if (child.getNodeName().equals("Tags")) { NodeList tags = child.getChildNodes(); if (tags != null) { for (int tag=0; tag<tags.getLength(); tag++) { if (!(tags.item(tag) instanceof Element)) { continue; } Element tagNode = (Element) tags.item(tag); String text = tagNode.getTextContent(); if (text != null) { if (tagNode.getNodeName().equals("StageXPosition")) { final Double number = Double.valueOf(text); stageX = new Length(number, UNITS.REFERENCEFRAME); } else if (tagNode.getNodeName().equals("StageYPosition")) { final Double number = Double.valueOf(text); stageY = new Length(number, UNITS.REFERENCEFRAME); } else if (tagNode.getNodeName().equals("FocusPosition")) { final Double number = Double.valueOf(text); stageZ = new Length(number, UNITS.REFERENCEFRAME); } else if (tagNode.getNodeName().equals("AcquisitionTime")) { Timestamp t = Timestamp.valueOf(text); if (t != null) timestamp = t.asInstant().getMillis() / 1000d; } else if (tagNode.getNodeName().equals("ExposureTime")) { exposureTime = new Double(text); } } } } } } metadata = null; } } private byte[] decode12BitCamera(byte[] data, int maxBytes) throws IOException { byte[] decoded = new byte[maxBytes]; RandomAccessInputStream bb = new RandomAccessInputStream( new ByteArrayHandle(data)); byte[] fourBits = new byte[(maxBytes / 2) * 3]; int pt = 0; while (pt < fourBits.length) { fourBits[pt++] = (byte) bb.readBits(4); } bb.close(); for (int index=0; index<fourBits.length-1; index++) { if ((index - 3) % 6 == 0) { byte middle = fourBits[index]; byte last = fourBits[index + 1]; byte first = fourBits[index - 1]; fourBits[index + 1] = middle; fourBits[index] = first; fourBits[index - 1] = last; } } int currentByte = 0; for (int index=0; index<fourBits.length;) { if (index % 3 == 0) { decoded[currentByte++] = fourBits[index++]; } else { decoded[currentByte++] = (byte) (fourBits[index++] << 4 | fourBits[index++]); } } if (isLittleEndian()) { core.get(0).littleEndian = false; } return decoded; } /** Segment with ID "ZISRAWDIRECTORY". */ class Directory extends Segment { public DirectoryEntry[] entries; @Override public void fillInData() throws IOException { super.fillInData(); RandomAccessInputStream s = getStream(); try { s.order(isLittleEndian()); s.seek(startingPosition + HEADER_SIZE); int entryCount = s.readInt(); s.skipBytes(124); entries = new DirectoryEntry[entryCount]; for (int i=0; i<entryCount; i++) { entries[i] = new DirectoryEntry(s); } } finally { if (stream == null) { s.close(); } } } } /** Segment with ID "ZISRAWATTDIR". */ class AttachmentDirectory extends Segment { public AttachmentEntry[] entries; @Override public void fillInData() throws IOException { super.fillInData(); RandomAccessInputStream s = getStream(); try { s.order(isLittleEndian()); s.seek(startingPosition + HEADER_SIZE); int entryCount = s.readInt(); s.skipBytes(252); entries = new AttachmentEntry[entryCount]; for (int i=0; i<entryCount; i++) { entries[i] = new AttachmentEntry(s); } } finally { if (stream == null) { s.close(); } } } } /** Segment with ID "ZISRAWATTACH". */ class Attachment extends Segment { public int dataSize; public AttachmentEntry attachment; public byte[] attachmentData; public long dataOffset; public Attachment(String filename, long position) throws IOException { super(filename); this.startingPosition = position; super.fillInData(); RandomAccessInputStream s = getStream(); try { s.order(isLittleEndian()); s.seek(startingPosition + HEADER_SIZE); dataSize = s.readInt(); s.skipBytes(12); // reserved attachment = new AttachmentEntry(s); dataOffset = s.getFilePointer() + 112; // skip reserved bytes } finally { if (stream == null) { s.close(); } } } @Override public void fillInData() throws IOException { RandomAccessInputStream s = getStream(); try { s.order(isLittleEndian()); s.seek(dataOffset); attachmentData = new byte[dataSize]; s.read(attachmentData); } finally { if (stream == null) { s.close(); } } } } class DirectoryEntry { public String schemaType; public int pixelType; public long filePosition; public int filePart; public int compression; public byte pyramidType; public int dimensionCount; public DimensionEntry[] dimensionEntries; public DirectoryEntry(RandomAccessInputStream s) throws IOException { schemaType = s.readString(2); pixelType = s.readInt(); filePosition = s.readLong(); filePart = s.readInt(); compression = s.readInt(); pyramidType = s.readByte(); if (pyramidType == 1) { prestitched = false; } s.skipBytes(1); // reserved s.skipBytes(4); // reserved dimensionCount = s.readInt(); dimensionEntries = new DimensionEntry[dimensionCount]; for (int i=0; i<dimensionEntries.length; i++) { dimensionEntries[i] = new DimensionEntry(s); // invalid dimension found; ignore it and the previous one if (dimensionEntries[i].dimension.length() > 1) { dimensionEntries[i] = null; if (i > 0) { dimensionEntries[i - 1] = null; } } } } } static class DimensionEntry { public String dimension; public int start; public int size; public float startCoordinate; public int storedSize; public DimensionEntry(RandomAccessInputStream s) throws IOException { dimension = s.readString(4).trim(); start = s.readInt(); size = s.readInt(); startCoordinate = s.readFloat(); storedSize = s.readInt(); } } static class AttachmentEntry { public String schemaType; public long filePosition; public int filePart; public String contentGUID; public String contentFileType; public String name; public AttachmentEntry(RandomAccessInputStream s) throws IOException { schemaType = s.readString(2); s.skipBytes(10); // reserved filePosition = s.readLong(); filePart = s.readInt(); contentGUID = s.readString(16); contentFileType = s.readString(8); name = s.readString(80); } } static class Channel { public String name; public String color; public IlluminationType illumination; public AcquisitionMode acquisitionMode; public String emission; public String excitation; public String pinhole; public Double exposure; public Double gain; public String fluor; public String filterSetRef; } static class Coordinate { public int series; public int plane; private int imageCount; public Coordinate(int series, int plane, int imageCount) { this.series = series; this.plane = plane; this.imageCount = imageCount; } @Override public boolean equals(Object o) { if (o == null || !(o instanceof Coordinate)) { return false; } return ((Coordinate) o).series == this.series && ((Coordinate) o).plane == this.plane; } @Override public int hashCode() { return series * imageCount + plane; } @Override public String toString() { return "[series = " + series + ", plane = " + plane + "]"; } } }