/* * #%L * OME Bio-Formats package for reading and converting biological file formats. * %% * Copyright (C) 2005 - 2015 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.DataTools; 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.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatReader; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.MissingLibraryException; import loci.formats.meta.MetadataStore; import loci.formats.services.NetCDFService; import loci.formats.services.NetCDFServiceImpl; import ome.xml.model.primitives.Color; import ome.xml.model.primitives.PositiveFloat; import ome.units.quantity.Length; /** * Reader for Bitplane Imaris 5.5 (HDF) files. */ public class ImarisHDFReader extends FormatReader { // -- Constants -- public static final String HDF_MAGIC_STRING = "HDF"; private static final String[] DELIMITERS = {" ", "-", "."}; // -- Fields -- private double pixelSizeX, pixelSizeY, pixelSizeZ; private double minX, minY, minZ, maxX, maxY, maxZ; private int seriesCount; private NetCDFService netcdf; // channel parameters private List<String> emWave, exWave, channelMin, channelMax; private List<String> gain, pinhole, channelName, microscopyMode; private List<double[]> colors; private int lastChannel = 0; // -- Constructor -- /** Constructs a new Imaris HDF reader. */ public ImarisHDFReader() { super("Bitplane Imaris 5.5 (HDF)", "ims"); suffixSufficient = false; domains = new String[] {FormatTools.UNKNOWN_DOMAIN}; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#getOptimalTileWidth() */ @Override public int getOptimalTileWidth() { return core.get(core.size() - 1).sizeX; } /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ @Override public int getOptimalTileHeight() { return core.get(core.size() - 1).sizeY; } /* @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.readString(blockLen).indexOf(HDF_MAGIC_STRING) >= 0; } /* @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 >= colors.size()) { return null; } double[] color = colors.get(lastChannel); byte[][] lut = new byte[3][256]; for (int c=0; c<lut.length; c++) { double max = color[c] * 255; for (int p=0; p<lut[c].length; p++) { lut[c][p] = (byte) ((p / 255.0) * max); } } return lut; } /* @see loci.formats.IFormatReaderget16BitLookupTable() */ @Override public short[][] get16BitLookupTable() { FormatTools.assertId(currentId, true, 1); if (getPixelType() != FormatTools.UINT16 || !isIndexed()) return null; if (lastChannel < 0 || lastChannel >= colors.size()) { return null; } double[] color = colors.get(lastChannel); short[][] lut = new short[3][65536]; for (int c=0; c<lut.length; c++) { double max = color[c] * 65535; for (int p=0; p<lut[c].length; p++) { lut[c][p] = (short) ((p / 65535.0) * max); } } 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); lastChannel = getZCTCoords(no)[1]; // pixel data is stored in XYZ blocks Object image = getImageData(no, x, y, w, h); boolean little = isLittleEndian(); int bpp = FormatTools.getBytesPerPixel(getPixelType()); for (int row=0; row<h; row++) { int rowlen = w * bpp; int base = row * rowlen; // indexes into the source array add (data.length - h) and // (rowData.length - w) to account for cases where the source array // represents a tile that is larger than the desired tile if (image instanceof byte[][]) { byte[][] data = (byte[][]) image; byte[] rowData = data[row + data.length - h]; System.arraycopy(rowData, rowData.length - w, buf, base, w); } else if (image instanceof short[][]) { short[][] data = (short[][]) image; short[] rowData = data[row + data.length - h]; int index = rowData.length - w; for (int i=0; i<w; i++) { DataTools.unpackBytes(rowData[i + index], buf, base + 2*i, 2, little); } } else if (image instanceof int[][]) { int[][] data = (int[][]) image; int[] rowData = data[row + data.length - h]; int index = rowData.length - w; for (int i=0; i<w; i++) { DataTools.unpackBytes(rowData[i + index], buf, base + i*4, 4, little); } } else if (image instanceof float[][]) { float[][] data = (float[][]) image; float[] rowData = data[row + data.length - h]; int index = rowData.length - w; for (int i=0; i<w; i++) { int v = Float.floatToIntBits(rowData[i + index]); DataTools.unpackBytes(v, buf, base + i*4, 4, little); } } else if (image instanceof double[][]) { double[][] data = (double[][]) image; double[] rowData = data[row + data.length - h]; int index = rowData.length - w; for (int i=0; i<w; i++) { long v = Double.doubleToLongBits(rowData[i + index]); DataTools.unpackBytes(v, buf, base + i * 8, 8, little); } } } return buf; } /* @see loci.formats.IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { seriesCount = 0; pixelSizeX = pixelSizeY = pixelSizeZ = 0; minX = minY = minZ = maxX = maxY = maxZ = 0; if (netcdf != null) netcdf.close(); netcdf = null; emWave = exWave = channelMin = channelMax = null; gain = pinhole = channelName = microscopyMode = null; colors = null; lastChannel = 0; } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ @Override protected void initFile(String id) throws FormatException, IOException { super.initFile(id); try { ServiceFactory factory = new ServiceFactory(); netcdf = factory.getInstance(NetCDFService.class); netcdf.setFile(id); } catch (DependencyException e) { throw new MissingLibraryException(NetCDFServiceImpl.NO_NETCDF_MSG, e); } pixelSizeX = pixelSizeY = pixelSizeZ = 1; emWave = new ArrayList<String>(); exWave = new ArrayList<String>(); channelMin = new ArrayList<String>(); channelMax = new ArrayList<String>(); gain = new ArrayList<String>(); pinhole = new ArrayList<String>(); channelName = new ArrayList<String>(); microscopyMode = new ArrayList<String>(); colors = new ArrayList<double[]>(); seriesCount = 0; // read all of the metadata key/value pairs parseAttributes(); CoreMetadata ms0 = core.get(0); if (seriesCount > 1) { for (int i=1; i<seriesCount; i++) { core.add(new CoreMetadata()); } for (int i=1; i<seriesCount; i++) { CoreMetadata ms = core.get(i); String groupPath = "DataSet/ResolutionLevel_" + i + "/TimePoint_0/Channel_0"; ms.sizeX = Integer.parseInt(netcdf.getAttributeValue(groupPath + "/ImageSizeX")); ms.sizeY = Integer.parseInt(netcdf.getAttributeValue(groupPath + "/ImageSizeY")); ms.sizeZ = Integer.parseInt(netcdf.getAttributeValue(groupPath + "/ImageSizeZ")); ms.imageCount = ms.sizeZ * getSizeC() * getSizeT(); ms.sizeC = getSizeC(); ms.sizeT = getSizeT(); ms.thumbnail = true; if (ms.sizeZ == ms0.sizeZ && ms.sizeC == ms0.sizeC && ms.sizeT == ms0.sizeT) { // do not assume that all series will have the same dimensions // if the Z, C or T size is different, then it cannot // be a subresolution ms0.resolutionCount++; } } } ms0.imageCount = getSizeZ() * getSizeC() * getSizeT(); ms0.thumbnail = false; ms0.dimensionOrder = "XYZCT"; // determine pixel type - this isn't stored in the metadata, so we need // to check the pixels themselves int type = -1; Object pix = getImageData(0, 0, 0, 1, 1); if (pix instanceof byte[][]) type = FormatTools.UINT8; else if (pix instanceof short[][]) type = FormatTools.UINT16; else if (pix instanceof int[][]) type = FormatTools.UINT32; else if (pix instanceof float[][]) type = FormatTools.FLOAT; else if (pix instanceof double[][]) type = FormatTools.DOUBLE; else { throw new FormatException("Unknown pixel type: " + pix); } for (int i=0; i<core.size(); i++) { CoreMetadata ms = core.get(i); ms.pixelType = type; ms.dimensionOrder = "XYZCT"; ms.rgb = false; ms.thumbSizeX = 128; ms.thumbSizeY = 128; ms.orderCertain = true; ms.littleEndian = true; ms.interleaved = false; ms.indexed = colors.size() >= getSizeC(); } MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this); String imageName = new Location(getCurrentFile()).getName(); for (int s=0; s<getSeriesCount(); s++) { store.setImageName(imageName + " Resolution Level " + (s + 1), s); } if (getMetadataOptions().getMetadataLevel() == MetadataLevel.MINIMUM) { return; } int cIndex = 0; for (int s=0; s<getSeriesCount(); s++) { setSeries(s); double px = pixelSizeX, py = pixelSizeY, pz = pixelSizeZ; if (px == 1) px = (maxX - minX) / getSizeX(); if (py == 1) py = (maxY - minY) / getSizeY(); if (pz == 1) pz = (maxZ - minZ) / getSizeZ(); Length sizeX = FormatTools.getPhysicalSizeX(px); Length sizeY = FormatTools.getPhysicalSizeY(py); Length sizeZ = FormatTools.getPhysicalSizeZ(pz); if (sizeX != null) { store.setPixelsPhysicalSizeX(sizeX, s); } if (sizeY != null) { store.setPixelsPhysicalSizeY(sizeY, s); } if (sizeZ != null) { store.setPixelsPhysicalSizeZ(sizeZ, s); } for (int i=0; i<getSizeC(); i++, cIndex++) { Float gainValue = null; Integer pinholeValue = null, emWaveValue = null, exWaveValue; if (cIndex < gain.size()) { try { gainValue = new Float(gain.get(cIndex)); } catch (NumberFormatException e) { } } if (cIndex < pinhole.size()) { try { pinholeValue = new Integer(pinhole.get(cIndex)); } catch (NumberFormatException e) { } } if (cIndex < emWave.size()) { try { emWaveValue = new Integer(emWave.get(cIndex)); } catch (NumberFormatException e) { } } if (cIndex < exWave.size()) { try { exWaveValue = new Integer(exWave.get(cIndex)); } catch (NumberFormatException e) { } } Double minValue = null, maxValue = null; if (cIndex < channelMin.size()) { try { minValue = new Double(channelMin.get(cIndex)); } catch (NumberFormatException e) { } } if (cIndex < channelMax.size()) { try { maxValue = new Double(channelMax.get(cIndex)); } catch (NumberFormatException e) { } } if (i < colors.size()) { double[] color = colors.get(i); Color realColor = new Color( (int) (color[0] * 255), (int) (color[1] * 255), (int) (color[2] * 255), 255); store.setChannelColor(realColor, s, i); } } } setSeries(0); } // -- Helper methods -- /** * Retrieve an array corresponding to the specified image tile. * In some cases, the returned tile will be larger than the requested tile; * openBytes will correct for this as needed. */ private Object getImageData(int no, int x, int y, int width, int height) throws FormatException { int[] zct = getZCTCoords(no); String path = "/DataSet/ResolutionLevel_" + getCoreIndex() + "/TimePoint_" + zct[2] + "/Channel_" + zct[1] + "/Data"; Object image = null; // the width and height cannot be 1, because then netCDF will give us a // singleton instead of an array if (height == 1) { height++; // if we only wanted the last row, the Y coordinate must be adjusted // so that we don't attempt to read past the end of the image if (y == getSizeY() - 1) { y--; } } if (width == 1) { width++; // if we only wanted the last column, the X coordinate must be adjusted // so that we don't attempt to read past the end of the image if (x == getSizeX() - 1) { x--; } } // netCDF sometimes returns incorrect pixel values if the (X, Y) coordinate // is in the lower right quadrant of the image. We correct for this by // moving the X coordinate to the left and adjusting the width. if (x >= getSizeX() / 2 && y >= getSizeY() / 2) { width += x - (getSizeX() / 2) + 1; x = (getSizeX() / 2) - 1; } int[] dimensions = new int[] {1, height, width}; int[] indices = new int[] {zct[0], y, x}; try { image = netcdf.getArray(path, indices, dimensions); } catch (ServiceException e) { throw new FormatException(e); } return image; } private void parseAttributes() { final List<String> attributes = netcdf.getAttributeList(); CoreMetadata ms0 = core.get(0); for (String attr : attributes) { String name = attr.substring(attr.lastIndexOf("/") + 1); String value = netcdf.getAttributeValue(attr); if (value == null) continue; value = value.trim(); if (name.equals("X") || name.equals("ImageSizeX")) { try { ms0.sizeX = Integer.parseInt(value); } catch (NumberFormatException e) { LOGGER.trace("Failed to parse '" + name + "'", e); } } else if (name.equals("Y") || name.equals("ImageSizeY")) { try { ms0.sizeY = Integer.parseInt(value); } catch (NumberFormatException e) { LOGGER.trace("Failed to parse '" + name + "'", e); } } else if (name.equals("Z") || name.equals("ImageSizeZ")) { try { ms0.sizeZ = Integer.parseInt(value); } catch (NumberFormatException e) { LOGGER.trace("Failed to parse '" + name + "'", e); } } else if (name.equals("FileTimePoints")) { ms0.sizeT = Integer.parseInt(value); } else if (name.equals("NumberOfChannels") && getSizeC() == 0) { ms0.sizeC = Integer.parseInt(value); } else if (name.equals("RecordingEntrySampleSpacing")) { pixelSizeX = Double.parseDouble(value); } else if (name.equals("RecordingEntryLineSpacing")) { pixelSizeY = Double.parseDouble(value); } else if (name.equals("RecordingEntryPlaneSpacing")) { pixelSizeZ = Double.parseDouble(value); } else if (name.equals("ExtMax0")) maxX = Double.parseDouble(value); else if (name.equals("ExtMax1")) maxY = Double.parseDouble(value); else if (name.equals("ExtMax2")) maxZ = Double.parseDouble(value); else if (name.equals("ExtMin0")) minX = Double.parseDouble(value); else if (name.equals("ExtMin1")) minY = Double.parseDouble(value); else if (name.equals("ExtMin2")) minZ = Double.parseDouble(value); if (attr.startsWith("DataSet/ResolutionLevel_")) { int slash = attr.indexOf("/", 24); int n = Integer.parseInt(attr.substring(24, slash == -1 ? attr.length() : slash)); if (n == seriesCount) seriesCount++; } if (attr.startsWith("DataSetInfo/Channel_")) { String originalValue = value; for (String d : DELIMITERS) { if (value.indexOf(d) != -1) { value = value.substring(value.indexOf(d) + 1); } } int underscore = attr.indexOf("_") + 1; int cIndex = Integer.parseInt(attr.substring(underscore, attr.indexOf("/", underscore))); while (cIndex >= getSizeC()) ms0.sizeC++; if (name.equals("Gain")) gain.add(value); else if (name.equals("LSMEmissionWavelength")) emWave.add(value); else if (name.equals("LSMExcitationWavelength")) exWave.add(value); else if (name.equals("Max")) channelMax.add(value); else if (name.equals("Min")) channelMin.add(value); else if (name.equals("Pinhole")) pinhole.add(value); else if (name.equals("Name")) channelName.add(value); else if (name.equals("MicroscopyMode")) microscopyMode.add(value); else if (name.equals("Color")) { double[] color = new double[3]; String[] intensity = originalValue.split(" "); for (int i=0; i<intensity.length; i++) { color[i] = Double.parseDouble(intensity[i]); } colors.add(color); } } if (value != null) addGlobalMeta(name, value); } } }