// // FlexReader.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 3 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.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Vector; import loci.common.DataTools; import loci.common.DateTools; import loci.common.IRandomAccess; import loci.common.Location; import loci.common.NIOFileHandle; 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.IFDList; import loci.formats.tiff.TiffCompression; import loci.formats.tiff.TiffConstants; import loci.formats.tiff.TiffParser; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PositiveInteger; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; /** * FlexReader is a file format reader for Evotec Flex files. * To use it, the LuraWave decoder library, lwf_jsdk2.6.jar, must be available, * and a LuraWave license key must be specified in the lurawave.license system * property (e.g., <code>-Dlurawave.license=XXXX</code> on the command line). * * <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/FlexReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/FlexReader.java;hb=HEAD">Gitweb</a></dd></dl> */ public class FlexReader extends FormatReader { // -- Constants -- /** Custom IFD entry for Flex XML. */ public static final int FLEX = 65200; public static final String FLEX_SUFFIX = "flex"; public static final String MEA_SUFFIX = "mea"; public static final String RES_SUFFIX = "res"; public static final String[] MEASUREMENT_SUFFIXES = new String[] {MEA_SUFFIX, RES_SUFFIX}; public static final String[] SUFFIXES = new String[] {FLEX_SUFFIX, MEA_SUFFIX, RES_SUFFIX}; public static final String SCREENING = "Screening"; public static final String ARCHIVE = "Archive"; // -- Static fields -- /** * Mapping from server names stored in the .mea file to actual server names. */ private static HashMap<String, String[]> serverMap = new HashMap<String, String[]>(); // -- Fields -- /** Scale factor for each image. */ protected double[][][] factors; /** Camera binning values. */ private int binX, binY; private int plateCount; private int wellCount; private int fieldCount; private int wellRows, wellColumns; private String[] channelNames; private Vector<Double> xPositions, yPositions; private Vector<Double> xSizes, ySizes; private Vector<String> cameraIDs, objectiveIDs, lightSourceIDs; private HashMap<String, Vector<String>> lightSourceCombinationIDs; private Vector<String> cameraRefs, binnings, objectiveRefs; private Vector<String> lightSourceCombinationRefs; private Vector<String> filterSets; private HashMap<String, FilterGroup> filterSetMap; private Vector<String> measurementFiles; private String plateName, plateBarcode; private int nRows = 0, nCols = 0; private RandomAccessInputStream firstStream; private String plateAcqStartTime; private ArrayList<Double> planePositionX = new ArrayList<Double>(); private ArrayList<Double> planePositionY = new ArrayList<Double>(); private ArrayList<Double> planePositionZ = new ArrayList<Double>(); private ArrayList<Double> planeExposureTime = new ArrayList<Double>(); private ArrayList<Double> planeDeltaT = new ArrayList<Double>(); /** * List of .flex files belonging to this dataset. * Indices into the array are the well row and well column. */ private String[][] flexFiles; private IFDList[][] ifds; private long[][][] offsets; /** Specifies the row and column index into 'flexFiles' for a given well. */ private int[][] wellNumber; // -- Constructor -- /** Constructs a new Flex reader. */ public FlexReader() { super("Evotec Flex", SUFFIXES); domains = new String[] {FormatTools.HCS_DOMAIN}; hasCompanionFiles = true; datasetDescription = "One directory containing one or more .flex files, " + "and an optional directory containing an .mea and .res file. The .mea " + "and .res files may also be in the same directory as the .flex file(s)."; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#fileGroupOption(String) */ public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.MUST_GROUP; } /* @see loci.formats.IFormatReader#isSingleFile(String) */ public boolean isSingleFile(String id) throws FormatException, IOException { if (!checkSuffix(id, FLEX_SUFFIX)) return false; return serverMap.size() == 0 || !isGroupFiles(); } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); Vector<String> files = new Vector<String>(); files.addAll(measurementFiles); if (!noPixels) { if (fieldCount > 0 && wellCount > 0 && plateCount > 0) { int[] lengths = new int[] {fieldCount, wellCount, plateCount}; int[] pos = FormatTools.rasterToPosition(lengths, getSeries()); if (pos[1] >= 0 && pos[1] < wellNumber.length) { int row = wellCount == 1 ? 0 : wellNumber[pos[1]][0]; int col = wellCount == 1 ? 0 : wellNumber[pos[1]][1]; if (row < flexFiles.length && col < flexFiles[row].length) { files.add(flexFiles[row][col]); } } } } return files.toArray(new String[files.size()]); } /* @see loci.formats.IFormatReader#getOptimalTileWidth() */ public int getOptimalTileWidth() { FormatTools.assertId(currentId, true, 1); int[] lengths = new int[] {fieldCount, wellCount, plateCount}; int[] pos = FormatTools.rasterToPosition(lengths, getSeries()); int wellRow = wellNumber[pos[1]][0]; int wellCol = wellNumber[pos[1]][1]; if (wellCount == 1) { wellRow = 0; wellCol = 0; } IFD ifd = ifds[wellRow][wellCol].get(0); try { return (int) ifd.getTileWidth(); } catch (FormatException 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); int[] lengths = new int[] {fieldCount, wellCount, plateCount}; int[] pos = FormatTools.rasterToPosition(lengths, getSeries()); int wellRow = wellNumber[pos[1]][0]; int wellCol = wellNumber[pos[1]][1]; if (wellCount == 1) { wellRow = 0; wellCol = 0; } IFD ifd = ifds[wellRow][wellCol].get(0); try { return (int) ifd.getTileLength(); } catch (FormatException e) { LOGGER.debug("Could not retrieve tile height", e); } return super.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); int[] lengths = new int[] {fieldCount, wellCount, plateCount}; int[] pos = FormatTools.rasterToPosition(lengths, getSeries()); int wellRow = wellNumber[pos[1]][0]; int wellCol = wellNumber[pos[1]][1]; if (wellCount == 1) { wellRow = 0; wellCol = 0; } int imageNumber = offsets[wellRow][wellCol] == null ? getImageCount() * pos[0] + no : 0; IFD ifd = offsets[wellRow][wellCol] == null ? ifds[wellRow][wellCol].get(imageNumber) : ifds[0][0].get(0); RandomAccessInputStream s = (wellRow == 0 && wellCol == 0) ? firstStream : new RandomAccessInputStream(getFileHandle(flexFiles[wellRow][wellCol])); int nBytes = ifd.getBitsPerSample()[0] / 8; int bpp = FormatTools.getBytesPerPixel(getPixelType()); int planeSize = getSizeX() * getSizeY() * getRGBChannelCount() * nBytes; double factor = 1d; // read pixels from the file if (ifd.getCompression() != TiffCompression.UNCOMPRESSED || nBytes != bpp || offsets[wellRow][wellCol] == null) { TiffParser tp = new TiffParser(s); tp.getSamples(ifd, buf, x, y, w, h); factor = factors[wellRow][wellCol][imageNumber]; } else { int index = getImageCount() * pos[0] + no; long offset = index == offsets[wellRow][wellCol].length - 1 ? s.length() : offsets[wellRow][wellCol][index + 1]; s.seek(offset - planeSize); readPlane(s, x, y, w, h, buf); factor = factors[0][0][index]; } if (wellRow != 0 || wellCol != 0) s.close(); // expand pixel values with multiplication by factor[no] int num = buf.length / bpp; if (factor != 1d || nBytes != bpp) { for (int i=num-1; i>=0; i--) { int q = nBytes == 1 ? buf[i] & 0xff : DataTools.bytesToInt(buf, i * bpp, bpp, isLittleEndian()); if (q != 0) { q = (int) (q * factor); DataTools.unpackBytes(q, buf, i * bpp, bpp, isLittleEndian()); } } } return buf; } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { factors = null; binX = binY = 0; plateCount = wellCount = fieldCount = 0; channelNames = null; measurementFiles = null; xSizes = ySizes = null; cameraIDs = objectiveIDs = lightSourceIDs = null; lightSourceCombinationIDs = null; lightSourceCombinationRefs = null; cameraRefs = objectiveRefs = binnings = null; wellRows = wellColumns = 0; xPositions = yPositions = null; filterSets = null; filterSetMap = null; plateName = plateBarcode = null; nRows = nCols = 0; flexFiles = null; ifds = null; offsets = null; wellNumber = null; if (firstStream != null) firstStream.close(); firstStream = null; planePositionX.clear(); planePositionY.clear(); planePositionZ.clear(); planeExposureTime.clear(); planeDeltaT.clear(); } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { super.initFile(id); measurementFiles = new Vector<String>(); if (checkSuffix(id, FLEX_SUFFIX)) { initFlexFile(id); } else if (checkSuffix(id, RES_SUFFIX)) { initResFile(id); } else initMeaFile(id); } // -- Helper methods -- /** Initialize the dataset from a .res file. */ private void initResFile(String id) throws FormatException, IOException { LOGGER.debug("initResFile({})", id); parseResFile(id); Location thisFile = new Location(id).getAbsoluteFile(); Location parent = thisFile.getParentFile(); LOGGER.debug(" Looking for an .mea file in {}", parent.getAbsolutePath()); String[] list = parent.list(); for (String file : list) { if (checkSuffix(file, MEA_SUFFIX)) { String mea = new Location(parent, file).getAbsolutePath(); LOGGER.debug(" Found .mea file {}", mea); initMeaFile(mea); if (!measurementFiles.contains(thisFile.getAbsolutePath())) { measurementFiles.add(thisFile.getAbsolutePath()); } return; } } throw new FormatException("Could not find an .mea file."); } /** Initialize the dataset from a .mea file. */ private void initMeaFile(String id) throws FormatException, IOException { LOGGER.debug("initMeaFile({})", id); Location file = new Location(id).getAbsoluteFile(); if (!measurementFiles.contains(file.getAbsolutePath())) { measurementFiles.add(file.getAbsolutePath()); } // parse the .mea file to get a list of .flex files MeaHandler handler = new MeaHandler(); LOGGER.info("Reading contents of .mea file"); LOGGER.info("Parsing XML from .mea file"); RandomAccessInputStream s = new RandomAccessInputStream(id); XMLTools.parseXML(s, handler); s.close(); Vector<String> flex = handler.getFlexFiles(); if (flex.size() == 0) { LOGGER.debug("Could not build .flex list from .mea."); LOGGER.info("Building list of valid .flex files"); String[] files = findFiles(file); if (files != null) { for (String f : files) { if (checkSuffix(f, FLEX_SUFFIX)) flex.add(f); } } if (flex.size() == 0) { throw new FormatException(".flex files were not found. " + "Did you forget to specify the server names?"); } } LOGGER.info("Looking for corresponding .res file"); String[] files = findFiles(file, new String[] {RES_SUFFIX}); if (files != null) { for (String f : files) { if (!measurementFiles.contains(f)) { measurementFiles.add(f); } parseResFile(f); } } MetadataStore store = makeFilterMetadata(); groupFiles(flex.toArray(new String[flex.size()]), store); populateMetadataStore(store); } private void initFlexFile(String id) throws FormatException, IOException { LOGGER.debug("initFlexFile({})", id); boolean doGrouping = true; Location currentFile = new Location(id).getAbsoluteFile(); LOGGER.info("Storing well indices"); try { String name = currentFile.getName(); int[] well = getWell(name); if (well[0] > nRows) nRows = well[0]; if (well[1] > nCols) nCols = well[1]; } catch (NumberFormatException e) { LOGGER.debug("Could not parse well indices", e); doGrouping = false; } LOGGER.info("Looking for other .flex files"); if (!isGroupFiles()) doGrouping = false; if (isGroupFiles()) { LOGGER.debug("Attempting to find files in the same dataset."); try { findFiles(currentFile); } catch (NullPointerException e) { LOGGER.debug("", e); } catch (IOException e) { LOGGER.debug("", e); } if (measurementFiles.size() == 0) { LOGGER.warn("Measurement files not found."); } else { for (String f : measurementFiles) { if (checkSuffix(f, RES_SUFFIX)) { parseResFile(f); } } } } MetadataStore store = makeFilterMetadata(); LOGGER.info("Making sure that all .flex files are valid"); Vector<String> flex = new Vector<String>(); if (doGrouping) { // group together .flex files that are in the same directory Location dir = currentFile.getParentFile(); String[] files = dir.list(true); for (String file : files) { // file names should be nnnnnnnnn.flex, where 'n' is 0-9 LOGGER.debug("Checking if {} belongs in the same dataset.", file); if (file.endsWith(".flex") && file.length() == 14) { flex.add(new Location(dir, file).getAbsolutePath()); LOGGER.debug("Added {} to dataset.", flex.get(flex.size() - 1)); } } } String[] files = doGrouping ? flex.toArray(new String[flex.size()]) : new String[] {currentFile.getAbsolutePath()}; if (files.length == 0) { if (Location.getMappedFile(currentFile.getName()) != null) { files = new String[] {currentFile.getName()}; } else { files = new String[] {currentFile.getAbsolutePath()}; } } LOGGER.debug("Determined that {} .flex files belong together.", files.length); groupFiles(files, store); populateMetadataStore(store); } private void populateMetadataStore(MetadataStore store) throws FormatException { LOGGER.info("Populating MetadataStore"); MetadataTools.populatePixels(store, this, true); Location currentFile = new Location(getCurrentFile()).getAbsoluteFile(); int[] lengths = new int[] {fieldCount, wellCount, plateCount}; store.setPlateID(MetadataTools.createLSID("Plate", 0), 0); String plateAcqID = MetadataTools.createLSID("PlateAcquisition", 0, 0); store.setPlateAcquisitionID(plateAcqID, 0, 0); store.setPlateAcquisitionMaximumFieldCount( new PositiveInteger(fieldCount), 0, 0); plateAcqStartTime = DateTools.formatDate(plateAcqStartTime, "dd.MM.yyyy HH:mm:ss"); store.setPlateAcquisitionStartTime(plateAcqStartTime, 0, 0); for (int row=0; row<wellRows; row++) { for (int col=0; col<wellColumns; col++) { int well = row * wellColumns + col; store.setWellID(MetadataTools.createLSID("Well", 0, well), 0, well); store.setWellRow(new NonNegativeInteger(row), 0, well); store.setWellColumn(new NonNegativeInteger(col), 0, well); } } for (int i=0; i<getSeriesCount(); i++) { int[] pos = FormatTools.rasterToPosition(lengths, i); String imageID = MetadataTools.createLSID("Image", i); store.setImageID(imageID, i); int well = wellNumber[pos[1]][0] * wellColumns + wellNumber[pos[1]][1]; char wellRow = (char) ('A' + wellNumber[pos[1]][0]); store.setImageName("Well " + wellRow + "-" + (wellNumber[pos[1]][1] + 1) + "; Field #" + (pos[0] + 1), i); if (wellRows == 0 && wellColumns == 0) { well = pos[1]; NonNegativeInteger row = new NonNegativeInteger(wellNumber[pos[1]][0]); NonNegativeInteger col = new NonNegativeInteger(wellNumber[pos[1]][1]); String wellID = MetadataTools.createLSID("Well", pos[2], well); store.setWellID(wellID, pos[2], well); store.setWellRow(row, pos[2], pos[1]); store.setWellColumn(col, pos[2], pos[1]); } String wellSample = MetadataTools.createLSID("WellSample", pos[2], well, pos[0]); store.setWellSampleID(wellSample, pos[2], well, pos[0]); store.setWellSampleIndex(new NonNegativeInteger(i), pos[2], well, pos[0]); store.setWellSampleImageRef(imageID, pos[2], well, pos[0]); store.setPlateAcquisitionWellSampleRef(wellSample, 0, 0, i); } if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { String instrumentID = MetadataTools.createLSID("Instrument", 0); store.setInstrumentID(instrumentID, 0); if (plateName == null) plateName = currentFile.getParentFile().getName(); if (plateBarcode != null) plateName = plateBarcode + " " + plateName; store.setPlateName(plateName, 0); store.setPlateRowNamingConvention(getNamingConvention("Letter"), 0); store.setPlateColumnNamingConvention(getNamingConvention("Number"), 0); for (int i=0; i<getSeriesCount(); i++) { int[] pos = FormatTools.rasterToPosition(lengths, i); store.setImageInstrumentRef(instrumentID, i); int seriesIndex = i * getImageCount(); if (seriesIndex < objectiveRefs.size()) { store.setImageObjectiveSettingsID(objectiveRefs.get(seriesIndex), i); } for (int c=0; c<getEffectiveSizeC(); c++) { int channelIndex = seriesIndex + c; if (channelNames != null && channelIndex < channelNames.length) { store.setChannelName(channelNames[channelIndex], i, c); } } if (seriesIndex < lightSourceCombinationRefs.size()) { String lightSourceCombo = lightSourceCombinationRefs.get(seriesIndex); Vector<String> lightSources = lightSourceCombinationIDs.get(lightSourceCombo); for (int c=0; c<getEffectiveSizeC(); c++) { int index = seriesIndex + c; if (index < cameraRefs.size()) { store.setDetectorSettingsID(cameraRefs.get(index), i, c); } if (index < binnings.size()) { store.setDetectorSettingsBinning( getBinning(binnings.get(index)), i, c); } if (lightSources != null && c < lightSources.size()) { store.setChannelLightSourceSettingsID(lightSources.get(c), i, c); } else if (c > 0 && lightSources != null && lightSources.size() == 1) { lightSourceCombo = lightSourceCombinationRefs.get(seriesIndex + c); lightSources = lightSourceCombinationIDs.get(lightSourceCombo); store.setChannelLightSourceSettingsID(lightSources.get(0), i, c); } if (index < filterSets.size()) { FilterGroup group = filterSetMap.get(filterSets.get(index)); if (group.emission != null) { store.setLightPathEmissionFilterRef(group.emission, i, c, 0); } if (group.excitation != null) { store.setLightPathExcitationFilterRef( group.excitation, i, c, 0); } if (group.dichroic != null) { store.setLightPathDichroicRef(group.dichroic, i, c); } } } } if (seriesIndex < xSizes.size()) { store.setPixelsPhysicalSizeX( new PositiveFloat(xSizes.get(seriesIndex)), i); } if (seriesIndex < ySizes.size()) { store.setPixelsPhysicalSizeY( new PositiveFloat(ySizes.get(seriesIndex)), i); } int well = wellNumber[pos[1]][0] * wellColumns + wellNumber[pos[1]][1]; if (wellRows == 0 && wellColumns == 0) { well = pos[1]; } if (pos[0] < xPositions.size()) { store.setWellSamplePositionX( xPositions.get(pos[0]), pos[2], well, pos[0]); } if (pos[0] < yPositions.size()) { store.setWellSamplePositionY( yPositions.get(pos[0]), pos[2], well, pos[0]); } for (int image=0; image<getImageCount(); image++) { int plane = i * getImageCount() + image; if (plane < planePositionX.size()) { store.setPlanePositionX(planePositionX.get(plane), i, image); } if (plane < planePositionY.size()) { store.setPlanePositionY(planePositionY.get(plane), i, image); } if (plane < planePositionZ.size()) { store.setPlanePositionZ(planePositionZ.get(plane), i, image); } if (plane < planeExposureTime.size()) { store.setPlaneExposureTime(planeExposureTime.get(plane), i, image); } if (plane < planeDeltaT.size()) { store.setPlaneDeltaT(planeDeltaT.get(plane), i, image); } } } } } private void parseResFile(String id) throws IOException { ResHandler handler = new ResHandler(); String resXML = DataTools.readFile(id); XMLTools.parseXML(resXML, handler); } /** * Returns a two-element array containing the well row and well column * corresponding to the given file. */ private int[] getWell(String file) { String name = file.substring(file.lastIndexOf(File.separator) + 1); if (name.length() == 14) { // expect nnnnnnnnn.flex try { int row = Integer.parseInt(name.substring(0, 3)) - 1; int col = Integer.parseInt(name.substring(3, 6)) - 1; return new int[] {row, col}; } catch (NumberFormatException e) { } } return new int[] {0, 0}; } /** * Returns the number of planes in the first well that has data. May not be * <code>[0][0]</code> as the acquisition may have been column or row offset. */ private int firstWellPlanes() { for (int i=0; i<offsets.length; i++) { for (int j=0; j<offsets[i].length; j++) { if (offsets[i][j] != null) { return offsets[i][j].length; } } } for (int i = 0; i < ifds.length; i++) { for (int j = 0; j < ifds[i].length; j++) { if (ifds[i][j] != null) { return ifds[i][j].size(); } } } return 0; } /** * Parses XML metadata from the Flex file corresponding to the given well. * If the 'firstFile' flag is set, then the core metadata is also * populated. */ private void parseFlexFile(int currentWell, int wellRow, int wellCol, boolean firstFile, MetadataStore store) throws FormatException, IOException { LOGGER.info("Parsing .flex file (well {}{})", wellRow + 'A', wellCol + 1); if (flexFiles[wellRow][wellCol] == null) return; if (xPositions == null) xPositions = new Vector<Double>(); if (yPositions == null) yPositions = new Vector<Double>(); if (xSizes == null) xSizes = new Vector<Double>(); if (ySizes == null) ySizes = new Vector<Double>(); if (cameraIDs == null) cameraIDs = new Vector<String>(); if (lightSourceIDs == null) lightSourceIDs = new Vector<String>(); if (objectiveIDs == null) objectiveIDs = new Vector<String>(); if (lightSourceCombinationIDs == null) { lightSourceCombinationIDs = new HashMap<String, Vector<String>>(); } if (lightSourceCombinationRefs == null) { lightSourceCombinationRefs = new Vector<String>(); } if (cameraRefs == null) cameraRefs = new Vector<String>(); if (objectiveRefs == null) objectiveRefs = new Vector<String>(); if (binnings == null) binnings = new Vector<String>(); if (filterSets == null) filterSets = new Vector<String>(); if (filterSetMap == null) filterSetMap = new HashMap<String, FilterGroup>(); // parse factors from XML LOGGER.debug("Parsing XML from {}", flexFiles[wellRow][wellCol]); int nOffsets = offsets[wellRow][wellCol] != null ? offsets[wellRow][wellCol].length : 0; int oldWellRow = wellRow; int oldWellCol = wellCol; if (wellRow >= ifds.length || wellCol >= ifds[wellRow].length) { wellRow = 0; wellCol = 0; } IFD ifd = ifds[wellRow][wellCol].get(0); String xml = XMLTools.sanitizeXML(ifd.getIFDStringValue(FLEX)); Vector<String> n = new Vector<String>(); Vector<String> f = new Vector<String>(); DefaultHandler handler = new FlexHandler(n, f, store, firstFile, currentWell); LOGGER.info("Parsing XML in .flex file"); XMLTools.parseXML(xml.getBytes(), handler); channelNames = n.toArray(new String[n.size()]); if (firstFile) populateCoreMetadata(oldWellRow, oldWellCol, n); int totalPlanes = getSeriesCount() * getImageCount(); LOGGER.info("Populating pixel scaling factors"); // verify factor count int nsize = n.size(); int fsize = f.size(); if (nsize != fsize || nsize != totalPlanes) { LOGGER.warn("mismatch between image count, names and factors " + "(count={}, names={}, factors={})", new Object[] {totalPlanes, nsize, fsize}); } for (int i=0; i<nsize; i++) addGlobalMeta("Name " + i, n.get(i)); for (int i=0; i<fsize; i++) addGlobalMeta("Factor " + i, f.get(i)); // parse factor values factors[wellRow][wellCol] = new double[totalPlanes]; int max = 0; for (int i=0; i<fsize; i++) { String factor = f.get(i); double q = 1; try { q = Double.parseDouble(factor); } catch (NumberFormatException exc) { LOGGER.warn("invalid factor #{}: {}", i, factor); } if (i < factors[wellRow][wellCol].length) { factors[wellRow][wellCol][i] = q; if (q > factors[wellRow][wellCol][max]) max = i; } } if (fsize < factors[wellRow][wellCol].length) { Arrays.fill(factors[wellRow][wellCol], fsize, factors[wellRow][wellCol].length, 1); } // determine pixel type if (factors[wellRow][wellCol][max] > 256) { core[0].pixelType = FormatTools.UINT32; } else if (factors[wellRow][wellCol][max] > 1) { core[0].pixelType = FormatTools.UINT16; } for (int i=1; i<core.length; i++) { core[i].pixelType = getPixelType(); } } /** Populate core metadata using the given list of image names. */ private void populateCoreMetadata(int wellRow, int wellCol, Vector<String> imageNames) throws FormatException { LOGGER.info("Populating core metadata for well row " + wellRow + ", column " + wellCol); if (getSizeC() == 0 && getSizeT() == 0) { Vector<String> uniqueChannels = new Vector<String>(); for (int i=0; i<imageNames.size(); i++) { String name = imageNames.get(i); String[] tokens = name.split("_"); if (tokens.length > 1) { // fields are indexed from 1 int fieldIndex = Integer.parseInt(tokens[0]); if (fieldIndex > fieldCount) fieldCount = fieldIndex; } else tokens = name.split(":"); String channel = tokens[tokens.length - 1]; if (!uniqueChannels.contains(channel)) uniqueChannels.add(channel); } if (fieldCount == 0) fieldCount = 1; core[0].sizeC = (int) Math.max(uniqueChannels.size(), 1); if (getSizeZ() == 0) core[0].sizeZ = 1; core[0].sizeT = imageNames.size() / (fieldCount * getSizeC() * getSizeZ()); } if (getSizeC() == 0) { core[0].sizeC = (int) Math.max(channelNames.length, 1); } if (getSizeZ() == 0) core[0].sizeZ = 1; if (getSizeT() == 0) core[0].sizeT = 1; if (plateCount == 0) plateCount = 1; if (wellCount == 0) wellCount = 1; if (fieldCount == 0) fieldCount = 1; // adjust dimensions if the number of IFDs doesn't match the number // of reported images IFDList ifdList = wellRow < ifds.length && wellCol < ifds[wellRow].length ? ifds[wellRow][wellCol] : ifds[0][0]; IFD ifd = ifdList.get(0); int nPlanes = ifdList.size(); if (offsets[wellRow][wellCol] != null) { nPlanes = offsets[wellRow][wellCol].length; } core[0].imageCount = getSizeZ() * getSizeC() * getSizeT(); if (getImageCount() * fieldCount != nPlanes) { core[0].imageCount = nPlanes / fieldCount; core[0].sizeZ = 1; core[0].sizeC = 1; core[0].sizeT = nPlanes / fieldCount; } core[0].sizeX = (int) ifd.getImageWidth(); core[0].sizeY = (int) ifd.getImageLength(); core[0].dimensionOrder = "XYCZT"; core[0].rgb = false; core[0].interleaved = false; core[0].indexed = false; core[0].littleEndian = ifd.isLittleEndian(); core[0].pixelType = ifd.getPixelType(); int seriesCount = plateCount * wellCount * fieldCount; if (seriesCount > 1) { CoreMetadata oldCore = core[0]; core = new CoreMetadata[seriesCount]; Arrays.fill(core, oldCore); } } /** * Search for files that correspond to the given file. * If the given file is a .mea file, then the corresponding files will be * .res and .flex files. * If the given file is a .flex file, then the corresponding files will be * .res and .mea files. */ private String[] findFiles(Location baseFile) throws IOException { String[] suffixes = new String[0]; if (checkSuffix(baseFile.getName(), FLEX_SUFFIX)) { suffixes = new String[] {MEA_SUFFIX, RES_SUFFIX}; LOGGER.debug("Looking for files with the suffix '{}' or '{}'.", MEA_SUFFIX, RES_SUFFIX); } else if (checkSuffix(baseFile.getName(), MEA_SUFFIX)) { suffixes = new String[] {FLEX_SUFFIX, RES_SUFFIX}; LOGGER.debug("Looking for files with the suffix '{}' or '{}'.", FLEX_SUFFIX, RES_SUFFIX); } return findFiles(baseFile, suffixes); } private String[] findFiles(Location baseFile, String[] suffixes) throws IOException { // we're assuming that the directory structure looks something like this: // // top level directory // / \ // top level flex dir top level measurement dir // / | \ / | \ // plate #0 ... plate #n plate #0 ... plate #n // / | \ / \ // .flex ... .flex .mea .res // // or like this: // // top level directory // / | \ / | \ // Flex plate #0 ... #n #0 ... Measurement plate #n // // or that the .mea and .res are in the same directory as the .flex files LOGGER.debug("findFiles({})", baseFile.getAbsolutePath()); LOGGER.info("Looking for files that are in the same dataset as " + baseFile.getAbsolutePath()); Vector<String> fileList = new Vector<String>(); Location plateDir = baseFile.getParentFile(); String[] files = plateDir.list(true); // check if the measurement files are in the same directory LOGGER.debug("Looking for files in {}", plateDir.getAbsolutePath()); for (String file : files) { String lfile = file.toLowerCase(); String path = new Location(plateDir, file).getAbsolutePath(); if (checkSuffix(file, suffixes)) { fileList.add(path); LOGGER.debug("Found file {}", path); } } // file list is valid (i.e. can be returned) if there is at least // one file with each of the desired suffixes LOGGER.debug( "Checking to see if at least one file with each suffix was found..."); boolean validList = true; for (String suffix : suffixes) { boolean foundSuffix = false; for (String file : fileList) { if (checkSuffix(file, suffix)) { foundSuffix = true; break; } } if (!foundSuffix) { validList = false; break; } } LOGGER.debug("{} required files.", validList ? "Found" : "Did not find"); if (validList) { LOGGER.debug("Returning file list:"); for (String file : fileList) { LOGGER.debug(" {}", file); if (checkSuffix(file, MEASUREMENT_SUFFIXES) && !measurementFiles.contains(file)) { measurementFiles.add(file); } } return fileList.toArray(new String[fileList.size()]); } Location flexDir = null; try { flexDir = plateDir.getParentFile(); } catch (NullPointerException e) { } LOGGER.debug("Looking for files in {}", flexDir); if (flexDir == null) return null; // check if the measurement directory and the Flex directory // have the same parent Location measurementDir = null; String[] flexDirList = flexDir.list(true); if (flexDirList.length > 1) { String plateName = plateDir.getName(); for (String file : flexDirList) { if (!file.equals(plateName) && (plateName.startsWith(file) || file.startsWith(plateName))) { measurementDir = new Location(flexDir, file); LOGGER.debug("Expect measurement files to be in {}", measurementDir.getAbsolutePath()); break; } } } // check if Flex directories and measurement directories have // a different parent if (measurementDir == null) { Location topDir = flexDir.getParentFile(); LOGGER.debug("First attempt at finding measurement file directory " + "failed. Looking for an appropriate measurement directory in {}.", topDir.getAbsolutePath()); String[] topDirList = topDir.list(true); for (String file : topDirList) { if (!flexDir.getAbsolutePath().endsWith(file)) { measurementDir = new Location(topDir, file); LOGGER.debug("Expect measurement files to be in {}", measurementDir.getAbsolutePath()); break; } } if (measurementDir == null) { LOGGER.debug("Failed to find measurement file directory."); return null; } } else plateDir = measurementDir; if (!plateDir.getAbsolutePath().equals(measurementDir.getAbsolutePath())) { LOGGER.debug("Measurement files are in a subdirectory of {}", measurementDir.getAbsolutePath()); String[] measurementPlates = measurementDir.list(true); String plate = plateDir.getName(); LOGGER.debug("Determining which subdirectory contains the measurements " + "for plate {}", plate); plateDir = null; if (measurementPlates != null) { for (String file : measurementPlates) { LOGGER.debug("Checking {}", file); if (file.indexOf(plate) != -1 || plate.indexOf(file) != -1) { plateDir = new Location(measurementDir, file); LOGGER.debug("Measurement files are in {}", plateDir.getAbsolutePath()); break; } } } } if (plateDir == null) { LOGGER.debug("Could not find appropriate subdirectory."); return null; } files = plateDir.list(true); for (String file : files) { fileList.add(new Location(plateDir, file).getAbsolutePath()); } LOGGER.debug("Returning file list:"); for (String file : fileList) { LOGGER.debug(" {}", file); if (checkSuffix(file, MEASUREMENT_SUFFIXES) && !measurementFiles.contains(file)) { measurementFiles.add(file); } } return fileList.toArray(new String[fileList.size()]); } private void groupFiles(String[] fileList, MetadataStore store) throws FormatException, IOException { LOGGER.info("Grouping together files in the same dataset"); HashMap<String, String> v = new HashMap<String, String>(); Boolean firstCompressed = null; int firstIFDCount = 0; for (String file : fileList) { RandomAccessInputStream s = new RandomAccessInputStream(file); TiffParser parser = new TiffParser(s); IFD firstIFD = parser.getFirstIFD(); int ifdCount = parser.getIFDOffsets().length; s.close(); boolean compressed = firstIFD.getCompression() != TiffCompression.UNCOMPRESSED; if (firstCompressed == null) { firstCompressed = compressed; firstIFDCount = ifdCount; } if (compressed == firstCompressed && ifdCount == firstIFDCount) { int[] well = getWell(file); if (well[0] > nRows) nRows = well[0]; if (well[1] > nCols) nCols = well[1]; if (fileList.length == 1) { well[0] = 0; well[1] = 0; } v.put(well[0] + "," + well[1], file); } else { v.clear(); v.put("0,0", currentId); fileList = new String[] {currentId}; break; } } nRows++; nCols++; if (fileList.length == 1) { nRows = 1; nCols = 1; } LOGGER.debug("Determined that there are {} rows and {} columns of wells.", nRows, nCols); flexFiles = new String[nRows][nCols]; offsets = new long[nRows][nCols][]; wellCount = v.size(); wellNumber = new int[wellCount][2]; RandomAccessInputStream s = null; boolean firstFile = true; boolean compressed = false; int nOffsets = 1; int currentWell = 0; for (int row=0; row<nRows; row++) { for (int col=0; col<nCols; col++) { flexFiles[row][col] = v.get(row + "," + col); if (flexFiles[row][col] == null) { continue; } wellNumber[currentWell][0] = row; wellNumber[currentWell][1] = col; s = new RandomAccessInputStream(getFileHandle(flexFiles[row][col])); if (currentWell == 0) firstStream = s; TiffParser tp = new TiffParser(s); if (compressed || firstFile) { LOGGER.info("Parsing IFDs for well {}{}", row + 'A', col + 1); IFD firstIFD = tp.getFirstIFD(); compressed = firstIFD.getCompression() != TiffCompression.UNCOMPRESSED; if (compressed) { if (ifds == null) { ifds = new IFDList[nRows][nCols]; factors = new double[nRows][nCols][]; } tp.setDoCaching(false); ifds[row][col] = tp.getIFDs(); ifds[row][col].set(0, firstIFD); parseFlexFile(currentWell, row, col, firstFile, store); } else { // if the pixel data is uncompressed, we can assume that // the pixel data for image #0 is located immediately before // IFD #1; as a result, we only need to parse the first IFD if (ifds == null) { ifds = new IFDList[1][1]; factors = new double[1][1][]; } offsets[row][col] = tp.getIFDOffsets(); nOffsets = offsets[row][col].length; ifds[0][0] = new IFDList(); ifds[0][0].add(firstIFD); parseFlexFile(currentWell, row, col, firstFile, store); } } else { // retrieve the offsets to each IFD, instead of parsing // all of the IFDs LOGGER.info("Retrieving IFD offsets for well {}{}", row + 'A', col + 1); offsets[row][col] = new long[nOffsets]; // Assume that all IFDs after the first are evenly spaced. // TiffParser.getIFDOffsets() could be used instead, but is // substantially slower. offsets[row][col][0] = tp.getFirstOffset(); if (offsets[row][col].length > 1) { s.seek(offsets[row][col][0]); s.skipBytes(s.readShort() * TiffConstants.BYTES_PER_ENTRY); offsets[row][col][1] = s.readInt(); int size = FormatTools.getPlaneSize(this) + 174; for (int i=2; i<offsets[row][col].length; i++) { offsets[row][col][i] = offsets[row][col][i - 1] + size; } } parseFlexFile(currentWell, row, col, firstFile, store); } if (currentWell != 0) s.close(); if (firstFile) firstFile = false; currentWell++; } } } private IRandomAccess getFileHandle(String flexFile) throws IOException { if (Location.getMappedFile(flexFile) != null) { return Location.getMappedFile(flexFile); } return new NIOFileHandle(flexFile, "r"); } // -- Helper classes -- /** SAX handler for parsing XML. */ public class FlexHandler extends DefaultHandler { private Vector<String> names, factors; private MetadataStore store; private int nextLaser = -1; private int nextCamera = 0; private int nextObjective = -1; private int nextImage = 0; private int nextPlate = 0; private String parentQName; private String lightSourceID; private String sliderName; private int nextFilter; private int nextDichroic; private int nextFilterSet; private int nextSliderRef; private boolean populateCore = true; private int well = 0; private HashMap<String, String> filterMap; private HashMap<String, String> dichroicMap; private MetadataLevel level; private String filterSet; private StringBuffer charData = new StringBuffer(); public FlexHandler(Vector<String> names, Vector<String> factors, MetadataStore store, boolean populateCore, int well) { this.names = names; this.factors = factors; this.store = store; this.populateCore = populateCore; this.well = well; filterMap = new HashMap<String, String>(); dichroicMap = new HashMap<String, String>(); level = getMetadataOptions().getMetadataLevel(); } public void characters(char[] ch, int start, int length) { charData.append(new String(ch, start, length)); } public void endElement(String uri, String localName, String qName) { String value = charData.toString(); charData = new StringBuffer(); if (qName.equals("XSize") && "Plate".equals(parentQName)) { wellRows = Integer.parseInt(value); } else if (qName.equals("YSize") && "Plate".equals(parentQName)) { wellColumns = Integer.parseInt(value); } else if ("Image".equals(parentQName)) { if (fieldCount == 0) fieldCount = 1; int nImages = firstWellPlanes() / fieldCount; if (nImages == 0) nImages = 1; // probably a manually altered dataset int currentSeries = (nextImage - 1) / nImages; currentSeries += well * fieldCount; int currentImage = (nextImage - 1) % nImages; int seriesCount = 1; if (plateCount > 0) seriesCount *= plateCount; if (wellCount > 0) seriesCount *= wellCount; if (fieldCount > 0) seriesCount *= fieldCount; if (currentSeries >= seriesCount) return; if (qName.equals("DateTime")) { store.setImageAcquiredDate(value, currentSeries); } } if (level == MetadataLevel.MINIMUM) return; if (qName.equals("Image")) { binnings.add(binX + "x" + binY); } else if (qName.equals("PlateName")) { if (plateName == null) plateName = value; } else if (qName.equals("Barcode")) { if (plateBarcode == null) plateBarcode = value; store.setPlateExternalIdentifier(value, nextPlate - 1); } else if (qName.equals("Wavelength")) { String lsid = MetadataTools.createLSID("LightSource", 0, nextLaser); store.setLaserID(lsid, 0, nextLaser); store.setLaserWavelength( new PositiveInteger(new Integer(value)), 0, nextLaser); try { store.setLaserType(getLaserType("Other"), 0, nextLaser); store.setLaserLaserMedium(getLaserMedium("Other"), 0, nextLaser); } catch (FormatException e) { LOGGER.warn("", e); } } else if (qName.equals("Magnification")) { store.setObjectiveCalibratedMagnification(new Double(value), 0, nextObjective); } else if (qName.equals("NumAperture")) { store.setObjectiveLensNA(new Double(value), 0, nextObjective); } else if (qName.equals("Immersion")) { String immersion = "Other"; if (value.equals("1.33")) immersion = "Water"; else if (value.equals("1.00")) immersion = "Air"; else LOGGER.warn("Unknown immersion medium: {}", value); try { store.setObjectiveImmersion( getImmersion(immersion), 0, nextObjective); } catch (FormatException e) { LOGGER.warn("", e); } } else if (qName.equals("OffsetX") || qName.equals("OffsetY")) { Double offset = new Double(Double.parseDouble(value) * 1000000); if (qName.equals("OffsetX")) xPositions.add(offset); else yPositions.add(offset); } else if ("Image".equals(parentQName)) { if (fieldCount == 0) fieldCount = 1; int nImages = firstWellPlanes() / fieldCount; if (nImages == 0) nImages = 1; // probably a manually altered dataset int currentSeries = (nextImage - 1) / nImages; currentSeries += well * fieldCount; int currentImage = (nextImage - 1) % nImages; int seriesCount = 1; if (plateCount > 0) seriesCount *= plateCount; if (wellCount > 0) seriesCount *= wellCount; if (fieldCount > 0) seriesCount *= fieldCount; if (currentSeries >= seriesCount) return; if (qName.equals("CameraBinningX")) { binX = Integer.parseInt(value); } else if (qName.equals("CameraBinningY")) { binY = Integer.parseInt(value); } else if (qName.equals("ObjectiveRef")) { String objectiveID = MetadataTools.createLSID( "Objective", 0, objectiveIDs.indexOf(value)); objectiveRefs.add(objectiveID); } else if (qName.equals("CameraRef")) { String detectorID = MetadataTools.createLSID("Detector", 0, cameraIDs.indexOf(value)); cameraRefs.add(detectorID); } else if (qName.equals("ImageResolutionX")) { double v = Double.parseDouble(value) * 1000000; xSizes.add(new Double(v)); } else if (qName.equals("ImageResolutionY")) { double v = Double.parseDouble(value) * 1000000; ySizes.add(new Double(v)); } else if (qName.equals("PositionX")) { Double v = new Double(Double.parseDouble(value) * 1000000); planePositionX.add(v); addGlobalMeta("X position for position #" + (currentSeries + 1), v); } else if (qName.equals("PositionY")) { Double v = new Double(Double.parseDouble(value) * 1000000); planePositionY.add(v); addGlobalMeta("Y position for position #" + (currentSeries + 1), v); } else if (qName.equals("PositionZ")) { Double v = new Double(Double.parseDouble(value) * 1000000); planePositionZ.add(v); addGlobalMeta("Z position for position #" + (currentSeries + 1), v); } else if (qName.equals("TimepointOffsetUsed")) { planeDeltaT.add(new Double(value)); } else if (qName.equals("CameraExposureTime")) { planeExposureTime.add(new Double(value)); } else if (qName.equals("LightSourceCombinationRef")) { lightSourceCombinationRefs.add(value); } else if (qName.equals("FilterCombinationRef")) { filterSets.add("FilterSet:" + value); } } else if (qName.equals("FilterCombination")) { nextFilterSet++; nextSliderRef = 0; } } public void startElement(String uri, String localName, String qName, Attributes attributes) { if (qName.equals("Array")) { int len = attributes.getLength(); for (int i=0; i<len; i++) { String name = attributes.getQName(i); if (name.equals("Name")) { names.add(attributes.getValue(i)); } else if (name.equals("Factor")) factors.add(attributes.getValue(i)); } } else if (qName.equals("LightSource") && level != MetadataLevel.MINIMUM) { parentQName = qName; String type = attributes.getValue("LightSourceType"); lightSourceIDs.add(attributes.getValue("ID")); nextLaser++; } else if (qName.equals("LightSourceCombination") && level != MetadataLevel.MINIMUM) { lightSourceID = attributes.getValue("ID"); lightSourceCombinationIDs.put(lightSourceID, new Vector<String>()); } else if (qName.equals("LightSourceRef") && level != MetadataLevel.MINIMUM) { Vector<String> v = lightSourceCombinationIDs.get(lightSourceID); if (v != null) { int id = lightSourceIDs.indexOf(attributes.getValue("ID")); String lightSourceID = MetadataTools.createLSID("LightSource", 0, id); v.add(lightSourceID); lightSourceCombinationIDs.put(lightSourceID, v); } } else if (qName.equals("Camera") && level != MetadataLevel.MINIMUM) { parentQName = qName; String detectorID = MetadataTools.createLSID("Detector", 0, nextCamera); store.setDetectorID(detectorID, 0, nextCamera); try { store.setDetectorType(getDetectorType( attributes.getValue("CameraType")), 0, nextCamera); } catch (FormatException e) { LOGGER.warn("", e); } cameraIDs.add(attributes.getValue("ID")); nextCamera++; } else if (qName.equals("Objective") && level != MetadataLevel.MINIMUM) { parentQName = qName; nextObjective++; String objectiveID = MetadataTools.createLSID("Objective", 0, nextObjective); store.setObjectiveID(objectiveID, 0, nextObjective); try { store.setObjectiveCorrection( getCorrection("Other"), 0, nextObjective); } catch (FormatException e) { LOGGER.warn("", e); } objectiveIDs.add(attributes.getValue("ID")); } else if (qName.equals("Field")) { parentQName = qName; int fieldNo = Integer.parseInt(attributes.getValue("No")); if (fieldNo > fieldCount && fieldCount < firstWellPlanes()) { fieldCount++; } } else if (qName.equals("Plane")) { parentQName = qName; int planeNo = Integer.parseInt(attributes.getValue("No")); if (planeNo > getSizeZ() && populateCore) core[0].sizeZ++; } else if (qName.equals("WellShape")) { parentQName = qName; } else if (qName.equals("Image")) { parentQName = qName; nextImage++; if (level != MetadataLevel.MINIMUM) { //Implemented for FLEX v1.7 and below String x = attributes.getValue("CameraBinningX"); String y = attributes.getValue("CameraBinningY"); if (x != null) binX = Integer.parseInt(x); if (y != null) binY = Integer.parseInt(y); } } else if (qName.equals("Plate")) { parentQName = qName; if (qName.equals("Plate")) { nextPlate++; plateCount++; } } else if (qName.equals("WellCoordinate")) { if (wellNumber.length == 1) { wellNumber[0][0] = Integer.parseInt(attributes.getValue("Row")) - 1; wellNumber[0][1] = Integer.parseInt(attributes.getValue("Col")) - 1; } } else if (qName.equals("Slider") && level != MetadataLevel.MINIMUM) { sliderName = attributes.getValue("Name"); } else if (qName.equals("Filter") && level != MetadataLevel.MINIMUM) { String id = attributes.getValue("ID"); if (sliderName.endsWith("Dichro")) { String dichroicID = MetadataTools.createLSID("Dichroic", 0, nextDichroic); dichroicMap.put(id, dichroicID); store.setDichroicID(dichroicID, 0, nextDichroic); store.setDichroicModel(id, 0, nextDichroic); nextDichroic++; } else { String filterID = MetadataTools.createLSID("Filter", 0, nextFilter); filterMap.put(id, filterID); store.setFilterID(filterID, 0, nextFilter); store.setFilterModel(id, 0, nextFilter); store.setFilterFilterWheel(sliderName, 0, nextFilter); nextFilter++; } } else if (qName.equals("FilterCombination") && level != MetadataLevel.MINIMUM) { filterSet = "FilterSet:" + attributes.getValue("ID"); filterSetMap.put(filterSet, new FilterGroup()); } else if (qName.equals("SliderRef") && level != MetadataLevel.MINIMUM) { String filterName = attributes.getValue("Filter"); String slider = attributes.getValue("ID"); FilterGroup group = filterSetMap.get(filterSet); if (nextSliderRef == 0 && slider.startsWith("Camera")) { group.emission = filterMap.get(filterName); } else if (nextSliderRef == 1 && slider.startsWith("Camera")) { group.excitation = filterMap.get(filterName); } else if (slider.equals("Primary_Dichro")) { group.dichroic = dichroicMap.get(filterName); } String lname = filterName.toLowerCase(); if (!lname.startsWith("empty") && !lname.startsWith("blocked")) { nextSliderRef++; } filterSetMap.put(filterSet, group); } } } /** SAX handler for parsing XML from .mea files. */ public class MeaHandler extends DefaultHandler { private Vector<String> flex = new Vector<String>(); private String[] hostnames = null; // -- MeaHandler API methods -- public Vector<String> getFlexFiles() { return flex; } // -- DefaultHandler API methods -- public void startElement(String uri, String localName, String qName, Attributes attributes) { if (qName.equals("Host")) { String hostname = attributes.getValue("name"); LOGGER.debug("FlexHandler: found hostname '{}'", hostname); hostnames = serverMap.get(hostname); if (hostnames != null) { LOGGER.debug("Sanitizing hostnames..."); for (int i=0; i<hostnames.length; i++) { String host = hostnames[i]; hostnames[i] = hostnames[i].replace('/', File.separatorChar); hostnames[i] = hostnames[i].replace('\\', File.separatorChar); LOGGER.debug("Hostname #{} was {}, is now {}", new Object[] {i, host, hostnames[i]}); } } } else if (qName.equals("Picture")) { String path = attributes.getValue("path"); if (!path.endsWith(".flex")) path += ".flex"; path = path.replace('/', File.separatorChar); path = path.replace('\\', File.separatorChar); LOGGER.debug("Found .flex in .mea: {}", path); if (hostnames != null) { int numberOfFlexFiles = flex.size(); for (String hostname : hostnames) { String filename = hostname + File.separator + path; if (new Location(filename).exists()) { flex.add(filename); } } if (flex.size() == numberOfFlexFiles) { LOGGER.warn("{} was in .mea, but does not actually exist.", path); } } } } } /** SAX handler for parsing XML from .res files. */ public class ResHandler extends DefaultHandler { // -- DefaultHandler API methods -- public void startElement(String uri, String localName, String qName, Attributes attributes) { if (qName.equals("AnalysisResults")) { plateAcqStartTime = attributes.getValue("date"); } } } /** Stores a grouping of filters. */ class FilterGroup { public String emission; public String excitation; public String dichroic; public String id; } // -- FlexReader API methods -- /** * Add the path 'realName' to the mapping for the server named 'alias'. * @throws FormatException if 'realName' does not exist */ public static void appendServerMap(String alias, String realName) throws FormatException { LOGGER.debug("appendServerMap({}, {})", alias, realName); if (alias != null) { if (realName == null) { LOGGER.debug("removing mapping for {}", alias); serverMap.remove(alias); } else { // verify that 'realName' exists Location server = new Location(realName); if (!server.exists()) { throw new FormatException("Server " + realName + " was not found."); } String[] names = serverMap.get(alias); if (names == null) { serverMap.put(alias, new String[] {realName}); } else { String[] tmpNames = new String[names.length + 1]; System.arraycopy(names, 0, tmpNames, 0, names.length); tmpNames[tmpNames.length - 1] = realName; serverMap.put(alias, tmpNames); } } } } /** * Map the server named 'alias' to the path 'realName'. * If other paths were mapped to 'alias', they will be overwritten. */ public static void mapServer(String alias, String realName) { LOGGER.debug("mapSever({}, {})", alias, realName); if (alias != null) { if (realName == null) { LOGGER.debug("removing mapping for {}", alias); serverMap.remove(alias); } else { LOGGER.debug("Finding base server name..."); if (realName.endsWith(File.separator)) { realName = realName.substring(0, realName.length() - 1); } String baseName = realName; if (baseName.endsWith(SCREENING)) { baseName = baseName.substring(0, baseName.lastIndexOf(SCREENING)); } else if (baseName.endsWith(ARCHIVE)) { baseName = baseName.substring(0, baseName.lastIndexOf(ARCHIVE)); } LOGGER.debug("Base server name is {}", baseName); Vector<String> names = new Vector<String>(); names.add(realName); Location screening = new Location(baseName + File.separator + SCREENING); Location archive = new Location(baseName + File.separator + ARCHIVE); if (screening.exists()) names.add(screening.getAbsolutePath()); if (archive.exists()) names.add(archive.getAbsolutePath()); LOGGER.debug("Server names for {}:", alias); for (String name : names) { LOGGER.debug(" {}", name); } mapServer(alias, names.toArray(new String[names.size()])); } } } /** * Map the server named 'alias' to the paths in 'realNames'. * If other paths were mapped to 'alias', they will be overwritten. */ public static void mapServer(String alias, String[] realNames) { StringBuffer msg = new StringBuffer("mapServer("); msg.append(alias); if (realNames != null) { msg.append(", ["); for (String name : realNames) { msg.append(name); msg.append(", "); } msg.append("])"); } else msg.append(", null)"); LOGGER.debug(msg.toString()); if (alias != null) { if (realNames == null) { LOGGER.debug("Removing mapping for {}", alias); serverMap.remove(alias); } else { for (String server : realNames) { try { appendServerMap(alias, server); } catch (FormatException e) { LOGGER.debug("Failed to map server '{}'", server, e); } } } } } /** * Read a configuration file with lines of the form: * * <server alias>=<real server name> * * and call mapServer(String, String) accordingly. * * @throws FormatException if configFile does not exist. * @see #mapServer(String, String) */ public static void mapServersFromConfigurationFile(String configFile) throws FormatException, IOException { LOGGER.debug("mapServersFromConfigurationFile({})", configFile); Location file = new Location(configFile); if (!file.exists()) { throw new FormatException( "Configuration file " + configFile + " does not exist."); } String[] lines = DataTools.readFile(configFile).split("[\r\n]"); for (String line : lines) { LOGGER.trace(line); int eq = line.indexOf("="); if (eq == -1 || line.startsWith("#")) continue; String alias = line.substring(0, eq).trim(); String[] servers = line.substring(eq + 1).trim().split(";"); mapServer(alias, servers); } } }