gt:           if (correction == null || correction.trim().isEmpty()) {
context: /*
   * #%L
   * OME Bio-Formats package for reading and converting biological file formats.
   * %%
   * Copyright (C) 2005 - 2017 Open Microscopy Environment:
   *   - Board of Regents of the University of Wisconsin-Madison
   *   - Glencoe Software, Inc.
   *   - University of Dundee
   * %%
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as
   * published by the Free Software Foundation, either version 2 of the 
   * License, or (at your option) any later version.
   * 
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public 
   * License along with this program.  If not, see
   * <http://www.gnu.org/licenses/gpl-2.0.html>.
   * #L%
   */
  
  package loci.formats.in;
  
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.util.ArrayDeque;
  import java.util.Arrays;
  import java.util.ArrayList;
  import java.util.Deque;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.HashSet;
  import java.util.Set;
  import java.util.StringTokenizer;
  
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  import javax.xml.parsers.ParserConfigurationException;
  
  import loci.common.DataTools;
  import loci.common.DateTools;
  import loci.common.Location;
  import loci.common.RandomAccessInputStream;
  import loci.common.services.DependencyException;
  import loci.common.services.ServiceException;
  import loci.common.services.ServiceFactory;
  import loci.common.xml.XMLTools;
  import loci.formats.CoreMetadata;
  import loci.formats.FormatException;
  import loci.formats.FormatReader;
  import loci.formats.FormatTools;
  import loci.formats.ImageTools;
  import loci.formats.MetadataTools;
  import loci.formats.meta.MetadataStore;
  import loci.formats.services.OMEXMLService;
  import ome.xml.model.enums.DetectorType;
  import ome.xml.model.enums.LaserMedium;
  import ome.xml.model.enums.LaserType;
  import ome.xml.model.primitives.Color;
  import ome.xml.model.primitives.PercentFraction;
  import ome.xml.model.primitives.PositiveFloat;
  import ome.xml.model.primitives.Timestamp;
  import ome.units.quantity.Length;
  import ome.units.quantity.Time;
  import ome.units.UNITS;
  
  import org.xml.sax.SAXException;
  import org.apache.commons.lang.StringUtils;
  import org.w3c.dom.Attr;
  import org.w3c.dom.Element;
  import org.w3c.dom.NamedNodeMap;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  import com.google.common.collect.ImmutableMap;
  import com.google.common.collect.ImmutableMap.Builder;
  
  /**
   * LIFReader is the file format reader for Leica LIF files.
   *
   * @author Melissa Linkert melissa at glencoesoftware.com
   */
  public class LIFReader extends FormatReader {
  
    // -- Constants --
  
    public static final String OLD_PHYSICAL_SIZE_KEY =
      "leicalif.old_physical_size";
    public static final boolean OLD_PHYSICAL_SIZE_DEFAULT = false;
  
    public static final byte LIF_MAGIC_BYTE = 0x70;
    public static final byte LIF_MEMORY_BYTE = 0x2a;
  
    /** The encoding used in this file.*/
    private static final String ENCODING = "ISO-8859-1";
  
    private static final String LOGO_FILE = "LeicaLogo.jpg";
    private static final String STYLESHEET_FILE = "LASAF_CIP.xsl";
  
    private static final ImmutableMap<String, Integer> CHANNEL_PRIORITIES =
      createChannelPriorities();
  
    private static ImmutableMap<String, Integer> createChannelPriorities() {
      final Builder<String, Integer> h = ImmutableMap.builder();
  
      h.put("red", 0);
      h.put("green", 1);
      h.put("blue", 2);
      h.put("cyan", 3);
      h.put("magenta", 4);
      h.put("yellow", 5);
      h.put("black", 6);
      h.put("gray", 7);
      h.put("", 8);
  
      return h.build();
    }
  
    // -- Fields --
  
    /** Offsets to memory blocks, paired with their corresponding description. */
    private List<Long> offsets;
  
    private int[][] realChannel;
    private int lastChannel = 0;
  
    private List<String> lutNames = new ArrayList<String>();
    private List<Double> physicalSizeXs = new ArrayList<Double>();
    private List<Double> physicalSizeYs = new ArrayList<Double>();
    private List<Length> fieldPosX = new ArrayList<Length>();
    private List<Length> fieldPosY = new ArrayList<Length>();
  
    private String[] descriptions, microscopeModels, serialNumber;
    private Double[] pinholes, zooms, zSteps, tSteps, lensNA;
    private Double[][] expTimes, gains, detectorOffsets;
    private String[][] channelNames;
    private List[] detectorModels;
    private Double[][] exWaves;
    private List[] activeDetector;
    private HashMap[] detectorIndexes;
  
    private String[] immersions, corrections, objectiveModels;
    private Double[] magnification;
    private Length[] posX, posY, posZ;
    private Double[] refractiveIndex;
    private List[] cutIns, cutOuts, filterModels;
    private Double[][] timestamps;
    private List[] laserWavelength, laserIntensity, laserActive, laserFrap;
  
    private ROI[][] imageROIs;
    private boolean alternateCenter = false;
    private String[] imageNames;
    private double[] acquiredDate;
  
    private int[] tileCount;
    private long[] tileBytesInc;
    private long endPointer;
  
    // -- Constructor --
  
    /** Constructs a new Leica LIF reader. */
    public LIFReader() {
      super("Leica Image File Format", "lif");
      suffixNecessary = false;
      hasCompanionFiles = true;
      domains = new String[] {FormatTools.LM_DOMAIN};
    }
  
    public boolean useOldPhysicalSizeCalculation() {
      MetadataOptions options = getMetadataOptions();
      if (options instanceof DynamicMetadataOptions) {
        return ((DynamicMetadataOptions) options).getBoolean(
          OLD_PHYSICAL_SIZE_KEY, OLD_PHYSICAL_SIZE_DEFAULT);
      }
      return OLD_PHYSICAL_SIZE_DEFAULT;
    }
  
    // -- IFormatReader API methods --
  
    /* @see loci.formats.IFormatReader#getOptimalTileHeight() */
    @Override
    public int getOptimalTileHeight() {
      FormatTools.assertId(currentId, true, 1);
      return getSizeY();
    }
  
    /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
    @Override
    public boolean isThisType(RandomAccessInputStream stream) throws IOException {
      final int blockLen = 1;
      if (!FormatTools.validStream(stream, blockLen, true)) return false;
      return stream.read() == LIF_MAGIC_BYTE;
    }
  
    /* @see loci.formats.IFormatReader#get8BitLookupTable() */
    @Override
    public byte[][] get8BitLookupTable() {
      FormatTools.assertId(currentId, true, 1);
      if (getPixelType() != FormatTools.UINT8 || !isIndexed()) return null;
  
      if (lastChannel < 0 || lastChannel >= 9) {
        return null;
      }
  
      byte[][] lut = new byte[3][256];
      for (int i=0; i<256; i++) {
        switch (lastChannel) {
          case 0:
            // red
            lut[0][i] = (byte) (i & 0xff);
            break;
          case 1:
            // green
            lut[1][i] = (byte) (i & 0xff);
            break;
          case 2:
            // blue
            lut[2][i] = (byte) (i & 0xff);
            break;
          case 3:
            // cyan
            lut[1][i] = (byte) (i & 0xff);
            lut[2][i] = (byte) (i & 0xff);
            break;
          case 4:
            // magenta
            lut[0][i] = (byte) (i & 0xff);
            lut[2][i] = (byte) (i & 0xff);
            break;
          case 5:
            // yellow
            lut[0][i] = (byte) (i & 0xff);
            lut[1][i] = (byte) (i & 0xff);
            break;
          default:
            // gray
            lut[0][i] = (byte) (i & 0xff);
            lut[1][i] = (byte) (i & 0xff);
            lut[2][i] = (byte) (i & 0xff);
        }
      }
      return lut;
    }
  
    /* @see loci.formats.IFormatReader#get16BitLookupTable() */
    @Override
    public short[][] get16BitLookupTable() {
      FormatTools.assertId(currentId, true, 1);
      if (getPixelType() != FormatTools.UINT16 || !isIndexed()) return null;
  
      if (lastChannel < 0 || lastChannel >= 9) {
        return null;
      }
  
      short[][] lut = new short[3][65536];
      for (int i=0; i<65536; i++) {
        switch (lastChannel) {
          case 0:
            // red
            lut[0][i] = (short) (i & 0xffff);
            break;
          case 1:
            // green
            lut[1][i] = (short) (i & 0xffff);
            break;
          case 2:
            // blue
            lut[2][i] = (short) (i & 0xffff);
            break;
          case 3:
            // cyan
            lut[1][i] = (short) (i & 0xffff);
            lut[2][i] = (short) (i & 0xffff);
            break;
          case 4:
            // magenta
            lut[0][i] = (short) (i & 0xffff);
            lut[2][i] = (short) (i & 0xffff);
            break;
          case 5:
            // yellow
            lut[0][i] = (short) (i & 0xffff);
            lut[1][i] = (short) (i & 0xffff);
            break;
          default:
            // gray
            lut[0][i] = (short) (i & 0xffff);
            lut[1][i] = (short) (i & 0xffff);
            lut[2][i] = (short) (i & 0xffff);
        }
      }
      return lut;
    }
  
    /**
     * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
     */
    @Override
    public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
      throws FormatException, IOException
    {
      FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
  
      if (!isRGB()) {
        int[] pos = getZCTCoords(no);
        lastChannel = realChannel[getTileIndex(series)][pos[1]];
      }
  
      int index = getTileIndex(series);
      if (index >= offsets.size()) {
        // truncated file; imitate LAS AF and return black planes
        Arrays.fill(buf, (byte) 0);
        return buf;
      }
  
      long offset = offsets.get(index).longValue();
      int bytes = FormatTools.getBytesPerPixel(getPixelType());
      int bpp = bytes * getRGBChannelCount();
  
      long planeSize = (long) getSizeX() * getSizeY() * bpp;
      long nextOffset = index + 1 < offsets.size() ?
        offsets.get(index + 1).longValue() : endPointer;
      int bytesToSkip = (int) (nextOffset - offset - planeSize * getImageCount());
      bytesToSkip /= getSizeY();
      if ((getSizeX() % 4) == 0) bytesToSkip = 0;
  
      if (offset + (planeSize + bytesToSkip * getSizeY()) * no >= in.length()) {
        // truncated file; imitate LAS AF and return black planes
        Arrays.fill(buf, (byte) 0);
        return buf;
      }
  
      seekStartOfPlane(no, offset, planeSize);
  
      if (bytesToSkip == 0) {
        readPlane(in, x, y, w, h, buf);
      }
      else {
        in.skipBytes(bytesToSkip * getSizeY() * no);
        in.skipBytes(y * (getSizeX() * bpp + bytesToSkip));
        for (int row=0; row<h; row++) {
          in.skipBytes(x * bpp);
          in.read(buf, row * w * bpp, w * bpp);
          in.skipBytes(bpp * (getSizeX() - w - x) + bytesToSkip);
        }
      }
  
      // color planes are stored in BGR order
      if (getRGBChannelCount() == 3) {
        ImageTools.bgrToRgb(buf, isInterleaved(), bytes, getRGBChannelCount());
      }
  
      return buf;
    }
    
    private void seekStartOfPlane(int no, long dataOffset, long planeSize)
      throws IOException
    {
      int index = getTileIndex(series);
      long posInFile;
  
      int numberOfTiles = tileCount[index];
      if (numberOfTiles > 1) {
        // LAS AF treats tiles just like any other dimension, while we do not.
        // Hence we need to take the tiles into account for a frame's position.
        long bytesIncPerTile = tileBytesInc[index];
        long framesPerTile = bytesIncPerTile / planeSize;
  
        if (framesPerTile > Integer.MAX_VALUE) {
          throw new IOException("Could not read frame due to int overflow");
        }
  
        int noOutsideTiles = no / (int) framesPerTile;
        int noInsideTiles = no % (int) framesPerTile;
  
        int tile = series;
        for (int i = 0; i < index; i++) {
          tile -= tileCount[i];
        }
  
        posInFile = dataOffset;
        posInFile += noOutsideTiles * bytesIncPerTile * numberOfTiles;
        posInFile += tile * bytesIncPerTile;
        posInFile += noInsideTiles * planeSize;
      }
      else {
        posInFile = dataOffset + no * planeSize;
      }
      
      // seek instead of skipBytes to prevent dangerous int cast
      in.seek(posInFile);
    }
  
    /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */
    @Override
    public String[] getSeriesUsedFiles(boolean noPixels) {
      FormatTools.assertId(currentId, true, 1);
      final List<String> files = new ArrayList<String>();
      files.add(currentId);
  
      Location currentFile = new Location(currentId).getAbsoluteFile();
      Location parent = currentFile.getParentFile();
      if (parent != null && getSeries() < imageNames.length &&
        imageNames[getSeries()] != null)
      {
        // look for an XML file with the same name as this series
        Location xmlFile = new Location(parent, imageNames[getSeries()].trim() + ".xml");
        if (xmlFile.exists()) {
          files.add(xmlFile.getAbsolutePath());
        }
  
        Location logoFile = new Location(parent, LOGO_FILE);
        if (logoFile.exists()) {
          files.add(logoFile.getAbsolutePath());
        }
  
        Location stylesheetFile = new Location(parent, STYLESHEET_FILE);
        if (stylesheetFile.exists()) {
          files.add(stylesheetFile.getAbsolutePath());
        }
      }
  
      return files.toArray(new String[files.size()]);
    }
  
    /* @see loci.formats.IFormatReader#close(boolean) */
    @Override
    public void close(boolean fileOnly) throws IOException {
      super.close(fileOnly);
      if (!fileOnly) {
        offsets = null;
        realChannel = null;
        lastChannel = 0;
        lutNames.clear();
        physicalSizeXs.clear();
        physicalSizeYs.clear();
        descriptions = microscopeModels = serialNumber = null;
        pinholes = zooms = lensNA = null;
        zSteps = tSteps = null;
        expTimes = gains = null;
        detectorOffsets = null;
        channelNames = null;
        detectorModels = null;
        exWaves = null;
        activeDetector = null;
        immersions = corrections = null;
        magnification = null;
        objectiveModels = null;
        posX = posY = posZ = null;
        refractiveIndex = null;
        cutIns = cutOuts = filterModels = null;
        timestamps = null;
        laserWavelength = laserIntensity = laserActive = laserFrap = null;
        imageROIs = null;
        alternateCenter = false;
        imageNames = null;
        acquiredDate = null;
        detectorIndexes = null;
        tileCount = null;
        tileBytesInc = null;
        fieldPosX.clear();
        fieldPosY.clear();
        endPointer = 0;
      }
    }
  
    // -- Internal FormatReader API methods --
  
    /* @see loci.formats.FormatReader#initFile(String) */
    @Override
    protected void initFile(String id) throws FormatException, IOException {
      super.initFile(id);
      in = new RandomAccessInputStream(id);
      in.setEncoding(ENCODING);
      offsets = new ArrayList<Long>();
  
      in.order(true);
  
      // read the header
  
      LOGGER.info("Reading header");
  
      byte checkOne = in.readByte();
      in.skipBytes(2);
      byte checkTwo = in.readByte();
      if (checkOne != LIF_MAGIC_BYTE && checkTwo != LIF_MAGIC_BYTE) {
        throw new FormatException(id + " is not a valid Leica LIF file");
      }
  
      in.skipBytes(4);
  
      // read and parse the XML description
  
      if (in.read() != LIF_MEMORY_BYTE) {
        throw new FormatException("Invalid XML description");
      }
  
      // number of Unicode characters in the XML block
      int nc = in.readInt();
      String xml = DataTools.stripString(in.readString(nc * 2));
  
      LOGGER.info("Finding image offsets");
  
      while (in.getFilePointer() < in.length()) {
        LOGGER.debug("Looking for a block at {}; {} blocks read",
          in.getFilePointer(), offsets.size());
        int check = in.readInt();
        if (check != LIF_MAGIC_BYTE) {
          if (check == 0 && offsets.size() > 0) {
            // newer .lif file; the remainder of the file is all 0s
            endPointer = in.getFilePointer();
            break;
          }
          throw new FormatException("Invalid Memory Block: found magic bytes " +
            check + ", expected " + LIF_MAGIC_BYTE);
        }
  
        in.skipBytes(4);
        check = in.read();
        if (check != LIF_MEMORY_BYTE) {
          throw new FormatException("Invalid Memory Description: found magic " +
            "byte " + check + ", expected " + LIF_MEMORY_BYTE);
        }
  
        long blockLength = in.readInt();
        if (in.read() != LIF_MEMORY_BYTE) {
          in.seek(in.getFilePointer() - 5);
          blockLength = in.readLong();
          check = in.read();
          if (check != LIF_MEMORY_BYTE) {
            throw new FormatException("Invalid Memory Description: found magic " +
              "byte " + check + ", expected " + LIF_MEMORY_BYTE);
          }
        }
  
        int descrLength = in.readInt() * 2;
  
        if (blockLength > 0) {
          offsets.add(in.getFilePointer() + descrLength);
        }
  
        in.seek(in.getFilePointer() + descrLength + blockLength);
      }
      initMetadata(xml);
      xml = null;
  
      if (endPointer == 0) {
        endPointer = in.length();
      }
  
      // correct offsets, if necessary
      if (offsets.size() > getSeriesCount()) {
        Long[] storedOffsets = offsets.toArray(new Long[offsets.size()]);
        offsets.clear();
        int index = 0;
        for (int i=0; i<getSeriesCount(); i++) {
          setSeries(i);
          long nBytes = (long) FormatTools.getPlaneSize(this) * getImageCount();
          long start = storedOffsets[index];
          long end = index == storedOffsets.length - 1 ? in.length() :
            storedOffsets[index + 1];
          while (end - start < nBytes && ((end - start) / nBytes) != 1) {
            index++;
            start = storedOffsets[index];
            end = index == storedOffsets.length - 1 ? in.length() :
              storedOffsets[index + 1];
          }
          offsets.add(storedOffsets[index]);
          index++;
        }
        setSeries(0);
      }
    }
  
    // -- Helper methods --
  
    /** Parses a string of XML and puts the values in a Hashtable. */
    private void initMetadata(String xml) throws FormatException, IOException {
      try {
        ServiceFactory factory = new ServiceFactory();
        OMEXMLService service = factory.getInstance(OMEXMLService.class);
        service.createOMEXMLMetadata();
      }
      catch (DependencyException exc) {
        throw new FormatException("Could not create OME-XML store.", exc);
      }
      catch (ServiceException exc) {
        throw new FormatException("Could not create OME-XML store.", exc);
      }
      MetadataStore store = makeFilterMetadata();
  
      // the XML blocks stored in a LIF file are invalid,
      // because they don't have a root node
  
      xml = "<?xml version=\"1.0\" encoding=\""+ENCODING+"\"?><LEICA>" + xml +
        "</LEICA>";
  
      xml = XMLTools.sanitizeXML(xml);
      LOGGER.trace(xml);
  
      translateMetadata(getMetadataRoot(xml));
  
      for (int i=0; i<imageNames.length; i++) {
        setSeries(i);
        addSeriesMeta("Image name", imageNames[i]);
      }
      setSeries(0);
  
      // set up mapping to rearrange channels
      // for instance, the green channel may be #0, and the red channel may be #1
      realChannel = new int[tileCount.length][];
      int nextLut = 0;
  
      for (int i=0; i<core.size(); i++) {
        int index = getTileIndex(i);
        if (realChannel[index] != null) {
          continue;
        }
        CoreMetadata ms = core.get(i);
        realChannel[index] = new int[ms.sizeC];
  
        for (int q=0; q<ms.sizeC; q++) {
          String lut = "";
          if (nextLut < lutNames.size()) {
            lut = lutNames.get(nextLut++).toLowerCase();
          }
          if (!CHANNEL_PRIORITIES.containsKey(lut)) lut = "";
          realChannel[index][q] = CHANNEL_PRIORITIES.get(lut).intValue();
        }
  
        int[] sorted = new int[ms.sizeC];
        Arrays.fill(sorted, -1);
  
        for (int q=0; q<sorted.length; q++) {
          int min = Integer.MAX_VALUE;
          int minIndex = -1;
          for (int n=0; n<ms.sizeC; n++) {
            if (realChannel[index][n] < min &&
              !DataTools.containsValue(sorted, n))
            {
              min = realChannel[index][n];
              minIndex = n;
            }
          }
  
          sorted[q] = minIndex;
        }
      }
  
      MetadataTools.populatePixels(store, this, true, false);
  
      int roiCount = 0;
      for (int i=0; i<getSeriesCount(); i++) {
        setSeries(i);
  
        String instrumentID = MetadataTools.createLSID("Instrument", i);
        store.setInstrumentID(instrumentID, i);
  
        int index = getTileIndex(i);
  
        store.setMicroscopeModel(microscopeModels[index], i);
        store.setMicroscopeType(MetadataTools.getMicroscopeType("Other"), i);
  
        String objectiveID = MetadataTools.createLSID("Objective", i, 0);
        store.setObjectiveID(objectiveID, i, 0);
        store.setObjectiveLensNA(lensNA[index], i, 0);
        store.setObjectiveSerialNumber(serialNumber[index], i, 0);
        if (magnification[index] != null) {
          store.setObjectiveNominalMagnification(magnification[index], i, 0);
        }
        store.setObjectiveImmersion(MetadataTools.getImmersion(immersions[index]), i, 0);
        store.setObjectiveCorrection(MetadataTools.getCorrection(corrections[index]), i, 0);
        store.setObjectiveModel(objectiveModels[index], i, 0);
  
        if (cutIns[index] != null && filterModels[index] != null) {
          int channel = 0;
          if (cutIns[index].size() >= filterModels[index].size() * 2) {
            int diff = cutIns[index].size() - filterModels[index].size();
            for (int q=0; q<diff; q++) {
              cutIns[index].remove(filterModels[index].size());
            }
          }
          for (int filter=0; filter<cutIns[index].size(); filter++) {
            String filterID = MetadataTools.createLSID("Filter", i, filter);
            store.setFilterID(filterID, i, filter);
            if (filterModels[index] != null &&
              filter < filterModels[index].size())
            {
              store.setFilterModel(
                (String) filterModels[index].get(filter), i, filter);
            }
            store.setTransmittanceRangeCutIn(
              (Length) cutIns[index].get(filter), i, filter);
            store.setTransmittanceRangeCutOut(
              (Length) cutOuts[index].get(filter), i, filter);
          }
        }
  
        final List<Double> lasers = laserWavelength[index];
        final List<Double> laserIntensities = laserIntensity[index];
  
        final List<Boolean> active = laserActive[index];
        final List<Boolean> frap = laserFrap[index];
        int nextChannel = 0;
  
        if (lasers != null) {
          int laserIndex = 0;
          while (laserIndex < lasers.size()) {
            if ((Double) lasers.get(laserIndex) == 0) {
              lasers.remove(laserIndex);
            }
            else {
              laserIndex++;
            }
          }
  
          for (int laser=0; laser<lasers.size(); laser++) {
            String id = MetadataTools.createLSID("LightSource", i, laser);
            store.setLaserID(id, i, laser);
            store.setLaserType(LaserType.OTHER, i, laser);
            store.setLaserLaserMedium(LaserMedium.OTHER, i, laser);
            Double wavelength = (Double) lasers.get(laser);
            Length wave = FormatTools.getWavelength(wavelength);
            if (wave != null) {
              store.setLaserWavelength(wave, i, laser);
            }
          }
  
          Set<Integer> ignoredChannels = new HashSet<Integer>();
          final List<Integer> validIntensities = new ArrayList<Integer>();
          int size = lasers.size();
          int channel = 0;
          Set<Integer> channels = new HashSet<Integer>();
  
          for (int laser=0; laser<laserIntensities.size(); laser++) {
            double intensity = (Double) laserIntensities.get(laser);
            channel = laser/size;
            if (intensity < 100) {
              validIntensities.add(laser);
              channels.add(channel);
            }
            ignoredChannels.add(channel);
          }
          //remove channels w/o valid intensities
          ignoredChannels.removeAll(channels);
          //remove entries if channel has 2 wavelengths
          //e.g. 30% 458 70% 633
          int s = validIntensities.size();
  
          int jj;
          Set<Integer> toRemove = new HashSet<Integer>();
  
          int as = active.size();
          for (int j = 0; j < s; j++) {
            if (j < as && !(Boolean) active.get(j)) {
              toRemove.add(validIntensities.get(j));
            }
            jj = j+1;
            if (jj < s) {
              int v = validIntensities.get(j)/size;
              int vv = validIntensities.get(jj)/size;
              if (vv == v) {//do not consider that channel.
                toRemove.add(validIntensities.get(j));
                toRemove.add(validIntensities.get(jj));
                ignoredChannels.add(j);
              }
            }
          }
          if (toRemove.size() > 0) {
            validIntensities.removeAll(toRemove);
          }
  
          boolean noNames = true;
          if (channelNames[index] != null) {
            for (String name : channelNames[index]) {
              if (name != null && !name.equals("")) {
                noNames = false;
                break;
              }
            }
          }
          if (!noNames && frap != null) { //only use name for frap.
            for (int k = 0; k < frap.size(); k++) {
              if (!frap.get(k)) {
                noNames = true;
                break;
              }
            }
          }
  
          int nextFilter = 0;
          //int nextFilter = cutIns[i].size() - getEffectiveSizeC();
          for (int k=0; k<validIntensities.size(); k++, nextChannel++) {
            int laserArrayIndex = validIntensities.get(k);
            double intensity = (Double) laserIntensities.get(laserArrayIndex);
            int laser = laserArrayIndex % lasers.size();
            Double wavelength = (Double) lasers.get(laser);
            if (wavelength != 0) {
              while (ignoredChannels.contains(nextChannel)) {
                nextChannel++;
              }
              while (channelNames != null && nextChannel < getEffectiveSizeC() &&
                channelNames[index] != null &&
                ((channelNames[index][nextChannel] == null ||
                channelNames[index][nextChannel].equals("")) && !noNames))
              {
                nextChannel++;
              }
              if (nextChannel < getEffectiveSizeC()) {
                String id = MetadataTools.createLSID("LightSource", i, laser);
                store.setChannelLightSourceSettingsID(id, i, nextChannel);
                store.setChannelLightSourceSettingsAttenuation(
                  new PercentFraction((float) intensity / 100f), i, nextChannel);
  
                Length ex = FormatTools.getExcitationWavelength(wavelength);
                if (ex != null) {
                  store.setChannelExcitationWavelength(ex, i, nextChannel);
                }
  
                if (wavelength > 0) {
                  if (cutIns[index] == null || nextFilter >= cutIns[index].size())
                  {
                    continue;
                  }
                  Double cutIn =
                    ((Length) cutIns[index].get(nextFilter)).value(UNITS.NANOMETER).doubleValue();
                  while (cutIn - wavelength > 20) {
                    nextFilter++;
                    if (nextFilter < cutIns[index].size()) {
                      cutIn = ((Length)
                        cutIns[index].get(nextFilter)).value(UNITS.NANOMETER).doubleValue();
                    }
                    else {
                      break;
                    }
                  }
                  if (nextFilter < cutIns[index].size()) {
                    String fid =
                      MetadataTools.createLSID("Filter", i, nextFilter);
                    //store.setLightPathEmissionFilterRef(fid, i, nextChannel, 0);
                    nextFilter++;
                  }
                }
              }
            }
          }
        }
  
        store.setImageInstrumentRef(instrumentID, i);
        store.setObjectiveSettingsID(objectiveID, i);
        store.setObjectiveSettingsRefractiveIndex(refractiveIndex[index], i);
  
        store.setImageDescription(descriptions[index], i);
        if (acquiredDate[index] > 0) {
          store.setImageAcquisitionDate(new Timestamp(DateTools.convertDate(
            (long) (acquiredDate[index] * 1000), DateTools.COBOL,
            DateTools.ISO8601_FORMAT, false)), i);
        }
        store.setImageName(imageNames[index].trim(), i);
  
        Length sizeX =
          FormatTools.getPhysicalSizeX(physicalSizeXs.get(index));
        Length sizeY =
          FormatTools.getPhysicalSizeY(physicalSizeYs.get(index));
        Length sizeZ = FormatTools.getPhysicalSizeZ(zSteps[index]);
  
        if (sizeX != null) {
          store.setPixelsPhysicalSizeX(sizeX, i);
        }
        if (sizeY != null) {
          store.setPixelsPhysicalSizeY(sizeY, i);
        }
        if (sizeZ != null) {
          store.setPixelsPhysicalSizeZ(sizeZ, i);
        }
        if (tSteps[index] != null) {
          store.setPixelsTimeIncrement(new Time(tSteps[index], UNITS.SECOND), i);
        }
  
        final List<String> detectors = detectorModels[index];
        if (detectors != null) {
          nextChannel = 0;
          int start = detectors.size() - getEffectiveSizeC();
          if (start < 0) {
            start = 0;
          }
          for (int detector=start; detector<detectors.size(); detector++) {
            int dIndex = detector - start;
            String detectorID = MetadataTools.createLSID("Detector", i, dIndex);
            store.setDetectorID(detectorID, i, dIndex);
            store.setDetectorModel((String) detectors.get(detector), i, dIndex);
  
            store.setDetectorZoom(zooms[index], i, dIndex);
            store.setDetectorType(DetectorType.PMT, i, dIndex);
  
            if (activeDetector[index] != null) {
              int detectorIndex =
                activeDetector[index].size() - getEffectiveSizeC() + dIndex;
              if (detectorIndex >= 0 &&
                detectorIndex < activeDetector[index].size() &&
                (Boolean) activeDetector[index].get(detectorIndex) &&
                detectorOffsets[index] != null &&
                nextChannel < detectorOffsets[index].length)
              {
                store.setDetectorOffset(
                  detectorOffsets[index][nextChannel++], i, dIndex);
              }
            }
          }
        }
  
        final List<Boolean> activeDetectors = activeDetector[index];
        int firstDetector = activeDetectors == null ? 0 :
          activeDetectors.size() - getEffectiveSizeC();
        int nextDetector = firstDetector;
  
        int nextFilter = 0;
        int nextFilterDetector = 0;
  
        if (activeDetectors != null &&
          activeDetectors.size() > cutIns[index].size() &&
          (Boolean) activeDetectors.get(activeDetectors.size() - 1) &&
          (Boolean) activeDetectors.get(activeDetectors.size() - 2))
        {
          nextFilterDetector = activeDetectors.size() - cutIns[index].size();
  
          if (cutIns[index].size() > filterModels[index].size()) {
            nextFilterDetector += filterModels[index].size();
            nextFilter += filterModels[index].size();
          }
        }
  
        for (int c=0; c<getEffectiveSizeC(); c++) {
          if (activeDetectors != null) {
            while (nextDetector >= 0 && nextDetector < activeDetectors.size() &&
              !(Boolean) activeDetectors.get(nextDetector))
            {
              nextDetector++;
            }
            if (nextDetector < activeDetectors.size() && detectors != null &&
              nextDetector - firstDetector < detectors.size())
            {
              String detectorID = MetadataTools.createLSID(
                "Detector", i, nextDetector - firstDetector);
              store.setDetectorSettingsID(detectorID, i, c);
              nextDetector++;
  
              if (detectorOffsets[index] != null &&
                c < detectorOffsets[index].length)
              {
                store.setDetectorSettingsOffset(detectorOffsets[index][c], i, c);
              }
  
              if (gains[index] != null) {
                store.setDetectorSettingsGain(gains[index][c], i, c);
              }
            }
          }
  
          if (channelNames[index] != null) {
            store.setChannelName(channelNames[index][c], i, c);
          }
          if (pinholes[index] != null) {
            store.setChannelPinholeSize(new Length(pinholes[index], UNITS.MICROMETER), i, c);
          }
          if (exWaves[index] != null) {
            if (exWaves[index][c] != null && exWaves[index][c] > 1) {
              Length ex =
                FormatTools.getExcitationWavelength(exWaves[index][c]);
              if (ex != null) {
                store.setChannelExcitationWavelength(ex, i, c);
              }
            }
          }
  
          // channel coloring is implicit if the image is stored as RGB
          Color channelColor = getChannelColor(realChannel[index][c]);
          if (!isRGB()) {
            store.setChannelColor(channelColor, i, c);
          }
  
          if (channelColor.getValue() != -1 && nextFilter >= 0) {
            if (nextDetector - firstDetector != getSizeC() &&
              cutIns[index] != null && nextDetector >= cutIns[index].size())
            {
              while (nextFilterDetector < firstDetector) {
                String filterID =
                  MetadataTools.createLSID("Filter", i, nextFilter);
                store.setFilterID(filterID, i, nextFilter);
  
                nextFilterDetector++;
                nextFilter++;
              }
            }
            while (activeDetectors != null &&
              nextFilterDetector < activeDetectors.size() &&
              !(Boolean) activeDetectors.get(nextFilterDetector))
            {
              String filterID = MetadataTools.createLSID("Filter", i, nextFilter);
              store.setFilterID(filterID, i, nextFilter);
              nextFilterDetector++;
              nextFilter++;
            }
            String filterID = MetadataTools.createLSID("Filter", i, nextFilter);
            store.setFilterID(filterID, i, nextFilter);
            store.setLightPathEmissionFilterRef(filterID, i, c, 0);
            nextFilterDetector++;
            nextFilter++;
          }
        }
  
        for (int image=0; image<getImageCount(); image++) {
          Length xPos = posX[index];
          Length yPos = posY[index];
          if (i < fieldPosX.size() && fieldPosX.get(i) != null) {
            xPos = fieldPosX.get(i);
          }
          if (i < fieldPosY.size() && fieldPosY.get(i) != null) {
            yPos = fieldPosY.get(i);
          }
          if (xPos != null) {
            store.setPlanePositionX(xPos, i, image);
          }
          if (yPos != null) {
            store.setPlanePositionY(yPos, i, image);
          }
          store.setPlanePositionZ(posZ[index], i, image);
          if (timestamps[index] != null) {
            if (timestamps[index][image] != null) {
              double timestamp = timestamps[index][image];
              if (timestamps[index][0] == acquiredDate[index]) {
                timestamp -= acquiredDate[index];
              }
              else if (timestamp == acquiredDate[index] && image > 0) {
                timestamp = timestamps[index][0];
              }
              store.setPlaneDeltaT(new Time(timestamp, UNITS.SECOND), i, image);
            }
          }
  
          if (expTimes[index] != null) {
            int c = getZCTCoords(image)[1];
            if (expTimes[index][c] != null)
            {
              store.setPlaneExposureTime(new Time(expTimes[index][c], UNITS.SECOND), i, image);
            }
          }
        }
  
        if (imageROIs[index] != null) {
          for (int roi=0; roi<imageROIs[index].length; roi++) {
            if (imageROIs[index][roi] != null) {
              imageROIs[index][roi].storeROI(store, i, roiCount++, roi);
            }
          }
        }
      }
    }
  
    private Element getMetadataRoot(String xml)
      throws FormatException, IOException
    {
      ByteArrayInputStream s = null;
      try {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder parser = factory.newDocumentBuilder();
        s = new ByteArrayInputStream(xml.getBytes(ENCODING));
        return parser.parse(s).getDocumentElement();
      }
      catch (ParserConfigurationException e) {
        throw new FormatException(e);
      }
      catch (SAXException e) {
        throw new FormatException(e);
      } finally {
          if (s != null) s.close();
      }
    }
  
    private void translateMetadata(Element root) throws FormatException {
      Element realRoot = (Element) root.getChildNodes().item(0);
  
      NodeList toPrune = getNodes(realRoot, "LDM_Block_Sequential_Master");
      if (toPrune != null) {
        for (int i=0; i<toPrune.getLength(); i++) {
          Element prune = (Element) toPrune.item(i);
          Element parent = (Element) prune.getParentNode();
          parent.removeChild(prune);
        }
      }
  
      NodeList images = getNodes(realRoot, "Image");
      List<Element> imageNodes = new ArrayList<Element>();
      Long[] oldOffsets = null;
      if (images.getLength() > offsets.size()) {
        oldOffsets = offsets.toArray(new Long[offsets.size()]);
        offsets.clear();
      }
  
      int nextOffset = 0;
      for (int i=0; i<images.getLength(); i++) {
        Element image = (Element) images.item(i);
        Element grandparent = (Element) image.getParentNode();
        if (grandparent == null) {
          continue;
        }
        grandparent = (Element) grandparent.getParentNode();
        if (grandparent == null) {
          continue;
        }
        if (!"ProcessingHistory".equals(grandparent.getNodeName())) {
          // image is being referenced from an event list
          imageNodes.add(image);
          if (oldOffsets != null && nextOffset < oldOffsets.length) {
            offsets.add(oldOffsets[nextOffset]);
          }
        }
        grandparent = (Element) grandparent.getParentNode();
        if (grandparent == null) {
          continue;
        }
        grandparent = (Element) grandparent.getParentNode();
        if (grandparent != null) {
          if (!"Image".equals(grandparent.getNodeName())) {
            nextOffset++;
          }
        }
      }
  
      tileCount = new int[imageNodes.size()];
      Arrays.fill(tileCount, 1);
      tileBytesInc = new long[imageNodes.size()];
      core = new ArrayList<CoreMetadata>(imageNodes.size());
      acquiredDate = new double[imageNodes.size()];
      descriptions = new String[imageNodes.size()];
      laserWavelength = new List[imageNodes.size()];
      laserIntensity = new List[imageNodes.size()];
      laserActive = new List[imageNodes.size()];
      laserFrap = new List[imageNodes.size()];
      timestamps = new Double[imageNodes.size()][];
      activeDetector = new List[imageNodes.size()];
      serialNumber = new String[imageNodes.size()];
      lensNA = new Double[imageNodes.size()];
      magnification = new Double[imageNodes.size()];
      immersions = new String[imageNodes.size()];
      corrections = new String[imageNodes.size()];
      objectiveModels = new String[imageNodes.size()];
      posX = new Length[imageNodes.size()];
      posY = new Length[imageNodes.size()];
      posZ = new Length[imageNodes.size()];
      refractiveIndex = new Double[imageNodes.size()];
      cutIns = new List[imageNodes.size()];
      cutOuts = new List[imageNodes.size()];
      filterModels = new List[imageNodes.size()];
      microscopeModels = new String[imageNodes.size()];
      detectorModels = new List[imageNodes.size()];
      detectorIndexes = new HashMap[imageNodes.size()];
      zSteps = new Double[imageNodes.size()];
      tSteps = new Double[imageNodes.size()];
      pinholes = new Double[imageNodes.size()];
      zooms = new Double[imageNodes.size()];
  
      expTimes = new Double[imageNodes.size()][];
      gains = new Double[imageNodes.size()][];
      detectorOffsets = new Double[imageNodes.size()][];
      channelNames = new String[imageNodes.size()][];
      exWaves = new Double[imageNodes.size()][];
      imageROIs = new ROI[imageNodes.size()][];
      imageNames = new String[imageNodes.size()];
  
      core.clear();
      for (int i=0; i<imageNodes.size(); i++) {
        Element image = imageNodes.get(i);
  
        CoreMetadata ms = new CoreMetadata();
        core.add(ms);
  
        int index = core.size() - 1;
        setSeries(index);
  
        translateImageNames(image, index);
        translateImageNodes(image, index);
        translateAttachmentNodes(image, index);
        translateScannerSettings(image, index);
        translateFilterSettings(image, index);
        translateTimestamps(image, index);
        translateLaserLines(image, index);
        translateROIs(image, index);
        translateSingleROIs(image, index);
        translateDetectors(image, index);
  
        final Deque<String> nameStack = new ArrayDeque<String>();
        populateOriginalMetadata(image, nameStack);
        addUserCommentMeta(image, i);
      }
      setSeries(0);
  
      int totalSeries = 0;
      for (int count : tileCount) {
        totalSeries += count;
      }
      ArrayList<CoreMetadata> newCore = new ArrayList<CoreMetadata>();
      for (int i=0; i<core.size(); i++) {
        for (int tile=0; tile<tileCount[i]; tile++) {
          newCore.add(core.get(i));
        }
      }
      core = newCore;
    }
  
    private void populateOriginalMetadata(Element root, Deque<String> nameStack) {
      String name = root.getNodeName();
      if (root.hasAttributes() && !name.equals("Element") &&
        !name.equals("Attachment") && !name.equals("LMSDataContainerHeader"))
      {
        nameStack.push(name);
  
        String suffix = root.getAttribute("Identifier");
        String value = root.getAttribute("Variant");
        if (suffix == null || suffix.trim().length() == 0) {
          suffix = root.getAttribute("Description");
        }
        final StringBuilder key = new StringBuilder();
        final Iterator<String> nameStackIterator = nameStack.descendingIterator();
        while (nameStackIterator.hasNext()) {
          final String k = nameStackIterator.next();
          key.append(k);
          key.append("|");
        }
        if (suffix != null && value != null && suffix.length() > 0 &&
          value.length() > 0 && !suffix.equals("HighInteger") &&
          !suffix.equals("LowInteger"))
        {
          addSeriesMetaList(key.toString() + suffix, value);
        }
        else {
          NamedNodeMap attributes = root.getAttributes();
          for (int i=0; i<attributes.getLength(); i++) {
            Attr attr = (Attr) attributes.item(i);
            if (!attr.getName().equals("HighInteger") &&
              !attr.getName().equals("LowInteger"))
            {
              addSeriesMeta(key.toString() + attr.getName(), attr.getValue());
            }
          }
        }
      }
  
      NodeList children = root.getChildNodes();
      for (int i=0; i<children.getLength(); i++) {
        Object child = children.item(i);
        if (child instanceof Element) {
          populateOriginalMetadata((Element) child, nameStack);
        }
      }
  
      if (root.hasAttributes() && !name.equals("Element") &&
        !name.equals("Attachment") && !name.equals("LMSDataContainerHeader"))
      {
        nameStack.pop();
      }
    }
  
    private void translateImageNames(Element imageNode, int image) {
      final List<String> names = new ArrayList<String>();
      Element parent = imageNode;
      while (true) {
        parent = (Element) parent.getParentNode();
        if (parent == null || parent.getNodeName().equals("LEICA")) {
          break;
        }
        if (parent.getNodeName().equals("Element")) {
          names.add(parent.getAttribute("Name"));
        }
      }
      imageNames[image] = "";
      for (int i=names.size() - 2; i>=0; i--) {
        imageNames[image] += names.get(i);
        if (i > 0) imageNames[image] += '/';
      }
    }
  
    private void translateDetectors(Element imageNode, int image)
      throws FormatException
    {
      NodeList definitions = getNodes(imageNode, "ATLConfocalSettingDefinition");
      if (definitions == null) return;
  
      final List<String> channels = new ArrayList<String>();
      laserActive[image] = new ArrayList<Boolean>();
      int nextChannel = 0;
      for (int definition=0; definition<definitions.getLength(); definition++) {
        Element definitionNode = (Element) definitions.item(definition);
        String parentName = definitionNode.getParentNode().getNodeName();
        boolean isMaster = parentName.endsWith("Master");
        NodeList detectors = getNodes(definitionNode, "Detector");
        if (detectors == null) return;
        int count = 0;
        for (int d=0; d<detectors.getLength(); d++) {
          Element detector = (Element) detectors.item(d);
          NodeList multibands = null;
          if (!isMaster) {
            multibands = getNodes(definitionNode, "MultiBand");
          }
  
          String v = detector.getAttribute("Gain");
          Double gain =
            v == null || v.trim().isEmpty() ? null : DataTools.parseDouble(v.trim());
          v = detector.getAttribute("Offset");
          Double offset =
            v == null || v.trim().isEmpty() ? null : DataTools.parseDouble(v.trim());
  
          boolean active = "1".equals(detector.getAttribute("IsActive"));
          String c = detector.getAttribute("Channel");
          int channel = (c == null || c.trim().length() == 0) ? 0 : Integer.parseInt(c);
          if (active) {
            if (detectorIndexes[image] != null && detectorModels[image] != null) {
              detectorModels[image].add(detectorIndexes[image].get(channel));
            }
  
            Element multiband = null;
  
            if (multibands != null) {
              for (int i=0; i<multibands.getLength(); i++) {
                Element mb = (Element) multibands.item(i);
                if (channel == Integer.parseInt(mb.getAttribute("Channel"))) {
                  multiband = mb;
                  break;
                }
              }
            }
  
            if (multiband != null) {
              String dye = multiband.getAttribute("DyeName");
              if (!channels.contains(dye)) {
                channels.add(dye);
              }
  
              Double cutIn = DataTools.parseDouble(multiband.getAttribute("LeftWorld"));
              Double cutOut = DataTools.parseDouble(multiband.getAttribute("RightWorld"));
              if (cutIn != null && cutIn.intValue() > 0) {
                if (cutIns[image] == null) {
                  cutIns[image] = new ArrayList<PositiveFloat>();
                }
                Length in =
                  FormatTools.getCutIn((double) Math.round(cutIn));
                if (in != null) {
                  cutIns[image].add(in);
                }
              }
              if (cutOut != null && cutOut.intValue() > 0) {
                if (cutOuts[image] == null) {
                  cutOuts[image] = new ArrayList<PositiveFloat>();
                }
                Length out =
                  FormatTools.getCutOut((double) Math.round(cutOut));
                if (out != null) {
                  cutOuts[image].add(out);
                }
              }
            }
            else {
              channels.add("");
            }
  
            
            if (!isMaster) {
              if (channel < nextChannel) {
                nextChannel = 0;
              }
  
              if (nextChannel < getEffectiveSizeC()) {
                if (gains[image] != null) {
                  gains[image][nextChannel] = gain;
                }
                if (detectorOffsets[image] != null) {
                  detectorOffsets[image][nextChannel] = offset;
                }
              }
  
              nextChannel++;
            }
          } else {
            count++;
          }
          if (active && activeDetector[image] != null) {
            activeDetector[image].add(active);
          }
        }
        //Store values to check if actually it is active.
        if (!isMaster) {
          laserActive[image].add(count < detectors.getLength());
        }
      }
  
      if (channels != null && channelNames[image] != null) {
        for (int i=0; i<getEffectiveSizeC(); i++) {
          int index = i + channels.size() - getEffectiveSizeC();
          if (index >= 0 && index < channels.size()) {
            if (channelNames[image][i] == null ||
              channelNames[image][i].trim().isEmpty())
            {
              channelNames[image][i] = channels.get(index);
            }
          }
        }
      }
    }
  
    private void translateROIs(Element imageNode, int image)
      throws FormatException
    {
      NodeList rois = getNodes(imageNode, "Annotation");
      if (rois == null) return;
      imageROIs[image] = new ROI[rois.getLength()];
  
      for (int r=0; r<rois.getLength(); r++) {
        Element roiNode = (Element) rois.item(r);
  
        ROI roi = new ROI();
  
        String type = roiNode.getAttribute("type");
        if (type != null && !type.trim().isEmpty()) {
          roi.type = Integer.parseInt(type.trim());
        }
        String color = roiNode.getAttribute("color");
        if (color != null && !color.trim().isEmpty()) {
          roi.color = Long.parseLong(color.trim());
        }
        roi.name = roiNode.getAttribute("name");
        roi.fontName = roiNode.getAttribute("fontName");
        roi.fontSize = roiNode.getAttribute("fontSize");
  
        Double transX = DataTools.parseDouble(roiNode.getAttribute("transTransX"));
        if (transX != null) {
          roi.transX = transX;
        }
        Double transY = DataTools.parseDouble(roiNode.getAttribute("transTransY"));
        if (transY != null) {
          roi.transY = transY;
        }
        transX = DataTools.parseDouble(roiNode.getAttribute("transScalingX"));
        if (transX != null) {
          roi.scaleX = transX;
        }
        transY = DataTools.parseDouble(roiNode.getAttribute("transScalingY"));
        if (transY != null) {
          roi.scaleY = transY;
        }
        Double rotation = DataTools.parseDouble(roiNode.getAttribute("transRotation"));
        if (rotation != null) {
          roi.rotation = rotation;
        }
        String linewidth = roiNode.getAttribute("linewidth");
        try {
          if (linewidth != null && !linewidth.trim().isEmpty()) {
            roi.linewidth = Integer.parseInt(linewidth.trim());
          }
        }
        catch (NumberFormatException e) { }
  
        roi.text = roiNode.getAttribute("text");
  
        NodeList vertices = getNodes(roiNode, "Vertex");
        if (vertices == null) {
          continue;
        }
  
        for (int v=0; v<vertices.getLength(); v++) {
          Element vertex = (Element) vertices.item(v);
          String xx = vertex.getAttribute("x");
          String yy = vertex.getAttribute("y");
  
          if (xx != null && !xx.trim().isEmpty()) {
            roi.x.add(DataTools.parseDouble(xx.trim()));
          }
          if (yy != null && !yy.trim().isEmpty()) {
            roi.y.add(DataTools.parseDouble(yy.trim()));
          }
        }
        imageROIs[image][r] = roi;
  
        if (getNodes(imageNode, "ROI") != null) {
          alternateCenter = true;
        }
      }
    }
  
    private void translateSingleROIs(Element imageNode, int image)
      throws FormatException
    {
      if (imageROIs[image] != null) return;
      NodeList children = getNodes(imageNode, "ROI");
      if (children == null) return;
      children = getNodes((Element) children.item(0), "Children");
      if (children == null) return;
      children = getNodes((Element) children.item(0), "Element");
      if (children == null) return;
      imageROIs[image] = new ROI[children.getLength()];
  
      for (int r=0; r<children.getLength(); r++) {
        NodeList rois = getNodes((Element) children.item(r), "ROISingle");
  
        Element roiNode = (Element) rois.item(0);
        ROI roi = new ROI();
  
        String type = roiNode.getAttribute("RoiType");
        if (type != null && !type.trim().isEmpty()) {
          roi.type = Integer.parseInt(type.trim());
        }
        String color = roiNode.getAttribute("Color");
        if (color != null && !color.trim().isEmpty()) {
          roi.color = Long.parseLong(color.trim());
        }
        Element parent = (Element) roiNode.getParentNode();
        parent = (Element) parent.getParentNode();
        roi.name = parent.getAttribute("Name");
  
        NodeList vertices = getNodes(roiNode, "P");
  
        double sizeX = physicalSizeXs.get(image);
        double sizeY = physicalSizeYs.get(image);
  
        for (int v=0; v<vertices.getLength(); v++) {
          Element vertex = (Element) vertices.item(v);
          String xx = vertex.getAttribute("X");
          String yy = vertex.getAttribute("Y");
  
          if (xx != null && !xx.trim().isEmpty()) {
            Double x = DataTools.parseDouble(xx.trim());
            if (x != null) {
              roi.x.add(x / sizeX);
            }
          }
          if (yy != null && !yy.trim().isEmpty()) {
            Double y = DataTools.parseDouble(yy.trim());
            if (y != null) {
              roi.y.add(y / sizeY);
            }
          }
        }
  
        Element transform = (Element) getNodes(roiNode, "Transformation").item(0);
  
        Double rotation = DataTools.parseDouble(transform.getAttribute("Rotation"));
        if (rotation != null) {
          roi.rotation = rotation;
        }
  
        Element scaling = (Element) getNodes(transform, "Scaling").item(0);
        Double scaleX = DataTools.parseDouble(scaling.getAttribute("XScale"));
        Double scaleY = DataTools.parseDouble(scaling.getAttribute("YScale"));
        if (scaleX != null) {
          roi.scaleX = scaleX;
        }
        if (scaleY != null) {
          roi.scaleY = scaleY;
        }
  
        Element translation =
          (Element) getNodes(transform, "Translation").item(0);
        Double transX = DataTools.parseDouble(translation.getAttribute("X"));
        Double transY = DataTools.parseDouble(translation.getAttribute("Y"));
        if (transX != null) {
          roi.transX = transX / sizeX;
        }
        if (transY != null) {
          roi.transY = transY / sizeY;
        }
  
        imageROIs[image][r] = roi;
      }
    }
  
    private void translateLaserLines(Element imageNode, int image)
      throws FormatException
    {
      NodeList aotfLists = getNodes(imageNode, "AotfList");
      if (aotfLists == null || aotfLists.getLength() == 0) return;
  
      laserWavelength[image] = new ArrayList<Double>();
      laserIntensity[image] = new ArrayList<Double>();
      laserFrap[image] = new ArrayList<Boolean>();
  
      int baseIntensityIndex = 0;
  
      for (int channel=0; channel<aotfLists.getLength(); channel++) {
        Element aotf = (Element) aotfLists.item(channel);
        NodeList laserLines = getNodes(aotf, "LaserLineSetting");
        if (laserLines == null) return;
        String gpName = aotf.getParentNode().getParentNode().getNodeName();
        //might need parent for attachment
        boolean isMaster = gpName.endsWith("Sequential_Master") ||
                gpName.endsWith("Attachment");
        laserFrap[image].add(gpName.endsWith("FRAP_Master"));
        for (int laser=0; laser<laserLines.getLength(); laser++) {
          Element laserLine = (Element) laserLines.item(laser);
          
          if (isMaster) {
            continue;
          }
          String lineIndex = laserLine.getAttribute("LineIndex");
          String qual = laserLine.getAttribute("Qualifier");
          int index =
            lineIndex == null || lineIndex.trim().isEmpty() ? 0 :
            Integer.parseInt(lineIndex.trim());
          int qualifier =
            qual == null || qual.trim().isEmpty() ? 0:
            Integer.parseInt(qual.trim());
  
          index += (2 - (qualifier / 10));
          if (index < 0) {
              continue;
              //index = 0;
          }
  
          String v = laserLine.getAttribute("LaserLine");
          Double wavelength = 0d;
          if (v != null && !v.trim().isEmpty()) {
              wavelength = DataTools.parseDouble(v.trim());
          }
          if (index < laserWavelength[image].size()) {
            laserWavelength[image].set(index, wavelength);
          }
          else {
            for (int i=laserWavelength[image].size(); i<index; i++) {
              laserWavelength[image].add(Double.valueOf(0));
            }
            laserWavelength[image].add(wavelength);
          }
  
          String intensity = laserLine.getAttribute("IntensityDev");
          Double realIntensity = 0d;
          if (intensity != null && !intensity.trim().isEmpty()) {
            realIntensity = DataTools.parseDouble(intensity.trim());
            if (realIntensity == null) {
              realIntensity = 0d;
            }
          }
          realIntensity = 100d - realIntensity;
  
          int realIndex = baseIntensityIndex + index;
  
          if (realIndex < laserIntensity[image].size()) {
            laserIntensity[image].set(realIndex, realIntensity);
          }
          else {
            while (realIndex < laserIntensity[image].size()) {
              laserIntensity[image].add(100d);
            }
            laserIntensity[image].add(realIntensity);
          }
        }
  
        baseIntensityIndex += laserWavelength[image].size();
      }
    }
  
    private void translateTimestamps(Element imageNode, int image)
      throws FormatException
    {
      NodeList timeStampLists = getNodes(imageNode, "TimeStampList");
      if (timeStampLists == null) return;
  
      Element timeStampList = (Element)timeStampLists.item(0);
      timestamps[image] = new Double[getImageCount()];
      
      // probe if timestamps are saved in the format of LAS AF 3.1 or newer
      String numberOfTimeStamps = timeStampList.getAttribute("NumberOfTimeStamps");
      if (numberOfTimeStamps != null && !numberOfTimeStamps.isEmpty()) {
        // LAS AF 3.1 (or newer) timestamps are available
        String timeStampsRaw = timeStampList.getTextContent();
        List<String> timeStamps = Arrays.asList(timeStampsRaw.split(" "));
        for (int stamp = 0; stamp < timeStamps.size(); stamp++) {
          if (stamp < getImageCount()) {
            String timestamp = timeStamps.get(stamp);
            timestamps[image][stamp] = translateSingleTimestamp(timestamp);
          }
        }
      }
      else {
        // probe if timestamps are saved in the format of LAS AF 3.0 or older
        NodeList timestampNodes = getNodes(imageNode, "TimeStamp");
        if (timestampNodes != null) {
          // LAS AF 3.0 (or older) timestamps are available
          for (int stamp = 0; stamp < timestampNodes.getLength(); stamp++) {
            if (stamp < getImageCount()) {
              Element timestamp = (Element) timestampNodes.item(stamp);
              timestamps[image][stamp] = translateSingleTimestamp(timestamp);
            }
          }
        }
        else {
          return;
        }
      }
      
      acquiredDate[image] = timestamps[image][0];
    }
  
    private double translateSingleTimestamp(String timestamp) {
      timestamp = timestamp.trim();
      int stampLowStart = Math.max(0, timestamp.length() - 8);
      int stampHighEnd = Math.max(0, stampLowStart);
      String stampHigh = timestamp.substring(0, stampHighEnd);
      String stampLow = timestamp.substring(stampLowStart, timestamp.length());
      long high
              = stampHigh == null || stampHigh.trim().isEmpty() ? 0
                      : Long.parseLong(stampHigh.trim(), 16);
      long low
              = stampLow == null || stampLow.trim().isEmpty() ? 0
                      : Long.parseLong(stampLow.trim(), 16);
      long milliseconds = DateTools.getMillisFromTicks(high, low);
      double seconds = (double)milliseconds / 1000;
      return seconds;
    }
    
    private double translateSingleTimestamp(Element timestamp) {
      String stampHigh = timestamp.getAttribute("HighInteger");
      String stampLow = timestamp.getAttribute("LowInteger");
      long high
              = stampHigh == null || stampHigh.trim().isEmpty() ? 0
                      : Long.parseLong(stampHigh.trim());
      long low
              = stampLow == null || stampLow.trim().isEmpty() ? 0
                      : Long.parseLong(stampLow.trim());
  
      long milliseconds = DateTools.getMillisFromTicks(high, low);
      double seconds = (double)milliseconds / 1000;
      return seconds;
    }
  
    private void translateFilterSettings(Element imageNode, int image)
      throws FormatException
    {
      NodeList filterSettings = getNodes(imageNode, "FilterSettingRecord");
      if (filterSettings == null) return;
  
      activeDetector[image] = new ArrayList<Boolean>();
      cutIns[image] = new ArrayList<PositiveFloat>();
      cutOuts[image] = new ArrayList<PositiveFloat>();
      filterModels[image] = new ArrayList<String>();
      detectorIndexes[image] = new HashMap<Integer, String>();
  
      int nextChannel = 0;
  
      for (int i=0; i<filterSettings.getLength(); i++) {
        Element filterSetting = (Element) filterSettings.item(i);
  
        String object = filterSetting.getAttribute("ObjectName");
        String attribute = filterSetting.getAttribute("Attribute");
        String objectClass = filterSetting.getAttribute("ClassName");
        String variant = filterSetting.getAttribute("Variant");
        String data = filterSetting.getAttribute("Data");
  
        if (attribute.equals("NumericalAperture")) {
          if (variant != null && !variant.trim().isEmpty()) {
            lensNA[image] = DataTools.parseDouble(variant.trim());
          }
        }
        else if (attribute.equals("OrderNumber")) {
          if (variant != null && !variant.trim().isEmpty()) {
            serialNumber[image] = variant.trim();
          }
        }
        else if (objectClass.equals("CDetectionUnit")) {
          if (attribute.equals("State")) {
            int channel = getChannelIndex(filterSetting);
            if (channel < 0) continue;
  
            detectorIndexes[image].put(new Integer(data), object);
            activeDetector[image].add("Active".equals(variant.trim()));
          }
        }
        else if (attribute.equals("Objective")) {
          StringTokenizer tokens = new StringTokenizer(variant, " ");
          boolean foundMag = false;
          final StringBuilder model = new StringBuilder();
          while (!foundMag) {
            String token = tokens.nextToken();
            int x = token.indexOf('x');
            if (x != -1) {
              foundMag = true;
  
              String na = token.substring(x + 1);
  
              if (na != null && !na.trim().isEmpty()) {
                lensNA[image] = DataTools.parseDouble(na.trim());
              }
              na = token.substring(0, x);
              if (na != null && !na.trim().isEmpty()) {
                magnification[image] = DataTools.parseDouble(na.trim());
              }
            }
            else {
              model.append(token);
              model.append(" ");
            }
          }
  
          String immersion = "Other";
          if (tokens.hasMoreTokens()) {
            immersion = tokens.nextToken();
            if (immersion == null || immersion.trim().isEmpty()) {
              immersion = "Other";
            }
          }
          immersions[image] = immersion;
  
          String correction = "Other";
          if (tokens.hasMoreTokens()) {
            correction = tokens.nextToken();
  