// // PrairieReader.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.io.File; import java.io.IOException; import java.util.Hashtable; import java.util.Vector; import loci.common.DataTools; import loci.common.DateTools; import loci.common.Location; import loci.common.RandomAccessInputStream; 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.meta.MetadataStore; import ome.xml.model.primitives.PositiveFloat; import loci.formats.tiff.IFD; import loci.formats.tiff.TiffParser; import ome.xml.model.primitives.PositiveInteger; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; /** * PrairieReader is the file format reader for * Prairie Technologies' TIFF variant. * * <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/PrairieReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/PrairieReader.java;hb=HEAD">Gitweb</a></dd></dl> */ public class PrairieReader extends FormatReader { // -- Constants -- public static final String[] CFG_SUFFIX = {"cfg"}; public static final String[] XML_SUFFIX = {"xml"}; public static final String[] PRAIRIE_SUFFIXES = {"cfg", "xml"}; // Private tags present in Prairie TIFF files // IMPORTANT NOTE: these are the same as Metamorph's private tags - therefore, // it is likely that Prairie TIFF files will be incorrectly // identified unless the XML or CFG file is specified private static final int PRAIRIE_TAG_1 = 33628; private static final int PRAIRIE_TAG_2 = 33629; private static final int PRAIRIE_TAG_3 = 33630; // -- Fields -- /** List of files in the current dataset */ private String[] files; /** Helper reader for opening images */ private TiffReader tiff; /** Names of the associated XML files */ private String xmlFile, cfgFile; private boolean readXML = false, readCFG = false; private Vector<String> f, gains, offsets; private double pixelSizeX, pixelSizeY; private String date, laserPower; private String microscopeModel; private String objectiveManufacturer; private PositiveInteger magnification; private String immersion; private Double lensNA; private Double waitTime; private Vector<Double> positionX = new Vector<Double>(); private Vector<Double> positionY = new Vector<Double>(); private Vector<Double> positionZ = new Vector<Double>(); private Vector<String> channels = new Vector<String>(); private Hashtable<String, Double> relativeTimes = new Hashtable<String, Double>(); private Double zoom; // -- Constructor -- /** Constructs a new Prairie TIFF reader. */ public PrairieReader() { super("Prairie TIFF", new String[] {"tif", "tiff", "cfg", "xml"}); domains = new String[] {FormatTools.LM_DOMAIN}; hasCompanionFiles = true; datasetDescription = "One .xml file, one .cfg file, and one or more " + ".tif/.tiff files"; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isSingleFile(String) */ public boolean isSingleFile(String id) throws FormatException, IOException { return false; } /* @see loci.formats.IFormatReader#isThisType(String, boolean) */ public boolean isThisType(String name, boolean open) { if (!open) return false; // not allowed to touch the file system Location file = new Location(name).getAbsoluteFile(); Location parent = file.getParentFile(); String prefix = file.getName(); if (prefix.indexOf(".") != -1) { prefix = prefix.substring(0, prefix.lastIndexOf(".")); } if (checkSuffix(name, CFG_SUFFIX)) { if (prefix.lastIndexOf("Config") == -1) return false; prefix = prefix.substring(0, prefix.lastIndexOf("Config")); } if (prefix.indexOf("_") != -1) { prefix = prefix.substring(0, prefix.indexOf("_")); } // check for appropriately named XML and CFG files Location xml = new Location(parent, prefix + ".xml"); Location cfg = new Location(parent, prefix + "Config.cfg"); boolean hasMetadataFiles = xml.exists() && cfg.exists(); return hasMetadataFiles && super.isThisType(name, false); } /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ public boolean isThisType(RandomAccessInputStream stream) throws IOException { final int blockLen = 1048608; if (!FormatTools.validStream(stream, blockLen, false)) return false; String s = stream.readString(blockLen); if (s.indexOf("xml") != -1 && s.indexOf("PV") != -1) return true; TiffParser tp = new TiffParser(stream); IFD ifd = tp.getFirstIFD(); if (ifd == null) return false; String software = null; try { software = ifd.getIFDStringValue(IFD.SOFTWARE); } catch (FormatException exc) { return false; // no software tag, or tag is wrong type } if (software == null) return false; if (software.indexOf("Prairie") < 0) return false; // not Prairie software return ifd.containsKey(new Integer(PRAIRIE_TAG_1)) && ifd.containsKey(new Integer(PRAIRIE_TAG_2)) && ifd.containsKey(new Integer(PRAIRIE_TAG_3)); } /* @see loci.formats.IFormatReader#fileGroupOption(String) */ public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.MUST_GROUP; } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); if (noPixels) { return new String[] {xmlFile, cfgFile}; } Vector<String> s = new Vector<String>(); if (files != null) { for (String file : files) { s.add(file); } } if (xmlFile != null) s.add(xmlFile); if (cfgFile != null) s.add(cfgFile); return s.toArray(new String[s.size()]); } /* @see loci.formats.IFormatReader#getOptimalTileWidth() */ public int getOptimalTileWidth() { FormatTools.assertId(currentId, true, 1); return tiff.getOptimalTileWidth(); } /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ public int getOptimalTileHeight() { FormatTools.assertId(currentId, true, 1); return tiff.getOptimalTileHeight(); } /** * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ 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); tiff.setId(files[no]); return tiff.openBytes(0, buf, x, y, w, h); } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (tiff != null) tiff.close(fileOnly); if (!fileOnly) { xmlFile = cfgFile = null; tiff = null; files = null; readXML = false; readCFG = false; f = gains = offsets = null; pixelSizeX = pixelSizeY = 0; date = laserPower = null; microscopeModel = null; objectiveManufacturer = null; magnification = null; immersion = null; lensNA = null; positionX.clear(); positionY.clear(); positionZ.clear(); channels.clear(); zoom = null; waitTime = null; relativeTimes.clear(); } } // -- Internal FormatReader API methods -- /* @see loci.formats.IFormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { if (metadata == null) metadata = new Hashtable(); if (core == null) core = new CoreMetadata[] {new CoreMetadata()}; if (tiff == null) { tiff = new TiffReader(); } if (checkSuffix(id, PRAIRIE_SUFFIXES)) { // we have been given the XML file that lists TIFF files (best case) if (checkSuffix(id, XML_SUFFIX)) { LOGGER.info("Parsing XML"); super.initFile(id); xmlFile = id; readXML = true; } else if (checkSuffix(id, CFG_SUFFIX)) { LOGGER.info("Parsing CFG"); cfgFile = id; readCFG = true; currentId = id; } f = new Vector<String>(); gains = new Vector<String>(); offsets = new Vector<String>(); String xml = XMLTools.sanitizeXML(DataTools.readFile(id)).trim(); if (checkSuffix(id, XML_SUFFIX)) { core[0].imageCount = 0; } DefaultHandler handler = new PrairieHandler(); XMLTools.parseXML(xml, handler); boolean minimumMetadata = getMetadataOptions().getMetadataLevel() == MetadataLevel.MINIMUM; MetadataStore store = makeFilterMetadata(); if (checkSuffix(id, XML_SUFFIX)) { core[0].sizeT = getImageCount() / (getSizeZ() * getSizeC()); files = new String[f.size()]; f.copyInto(files); if (tiff == null) { tiff = new TiffReader(); } tiff.setId(files[0]); LOGGER.info("Populating metadata"); if (getSizeZ() == 0) core[0].sizeZ = 1; if (getSizeT() == 0) core[0].sizeT = 1; core[0].dimensionOrder = "XYCZT"; core[0].pixelType = FormatTools.UINT16; core[0].rgb = false; core[0].interleaved = false; core[0].littleEndian = tiff.isLittleEndian(); core[0].indexed = tiff.isIndexed(); core[0].falseColor = false; MetadataTools.populatePixels(store, this, !minimumMetadata); if (date != null) { date = DateTools.formatDate(date, "MM/dd/yyyy h:mm:ss a"); if (date != null) store.setImageAcquiredDate(date, 0); } else MetadataTools.setDefaultCreationDate(store, id, 0); if (!minimumMetadata) { // link Instrument and Image String instrumentID = MetadataTools.createLSID("Instrument", 0); store.setInstrumentID(instrumentID, 0); store.setImageInstrumentRef(instrumentID, 0); store.setPixelsPhysicalSizeX(new PositiveFloat(pixelSizeX), 0); store.setPixelsPhysicalSizeY(new PositiveFloat(pixelSizeY), 0); for (int i=0; i<getSizeC(); i++) { String gain = i < gains.size() ? gains.get(i) : null; String offset = i < offsets.size() ? offsets.get(i) : null; if (offset != null) { try { store.setDetectorSettingsOffset(new Double(offset), 0, i); } catch (NumberFormatException e) { } } if (gain != null) { try { store.setDetectorSettingsGain(new Double(gain), 0, i); } catch (NumberFormatException e) { } } // link DetectorSettings to an actual Detector String detectorID = MetadataTools.createLSID("Detector", 0, i); store.setDetectorID(detectorID, 0, i); store.setDetectorSettingsID(detectorID, 0, i); store.setDetectorType(getDetectorType("Other"), 0, i); store.setDetectorZoom(zoom, 0, i); if (i < channels.size()) { store.setChannelName(channels.get(i), 0, i); } } for (int i=0; i<getImageCount(); i++) { int[] zct = getZCTCoords(i); int index = FormatTools.getIndex(getDimensionOrder(), getSizeZ(), 1, getSizeT(), getImageCount() / getSizeC(), zct[0], 0, zct[2]); double xPos = positionX.get(index); double yPos = positionY.get(index); double zPos = positionZ.get(index); if (!Double.isNaN(xPos)) store.setPlanePositionX(xPos, 0, i); if (!Double.isNaN(yPos)) store.setPlanePositionY(yPos, 0, i); if (!Double.isNaN(zPos)) store.setPlanePositionZ(zPos, 0, i); store.setPlaneDeltaT( relativeTimes.get(String.valueOf(i + 1)), 0, i); } if (microscopeModel != null) { store.setMicroscopeModel(microscopeModel, 0); } String objective = MetadataTools.createLSID("Objective", 0, 0); store.setObjectiveID(objective, 0, 0); store.setImageObjectiveSettingsID(objective, 0); if (magnification != null) { store.setObjectiveNominalMagnification(magnification, 0, 0); } store.setObjectiveManufacturer(objectiveManufacturer, 0, 0); store.setObjectiveImmersion(getImmersion(immersion), 0, 0); store.setObjectiveCorrection(getCorrection("Other"), 0, 0); store.setObjectiveLensNA(lensNA, 0, 0); if (laserPower != null) { String laser = MetadataTools.createLSID("LightSource", 0 ,0); store.setLaserID(laser, 0, 0); try { store.setLaserPower(new Double(laserPower), 0, 0); } catch (NumberFormatException e) { } } } } else if (checkSuffix(id, CFG_SUFFIX)) { store.setPixelsTimeIncrement(waitTime, 0); } if (!readXML || !readCFG) { File file = new File(id).getAbsoluteFile(); File parent = file.getParentFile(); String[] listing = file.exists() ? parent.list() : Location.getIdMap().keySet().toArray(new String[0]); for (String name : listing) { if ((!readXML && checkSuffix(name, XML_SUFFIX)) || (readXML && checkSuffix(name, CFG_SUFFIX))) { String dir = ""; if (file.exists()) { dir = parent.getPath(); if (!dir.endsWith(File.separator)) dir += File.separator; } initFile(dir + name); } } } } else { // we have been given a TIFF file - reinitialize with the proper XML file if (isGroupFiles()) { LOGGER.info("Finding XML file"); Location file = new Location(id).getAbsoluteFile(); Location parent = file.getParentFile(); String[] listing = parent.list(); for (String name : listing) { if (checkSuffix(name, PRAIRIE_SUFFIXES)) { initFile(new Location(parent, name).getAbsolutePath()); return; } } } else { files = new String[] {id}; tiff.setId(files[0]); core = tiff.getCoreMetadata(); metadataStore = tiff.getMetadataStore(); Hashtable globalMetadata = tiff.getGlobalMetadata(); for (Object key : globalMetadata.keySet()) { addGlobalMeta(key.toString(), globalMetadata.get(key)); } } } if (currentId == null) currentId = id; } // -- Helper classes -- /** SAX handler for parsing XML. */ public class PrairieHandler extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) { if (qName.equals("PVScan")) { date = attributes.getValue("date"); } else if (qName.equals("Frame")) { String index = attributes.getValue("index"); if (index != null) { int zIndex = Integer.parseInt(index); if (zIndex > getSizeZ()) core[0].sizeZ++; } relativeTimes.put(index, new Double(attributes.getValue("relativeTime"))); } else if (qName.equals("File")) { core[0].imageCount++; File current = new File(currentId).getAbsoluteFile(); String dir = ""; if (current.exists()) { dir = current.getPath(); dir = dir.substring(0, dir.lastIndexOf(File.separator) + 1); } f.add(dir + attributes.getValue("filename")); String ch = attributes.getValue("channel"); String channelName = attributes.getValue("channelName"); if (channelName == null) channelName = ch; if (ch != null) { int cIndex = Integer.parseInt(ch); if (cIndex > getSizeC() && !channels.contains(channelName)) { core[0].sizeC++; channels.add(channelName); } } } else if (qName.equals("Key")) { String key = attributes.getValue("key"); String value = attributes.getValue("value"); addGlobalMeta(key, value); if (key.equals("pixelsPerLine")) { core[0].sizeX = Integer.parseInt(value); } else if (key.equals("linesPerFrame")) { core[0].sizeY = Integer.parseInt(value); } else if (key.equals("micronsPerPixel_XAxis")) { try { pixelSizeX = Double.parseDouble(value); } catch (NumberFormatException e) { } } else if (key.equals("micronsPerPixel_YAxis")) { try { pixelSizeY = Double.parseDouble(value); } catch (NumberFormatException e) { } } else if (key.equals("objectiveLens")) { String[] tokens = value.split(" "); if (tokens.length > 0) { objectiveManufacturer = tokens[0]; } if (tokens.length > 1) { String mag = tokens[1].toLowerCase().replaceAll("x", ""); try { magnification = new PositiveInteger(new Integer(mag)); } catch (NumberFormatException e) { } } if (tokens.length > 2) { immersion = tokens[2]; } } else if (key.equals("objectiveLensNA")) { try { lensNA = new Double(value); } catch (NumberFormatException e) { } } else if (key.equals("imagingDevice")) { microscopeModel = value; } else if (key.startsWith("pmtGain_")) gains.add(value); else if (key.startsWith("pmtOffset_")) offsets.add(value); else if (key.equals("laserPower_0")) laserPower = value; else if (key.equals("positionCurrent_XAxis")) { try { positionX.add(new Double(value)); addGlobalMeta("X position for position #" + positionX.size(), value); } catch (NumberFormatException e) { positionX.add(Double.NaN); } } else if (key.equals("positionCurrent_YAxis")) { try { positionY.add(new Double(value)); addGlobalMeta("Y position for position #" + positionY.size(), value); } catch (NumberFormatException e) { positionY.add(Double.NaN); } } else if (key.equals("positionCurrent_ZAxis")) { try { positionZ.add(new Double(value)); addGlobalMeta("Z position for position #" + positionZ.size(), value); } catch (NumberFormatException e) { positionZ.add(Double.NaN); } } else if (key.equals("opticalZoom")) { try { zoom = new Double(value); } catch (NumberFormatException e) { } } else if (key.equals("bitDepth")) { core[0].bitsPerPixel = Integer.parseInt(value); } } else if (qName.equals("PVTSeriesElementWait")) { try { waitTime = new Double(attributes.getValue("waitTime")); } catch (NumberFormatException e) { } } } } }