gt:     MetadataStore store = makeFilterMetadata();
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.IOException;
  import java.util.ArrayList;
  import java.util.List;
  
  import loci.common.ByteArrayHandle;
  import loci.common.Location;
  import loci.common.RandomAccessInputStream;
  import loci.formats.ChannelSeparator;
  import loci.formats.CoreMetadata;
  import loci.formats.FormatException;
  import loci.formats.FormatReader;
  import loci.formats.FormatTools;
  import loci.formats.IFormatReader;
  import loci.formats.MetadataTools;
  import loci.formats.codec.CodecOptions;
  import loci.formats.codec.LZOCodec;
  import loci.formats.meta.MetadataStore;
  
  import ome.units.UNITS;
  import ome.units.quantity.Length;
  
  /**
   * OpenlabReader is the file format reader for Openlab LIFF files.
   *
   * @author Melissa Linkert melissa at glencoesoftware.com
   * @author Eric Kjellman egkjellman at wisc.edu
   * @author Curtis Rueden ctrueden at wisc.edu
   * @author Jim Paris jim at jtan.com
   */
  public class OpenlabReader extends FormatReader {
  
    // -- Constants --
  
    public static final long LIFF_MAGIC_BYTES = 0xffff696d7072L;
  
    /** Image types. */
    private static final int MAC_1_BIT = 1;
    private static final int MAC_4_GREYS = 2;
    private static final int MAC_16_GREYS = 3;
    private static final int MAC_16_COLORS = 4;
    private static final int MAC_256_GREYS = 5;
    private static final int MAC_256_COLORS = 6;
    private static final int MAC_16_BIT_COLOR = 7;
    private static final int MAC_24_BIT_COLOR = 8;
    private static final int DEEP_GREY_9 = 9;
    private static final int DEEP_GREY_10 = 10;
    private static final int DEEP_GREY_11 = 11;
    private static final int DEEP_GREY_12 = 12;
    private static final int DEEP_GREY_13 = 13;
    private static final int DEEP_GREY_14 = 14;
    private static final int DEEP_GREY_15 = 15;
    private static final int DEEP_GREY_16 = 16;
  
    /** Tag types. */
    private static final int IMAGE_TYPE_1 = 67;
    private static final int IMAGE_TYPE_2 = 68;
    private static final int CALIBRATION = 69;
    private static final int USER = 72;
  
    // -- Static fields --
  
    /** Helper reader to read PICT data. */
    private PictReader pict = new PictReader();
  
    // -- Fields --
  
    /** LIFF version (should be 2 or 5). */
    private int version;
  
    private PlaneInfo[] planes;
    private float xcal, ycal;
  
    private long nextTag = 0;
    private int tag = 0, subTag = 0;
    private String fmt = "";
    private int[][] planeOffsets;
  
    private List<byte[][]> luts;
    private int lastPlane = 0;
  
    private String gain, detectorOffset, xPos, yPos, zPos;
    private boolean specialPlateNames = false;
  
    // -- Constructor --
  
    /** Constructs a new OpenlabReader. */
    public OpenlabReader() {
      super("Openlab LIFF", "liff");
      suffixNecessary = false;
      domains = new String[] {FormatTools.UNKNOWN_DOMAIN};
    }
  
    // -- 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 = 8;
      if (!FormatTools.validStream(stream, blockLen, false)) return false;
      return stream.readLong() == LIFF_MAGIC_BYTES;
    }
  
    /* @see loci.formats.IFormatReader#get8BitLookupTable() */
    @Override
    public byte[][] get8BitLookupTable() {
      if (luts != null) {
        if (getSeries() < planeOffsets.length &&
          lastPlane < planeOffsets[getSeries()].length)
        {
          return luts.get(planeOffsets[getSeries()][lastPlane]);
        }
        else if (lastPlane < luts.size()) {
          return luts.get(lastPlane);
        }
      }
      return null;
    }
  
    /**
     * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
     */
    @Override
    public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
      throws FormatException, IOException
    {
      FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
  
      lastPlane = no;
  
      PlaneInfo planeInfo = null;
      if (specialPlateNames) {
        planeInfo = getPlane(getZCTCoords(no));
      }
      else if (no < planeOffsets[getSeries()].length) {
        int index = planeOffsets[getSeries()][no];
        planeInfo = planes[index];
      }
      if (planeInfo == null) return buf;
  
      long first = planeInfo.planeOffset;
      long last = first + FormatTools.getPlaneSize(this) * 2;
  
      int bpp = FormatTools.getBytesPerPixel(getPixelType());
  
      if (!planeInfo.pict) {
        if (version == 2) {
          in.seek(first);
          readPlane(in, x, y, w, h, buf);
        }
        else {
          in.seek(first + 16);
          int bytes = bpp * getRGBChannelCount();
          byte[] b = new byte[(int) (last - first)];
          in.read(b);
  
          CodecOptions options = new CodecOptions();
          options.width = getSizeX();
          options.height = getSizeY();
          options.bitsPerSample = bytes * 8;
          options.maxBytes = getSizeX() * getSizeY() * bytes;
          b = new LZOCodec().decompress(b, options);
  
          if (getSizeX() * getSizeY() * 4 <= b.length) {
            for (int yy=y; yy<h + y; yy++) {
              for (int xx=x; xx<w + x; xx++) {
                System.arraycopy(b, (yy*(getSizeX()+4) + xx)*4 + 1, buf,
                  ((yy - y)*w + xx - x)*3, 3);
              }
            }
          }
          else {
            int src = b.length / getSizeY();
            if (src - (getSizeX() * bytes) != 16) src = getSizeX() * bytes;
            int dest = w * bytes;
            for (int row=0; row<h; row++) {
              System.arraycopy(b, (row + y)*src + x*bytes, buf, row*dest, dest);
            }
          }
          b = null;
        }
        if (planeInfo.volumeType == MAC_256_GREYS ||
          planeInfo.volumeType == MAC_256_COLORS)
        {
          for (int i=0; i<buf.length; i++) {
            buf[i] = (byte) (~buf[i] & 0xff);
          }
        }
      }
      else {
        // PICT plane
        Exception exc = null;
        if (getPixelType() == FormatTools.UINT8) {
          in.seek(first);
          byte[] b = new byte[(int) (last - first) + 512];
          in.read(b, 512, b.length - 512);
  
          IFormatReader r =
            getRGBChannelCount() == 1 ? new ChannelSeparator(pict) : pict;
          try {
            Location.mapFile("OPENLAB_PICT", new ByteArrayHandle(b));
            r.setId("OPENLAB_PICT");
  
            if (getPixelType() != r.getPixelType()) {
              throw new FormatException("Pixel type of inner PICT does not " +
                "match pixel type of Openlab file");
            }
  
            if (isIndexed()) {
              int index = no;
              if (getSeries() < planeOffsets.length) {
                index = planeOffsets[getSeries()][lastPlane];
              }
              luts.set(index, pict.get8BitLookupTable());
            }
  
            r.openBytes(0, buf, x, y, w, h);
          }
          catch (FormatException e) { exc = e; }
          catch (IOException e) { exc = e; }
          finally {
            r.close();
            // remove file from map
            Location.mapFile("OPENLAB_PICT", null);
          }
          b = null;
        }
        if (exc != null || getPixelType() != FormatTools.UINT8) {
          LOGGER.debug("", exc);
          in.seek(planeInfo.planeOffset - 298);
  
          if (in.readByte() == 1) in.skipBytes(297);
          else in.skipBytes(169);
  
          int size = 0, expectedBlock = 0, totalBlocks = -1, pixPos = 0;
  
          byte[] plane = new byte[FormatTools.getPlaneSize(this)];
  
          while (expectedBlock != totalBlocks && pixPos < plane.length &&
            in.getFilePointer() + 32 < last)
          {
            findNextBlock();
  
            if (in.getFilePointer() + 4 >= in.length()) break;
  
            int num = in.readInt();
            if (num != expectedBlock) {
              throw new FormatException("Expected iPic block not found");
            }
  
            expectedBlock++;
            if (totalBlocks == -1) {
              totalBlocks = in.readInt();
              in.skipBytes(8);
            }
            else in.skipBytes(12);
  
            size = in.readInt();
            in.skipBytes(4);
  
            if (size + pixPos > plane.length) size = plane.length - pixPos;
  
            in.read(plane, pixPos, size);
            pixPos += size;
          }
  
          try (RandomAccessInputStream pix = new RandomAccessInputStream(plane)) {
            readPlane(pix, x, y, w, h, buf);
          }
          plane = null;
        }
      }
  
      return buf;
    }
  
    /* @see loci.formats.IFormatReader#close(boolean) */
    @Override
    public void close(boolean fileOnly) throws IOException {
      super.close(fileOnly);
      if (pict != null) pict.close(fileOnly);
      if (!fileOnly) {
        planes = null;
        luts = null;
        lastPlane = 0;
        version = 0;
        xcal = ycal = 0f;
        nextTag = 0;
        tag = subTag = 0;
        fmt = "";
        planeOffsets = null;
        gain = detectorOffset = null;
        xPos = yPos = zPos = null;
        specialPlateNames = false;
      }
    }
  
    // -- 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);
  
      luts = new ArrayList<byte[][]>();
  
      LOGGER.info("Verifying Openlab LIFF format");
  
      in.order(false);
      in.seek(4);
      if (!in.readString(4).equals("impr")) {
        throw new FormatException("Invalid LIFF file.");
      }
  
      version = in.readInt();
  
      if (version != 2 && version != 5) {
        throw new FormatException("Invalid version : " + version);
      }
  
      // total number of planes in the file
      int planeCount = in.readShort();
  
      planes = new PlaneInfo[planeCount];
  
      // skip the ID seed
      in.skipBytes(2);
  
      // read offset to first plane
      int offset = in.readInt();
      in.seek(offset);
  
      LOGGER.info("Finding image offsets");
  
      xcal = ycal = 0.0f;
  
      // scan through the file, and read image information
  
      int imagesFound = 0;
  
      final List<PlaneInfo> representativePlanes = new ArrayList<PlaneInfo>();
  
      while (in.getFilePointer() + 8 < in.length()) {
        long fp = in.getFilePointer();
        readTagHeader();
        while (tag < IMAGE_TYPE_1 || tag > 76) {
          in.seek(--fp);
          readTagHeader();
        }
  
        if (tag == IMAGE_TYPE_1 || tag == IMAGE_TYPE_2) {
          // found an image
  
          planes[imagesFound] = new PlaneInfo();
  
          planes[imagesFound].pict = fmt.toLowerCase().equals("pict");
          planes[imagesFound].compressed = subTag == 0;
  
          in.skipBytes(24);
          planes[imagesFound].volumeType = in.readShort();
          in.skipBytes(16);
          long pointer = in.getFilePointer();
          planes[imagesFound].planeName = readCString().trim();
          in.skipBytes((int) (256 - in.getFilePointer() + pointer));
  
          planes[imagesFound].planeOffset = in.getFilePointer();
  
          // read the image dimensions
          if (version == 2) {
            in.skipBytes(2);
            int top = in.readShort();
            int left = in.readShort();
            int bottom = in.readShort();
            int right = in.readShort();
  
            planes[imagesFound].width = right - left;
            planes[imagesFound].height = bottom - top;
          }
          else {
            planes[imagesFound].width = in.readInt();
            planes[imagesFound].height = in.readInt();
          }
  
          for (int i=representativePlanes.size()-1; i>=0; i--) {
            PlaneInfo p = representativePlanes.get(i);
            if (planes[imagesFound].width == p.width &&
              planes[imagesFound].height == p.height &&
              (planes[imagesFound].volumeType == p.volumeType ||
              (planes[imagesFound].volumeType >= DEEP_GREY_9 &&
              p.volumeType >= DEEP_GREY_9)))
            {
              planes[imagesFound].series = i;
              break;
            }
          }
  
          if (planes[imagesFound].series == -1 &&
            !planes[imagesFound].planeName.equals("Original Image"))
          {
            planes[imagesFound].series = representativePlanes.size();
            representativePlanes.add(planes[imagesFound]);
          }
  
          // read the LUT, if present
  
          if (planes[imagesFound].volumeType == MAC_256_COLORS) {
            in.seek(nextTag - (257 * 8));
            byte[][] lut = new byte[3][256];
            for (int i=0; i<256; i++) {
              in.skipBytes(2);
              lut[0][255 - i] = (byte) (in.readShort() >> 8);
              lut[1][255 - i] = (byte) (in.readShort() >> 8);
              lut[2][255 - i] = (byte) (in.readShort() >> 8);
            }
            luts.add(lut);
          }
          else luts.add(null);
  
          imagesFound++;
        }
        else if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM)
        {
          if (tag == CALIBRATION) {
            in.skipBytes(4);
            short units = in.readShort();
            float scaling = units == 3 ? 0.001f : 1.0f;
            in.skipBytes(12);
  
            xcal = in.readFloat() * scaling;
            ycal = in.readFloat() * scaling;
          }
          else if (tag == USER) {
            String className = readCString();
  
            if (className.equals("CVariableList")) {
              byte check = in.readByte();
  
              if (check == 1) {
                int numVars = in.readShort();
                for (int i=0; i<numVars; i++) {
                  readVariable();
                }
              }
            }
          }
        }
  
        in.seek(nextTag);
      }
  
      int nSeries = representativePlanes.size();
      planeOffsets = new int[nSeries][];
      final List<Integer> tmpOffsets = new ArrayList<Integer>();
      final List<String> names = new ArrayList<String>();
  
      core.clear();
      for (int i=0; i<nSeries; i++) {
        CoreMetadata ms = new CoreMetadata();
        core.add(ms);
  
        setSeries(i);
  
        for (int q=0; q<planes.length; q++) {
          if (planes[q] != null && planes[q].series == i) {
            tmpOffsets.add(q);
            names.add(planes[q].planeName);
          }
        }
        planeOffsets[i] = new int[tmpOffsets.size()];
        for (int q=0; q<planeOffsets[i].length; q++) {
          planeOffsets[i][q] = tmpOffsets.get(q);
          addSeriesMetaList("Plane Name", names.get(q));
        }
        tmpOffsets.clear();
        names.clear();
      }
  
      // populate core metadata
  
      for (int i=0; i<nSeries; i++) {
        setSeries(i);
        CoreMetadata ms = core.get(i);
  
        ms.indexed = false;
        ms.sizeX = planes[planeOffsets[i][0]].width;
        ms.sizeY = planes[planeOffsets[i][0]].height;
        ms.imageCount = planeOffsets[i].length;
        ms.sizeC = 1;
  
        switch (planes[planeOffsets[i][0]].volumeType) {
          case MAC_1_BIT:
          case MAC_4_GREYS:
          case MAC_256_GREYS:
            ms.pixelType = FormatTools.UINT8;
            ms.indexed = planes[planeOffsets[i][0]].pict;
            break;
          case MAC_256_COLORS:
            ms.pixelType = FormatTools.UINT8;
            ms.indexed = true;
            break;
          case MAC_16_COLORS:
          case MAC_16_BIT_COLOR:
          case MAC_24_BIT_COLOR:
            ms.pixelType = FormatTools.UINT8;
            ms.sizeC = 3;
            break;
          case DEEP_GREY_9:
            ms.bitsPerPixel = 9;
            break;
          case DEEP_GREY_10:
            ms.bitsPerPixel = 10;
            break;
          case DEEP_GREY_11:
            ms.bitsPerPixel = 11;
            break;
          case DEEP_GREY_12:
            ms.bitsPerPixel = 12;
            break;
          case DEEP_GREY_13:
            ms.bitsPerPixel = 13;
            break;
          case DEEP_GREY_14:
            ms.bitsPerPixel = 14;
            break;
          case DEEP_GREY_15:
            ms.bitsPerPixel = 15;
            break;
          case MAC_16_GREYS:
          case DEEP_GREY_16:
            ms.pixelType = FormatTools.UINT16;
            break;
          default:
            throw new FormatException("Unsupported plane type: " +
              planes[planeOffsets[i][0]].volumeType);
        }
  
        if (getBitsPerPixel() > 8) ms.pixelType = FormatTools.UINT16;
        ms.rgb = getSizeC() > 1;
        ms.interleaved = isRGB() && version == 5;
        ms.sizeT = 1;
        ms.sizeZ = getImageCount();
        ms.dimensionOrder = "XYCZT";
        ms.littleEndian = false;
        ms.falseColor = false;
        ms.metadataComplete = true;
      }
  
      int seriesCount = getSeriesCount();
      for (int s=0; s<seriesCount; s++) {
        setSeries(s);
        parseImageNames(s);
      }
      setSeries(0);
  
  