// // FV1000Reader.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.BufferedReader; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; import java.util.HashMap; import java.util.Hashtable; import java.util.Vector; import loci.common.ByteArrayHandle; import loci.common.DataTools; import loci.common.DateTools; import loci.common.IniList; import loci.common.IniParser; import loci.common.IniTable; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.common.services.DependencyException; import loci.common.services.ServiceFactory; 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.services.POIService; import loci.formats.tiff.IFD; import loci.formats.tiff.IFDList; import loci.formats.tiff.TiffParser; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PositiveInteger; /** * FV1000Reader is the file format reader for Fluoview FV 1000 OIB and * Fluoview FV 1000 OIF files. * * <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/FV1000Reader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/FV1000Reader.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Melissa Linkert melissa at glencoesoftware.com */ public class FV1000Reader extends FormatReader { // -- Constants -- public static final String FV1000_MAGIC_STRING_1 = "FileInformation"; public static final String FV1000_MAGIC_STRING_2 = "Acquisition Parameters"; public static final String[] OIB_SUFFIX = {"oib"}; public static final String[] OIF_SUFFIX = {"oif"}; public static final String[] FV1000_SUFFIXES = {"oib", "oif"}; public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; private static final int NUM_DIMENSIONS = 9; /** ROI types. */ private static final int POINT = 2; private static final int LINE = 3; private static final int POLYLINE = 4; private static final int RECTANGLE = 5; private static final int CIRCLE = 6; private static final int ELLIPSE = 7; private static final int POLYGON = 8; private static final int FREE_SHAPE = 9; private static final int FREE_LINE = 10; private static final int GRID = 11; private static final int ARROW = 12; private static final int COLOR_BAR = 13; private static final int SCALE = 15; private static final String ROTATION = "rotate(%d %f %f)"; // -- Fields -- private IniParser parser = new IniParser(); /** Names of every TIFF file to open. */ private Vector<String> tiffs; /** Name of thumbnail file. */ private String thumbId; /** Helper reader for thumbnail. */ private BMPReader thumbReader; /** Used file list. */ private Vector<String> usedFiles; /** Flag indicating this is an OIB dataset. */ private boolean isOIB; /** File mappings for OIB file. */ private Hashtable<String, String> oibMapping; private String[] code, size; private Double[] pixelSize; private int imageDepth; private Vector<String> previewNames; private String pixelSizeX, pixelSizeY; private int validBits; private Vector<String> illuminations; private Vector<Integer> wavelengths; private String pinholeSize; private String magnification, lensNA, objectiveName, workingDistance; private String creationDate; private Vector<ChannelData> channels; private Vector<String> lutNames = new Vector<String>(); private Hashtable<Integer, String> filenames = new Hashtable<Integer, String>(); private Hashtable<Integer, String> roiFilenames = new Hashtable<Integer, String>(); private POIService poi; private short[][][] lut; private int lastChannel = 0; private double pixelSizeZ = 1, pixelSizeT = 1; private String ptyStart = null, ptyEnd = null, ptyPattern = null, line = null; // -- Constructor -- /** Constructs a new FV1000 reader. */ public FV1000Reader() { super("Olympus FV1000", new String[] {"oib", "oif", "pty", "lut"}); domains = new String[] {FormatTools.LM_DOMAIN}; hasCompanionFiles = true; datasetDescription = "Single .oib file or one .oif file and a " + "similarly-named directory containing .tif/.tiff files"; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#getOptimalTileWidth() */ public int getOptimalTileWidth() { FormatTools.assertId(currentId, true, 1); RandomAccessInputStream plane = getPlane(getSeries(), 0); if (plane == null) return super.getOptimalTileWidth(); try { TiffParser tp = new TiffParser(plane); IFD ifd = tp.getFirstIFD(); plane.close(); return (int) ifd.getTileWidth(); } catch (FormatException e) { LOGGER.debug("Could not retrieve tile width", e); } catch (IOException e) { LOGGER.debug("Could not retrieve tile width", e); } return super.getOptimalTileWidth(); } /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ public int getOptimalTileHeight() { FormatTools.assertId(currentId, true, 1); RandomAccessInputStream plane = getPlane(getSeries(), 0); if (plane == null) return super.getOptimalTileHeight(); try { TiffParser tp = new TiffParser(plane); IFD ifd = tp.getFirstIFD(); plane.close(); return (int) ifd.getTileLength(); } catch (FormatException e) { LOGGER.debug("Could not retrieve tile height", e); } catch (IOException e) { LOGGER.debug("Could not retrieve tile height", e); } return super.getOptimalTileHeight(); } /* @see loci.formats.IFormatReader#isSingleFile(String) */ public boolean isSingleFile(String id) throws FormatException, IOException { return checkSuffix(id, OIB_SUFFIX); } /* @see loci.formats.IFormatReader#isThisType(String, boolean) */ public boolean isThisType(String name, boolean open) { if (checkSuffix(name, FV1000_SUFFIXES)) return true; if (!open) return false; // not allowed to touch the file system try { Location oif = new Location(findOIFFile(name)); return oif.exists() && !oif.isDirectory() && checkSuffix(oif.getAbsolutePath(), "oif") && !checkSuffix(name, "bmp"); } catch (IndexOutOfBoundsException e) { } catch (NullPointerException e) { } catch (FormatException e) { } return false; } /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ public boolean isThisType(RandomAccessInputStream stream) throws IOException { final int blockLen = 1024; if (!FormatTools.validStream(stream, blockLen, false)) return false; String s = DataTools.stripString(stream.readString(blockLen)); return s.indexOf(FV1000_MAGIC_STRING_1) >= 0 || s.indexOf(FV1000_MAGIC_STRING_2) >= 0; } /* @see loci.formats.IFormatReader#fileGroupOption(String) */ public int fileGroupOption(String id) throws FormatException, IOException { if (checkSuffix(id, OIB_SUFFIX)) { return FormatTools.CANNOT_GROUP; } return FormatTools.MUST_GROUP; } /* @see loci.formats.IFormatReader#get16BitLookupTable() */ public short[][] get16BitLookupTable() { FormatTools.assertId(currentId, true, 1); return lut == null || !isIndexed() ? null : lut[lastChannel]; } /** * @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); int nFiles = getSeries() == 0 ? tiffs.size() : previewNames.size(); int image = no % (getImageCount() / nFiles); int[] coords = getZCTCoords(image); lastChannel = coords[1]; RandomAccessInputStream plane = getPlane(getSeries(), no); if (plane == null) return buf; TiffParser tp = new TiffParser(plane); IFDList ifds = tp.getIFDs(); if (image >= ifds.size()) return buf; IFD ifd = ifds.get(image); if (getSizeY() != ifd.getImageLength()) { tp.getSamples(ifd, buf, x, getIndex(coords[0], 0, coords[2]), w, 1); } else tp.getSamples(ifd, buf, x, y, w, h); plane.close(); plane = null; tp = null; return buf; } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); if (isOIB) { return noPixels ? null : new String[] {currentId}; } Vector<String> files = new Vector<String>(); if (usedFiles != null) { for (String file : usedFiles) { String f = file.toLowerCase(); if (!f.endsWith(".tif") && !f.endsWith(".tiff") && !f.endsWith(".bmp")) { files.add(file); } } } if (!noPixels) { if (getSeries() == 0 && tiffs != null) { files.addAll(tiffs); } else if (getSeries() == 1 && previewNames != null) { files.addAll(previewNames); } } return files.toArray(new String[0]); } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (thumbReader != null) thumbReader.close(fileOnly); if (!fileOnly) { tiffs = usedFiles = null; filenames = new Hashtable<Integer, String>(); roiFilenames = new Hashtable<Integer, String>(); thumbReader = null; thumbId = null; isOIB = false; previewNames = null; if (poi != null) poi.close(); poi = null; lastChannel = 0; wavelengths = null; illuminations = null; oibMapping = null; code = size = null; pixelSize = null; imageDepth = 0; pixelSizeX = pixelSizeY = null; validBits = 0; pinholeSize = null; magnification = lensNA = objectiveName = workingDistance = null; creationDate = null; lut = null; channels = null; if (lutNames != null) { lutNames.clear(); } } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { super.initFile(id); parser.setCommentDelimiter(null); isOIB = checkSuffix(id, OIB_SUFFIX); if (isOIB) { try { ServiceFactory factory = new ServiceFactory(); poi = factory.getInstance(POIService.class); } catch (DependencyException de) { throw new FormatException("POI library not found", de); } poi.initialize(Location.getMappedId(id)); } // mappedOIF is used to distinguish between datasets that are being read // directly (e.g. using ImageJ or showinf), and datasets that are being // imported through omebf. In the latter case, the necessary directory // structure is not preserved (only relative file names are stored in // OMEIS), so we will need to use slightly different logic to build the // list of associated files. boolean mappedOIF = !isOIB && !new File(id).getAbsoluteFile().exists(); wavelengths = new Vector<Integer>(); illuminations = new Vector<String>(); channels = new Vector<ChannelData>(); String oifName = null; if (isOIB) { oifName = mapOIBFiles(); } else { // make sure we have the OIF file, not a TIFF if (!checkSuffix(id, OIF_SUFFIX)) { currentId = findOIFFile(id); initFile(currentId); } oifName = currentId; } String oifPath = new Location(oifName).getAbsoluteFile().getAbsolutePath(); String path = (isOIB || !oifPath.endsWith(oifName) || mappedOIF) ? "" : oifPath.substring(0, oifPath.lastIndexOf(File.separator) + 1); try { RandomAccessInputStream s = getFile(oifName); s.close(); } catch (IOException e) { oifName = oifName.replaceAll(".oif", ".OIF"); } // parse key/value pairs from the OIF file code = new String[NUM_DIMENSIONS]; size = new String[NUM_DIMENSIONS]; pixelSize = new Double[NUM_DIMENSIONS]; previewNames = new Vector<String>(); boolean laserEnabled = true; IniList f = getIniFile(oifName); IniTable saveInfo = f.getTable("ProfileSaveInfo"); String[] saveKeys = saveInfo.keySet().toArray(new String[saveInfo.size()]); for (String key : saveKeys) { String value = saveInfo.get(key).toString(); value = sanitizeValue(value).trim(); if (key.startsWith("IniFileName") && key.indexOf("Thumb") == -1 && !isPreviewName(value)) { if (mappedOIF) { value = value.substring(value.lastIndexOf(File.separator) + 1).trim(); } filenames.put(new Integer(key.substring(11)), value); } else if (key.startsWith("RoiFileName") && key.indexOf("Thumb") == -1 && !isPreviewName(value)) { if (mappedOIF) { value = value.substring(value.lastIndexOf(File.separator) + 1).trim(); } try { roiFilenames.put(new Integer(key.substring(11)), value); } catch (NumberFormatException e) { } } else if (key.equals("PtyFileNameS")) ptyStart = value; else if (key.equals("PtyFileNameE")) ptyEnd = value; else if (key.equals("PtyFileNameT2")) ptyPattern = value; else if (key.indexOf("Thumb") != -1) { if (mappedOIF) { value = value.substring(value.lastIndexOf(File.separator) + 1); } if (thumbId == null) thumbId = value.trim(); } else if (key.startsWith("LutFileName")) { if (mappedOIF) { value = value.substring(value.lastIndexOf(File.separator) + 1); } lutNames.add(path + value); } else if (isPreviewName(value)) { if (mappedOIF) { value = value.substring(value.lastIndexOf(File.separator) + 1); } previewNames.add(path + value.trim()); } } if (filenames.size() == 0) addPtyFiles(); for (int i=0; i<NUM_DIMENSIONS; i++) { IniTable commonParams = f.getTable("Axis " + i + " Parameters Common"); code[i] = commonParams.get("AxisCode"); size[i] = commonParams.get("MaxSize"); double end = Double.parseDouble(commonParams.get("EndPosition")); double start = Double.parseDouble(commonParams.get("StartPosition")); pixelSize[i] = end - start; } IniTable referenceParams = f.getTable("Reference Image Parameter"); imageDepth = Integer.parseInt(referenceParams.get("ImageDepth")); pixelSizeX = referenceParams.get("WidthConvertValue"); pixelSizeY = referenceParams.get("HeightConvertValue"); String ripValidBitCounts = referenceParams.get("ValidBitCounts"); if (ripValidBitCounts != null) { validBits = Integer.parseInt(ripValidBitCounts); } int index = 0; IniTable laser = f.getTable("Laser " + index + " Parameters"); while (laser != null) { laserEnabled = laser.get("Laser Enable").equals("1"); if (laserEnabled) { wavelengths.add(new Integer(laser.get("LaserWavelength"))); } creationDate = laser.get("ImageCaputreDate"); if (creationDate == null) { creationDate = laser.get("ImageCaptureDate"); } index++; laser = f.getTable("Laser " + index + " Parameters"); } if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { index = 1; IniTable guiChannel = f.getTable("GUI Channel " + index + " Parameters"); while (guiChannel != null) { ChannelData channel = new ChannelData(); String gain = guiChannel.get("AnalogPMTGain"); if (gain != null) channel.gain = new Double(gain); String voltage = guiChannel.get("AnalogPMTVoltage"); if (voltage != null) channel.voltage = new Double(voltage); channel.barrierFilter = guiChannel.get("BF Name"); channel.active = Integer.parseInt(guiChannel.get("CH Activate")) != 0; channel.name = guiChannel.get("CH Name"); channel.dyeName = guiChannel.get("DyeName"); channel.emissionFilter = guiChannel.get("EmissionDM Name"); channel.emWave = new Integer(guiChannel.get("EmissionWavelength")); channel.excitationFilter = guiChannel.get("ExcitationDM Name"); channel.exWave = new Integer(guiChannel.get("ExcitationWavelength")); channels.add(channel); index++; guiChannel = f.getTable("GUI Channel " + index + " Parameters"); } index = 1; IniTable channel = f.getTable("Channel " + index + " Parameters"); while (channel != null) { String illumination = channel.get("LightType"); if (illumination != null) illumination = illumination.toLowerCase(); if (illumination == null) { // Ignored } else if (illumination.indexOf("fluorescence") != -1) { illumination = "Epifluorescence"; } else if (illumination.indexOf("transmitted") != -1) { illumination = "Transmitted"; } else illumination = null; illuminations.add(illumination); index++; channel = f.getTable("Channel " + index + " Parameters"); } HashMap<String, String> iniMap = f.flattenIntoHashMap(); metadata.putAll(iniMap); } LOGGER.info("Initializing helper readers"); // populate core metadata for preview series if (previewNames.size() > 0) { Vector<String> v = new Vector<String>(); for (int i=0; i<previewNames.size(); i++) { String ss = previewNames.get(i); ss = replaceExtension(ss, "pty", "tif"); if (ss.endsWith(".tif")) v.add(ss); } previewNames = v; if (previewNames.size() > 0) { core = new CoreMetadata[2]; core[0] = new CoreMetadata(); core[1] = new CoreMetadata(); IFDList ifds = null; for (String previewName : previewNames) { RandomAccessInputStream preview = getFile(previewName); TiffParser tp = new TiffParser(preview); ifds = tp.getIFDs(); preview.close(); tp = null; core[1].imageCount += ifds.size(); } core[1].sizeX = (int) ifds.get(0).getImageWidth(); core[1].sizeY = (int) ifds.get(0).getImageLength(); core[1].sizeZ = 1; core[1].sizeT = 1; core[1].sizeC = core[1].imageCount; core[1].rgb = false; int bits = ifds.get(0).getBitsPerSample()[0]; while ((bits % 8) != 0) bits++; bits /= 8; core[1].pixelType = FormatTools.pixelTypeFromBytes(bits, false, false); core[1].dimensionOrder = "XYCZT"; core[1].indexed = false; } } core[0].imageCount = filenames.size(); tiffs = new Vector<String>(getImageCount()); thumbReader = new BMPReader(); thumbId = replaceExtension(thumbId, "pty", "bmp"); thumbId = sanitizeFile(thumbId, path); LOGGER.info("Reading additional metadata"); // open each INI file (.pty extension) and build list of TIFF files String tiffPath = null; core[0].dimensionOrder = "XY"; Hashtable<String, String> values = new Hashtable<String, String>(); Vector<String> baseKeys = new Vector<String>(); for (int i=0, ii=0; ii<getImageCount(); i++, ii++) { String file = filenames.get(new Integer(i)); while (file == null) file = filenames.get(new Integer(++i)); file = sanitizeFile(file, path); if (file.indexOf(File.separator) != -1) { tiffPath = file.substring(0, file.lastIndexOf(File.separator)); } else tiffPath = file; Location ptyFile = new Location(file); if (!isOIB && !ptyFile.exists()) { LOGGER.warn("Could not find .pty file ({}); guessing at the " + "corresponding TIFF file.", file); String tiff = replaceExtension(file, ".pty", ".tif"); tiffs.add(ii, tiff); continue; } IniList pty = getIniFile(file); IniTable fileInfo = pty.getTable("File Info"); file = sanitizeValue(fileInfo.get("DataName")); if (!isPreviewName(file)) { while (file.indexOf("GST") != -1) { file = removeGST(file); } if (!mappedOIF) { if (isOIB) { file = tiffPath + File.separator + file; } else file = new Location(tiffPath, file).getAbsolutePath(); } tiffs.add(ii, file); } for (int dim=0; dim<NUM_DIMENSIONS; dim++) { IniTable axis = pty.getTable("Axis " + dim + " Parameters"); if (axis == null) break; boolean addAxis = Integer.parseInt(axis.get("Number")) > 1; if (dim == 2) { if (addAxis && getDimensionOrder().indexOf("C") == -1) { core[0].dimensionOrder += "C"; } } else if (dim == 3) { if (addAxis && getDimensionOrder().indexOf("Z") == -1) { core[0].dimensionOrder += "Z"; } } else if (dim == 4) { if (addAxis && getDimensionOrder().indexOf("T") == -1) { core[0].dimensionOrder += "T"; } } } core[0].bitsPerPixel = validBits; IniTable acquisition = pty.getTable("Acquisition Parameters Common"); if (acquisition != null) { magnification = acquisition.get("Magnification"); lensNA = acquisition.get("ObjectiveLens NAValue"); objectiveName = acquisition.get("ObjectiveLens Name"); workingDistance = acquisition.get("ObjectiveLens WDValue"); pinholeSize = acquisition.get("PinholeDiameter"); String validBitCounts = acquisition.get("ValidBitCounts"); if (validBitCounts != null) { core[0].bitsPerPixel = Integer.parseInt(validBitCounts); } } if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { for (IniTable table : pty) { String[] keys = table.keySet().toArray(new String[table.size()]); for (String key : keys) { values.put("Image " + ii + " : " + key, table.get(key)); if (!baseKeys.contains(key)) baseKeys.add(key); } } } } for (String key : baseKeys) { if (key.equals("DataName") || key.indexOf("FileName") >= 0) break; boolean equal = true; String first = values.get("Image 0 : " + key); for (int i=1; i<getImageCount(); i++) { if (!first.equals(values.get("Image " + i + " : " + key))) { equal = false; break; } } if (equal) { addGlobalMeta(key, first); } else { for (int i=0; i<getImageCount(); i++) { String k = "Image " + i + " : " + key; addGlobalMeta(k, values.get(k)); } } } if (tiffs.size() != getImageCount()) { core[0].imageCount = tiffs.size(); } usedFiles = new Vector<String>(); if (tiffPath != null) { usedFiles.add(isOIB ? id : oifName); if (!isOIB) { Location dir = new Location(tiffPath); if (!dir.exists()) { throw new FormatException( "Required directory " + tiffPath + " was not found."); } String[] list = mappedOIF ? Location.getIdMap().keySet().toArray(new String[0]) : dir.list(true); for (int i=0; i<list.length; i++) { if (mappedOIF) usedFiles.add(list[i]); else { String p = new Location(tiffPath, list[i]).getAbsolutePath(); String check = p.toLowerCase(); if (!check.endsWith(".tif") && !check.endsWith(".pty") && !check.endsWith(".roi") && !check.endsWith(".lut") && !check.endsWith(".bmp")) { continue; } usedFiles.add(p); } } } } LOGGER.info("Populating metadata"); // calculate axis sizes int realChannels = 0; for (int i=0; i<NUM_DIMENSIONS; i++) { int ss = Integer.parseInt(size[i]); if (pixelSize[i] == null) pixelSize[i] = 1.0; if (code[i].equals("X")) core[0].sizeX = ss; else if (code[i].equals("Y") && ss > 1) core[0].sizeY = ss; else if (code[i].equals("Z")) { if (getSizeY() == 0) { core[0].sizeY = ss; } else { core[0].sizeZ = ss; // Z size stored in nm pixelSizeZ = Math.abs((pixelSize[i].doubleValue() / (getSizeZ() - 1)) / 1000); } } else if (code[i].equals("T")) { if (getSizeY() == 0) { core[0].sizeY = ss; } else { core[0].sizeT = ss; pixelSizeT = Math.abs((pixelSize[i].doubleValue() / (getSizeT() - 1)) / 1000); } } else if (ss > 0) { if (getSizeC() == 0) core[0].sizeC = ss; else core[0].sizeC *= ss; if (code[i].equals("C")) realChannels = ss; } } if (getSizeZ() == 0) core[0].sizeZ = 1; if (getSizeC() == 0) core[0].sizeC = 1; if (getSizeT() == 0) core[0].sizeT = 1; if (getImageCount() == getSizeC() && getSizeY() == 1) { core[0].imageCount *= getSizeZ() * getSizeT(); } else if (getImageCount() == getSizeC()) { core[0].sizeZ = 1; core[0].sizeT = 1; } if (getSizeZ() * getSizeT() * getSizeC() != getImageCount()) { int diff = (getSizeZ() * getSizeC() * getSizeT()) - getImageCount(); if (diff == previewNames.size() || diff < 0) { diff /= getSizeC(); if (getSizeT() > 1 && getSizeZ() == 1) core[0].sizeT -= diff; else if (getSizeZ() > 1 && getSizeT() == 1) core[0].sizeZ -= diff; } else core[0].imageCount += diff; } if (getSizeC() > 1 && getSizeZ() == 1 && getSizeT() == 1) { if (getDimensionOrder().indexOf("C") == -1) core[0].dimensionOrder += "C"; } if (getDimensionOrder().indexOf("Z") == -1) core[0].dimensionOrder += "Z"; if (getDimensionOrder().indexOf("C") == -1) core[0].dimensionOrder += "C"; if (getDimensionOrder().indexOf("T") == -1) core[0].dimensionOrder += "T"; core[0].pixelType = FormatTools.pixelTypeFromBytes(imageDepth, false, false); // set up thumbnail file mapping try { RandomAccessInputStream thumb = getFile(thumbId); byte[] b = new byte[(int) thumb.length()]; thumb.read(b); thumb.close(); Location.mapFile("thumbnail.bmp", new ByteArrayHandle(b)); thumbReader.setId("thumbnail.bmp"); for (int i=0; i<getSeriesCount(); i++) { core[i].thumbSizeX = thumbReader.getSizeX(); core[i].thumbSizeY = thumbReader.getSizeY(); } Location.mapFile("thumbnail.bmp", null); } catch (IOException e) { LOGGER.debug("Could not read thumbnail", e); } catch (FormatException e) { LOGGER.debug("Could not read thumbnail", e); } // initialize lookup table lut = new short[getSizeC()][3][65536]; byte[] buffer = new byte[65536 * 4]; int count = (int) Math.min(getSizeC(), lutNames.size()); for (int c=0; c<count; c++) { Exception exc = null; try { RandomAccessInputStream stream = getFile(lutNames.get(c)); stream.seek(stream.length() - 65536 * 4); stream.read(buffer); stream.close(); for (int q=0; q<buffer.length; q+=4) { lut[c][0][q / 4] = (short) (buffer[q + 2] & 0xff); lut[c][1][q / 4] = (short) (buffer[q + 1] & 0xff); lut[c][2][q / 4] = (short) (buffer[q] & 0xff); } } catch (IOException e) { exc = e; } catch (FormatException e) { exc = e; } if (exc != null) { LOGGER.debug("Could not read LUT", exc); lut = null; break; } } for (int i=0; i<getSeriesCount(); i++) { core[i].rgb = false; core[i].littleEndian = true; core[i].interleaved = false; core[i].metadataComplete = true; core[i].indexed = lut != null; core[i].falseColor = true; } // populate MetadataStore MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this); if (creationDate != null) { creationDate = creationDate.replaceAll("'", ""); creationDate = DateTools.formatDate(creationDate, DATE_FORMAT); } for (int i=0; i<getSeriesCount(); i++) { // populate Image data store.setImageName("Series " + (i + 1), i); if (creationDate != null) store.setImageAcquiredDate(creationDate, i); else MetadataTools.setDefaultCreationDate(store, id, i); } if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { populateMetadataStore(store, path); } } private void populateMetadataStore(MetadataStore store, String path) throws FormatException, IOException { String instrumentID = MetadataTools.createLSID("Instrument", 0); store.setInstrumentID(instrumentID, 0); for (int i=0; i<getSeriesCount(); i++) { // link Instrument and Image store.setImageInstrumentRef(instrumentID, i); // populate Dimensions data if (pixelSizeX != null) { Double sizeX = new Double(pixelSizeX); if (sizeX > 0) { store.setPixelsPhysicalSizeX(new PositiveFloat(sizeX), i); } } if (pixelSizeY != null) { Double sizeY = new Double(pixelSizeY); if (sizeY > 0) { store.setPixelsPhysicalSizeY(new PositiveFloat(sizeY), i); } } if (pixelSizeZ == Double.NEGATIVE_INFINITY || pixelSizeZ == Double.POSITIVE_INFINITY || getSizeZ() == 1) { pixelSizeZ = 1d; } if (pixelSizeT == Double.NEGATIVE_INFINITY || pixelSizeT == Double.POSITIVE_INFINITY || getSizeT() == 1) { pixelSizeT = 1d; } if (pixelSizeZ > 0) { store.setPixelsPhysicalSizeZ(new PositiveFloat(pixelSizeZ), i); } store.setPixelsTimeIncrement(pixelSizeT, i); // populate LogicalChannel data for (int c=0; c<core[i].sizeC; c++) { if (c < illuminations.size()) { store.setChannelIlluminationType( getIlluminationType(illuminations.get(c)), i, c); } } } int channelIndex = 0; for (ChannelData channel : channels) { if (!channel.active) continue; if (channelIndex >= getEffectiveSizeC()) break; // populate Detector data String detectorID = MetadataTools.createLSID("Detector", 0, channelIndex); store.setDetectorID(detectorID, 0, channelIndex); store.setDetectorSettingsID(detectorID, 0, channelIndex); store.setDetectorGain(channel.gain, 0, channelIndex); store.setDetectorVoltage(channel.voltage, 0, channelIndex); store.setDetectorType(getDetectorType("PMT"), 0, channelIndex); // populate LogicalChannel data store.setChannelName(channel.name, 0, channelIndex); String lightSourceID = MetadataTools.createLSID("LightSource", 0, channelIndex); store.setChannelLightSourceSettingsID(lightSourceID, 0, channelIndex); if (channel.emWave.intValue() > 0) { store.setChannelEmissionWavelength( new PositiveInteger(channel.emWave), 0, channelIndex); } if (channel.exWave.intValue() > 0) { store.setChannelExcitationWavelength( new PositiveInteger(channel.exWave), 0, channelIndex); store.setChannelLightSourceSettingsWavelength( new PositiveInteger(channel.exWave), 0, channelIndex); } // populate Filter data if (channel.barrierFilter != null) { String filterID = MetadataTools.createLSID("Filter", 0, channelIndex); store.setFilterID(filterID, 0, channelIndex); store.setFilterModel(channel.barrierFilter, 0, channelIndex); if (channel.barrierFilter.indexOf("-") != -1) { String[] emValues = channel.barrierFilter.split("-"); for (int i=0; i<emValues.length; i++) { emValues[i] = emValues[i].replaceAll("\\D", ""); } try { store.setTransmittanceRangeCutIn( PositiveInteger.valueOf(emValues[0]), 0, channelIndex); store.setTransmittanceRangeCutOut( PositiveInteger.valueOf(emValues[1]), 0, channelIndex); } catch (NumberFormatException e) { } } store.setLightPathEmissionFilterRef(filterID, 0, channelIndex, 0); } // populate FilterSet data int emIndex = channelIndex * 2; int exIndex = channelIndex * 2 + 1; String emFilter = MetadataTools.createLSID("Dichroic", 0, emIndex); String exFilter = MetadataTools.createLSID("Dichroic", 0, exIndex); // populate Dichroic data store.setDichroicID(emFilter, 0, emIndex); store.setDichroicModel(channel.emissionFilter, 0, emIndex); store.setDichroicID(exFilter, 0, exIndex); store.setDichroicModel(channel.excitationFilter, 0, exIndex); store.setLightPathDichroicRef(exFilter, 0, channelIndex); // populate Laser data store.setLaserID(lightSourceID, 0, channelIndex); store.setLaserLaserMedium(getLaserMedium(channel.dyeName), 0, channelIndex); if (channelIndex < wavelengths.size()) { store.setLaserWavelength( new PositiveInteger(wavelengths.get(channelIndex)), 0, channelIndex); } store.setLaserType(getLaserType("Other"), 0, channelIndex); channelIndex++; } // populate Objective data if (lensNA != null) store.setObjectiveLensNA(new Double(lensNA), 0, 0); store.setObjectiveModel(objectiveName, 0, 0); if (magnification != null) { int mag = (int) Float.parseFloat(magnification); if (mag > 0) { store.setObjectiveNominalMagnification(new PositiveInteger(mag), 0, 0); } } if (workingDistance != null) { store.setObjectiveWorkingDistance(new Double(workingDistance), 0, 0); } store.setObjectiveCorrection(getCorrection("Other"), 0, 0); store.setObjectiveImmersion(getImmersion("Other"), 0, 0); // link Objective to Image using ObjectiveSettings String objectiveID = MetadataTools.createLSID("Objective", 0, 0); store.setObjectiveID(objectiveID, 0, 0); store.setImageObjectiveSettingsID(objectiveID, 0); if (getMetadataOptions().getMetadataLevel() != MetadataLevel.NO_OVERLAYS) { int nextROI = -1; // populate ROI data - there is one ROI file per plane for (int i=0; i<roiFilenames.size(); i++) { if (i >= getImageCount()) break; String filename = roiFilenames.get(new Integer(i)); filename = sanitizeFile(filename, path); nextROI = parseROIFile(filename, store, nextROI, i); } } } private int parseROIFile(String filename, MetadataStore store, int nextROI, int plane) throws FormatException, IOException { int[] coordinates = getZCTCoords(plane); IniList roiFile = null; try { roiFile = getIniFile(filename); } catch (FormatException e) { LOGGER.debug("Could not parse ROI file {}", filename, e); return nextROI; } catch (IOException e) { LOGGER.debug("Could not parse ROI file {}", filename, e); return nextROI; } boolean validROI = false; int shape = -1; int shapeType = -1; String[] xc = null, yc = null; int divide = 0; int fontSize = 0, lineWidth = 0, angle = 0; String fontName = null, name = null; for (IniTable table : roiFile) { String tableName = table.get(IniTable.HEADER_KEY); if (tableName.equals("ROIBase FileInformation")) { try { String roiName = table.get("Name").replaceAll("\"", ""); validROI = Integer.parseInt(roiName) > 1; } catch (NumberFormatException e) { validROI = false; } if (!validROI) continue; } else if (tableName.equals("ROIBase Body")) { shapeType = Integer.parseInt(table.get("SHAPE")); divide = Integer.parseInt(table.get("DIVIDE")); String[] fontAttributes = table.get("FONT").split(","); fontName = fontAttributes[0]; fontSize = Integer.parseInt(fontAttributes[1]); lineWidth = Integer.parseInt(table.get("LINEWIDTH")); name = table.get("NAME"); angle = Integer.parseInt(table.get("ANGLE")); xc = table.get("X").split(","); yc = table.get("Y").split(","); int x = Integer.parseInt(xc[0]); int width = xc.length > 1 ? Integer.parseInt(xc[1]) - x : 0; int y = Integer.parseInt(yc[0]); int height = yc.length > 1 ? Integer.parseInt(yc[1]) - y : 0; if (width + x <= getSizeX() && height + y <= getSizeY()) { shape++; Integer zIndex = new Integer(coordinates[0]); Integer tIndex = new Integer(coordinates[2]); if (shape == 0) { nextROI++; if (shapeType == POINT || shapeType == GRID || shapeType == RECTANGLE || shapeType == LINE || shapeType == CIRCLE || shapeType == ELLIPSE || shapeType == POLYGON || shapeType == FREE_SHAPE || shapeType == POLYLINE || shapeType == FREE_LINE) { String roiID = MetadataTools.createLSID("ROI", nextROI); store.setROIID(roiID, nextROI); store.setImageROIRef(roiID, 0, nextROI); } } String shapeID = MetadataTools.createLSID("Shape", nextROI, shape); if (shapeType == POINT) { store.setPointID(shapeID, nextROI, shape); store.setPointTheZ(new NonNegativeInteger(zIndex), nextROI, shape); store.setPointTheT(new NonNegativeInteger(tIndex), nextROI, shape); store.setPointFontSize( new NonNegativeInteger(fontSize), nextROI, shape); store.setPointStrokeWidth(new Double(lineWidth), nextROI, shape); store.setPointX(new Double(xc[0]), nextROI, shape); store.setPointY(new Double(yc[0]), nextROI, shape); } else if (shapeType == GRID || shapeType == RECTANGLE) { if (shapeType == RECTANGLE) divide = 1; width /= divide; height /= divide; for (int row=0; row<divide; row++) { for (int col=0; col<divide; col++) { double realX = x + col * width; double realY = y + row * height; shapeID = MetadataTools.createLSID("Shape", nextROI, shape); store.setRectangleID(shapeID, nextROI, shape); store.setRectangleX(realX, nextROI, shape); store.setRectangleY(realY, nextROI, shape); store.setRectangleWidth(new Double(width), nextROI, shape); store.setRectangleHeight(new Double(height), nextROI, shape); store.setRectangleTheZ( new NonNegativeInteger(zIndex), nextROI, shape); store.setRectangleTheT( new NonNegativeInteger(tIndex), nextROI, shape); store.setRectangleFontSize( new NonNegativeInteger(fontSize), nextROI, shape); store.setRectangleStrokeWidth( new Double(lineWidth), nextROI, shape); double centerX = realX + (width / 2); double centerY = realY + (height / 2); store.setRectangleTransform(String.format(ROTATION, angle, centerX, centerY), nextROI, shape); if (row < divide - 1 || col < divide - 1) shape++; } } } else if (shapeType == LINE) { store.setLineID(shapeID, nextROI, shape); store.setLineX1(new Double(x), nextROI, shape); store.setLineY1(new Double(y), nextROI, shape); store.setLineX2(new Double(x + width), nextROI, shape); store.setLineY2(new Double(y + height), nextROI, shape); store.setLineTheZ(new NonNegativeInteger(zIndex), nextROI, shape); store.setLineTheT(new NonNegativeInteger(tIndex), nextROI, shape); store.setLineFontSize( new NonNegativeInteger(fontSize), nextROI, shape); store.setLineStrokeWidth(new Double(lineWidth), nextROI, shape); int centerX = x + (width / 2); int centerY = y + (height / 2); store.setLineTransform(String.format(ROTATION, angle, (float) centerX, (float) centerY), nextROI, shape); } else if (shapeType == CIRCLE || shapeType == ELLIPSE) { double rx = width / 2; double ry = shapeType == CIRCLE ? rx : height / 2; store.setEllipseID(shapeID, nextROI, shape); store.setEllipseX(x + rx, nextROI, shape); store.setEllipseY(y + ry, nextROI, shape); store.setEllipseRadiusX(rx, nextROI, shape); store.setEllipseRadiusY(ry, nextROI, shape); store.setEllipseTheZ( new NonNegativeInteger(zIndex), nextROI, shape); store.setEllipseTheT( new NonNegativeInteger(tIndex), nextROI, shape); store.setEllipseFontSize( new NonNegativeInteger(fontSize), nextROI, shape); store.setEllipseStrokeWidth(new Double(lineWidth), nextROI, shape); store.setEllipseTransform(String.format(ROTATION, angle, x + rx, y + ry), nextROI, shape); } else if (shapeType == POLYGON || shapeType == FREE_SHAPE || shapeType == POLYLINE || shapeType == FREE_LINE) { StringBuffer points = new StringBuffer(); for (int point=0; point<xc.length; point++) { points.append(xc[point]); points.append(","); points.append(yc[point]); if (point < xc.length - 1) points.append(" "); } store.setPolylineID(shapeID, nextROI, shape); store.setPolylinePoints(points.toString(), nextROI, shape); store.setPolylineTransform("rotate(" + angle + ")", nextROI, shape); store.setPolylineClosed( shapeType == POLYGON || shapeType == FREE_SHAPE, nextROI, shape); store.setPolylineTheZ( new NonNegativeInteger(zIndex), nextROI, shape); store.setPolylineTheT( new NonNegativeInteger(tIndex), nextROI, shape); store.setPolylineFontSize( new NonNegativeInteger(fontSize), nextROI, shape); store.setPolylineStrokeWidth(new Double(lineWidth), nextROI, shape); } else { if (shape == 0) nextROI--; shape--; } } } } return nextROI; } private void addPtyFiles() throws FormatException { if (ptyStart != null && ptyEnd != null && ptyPattern != null) { // FV1000 version 2 gives the first .pty file, the last .pty and // the file name pattern. Version 1 lists each .pty file individually. // pattern is typically 's_C%03dT%03d.pty' // build list of block indexes String[] prefixes = ptyPattern.split("%03d"); // get first and last numbers for each block int[] first = scanFormat(ptyPattern, ptyStart); int[] last = scanFormat(ptyPattern, ptyEnd); int[] lengths = new int[prefixes.length - 1]; int totalFiles = 1; for (int i=0; i<first.length; i++) { lengths[i] = last[i] - first[i] + 1; totalFiles *= lengths[i]; } // add each .pty file for (int file=0; file<totalFiles; file++) { int[] pos = FormatTools.rasterToPosition(lengths, file); StringBuffer pty = new StringBuffer(); for (int block=0; block<prefixes.length; block++) { pty.append(prefixes[block]); if (block < pos.length) { String num = String.valueOf(pos[block] + 1); for (int q=0; q<3 - num.length(); q++) { pty.append("0"); } pty.append(num); } } filenames.put(new Integer(file), pty.toString()); } } } // -- Helper methods -- private String findOIFFile(String baseFile) throws FormatException { Location current = new Location(baseFile).getAbsoluteFile(); String parent = current.getParent(); Location tmp = new Location(parent).getParentFile(); parent = tmp.getAbsolutePath(); baseFile = current.getName(); if (baseFile == null || baseFile.indexOf("_") == -1) return null; baseFile = baseFile.substring(0, baseFile.lastIndexOf("_")); if (checkSuffix(current.getName(), new String[] {"roi", "lut"})) { if (!new Location(tmp, baseFile + ".oif").exists() && !new Location(tmp, baseFile + ".OIF").exists() && baseFile.indexOf("_") >= 0) { // some metadata files have an extra underscore baseFile = baseFile.substring(0, baseFile.lastIndexOf("_")); } } baseFile += ".oif"; tmp = new Location(tmp, baseFile); String oifFile = tmp.getAbsolutePath(); if (!tmp.exists()) { oifFile = oifFile.substring(0, oifFile.lastIndexOf(".")) + ".OIF"; tmp = new Location(oifFile); if (!tmp.exists()) { baseFile = current.getParent(); baseFile = baseFile.substring(0, baseFile.lastIndexOf(".")); baseFile = baseFile.substring(0, baseFile.lastIndexOf(".")); tmp = new Location(baseFile + ".oif"); oifFile = tmp.getAbsolutePath(); if (!tmp.exists()) { tmp = new Location(tmp.getParent(), tmp.getName().toUpperCase()); oifFile = tmp.getAbsolutePath(); if (!tmp.exists()) { // check in parent directory if (parent.endsWith(File.separator)) { parent = parent.substring(0, parent.length() - 1); } String dir = parent.substring(parent.lastIndexOf(File.separator)); dir = dir.substring(0, dir.lastIndexOf(".")); tmp = new Location(parent); oifFile = new Location(tmp, dir).getAbsolutePath(); if (!new Location(oifFile).exists()) { throw new FormatException("OIF file not found"); } } } } } return oifFile; } private String mapOIBFiles() throws FormatException, IOException { String oifName = null; String infoFile = null; Vector<String> list = poi.getDocumentList(); for (String name : list) { if (name.endsWith("OibInfo.txt")) { infoFile = name; break; } } if (infoFile == null) { throw new FormatException("OibInfo.txt not found in " + currentId); } RandomAccessInputStream ras = poi.getDocumentStream(infoFile); oibMapping = new Hashtable<String, String>(); // set up file name mappings String s = DataTools.stripString(ras.readString((int) ras.length())); ras.close(); String[] lines = s.split("\n"); // sort the lines to ensure that the // directory key is before the file names Arrays.sort(lines); String directoryKey = null, directoryValue = null, key = null, value = null; for (String line : lines) { line = line.trim(); if (line.indexOf("=") != -1) { key = line.substring(0, line.indexOf("=")); value = line.substring(line.indexOf("=") + 1); if (directoryKey != null && directoryValue != null) { value = value.replaceAll(directoryKey, directoryValue); } value = removeGST(value); if (key.startsWith("Stream")) { value = sanitizeFile(value, ""); if (checkSuffix(value, OIF_SUFFIX)) oifName = value; if (directoryKey != null && value.startsWith(directoryValue)) { oibMapping.put(value, "Root Entry" + File.separator + directoryKey + File.separator + key); } else { oibMapping.put(value, "Root Entry" + File.separator + key); } } else if (key.startsWith("Storage")) { directoryKey = key; directoryValue = value; } } } s = null; return oifName; } private String sanitizeValue(String value) { String f = value.replaceAll("\"", ""); f = f.replace('\\', File.separatorChar); f = f.replace('/', File.separatorChar); while (f.indexOf("GST") != -1) { f = removeGST(f); } return f; } private String sanitizeFile(String file, String path) { String f = sanitizeValue(file); if (path.equals("")) return f; return path + File.separator + f; } private String removeGST(String s) { if (s.indexOf("GST") != -1) { String first = s.substring(0, s.indexOf("GST")); int ndx = s.indexOf(File.separator) < s.indexOf("GST") ? s.length() : s.indexOf(File.separator); String last = s.substring(s.lastIndexOf("=", ndx) + 1); return first + last; } return s; } private RandomAccessInputStream getFile(String name) throws FormatException, IOException { if (isOIB) { name = name.replace('\\', File.separatorChar); name = name.replace('/', File.separatorChar); String realName = oibMapping.get(name); if (realName == null) { throw new FormatException("File " + name + " not found."); } return poi.getDocumentStream(realName); } else return new RandomAccessInputStream(name); } private RandomAccessInputStream getPlane(int seriesIndex, int planeIndex) { int file = planeIndex; if (seriesIndex == 0) { file = planeIndex / (getImageCount() / tiffs.size()); } else file = planeIndex / (getImageCount() / previewNames.size()); String filename = seriesIndex == 0 ? tiffs.get(file) : previewNames.get(file); RandomAccessInputStream plane = null; try { plane = getFile(filename); } catch (FormatException e) { } catch (IOException e) { } return plane; } private boolean isPreviewName(String name) { // "-R" in the file name indicates that this is a preview image int index = name.indexOf("-R"); return index == name.length() - 9; } private String replaceExtension(String name, String oldExt, String newExt) { if (!name.endsWith("." + oldExt)) { return name; } return name.substring(0, name.length() - oldExt.length()) + newExt; } /* Return the numbers in the given string matching %..d style patterns */ private static int[] scanFormat(String pattern, String string) throws FormatException { Vector<Integer> percentOffsets = new Vector<Integer>(); int offset = -1; for (;;) { offset = pattern.indexOf('%', offset + 1); if (offset < 0 || offset + 1 >= pattern.length()) { break; } if (pattern.charAt(offset + 1) != '0') { continue; } percentOffsets.add(new Integer(offset)); } int[] result = new int[percentOffsets.size()]; int patternOffset = 0; offset = 0; for (int i=0; i<result.length; i++) { int percent = percentOffsets.get(i).intValue(); if (!string.regionMatches(offset, pattern, patternOffset, percent - patternOffset)) { throw new FormatException("String '" + string + "' does not match format '" + pattern + "'"); } offset += percent - patternOffset; patternOffset = percent; int endOffset = offset; while (endOffset < string.length() && Character.isDigit(string.charAt(endOffset))) { endOffset++; } result[i] = Integer.parseInt(string.substring(offset, endOffset)); offset = endOffset; while (++patternOffset < pattern.length() && pattern.charAt(patternOffset - 1) != 'd') { ; /* do nothing */ } } int remaining = pattern.length() - patternOffset; if (string.length() - offset != remaining || !string.regionMatches(offset, pattern, patternOffset, remaining)) { throw new FormatException("String '" + string + "' does not match format '" + pattern + "'"); } return result; } private IniList getIniFile(String filename) throws FormatException, IOException { RandomAccessInputStream stream = getFile(filename); String data = stream.readString((int) stream.length()); if (!data.startsWith("[")) { data = data.substring(data.indexOf("["), data.length()); } data = DataTools.stripString(data); BufferedReader reader = new BufferedReader(new StringReader(data)); stream.close(); IniList list = parser.parseINI(reader); // most of the values will be wrapped in double quotes for (IniTable table : list) { LOGGER.debug(""); LOGGER.debug("[" + table.get(IniTable.HEADER_KEY) + "]"); String[] keys = table.keySet().toArray(new String[table.size()]); for (String key : keys) { String value = sanitizeValue(table.get(key)); LOGGER.debug(key + " = " + value); table.put(key, value); } } reader.close(); return list; } // -- Helper classes -- class ChannelData { public boolean active; public Double gain; public Double voltage; public String name; public String emissionFilter; public String excitationFilter; public Integer emWave; public Integer exWave; public String dyeName; public String barrierFilter; } }