/* * #%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.Arrays; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import loci.common.ByteArrayHandle; import loci.common.Constants; import loci.common.DataTools; import loci.common.DateTools; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.common.Region; 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.Codec; import loci.formats.codec.CodecOptions; import loci.formats.codec.JPEGCodec; import loci.formats.codec.JPEG2000Codec; import loci.formats.meta.MetadataStore; import loci.formats.tiff.IFD; import loci.formats.tiff.IFDList; import loci.formats.tiff.PhotoInterp; import loci.formats.tiff.TiffParser; import ome.units.UNITS; import ome.xml.model.primitives.PositiveFloat; import ome.xml.model.primitives.PositiveInteger; import ome.xml.model.primitives.Timestamp; /** * CellSensReader is the file format reader for cellSens .vsi files. */ public class CellSensReader extends FormatReader { // -- Constants -- // Compression types private static final int RAW = 0; private static final int JPEG = 2; private static final int JPEG_2000 = 3; private static final int PNG = 8; private static final int BMP = 9; // Pixel types private static final int CHAR = 1; private static final int UCHAR = 2; private static final int SHORT = 3; private static final int USHORT = 4; private static final int INT = 5; private static final int UINT = 6; private static final int LONG = 7; private static final int ULONG = 8; private static final int FLOAT = 9; private static final int DOUBLE = 10; // Simple field types private static final int COMPLEX = 11; private static final int BOOLEAN = 12; private static final int TCHAR = 13; private static final int DWORD = 14; private static final int TIMESTAMP = 17; private static final int DATE = 18; private static final int INT_2 = 256; private static final int INT_3 = 257; private static final int INT_4 = 258; private static final int INT_RECT = 259; private static final int DOUBLE_2 = 260; private static final int DOUBLE_3 = 261; private static final int DOUBLE_4 = 262; private static final int DOUBLE_RECT = 263; private static final int DOUBLE_2_2 = 264; private static final int DOUBLE_3_3 = 265; private static final int DOUBLE_4_4 = 266; private static final int INT_INTERVAL = 267; private static final int DOUBLE_INTERVAL = 268; private static final int RGB = 269; private static final int BGR = 270; private static final int FIELD_TYPE = 271; private static final int MEM_MODEL = 272; private static final int COLOR_SPACE = 273; private static final int INT_ARRAY_2 = 274; private static final int INT_ARRAY_3 = 275; private static final int INT_ARRAY_4 = 276; private static final int INT_ARRAY_5 = 277; private static final int DOUBLE_ARRAY_2 = 279; private static final int DOUBLE_ARRAY_3 = 280; private static final int UNICODE_TCHAR = 8192; private static final int DIM_INDEX_1 = 8195; private static final int DIM_INDEX_2 = 8199; private static final int VOLUME_INDEX = 8200; private static final int PIXEL_INFO_TYPE = 8470; // Extended field types private static final int NEW_VOLUME_HEADER = 0; private static final int PROPERTY_SET_VOLUME = 1; private static final int NEW_MDIM_VOLUME_HEADER = 2; private static final int TIFF_IFD = 10; private static final int VECTOR_DATA = 11; // Tag values private static final int COLLECTION_VOLUME = 2000; private static final int MULTIDIM_IMAGE_VOLUME = 2001; private static final int IMAGE_FRAME_VOLUME = 2002; private static final int DIMENSION_SIZE = 2003; private static final int IMAGE_COLLECTION_PROPERTIES = 2004; private static final int MULTIDIM_STACK_PROPERTIES = 2005; private static final int FRAME_PROPERTIES = 2006; private static final int DIMENSION_DESCRIPTION_VOLUME = 2007; private static final int CHANNEL_PROPERTIES = 2008; private static final int DISPLAY_MAPPING_VOLUME = 2011; private static final int LAYER_INFO_PROPERTIES = 2012; private static final int CHANNEL_INFO_PROPERTIES = 2013; private static final int DEFAULT_SAMPLE_IFD = 2016; private static final int VECTOR_LAYER_VOLUME = 2017; private static final int EXTERNAL_FILE_PROPERTIES = 2018; private static final int COARSE_FRAME_IFD = 2019; private static final int COARSE_PYRAMID_LEVEL = 2022; private static final int SERIALIZED_MASK = 2023; private static final int UNKNOWN_BLOBS_VOLUME = 2024; private static final int SAMPLE_ID_LIST = 2027; private static final int EXTRA_SAMPLES = 2028; private static final int EXTRA_SAMPLES_PROPERTIES = 2029; private static final int SAMPLE_FLAGS_LIST = 2030; private static final int FRAME_TYPE = 2031; private static final int DEFAULT_BACKGROUND_COLOR = 2034; private static final int VERSION_NUMBER = 2035; private static final int DOCUMENT_PROPERTIES = 2109; private static final int DOCUMENT_NAME = 11; private static final int DOCUMENT_NOTE = 13; private static final int DOCUMENT_TIME = 14; private static final int DOCUMENT_AUTHOR = 15; private static final int DOCUMENT_COMPANY = 16; private static final int DOCUMENT_CREATOR_NAME = 17; private static final int DOCUMENT_CREATOR_MAJOR_VERSION = 18; private static final int DOCUMENT_CREATOR_MINOR_VERSION = 19; private static final int DOCUMENT_CREATOR_SUB_VERSION = 20; private static final int DOCUMENT_CREATOR_BUILD_NUMBER = 21; private static final int DOCUMENT_CREATOR_PACKAGE = 22; private static final int DOCUMENT_PRODUCT = 23; private static final int DOCUMENT_PRODUCT_NAME = 24; private static final int DOCUMENT_PRODUCT_VERSION = 25; private static final int DOCUMENT_TYPE_HINT = 27; private static final int DOCUMENT_THUMB = 28; private static final int SLIDE_PROPERTIES = 2062; private static final int SLIDE_SPECIMEN = 2055; private static final int SLIDE_TISSUE = 2057; private static final int SLIDE_PREPARATION = 2058; private static final int SLIDE_STAINING = 2059; private static final int SLIDE_INFO = 2060; private static final int SLIDE_NAME = 2061; private static final int DYNAMIC_PROPERTIES = 27300; private static final int MAGNIFICATION = 1073741824; // External Raw Data properties (stored under EXTERNAL_FILE_PROPERTIES) private static final int IMAGE_BOUNDARY = 2053; private static final int TILE_SYSTEM = 20004; private static final int HAS_EXTERNAL_FILE = 20005; private static final int EXTERNAL_DATA_VOLUME = 20025; private static final int TILE_ORIGIN = 2410; // Camera property tags private static final int EXPOSURE_TIME = 100002; private static final int CAMERA_GAIN = 100003; private static final int CAMERA_OFFSET = 100004; private static final int CAMERA_GAMMA = 100005; private static final int SHARPNESS = 100006; private static final int RED_GAIN = 100007; private static final int GREEN_GAIN = 100008; private static final int BLUE_GAIN = 100009; private static final int RED_OFFSET = 100010; private static final int GREEN_OFFSET = 100011; private static final int BLUE_OFFSET = 100012; private static final int SHADING_SUB = 100013; private static final int SHADING_MUL = 100014; private static final int X_BINNING = 100015; private static final int Y_BINNING = 100016; private static final int CLIPPING = 100017; private static final int MIRROR_H = 100023; private static final int MIRROR_V = 100024; private static final int CLIPPING_STATE = 100025; private static final int ICC_ENABLED = 100030; private static final int BRIGHTNESS = 100031; private static final int CONTRAST = 100032; private static final int CONTRAST_TARGET = 100033; private static final int ACCUMULATION = 100034; private static final int AVERAGING = 100035; private static final int ISO_SENSITIVITY = 100038; private static final int ACCUMULATION_MODE = 100039; private static final int AUTOEXPOSURE = 100043; private static final int EXPOSURE_METERING_MODE = 100044; private static final int FRAME_SIZE = 100048; private static final int BIT_DEPTH = 100049; private static final int HDRI_ON = 100055; private static final int HDRI_FRAMES = 100056; private static final int HDRI_EXPOSURE_RANGE = 100057; private static final int HDRI_MAP_MODE = 100058; private static final int CUSTOM_GRAYSCALE = 100059; private static final int SATURATION = 100060; private static final int WB_PRESET_ID = 100061; private static final int WB_PRESET_NAME = 100062; private static final int WB_MODE = 100063; private static final int CCD_SENSITIVITY = 100064; private static final int ENHANCED_DYNAMIC_RANGE = 100065; private static final int PIXEL_CLOCK = 100066; private static final int COLORSPACE = 100067; private static final int COOLING_ON = 100068; private static final int FAN_SPEED = 100069; private static final int TEMPERATURE_TARGET = 100070; private static final int GAIN_UNIT = 100071; private static final int EM_GAIN = 100072; private static final int PHOTON_IMAGING_MODE = 100073; private static final int FRAME_TRANSFER = 100074; private static final int ANDOR_SHIFT_SPEED = 100075; private static final int VCLOCK_AMPLITUDE = 100076; private static final int SPURIOUS_NOISE_REMOVAL = 100077; private static final int SIGNAL_OUTPUT = 100078; private static final int BASELINE_OFFSET_CLAMP = 100079; private static final int DP80_FRAME_CENTERING = 100080; private static final int HOT_PIXEL_CORRECTION = 100081; private static final int NOISE_REDUCTION = 100082; private static final int WIDER = 100083; private static final int PHOTOBLEACHING = 100084; private static final int PREAMP_GAIN_VALUE = 100085; private static final int WIDER_ENABLED = 100086; // Dimension properties private static final int Z_START = 2012; private static final int Z_INCREMENT = 2013; private static final int Z_VALUE = 2014; private static final int TIME_START = 2100; private static final int TIME_INCREMENT = 2016; private static final int TIME_VALUE = 2017; private static final int LAMBDA_START = 2039; private static final int LAMBDA_INCREMENT = 2040; private static final int LAMBDA_VALUE = 2041; private static final int DIMENSION_NAME = 2021; private static final int DIMENSION_MEANING = 2023; private static final int DIMENSION_START_ID = 2025; private static final int DIMENSION_INCREMENT_ID = 2026; private static final int DIMENSION_VALUE_ID = 2027; private static final int CHANNEL_NAME = 2419; // Stack properties private static final int DISPLAY_LIMITS = 2003; private static final int STACK_DISPLAY_LUT = 2004; private static final int GAMMA_CORRECTION = 2005; private static final int FRAME_ORIGIN = 2006; private static final int FRAME_SCALE = 2007; private static final int DISPLAY_COLOR = 2008; private static final int CREATION_TIME = 2015; private static final int RWC_FRAME_ORIGIN = 2018; private static final int RWC_FRAME_SCALE = 2019; private static final int RWC_FRAME_UNIT = 2020; private static final int STACK_NAME = 2030; private static final int CHANNEL_DIM = 2031; private static final int OPTICAL_PATH = 2043; private static final int STACK_TYPE = 2074; private static final int LIVE_OVERFLOW = 2076; private static final int IS_TRANSMISSION = 20035; private static final int CONTRAST_BRIGHTNESS = 10047; private static final int ACQUISITION_PROPERTIES = 10048; private static final int GRADIENT_LUT = 10065; // Stack types private static final int DEFAULT_IMAGE = 0; private static final int OVERVIEW_IMAGE = 1; private static final int SAMPLE_MASK = 2; private static final int FOCUS_IMAGE = 4; private static final int EFI_SHARPNESS_MAP = 8; private static final int EFI_HEIGHT_MAP = 16; private static final int EFI_TEXTURE_MAP = 32; private static final int EFI_STACK = 64; private static final int MACRO_IMAGE = 256; // Display mapping tags private static final int DISPLAY_PROCESSOR_TYPE = 10000; private static final int RENDER_OPERATION_ID = 10001; private static final int DISPLAY_STACK_ID = 10005; private static final int TRANSPARENCY_ID = 10006; private static final int THIRD_ID = 10007; private static final int DISPLAY_VISIBLE = 10008; private static final int TRANSPARENCY_VALUE = 10009; private static final int DISPLAY_LUT = 10013; private static final int DISPLAY_STACK_INDEX = 10014; private static final int CHANNEL_TRANSPARENCY_VALUE = 10018; private static final int CHANNEL_VISIBLE = 10025; private static final int SELECTED_CHANNELS = 10028; private static final int DISPLAY_GAMMA_CORRECTION = 10032; private static final int CHANNEL_GAMMA_CORRECTION = 10033; private static final int DISPLAY_CONTRAST_BRIGHTNESS = 10045; private static final int CHANNEL_CONTRAST_BRIGHTNESS = 10046; private static final int ACTIVE_STACK_DIMENSION = 10049; private static final int SELECTED_FRAMES = 10050; private static final int DISPLAYED_LUT_ID = 10054; private static final int HIDDEN_LAYER = 10056; private static final int LAYER_XY_FIXED = 10057; private static final int ACTIVE_LAYER_VECTOR = 10060; private static final int ACTIVE_LAYER_INDEX_VECTOR = 10061; private static final int CHAINED_LAYERS = 10062; private static final int LAYER_SELECTION = 10063; private static final int LAYER_SELECTION_INDEX = 10064; private static final int CANVAS_COLOR_1 = 10066; private static final int CANVAS_COLOR_2 = 10067; private static final int ORIGINAL_FRAME_RATE = 10069; private static final int USE_ORIGINAL_FRAME_RATE = 10070; private static final int ACTIVE_CHANNEL = 10071; private static final int PLANE_UNIT = 2011; private static final int Y_PLANE_DIMENSION_UNIT = 2063; private static final int Y_DIMENSION_UNIT = 2064; private static final int PLANE_ORIGIN_RWC = 20006; private static final int PLANE_SCALE_RWC = 20007; private static final int CHANNEL_OVERFLOW = 2073; // Objective data private static final int OBJECTIVE_MAG = 120060; private static final int NUMERICAL_APERTURE = 120061; private static final int WORKING_DISTANCE = 120062; private static final int OBJECTIVE_NAME = 120063; private static final int OBJECTIVE_TYPE = 120064; private static final int REFRACTIVE_INDEX = 120079; private static final int DEVICE_NAME = 120116; private static final int DEVICE_ID = 120129; private static final int DEVICE_SUBTYPE = 120130; private static final int DEVICE_MANUFACTURER = 120133; private static final int VALUE = 268435458; // -- Fields -- private String[] usedFiles; private HashMap<Integer, String> fileMap = new HashMap<Integer, String>(); private TiffParser parser; private IFDList ifds; private ArrayList<Long[]> tileOffsets = new ArrayList<Long[]>(); private boolean jpeg = false; private ArrayList<Integer> rows = new ArrayList<Integer>(); private ArrayList<Integer> cols = new ArrayList<Integer>(); private ArrayList<Integer> compressionType = new ArrayList<Integer>(); private ArrayList<Integer> tileX = new ArrayList<Integer>(); private ArrayList<Integer> tileY = new ArrayList<Integer>(); private ArrayList<ArrayList<TileCoordinate>> tileMap = new ArrayList<ArrayList<TileCoordinate>>(); private ArrayList<Integer> nDimensions = new ArrayList<Integer>(); private boolean inDimensionProperties = false; private boolean foundChannelTag = false; private int dimensionTag; private HashMap<Integer, byte[]> backgroundColor = new HashMap<Integer, byte[]>(); private int metadataIndex = -1; private int previousTag = 0; private ArrayList<Pyramid> pyramids = new ArrayList<Pyramid>(); // -- Constructor -- /** Constructs a new cellSens reader. */ public CellSensReader() { super("CellSens VSI", new String[] {"vsi", "ets"}); domains = new String[] {FormatTools.HISTOLOGY_DOMAIN}; suffixSufficient = true; datasetDescription = "One .vsi file and an optional directory with a " + "similar name that contains at least one subdirectory with .ets files"; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#fileGroupOption(String) */ @Override public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.MUST_GROUP; } /* @see loci.formats.IFormatReader#isSingleFile(String) */ @Override public boolean isSingleFile(String id) throws FormatException, IOException { return false; } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ @Override public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); // all files contain pixels return noPixels ? null : usedFiles; } /* @see loci.formats.IFormatReader#getOptimalTileWidth() */ @Override public int getOptimalTileWidth() { FormatTools.assertId(currentId, true, 1); if (getCoreIndex() < core.size() - 1) { return tileX.get(getCoreIndex()); } int ifdIndex = 1 - (core.size() - getCoreIndex()); try { return (int) ifds.get(ifdIndex).getTileWidth(); } catch (FormatException e) { LOGGER.debug("Could not retrieve tile width", e); } return super.getOptimalTileWidth(); } /* @see loci.formats.IFormatReader#getOptimalTileHeight() */ @Override public int getOptimalTileHeight() { FormatTools.assertId(currentId, true, 1); if (getCoreIndex() < core.size() - 1) { return tileY.get(getCoreIndex()); } int ifdIndex = 1 - (core.size() - getCoreIndex()); try { return (int) ifds.get(ifdIndex).getTileLength(); } catch (FormatException e) { LOGGER.debug("Could not retrieve tile height", e); } return super.getOptimalTileHeight(); } /* @see loci.formats.IFormatHandler#openThumbBytes(int) */ @Override public byte[] openThumbBytes(int no) throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); int currentIndex = getCoreIndex(); int thumbSize = getThumbSizeX() * getThumbSizeY() * FormatTools.getBytesPerPixel(getPixelType()) * getRGBChannelCount(); if (getCoreIndex() >= fileMap.size() || usedFiles.length >= core.size()) { return super.openThumbBytes(no); } setCoreIndex(fileMap.size()); byte[] thumb = FormatTools.openThumbBytes(this, 0); setCoreIndex(currentIndex); if (thumb.length == thumbSize) { return thumb; } return super.openThumbBytes(no); } /** * @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 (getCoreIndex() < core.size() - 1) { int tileRows = rows.get(getCoreIndex()); int tileCols = cols.get(getCoreIndex()); Region image = new Region(x, y, w, h); int outputRow = 0, outputCol = 0; Region intersection = null; byte[] tileBuf = null; int pixel = getRGBChannelCount() * FormatTools.getBytesPerPixel(getPixelType()); int outputRowLen = w * pixel; for (int row=0; row<tileRows; row++) { for (int col=0; col<tileCols; col++) { int width = tileX.get(getCoreIndex()); int height = tileY.get(getCoreIndex()); Region tile = new Region(col * width, row * height, width, height); if (!tile.intersects(image)) { continue; } intersection = tile.intersection(image); int intersectionX = 0; if (tile.x < image.x) { intersectionX = image.x - tile.x; } tileBuf = decodeTile(no, row, col); int rowLen = pixel * (int) Math.min(intersection.width, width); int outputOffset = outputRow * outputRowLen + outputCol; for (int trow=0; trow<intersection.height; trow++) { int realRow = trow + intersection.y - tile.y; int inputOffset = pixel * (realRow * width + intersectionX); System.arraycopy(tileBuf, inputOffset, buf, outputOffset, rowLen); outputOffset += outputRowLen; } outputCol += rowLen; } if (intersection != null) { outputRow += intersection.height; outputCol = 0; } } return buf; } else { int ifdIndex = 1 - (core.size() - getCoreIndex()); return parser.getSamples(ifds.get(ifdIndex), buf, x, y, w, h); } } /* @see loci.formats.IFormatReader#reopenFile() */ public void reopenFile() throws IOException { super.reopenFile(); parser = new TiffParser(currentId); } /* @see loci.formats.IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { if (parser != null && parser.getStream() != null) { parser.getStream().close(); } parser = null; ifds = null; usedFiles = null; fileMap.clear(); tileOffsets.clear(); jpeg = false; rows.clear(); cols.clear(); compressionType.clear(); tileX.clear(); tileY.clear(); tileMap.clear(); nDimensions.clear(); inDimensionProperties = false; foundChannelTag = false; dimensionTag = 0; backgroundColor.clear(); metadataIndex = -1; previousTag = 0; } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ @Override protected void initFile(String id) throws FormatException, IOException { super.initFile(id); if (!checkSuffix(id, "vsi")) { Location current = new Location(id).getAbsoluteFile(); Location parent = current.getParentFile(); parent = parent.getParentFile(); Location grandparent = parent.getParentFile(); String vsi = parent.getName(); vsi = vsi.substring(1, vsi.length() - 1) + ".vsi"; Location vsiFile = new Location(grandparent, vsi); if (!vsiFile.exists()) { throw new FormatException("Could not find .vsi file."); } else { id = vsiFile.getAbsolutePath(); } } parser = new TiffParser(id); ifds = parser.getIFDs(); RandomAccessInputStream vsi = new RandomAccessInputStream(id); vsi.order(parser.getStream().isLittleEndian()); vsi.seek(8); readTags(vsi, false, ""); vsi.close(); ArrayList<String> files = new ArrayList<String>(); Location file = new Location(id).getAbsoluteFile(); Location dir = file.getParentFile(); String name = file.getName(); name = name.substring(0, name.lastIndexOf(".")); Location pixelsDir = new Location(dir, "_" + name + "_"); String[] stackDirs = pixelsDir.list(true); if (stackDirs != null) { Arrays.sort(stackDirs); for (String f : stackDirs) { Location stackDir = new Location(pixelsDir, f); String[] pixelsFiles = stackDir.list(true); if (pixelsFiles != null) { Arrays.sort(pixelsFiles); for (String pixelsFile : pixelsFiles) { if (checkSuffix(pixelsFile, "ets")) { files.add(new Location(stackDir, pixelsFile).getAbsolutePath()); } } } } } files.add(file.getAbsolutePath()); usedFiles = files.toArray(new String[files.size()]); int seriesCount = files.size(); core.clear(); IFDList exifs = parser.getExifIFDs(); int index = 0; for (int s=0; s<seriesCount; s++) { CoreMetadata ms = new CoreMetadata(); core.add(ms); if (s < files.size() - 1) { setCoreIndex(index); parseETSFile(files.get(s), s); ms.littleEndian = compressionType.get(index) == RAW; ms.interleaved = ms.rgb; for (int q=1; q<ms.resolutionCount; q++) { int res = core.size() - ms.resolutionCount + q; core.get(res).littleEndian = ms.littleEndian; core.get(res).interleaved = ms.interleaved; } if (s == 0 && exifs.size() > 0) { IFD exif = exifs.get(0); int newX = exif.getIFDIntValue(IFD.PIXEL_X_DIMENSION); int newY = exif.getIFDIntValue(IFD.PIXEL_Y_DIMENSION); if (getSizeX() > newX || getSizeY() > newY) { ms.sizeX = newX; ms.sizeY = newY; } } index += ms.resolutionCount; if (s < pyramids.size()) { ms.seriesMetadata = pyramids.get(s).originalMetadata; } setCoreIndex(0); } else { IFD ifd = ifds.get(s - files.size() + 1); PhotoInterp p = ifd.getPhotometricInterpretation(); int samples = ifd.getSamplesPerPixel(); ms.rgb = samples > 1 || p == PhotoInterp.RGB; ms.sizeX = (int) ifd.getImageWidth(); ms.sizeY = (int) ifd.getImageLength(); ms.sizeZ = 1; ms.sizeT = 1; ms.sizeC = ms.rgb ? samples : 1; ms.littleEndian = ifd.isLittleEndian(); ms.indexed = p == PhotoInterp.RGB_PALETTE && (get8BitLookupTable() != null || get16BitLookupTable() != null); ms.imageCount = 1; ms.pixelType = ifd.getPixelType(); ms.interleaved = false; ms.falseColor = false; ms.thumbnail = s != 0; index++; } ms.metadataComplete = true; ms.dimensionOrder = "XYCZT"; } MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this, true); String instrument = MetadataTools.createLSID("Instrument", 0); store.setInstrumentID(instrument, 0); for (int i=0; i<pyramids.size(); i++) { Pyramid pyramid = pyramids.get(i); store.setObjectiveID(MetadataTools.createLSID("Objective", 0, i), 0, i); store.setObjectiveNominalMagnification(pyramid.magnification, 0, i); store.setObjectiveWorkingDistance( FormatTools.createLength(pyramid.workingDistance, UNITS.MICROM), 0, i); for (int q=0; q<pyramid.objectiveTypes.size(); q++) { if (pyramid.objectiveTypes.get(q) == 1) { store.setObjectiveModel(pyramid.objectiveNames.get(q), 0, i); break; } } store.setObjectiveLensNA(pyramid.numericalAperture, 0, i); store.setDetectorID(MetadataTools.createLSID("Detector", 0, i), 0, i); store.setDetectorOffset(pyramid.offset, 0, i); store.setDetectorGain(pyramid.gain, 0, i); for (int q=0; q<pyramid.deviceTypes.size(); q++) { if (pyramid.deviceTypes.get(q).equals("Camera")) { if (q < pyramid.deviceNames.size()) { store.setDetectorModel(pyramid.deviceNames.get(q), 0, i); } if (q < pyramid.deviceIDs.size()) { store.setDetectorSerialNumber(pyramid.deviceIDs.get(q), 0, i); } if (q < pyramid.deviceManufacturers.size()) { store.setDetectorManufacturer(pyramid.deviceManufacturers.get(q), 0, i); } store.setDetectorType(getDetectorType("CCD"), 0, i); break; } } } int nextPyramid = 0; for (int i=0; i<core.size();) { setCoreIndex(i); Pyramid pyramid = null; if (nextPyramid < pyramids.size()) { pyramid = pyramids.get(nextPyramid++); } int ii = coreIndexToSeries(i); if (pyramid != null) { int nextPlane = 0; int effectiveSizeC = core.get(i).rgb ? 1 : core.get(i).sizeC; for (int c=0; c<effectiveSizeC; c++) { store.setDetectorSettingsID( MetadataTools.createLSID("Detector", 0, nextPyramid - 1), ii, c); store.setDetectorSettingsBinning( getBinning(pyramid.binningX + "x" + pyramid.binningY), ii, c); if (c == 0) { store.setDetectorSettingsGain(pyramid.redGain, ii, c); store.setDetectorSettingsOffset(pyramid.redOffset, ii, c); } else if (c == 1) { store.setDetectorSettingsGain(pyramid.greenGain, ii, c); store.setDetectorSettingsOffset(pyramid.greenOffset, ii, c); } else if (c == 2) { store.setDetectorSettingsGain(pyramid.blueGain, ii, c); store.setDetectorSettingsOffset(pyramid.blueOffset, ii, c); } if (c < pyramid.channelNames.size()) { store.setChannelName(pyramid.channelNames.get(c), ii, c); } if (c < pyramid.channelWavelengths.size()) { int wave = pyramid.channelWavelengths.get(c).intValue(); if (wave > 0) { store.setChannelEmissionWavelength( FormatTools.getEmissionWavelength((double) wave), ii, c); } } for (int z=0; z<core.get(i).sizeZ; z++) { for (int t=0; t<core.get(i).sizeT; t++) { nextPlane = getIndex(z, c, t); Long exp = pyramid.defaultExposureTime; if (c < pyramid.exposureTimes.size()) { exp = pyramid.exposureTimes.get(c); } if (exp != null) { store.setPlaneExposureTime( FormatTools.createTime(exp / 1000000.0, UNITS.S), ii, nextPlane); } store.setPlanePositionX( FormatTools.createLength(pyramid.originX, UNITS.MICROM), ii, nextPlane); store.setPlanePositionY( FormatTools.createLength(pyramid.originY, UNITS.MICROM), ii, nextPlane); } } } } store.setImageInstrumentRef(instrument, ii); if (pyramid != null) { String imageName = pyramid.name; boolean duplicate = false; for (int q=0; q<pyramids.size(); q++) { if (q != (nextPyramid - 1) && imageName.equals(pyramids.get(q).name)) { duplicate = true; break; } } if (!imageName.equals("Overview") && !imageName.equals("Label") && duplicate) { imageName += " #" + ii; } if (imageName.equals("Overview") || imageName.equals("Label")) { imageName = imageName.toLowerCase(); } store.setImageName(imageName, ii); store.setObjectiveSettingsID(MetadataTools.createLSID("Objective", 0, nextPyramid - 1), ii); store.setObjectiveSettingsRefractiveIndex(pyramid.refractiveIndex, ii); if (pyramid.physicalSizeX > 0) { store.setPixelsPhysicalSizeX(FormatTools.getPhysicalSizeX(pyramid.physicalSizeX), ii); } if (pyramid.physicalSizeY > 0) { store.setPixelsPhysicalSizeY(FormatTools.getPhysicalSizeY(pyramid.physicalSizeY), ii); } if (pyramid.acquisitionTime != null) { // acquisition time is stored in seconds store.setImageAcquisitionDate(new Timestamp(DateTools.convertDate( pyramid.acquisitionTime * 1000, DateTools.UNIX)), ii); } } else { store.setImageName("macro image", ii); } i += core.get(i).resolutionCount; } setCoreIndex(0); } // -- Helper methods -- private int getTileSize() { int channels = getRGBChannelCount(); int bpp = FormatTools.getBytesPerPixel(getPixelType()); int index = getCoreIndex(); return bpp * channels * tileX.get(index) * tileY.get(index); } private byte[] decodeTile(int no, int row, int col) throws FormatException, IOException { if (tileMap.get(getCoreIndex()) == null) { return new byte[getTileSize()]; } int[] zct = getZCTCoords(no); TileCoordinate t = new TileCoordinate(nDimensions.get(getCoreIndex())); t.coordinate[0] = col; t.coordinate[1] = row; int resIndex = getResolution(); int pyramidIndex = getSeries(); if (hasFlattenedResolutions()) { int index = 0; pyramidIndex = 0; for (int i=0; i<core.size(); ) { if (index + core.get(i).resolutionCount <= getSeries()) { index += core.get(i).resolutionCount; i += core.get(i).resolutionCount; pyramidIndex++; } else { resIndex = getSeries() - index; break; } } } Pyramid pyramid = pyramids.get(pyramidIndex); for (String dim : pyramid.dimensionOrdering.keySet()) { int index = pyramid.dimensionOrdering.get(dim) + 2; if (dim.equals("Z")) { t.coordinate[index] = zct[0]; } else if (dim.equals("C")) { t.coordinate[index] = zct[1]; } else if (dim.equals("T")) { t.coordinate[index] = zct[2]; } } if (resIndex > 0) { t.coordinate[t.coordinate.length - 1] = resIndex; } ArrayList<TileCoordinate> map = tileMap.get(getCoreIndex()); Integer index = map.indexOf(t); if (index == null || index < 0) { // fill in the tile with the stored background color // usually this is either black or white byte[] tile = new byte[getTileSize()]; byte[] color = backgroundColor.get(getCoreIndex()); if (color != null) { for (int q=0; q<getTileSize(); q+=color.length) { for (int i=0; i<color.length; i++) { tile[q + i] = color[i]; } } } return tile; } Long offset = tileOffsets.get(getCoreIndex())[index]; RandomAccessInputStream ets = new RandomAccessInputStream(fileMap.get(getCoreIndex())); ets.seek(offset); CodecOptions options = new CodecOptions(); options.interleaved = isInterleaved(); options.littleEndian = isLittleEndian(); int tileSize = getTileSize(); if (tileSize == 0) { tileSize = tileX.get(getCoreIndex()) * tileY.get(getCoreIndex()) * 10; } options.maxBytes = (int) (offset + tileSize); byte[] buf = null; long end = index < tileOffsets.get(getCoreIndex()).length - 1 ? tileOffsets.get(getCoreIndex())[index + 1] : ets.length(); IFormatReader reader = null; String file = null; switch (compressionType.get(getCoreIndex())) { case RAW: buf = new byte[tileSize]; ets.read(buf); break; case JPEG: Codec codec = new JPEGCodec(); buf = codec.decompress(ets, options); break; case JPEG_2000: codec = new JPEG2000Codec(); buf = codec.decompress(ets, options); break; case PNG: file = "tile.png"; reader = new APNGReader(); case BMP: if (reader == null) { file = "tile.bmp"; reader = new BMPReader(); } byte[] b = new byte[(int) (end - offset)]; ets.read(b); Location.mapFile(file, new ByteArrayHandle(b)); reader.setId(file); buf = reader.openBytes(0); Location.mapFile(file, null); break; } if (reader != null) { reader.close(); } ets.close(); return buf; } private void parseETSFile(String file, int s) throws FormatException, IOException { fileMap.put(core.size() - 1, file); RandomAccessInputStream etsFile = new RandomAccessInputStream(file); etsFile.order(true); CoreMetadata ms = core.get(getCoreIndex()); // read the volume header String magic = etsFile.readString(4).trim(); if (!magic.equals("SIS")) { throw new FormatException("Unknown magic bytes: " + magic); } int headerSize = etsFile.readInt(); int version = etsFile.readInt(); nDimensions.add(etsFile.readInt()); long additionalHeaderOffset = etsFile.readLong(); int additionalHeaderSize = etsFile.readInt(); etsFile.skipBytes(4); // reserved long usedChunkOffset = etsFile.readLong(); int nUsedChunks = etsFile.readInt(); etsFile.skipBytes(4); // reserved // read the additional header etsFile.seek(additionalHeaderOffset); String moreMagic = etsFile.readString(4).trim(); if (!moreMagic.equals("ETS")) { throw new FormatException("Unknown magic bytes: " + moreMagic); } etsFile.skipBytes(4); // extra version number int pixelType = etsFile.readInt(); ms.sizeC = etsFile.readInt(); int colorspace = etsFile.readInt(); compressionType.add(etsFile.readInt()); int compressionQuality = etsFile.readInt(); tileX.add(etsFile.readInt()); tileY.add(etsFile.readInt()); int tileZ = etsFile.readInt(); etsFile.skipBytes(4 * 17); // pixel info hints byte[] color = new byte[ ms.sizeC * FormatTools.getBytesPerPixel(convertPixelType(pixelType))]; etsFile.read(color); backgroundColor.put(getCoreIndex(), color); etsFile.skipBytes(4 * 10 - color.length); // background color etsFile.skipBytes(4); // component order boolean usePyramid = etsFile.readInt() != 0; ms.rgb = ms.sizeC > 1; // read the used chunks etsFile.seek(usedChunkOffset); tileOffsets.add(new Long[nUsedChunks]); ArrayList<TileCoordinate> tmpTiles = new ArrayList<TileCoordinate>(); for (int chunk=0; chunk<nUsedChunks; chunk++) { etsFile.skipBytes(4); int dimensions = nDimensions.get(nDimensions.size() - 1); TileCoordinate t = new TileCoordinate(dimensions); for (int i=0; i<dimensions; i++) { t.coordinate[i] = etsFile.readInt(); } tileOffsets.get(tileOffsets.size() - 1)[chunk] = etsFile.readLong(); int nBytes = etsFile.readInt(); etsFile.skipBytes(4); tmpTiles.add(t); } int maxResolution = 0; if (usePyramid) { for (TileCoordinate t : tmpTiles) { if (t.coordinate[t.coordinate.length - 1] > maxResolution) { maxResolution = t.coordinate[t.coordinate.length - 1]; } } } maxResolution++; int[] maxX = new int[maxResolution]; int[] maxY = new int[maxResolution]; int[] maxZ = new int[maxResolution]; int[] maxC = new int[maxResolution]; int[] maxT = new int[maxResolution]; HashMap<String, Integer> dimOrder = pyramids.get(s).dimensionOrdering; for (TileCoordinate t : tmpTiles) { int resolution = usePyramid ? t.coordinate[t.coordinate.length - 1] : 0; Integer tv = dimOrder.get("T"); Integer zv = dimOrder.get("Z"); Integer cv = dimOrder.get("C"); int tIndex = tv == null ? -1 : tv + 2; int zIndex = zv == null ? -1 : zv + 2; int cIndex = cv == null ? -1 : cv + 2; if (usePyramid && tIndex == t.coordinate.length - 1) { tv = null; tIndex = -1; } if (usePyramid && zIndex == t.coordinate.length - 1) { zv = null; zIndex = -1; } int upperLimit = usePyramid ? t.coordinate.length - 1 : t.coordinate.length; if ((tIndex < 0 || tIndex >= upperLimit) && (zIndex < 0 || zIndex >= upperLimit) && (cIndex < 0 || cIndex >= upperLimit)) { tIndex--; zIndex--; cIndex--; if (dimOrder.containsKey("T")) { dimOrder.put("T", tIndex - 2); } if (dimOrder.containsKey("Z")) { dimOrder.put("Z", zIndex - 2); } if (dimOrder.containsKey("C")) { dimOrder.put("C", cIndex - 2); } } if (tv == null && zv == null) { if (t.coordinate.length > 4 && cv == null) { cIndex = 2; dimOrder.put("C", cIndex - 2); } if (t.coordinate.length > 4) { if (cv == null) { tIndex = 3; } else { tIndex = cIndex + 2; } if (tIndex < t.coordinate.length) { dimOrder.put("T", tIndex - 2); } else { tIndex = -1; } } if (t.coordinate.length > 5) { if (cv == null) { zIndex = 4; } else { zIndex = cIndex + 1; } if (zIndex < t.coordinate.length) { dimOrder.put("Z", zIndex - 2); } else { zIndex = -1; } } } if (t.coordinate[0] > maxX[resolution]) { maxX[resolution] = t.coordinate[0]; } if (t.coordinate[1] > maxY[resolution]) { maxY[resolution] = t.coordinate[1]; } if (tIndex >= 0 && t.coordinate[tIndex] > maxT[resolution]) { maxT[resolution] = t.coordinate[tIndex]; } if (zIndex >= 0 && t.coordinate[zIndex] > maxZ[resolution]) { maxZ[resolution] = t.coordinate[zIndex]; } if (cIndex >= 0 && t.coordinate[cIndex] > maxC[resolution]) { maxC[resolution] = t.coordinate[cIndex]; } } if (pyramids.get(s).width != null) { ms.sizeX = pyramids.get(s).width; } if (pyramids.get(s).height != null) { ms.sizeY = pyramids.get(s).height; } ms.sizeZ = maxZ[0] + 1; if (maxC[0] > 0) { ms.sizeC *= (maxC[0] + 1); } ms.sizeT = maxT[0] + 1; if (ms.sizeZ == 0) { ms.sizeZ = 1; } ms.imageCount = ms.sizeZ * ms.sizeT; if (maxC[0] > 0) { ms.imageCount *= (maxC[0] + 1); } if (maxY[0] >= 1) { rows.add(maxY[0] + 1); } else { rows.add(1); } if (maxX[0] >= 1) { cols.add(maxX[0] + 1); } else { cols.add(1); } ArrayList<TileCoordinate> map = new ArrayList<TileCoordinate>(); for (int i=0; i<tmpTiles.size(); i++) { map.add(tmpTiles.get(i)); } tileMap.add(map); ms.pixelType = convertPixelType(pixelType); if (usePyramid) { int finalResolution = 1; int initialCoreSize = core.size(); for (int i=1; i<maxResolution; i++) { CoreMetadata newResolution = new CoreMetadata(ms); int previousX = core.get(core.size() - 1).sizeX; int previousY = core.get(core.size() - 1).sizeY; int maxSizeX = tileX.get(tileX.size() - 1) * (maxX[i] < 1 ? 1 : maxX[i] + 1); int maxSizeY = tileY.get(tileY.size() - 1) * (maxY[i] < 1 ? 1 : maxY[i] + 1); newResolution.sizeX = previousX / 2; if (previousX % 2 == 1 && newResolution.sizeX < maxSizeX) { newResolution.sizeX++; } else if (newResolution.sizeX > maxSizeX) { newResolution.sizeX = maxSizeX; } newResolution.sizeY = previousY / 2; if (previousY % 2 == 1 && newResolution.sizeY < maxSizeY) { newResolution.sizeY++; } else if (newResolution.sizeY > maxSizeY) { newResolution.sizeY = maxSizeY; } newResolution.sizeZ = maxZ[i] + 1; if (maxC[i] > 0 && newResolution.sizeC != (maxC[i] + 1)) { newResolution.sizeC *= (maxC[i] + 1); } newResolution.sizeT = maxT[i] + 1; if (newResolution.sizeZ == 0) { newResolution.sizeZ = 1; } newResolution.imageCount = newResolution.sizeZ * newResolution.sizeT; if (maxC[i] > 0) { newResolution.imageCount *= (maxC[i] + 1); } newResolution.metadataComplete = true; newResolution.dimensionOrder = "XYCZT"; core.add(newResolution); rows.add(maxY[i] >= 1 ? maxY[i] + 1 : 1); cols.add(maxX[i] >= 1 ? maxX[i] + 1 : 1); fileMap.put(core.size() - 1, file); finalResolution = core.size() - initialCoreSize + 1; tileX.add(tileX.get(tileX.size() - 1)); tileY.add(tileY.get(tileY.size() - 1)); compressionType.add(compressionType.get(compressionType.size() - 1)); tileMap.add(map); nDimensions.add(nDimensions.get(nDimensions.size() - 1)); tileOffsets.add(tileOffsets.get(tileOffsets.size() - 1)); backgroundColor.put(core.size() - 1, color); } ms.resolutionCount = finalResolution; } etsFile.close(); } private int convertPixelType(int pixelType) throws FormatException { switch (pixelType) { case CHAR: return FormatTools.INT8; case UCHAR: return FormatTools.UINT8; case SHORT: return FormatTools.INT16; case USHORT: return FormatTools.UINT16; case INT: return FormatTools.INT32; case UINT: return FormatTools.UINT32; case LONG: throw new FormatException("Unsupported pixel type: long"); case ULONG: throw new FormatException("Unsupported pixel type: unsigned long"); case FLOAT: return FormatTools.FLOAT; case DOUBLE: return FormatTools.DOUBLE; default: throw new FormatException("Unsupported pixel type: " + pixelType); } } private void readTags(RandomAccessInputStream vsi, boolean populateMetadata, String tagPrefix) { try { // read the VSI header long fp = vsi.getFilePointer(); if (fp + 24 >= vsi.length()) { return; } int headerSize = vsi.readShort(); // should always be 24 int version = vsi.readShort(); // always 21321 int volumeVersion = vsi.readInt(); long dataFieldOffset = vsi.readLong(); int flags = vsi.readInt(); vsi.skipBytes(4); int tagCount = flags & 0xfffffff; if (fp + dataFieldOffset < 0) { return; } vsi.seek(fp + dataFieldOffset); if (vsi.getFilePointer() >= vsi.length()) { return; } LOGGER.debug("parsing {} tags from {}", tagCount, vsi.getFilePointer()); if (tagCount > vsi.length()) { return; } for (int i=0; i<tagCount; i++) { if (vsi.getFilePointer() + 16 >= vsi.length()) { break; } // read the data field int fieldType = vsi.readInt(); int tag = vsi.readInt(); long nextField = vsi.readInt() & 0xffffffffL; int dataSize = vsi.readInt(); LOGGER.debug(" tag #{}: fieldType={}, tag={}, nextField={}, dataSize={}", new Object[] {i, fieldType, tag, nextField, dataSize}); boolean extraTag = ((fieldType & 0x8000000) >> 27) == 1; boolean extendedField = ((fieldType & 0x10000000) >> 28) == 1; boolean inlineData = ((fieldType & 0x40000000) >> 30) == 1; boolean array = (!inlineData && !extendedField) && ((fieldType & 0x20000000) >> 29) == 1; boolean newVolume = ((fieldType & 0x80000000) >> 31) == 1; int realType = fieldType & 0xffffff; int secondTag = -1; if (extraTag) { secondTag = vsi.readInt(); } LOGGER.debug(" inlineData = {}", inlineData); LOGGER.debug(" extraTag = {}", extraTag); LOGGER.debug(" extendedField = {}", extendedField); LOGGER.debug(" realType = {}", realType); if (tag < 0) { if (!inlineData && dataSize + vsi.getFilePointer() < vsi.length()) { vsi.skipBytes(dataSize); } return; } if (tag == EXTERNAL_FILE_PROPERTIES && previousTag == IMAGE_FRAME_VOLUME) { metadataIndex++; } else if (tag == DOCUMENT_PROPERTIES || tag == SLIDE_PROPERTIES) { metadataIndex = -1; } previousTag = tag; while (metadataIndex >= pyramids.size()) { pyramids.add(new Pyramid()); } if (extendedField && realType == NEW_VOLUME_HEADER) { if (tag == DIMENSION_DESCRIPTION_VOLUME) { dimensionTag = secondTag; inDimensionProperties = true; } long endPointer = vsi.getFilePointer() + dataSize; while (vsi.getFilePointer() < endPointer && vsi.getFilePointer() < vsi.length()) { long start = vsi.getFilePointer(); readTags(vsi, populateMetadata || inDimensionProperties, getVolumeName(tag)); long end = vsi.getFilePointer(); if (start >= end) { break; } } if (tag == DIMENSION_DESCRIPTION_VOLUME) { inDimensionProperties = false; foundChannelTag = false; } } else if (extendedField && (realType == PROPERTY_SET_VOLUME || realType == NEW_MDIM_VOLUME_HEADER)) { long endPointer = vsi.getFilePointer() + nextField; while (vsi.getFilePointer() < endPointer && vsi.getFilePointer() < vsi.length()) { long start = vsi.getFilePointer(); String tagName = realType == NEW_MDIM_VOLUME_HEADER ? getVolumeName(tag) : tagPrefix; readTags(vsi, tag != 2037, tagName); long end = vsi.getFilePointer(); if (start == end) { break; } } } else { String tagName = getTagName(tag); String value = inlineData ? String.valueOf(dataSize) : " "; Pyramid pyramid = metadataIndex < 0 ? null : pyramids.get(metadataIndex); if (!inlineData && dataSize > 0) { switch (realType) { case CHAR: case UCHAR: value = String.valueOf(vsi.read()); break; case SHORT: case USHORT: value = String.valueOf(vsi.readShort()); break; case INT: case UINT: case DWORD: case FIELD_TYPE: case MEM_MODEL: case COLOR_SPACE: int intValue = vsi.readInt(); value = String.valueOf(intValue); break; case LONG: case ULONG: case TIMESTAMP: long longValue = vsi.readLong(); value = String.valueOf(longValue); break; case FLOAT: value = String.valueOf(vsi.readFloat()); break; case DOUBLE: case DATE: value = String.valueOf(vsi.readDouble()); break; case BOOLEAN: value = new Boolean(vsi.readBoolean()).toString(); break; case TCHAR: case UNICODE_TCHAR: value = vsi.readString(dataSize); value = DataTools.stripString(value); if (tag == CHANNEL_NAME) { if (pyramid != null) { pyramid.channelNames.add(value); } } else if (tag == STACK_NAME && !value.equals("0")) { if (pyramid != null && pyramid.name == null) { pyramid.name = value; } } break; case INT_2: case INT_INTERVAL: case INT_ARRAY_2: case INT_3: case INT_ARRAY_3: case INT_4: case INT_RECT: case INT_ARRAY_4: case INT_ARRAY_5: case DIM_INDEX_1: case DIM_INDEX_2: case VOLUME_INDEX: case PIXEL_INFO_TYPE: int nIntValues = dataSize / 4; int[] intValues = new int[nIntValues]; value = nIntValues > 1 ? "(" : ""; for (int v=0; v<nIntValues; v++) { intValues[v] = vsi.readInt(); value += intValues[v]; if (v < nIntValues - 1) { value += ", "; } } if (nIntValues > 1) { value += ")"; } if (tag == IMAGE_BOUNDARY) { if (pyramid != null && pyramid.width == null) { pyramid.width = intValues[2]; pyramid.height = intValues[3]; } } break; case COMPLEX: case DOUBLE_2: case DOUBLE_INTERVAL: case DOUBLE_ARRAY_2: case DOUBLE_3: case DOUBLE_ARRAY_3: case DOUBLE_4: case DOUBLE_RECT: case DOUBLE_2_2: case DOUBLE_3_3: case DOUBLE_4_4: int nDoubleValues = dataSize / 8; double[] doubleValues = new double[nDoubleValues]; value = nDoubleValues > 1 ? "(" : ""; for (int v=0; v<nDoubleValues; v++) { doubleValues[v] = vsi.readDouble(); value += doubleValues[v]; if (v < nDoubleValues - 1) { value += ", "; } } if (nDoubleValues > 1) { value += ")"; } if (tag == RWC_FRAME_SCALE) { if (pyramid != null && pyramid.physicalSizeX == null) { pyramid.physicalSizeX = doubleValues[0]; pyramid.physicalSizeY = doubleValues[1]; } } else if (tag == RWC_FRAME_ORIGIN) { if (pyramid != null && pyramid.originX == null) { pyramid.originX = doubleValues[0]; pyramid.originY = doubleValues[1]; } } break; case RGB: int red = vsi.read(); int green = vsi.read(); int blue = vsi.read(); value = "red = " + red + ", green = " + green + ", blue = " + blue; break; case BGR: blue = vsi.read(); green = vsi.read(); red = vsi.read(); value = "red = " + red + ", green = " + green + ", blue = " + blue; break; } } if (metadataIndex >= 0) { try { if (tag == STACK_TYPE) { value = getStackType(value); } else if (tag == DEVICE_SUBTYPE) { value = getDeviceSubtype(value); pyramid.deviceTypes.add(value); } else if (tag == DEVICE_ID) { pyramid.deviceIDs.add(value); } else if (tag == DEVICE_NAME) { pyramid.deviceNames.add(value); } else if (tag == DEVICE_MANUFACTURER) { pyramid.deviceManufacturers.add(value); } else if (tag == EXPOSURE_TIME && tagPrefix.length() == 0) { pyramid.exposureTimes.add(new Long(value)); } else if (tag == EXPOSURE_TIME) { pyramid.defaultExposureTime = new Long(value); } else if (tag == CREATION_TIME && pyramid.acquisitionTime == null) { pyramid.acquisitionTime = new Long(value); } else if (tag == REFRACTIVE_INDEX) { pyramid.refractiveIndex = new Double(value); } else if (tag == OBJECTIVE_MAG) { pyramid.magnification = new Double(value); } else if (tag == NUMERICAL_APERTURE) { pyramid.numericalAperture = new Double(value); } else if (tag == WORKING_DISTANCE) { pyramid.workingDistance = new Double(value); } else if (tag == OBJECTIVE_NAME) { pyramid.objectiveNames.add(value); } else if (tag == OBJECTIVE_TYPE) { pyramid.objectiveTypes.add(new Integer(value)); } else if (tag == BIT_DEPTH) { pyramid.bitDepth = new Integer(value); } else if (tag == X_BINNING) { pyramid.binningX = new Integer(value); } else if (tag == Y_BINNING) { pyramid.binningY = new Integer(value); } else if (tag == CAMERA_GAIN) { pyramid.gain = new Double(value); } else if (tag == CAMERA_OFFSET) { pyramid.offset = new Double(value); } else if (tag == RED_GAIN) { pyramid.redGain = new Double(value); } else if (tag == GREEN_GAIN) { pyramid.greenGain = new Double(value); } else if (tag == BLUE_GAIN) { pyramid.blueGain = new Double(value); } else if (tag == RED_OFFSET) { pyramid.redOffset = new Double(value); } else if (tag == GREEN_OFFSET) { pyramid.greenOffset = new Double(value); } else if (tag == BLUE_OFFSET) { pyramid.blueOffset = new Double(value); } else if (tag == VALUE) { if (tagPrefix.equals("Channel Wavelength ")) { pyramid.channelWavelengths.add(new Double(value)); } else if (tagPrefix.startsWith("Objective Working Distance")) { pyramid.workingDistance = new Double(value); } } } catch (NumberFormatException e) { LOGGER.debug("Could not parse tag " + tag, e); } } if (tag == DOCUMENT_TIME || tag == CREATION_TIME) { value = DateTools.convertDate( Long.parseLong(value) * 1000, DateTools.UNIX); } if (tagName != null && populateMetadata) { if (metadataIndex >= 0) { addMetaList(tagPrefix + tagName, value, pyramids.get(metadataIndex).originalMetadata); } else if (tag != VALUE || tagPrefix.length() > 0) { addGlobalMetaList(tagPrefix + tagName, value); } } } if (inDimensionProperties) { Pyramid p = pyramids.get(metadataIndex); if (tag == Z_START && !p.dimensionOrdering.containsValue(dimensionTag)) { p.dimensionOrdering.put("Z", dimensionTag); } else if ((tag == TIME_START || tag == DIMENSION_VALUE_ID) && !p.dimensionOrdering.containsValue(dimensionTag)) { p.dimensionOrdering.put("T", dimensionTag); } else if (tag == LAMBDA_START && !p.dimensionOrdering.containsValue(dimensionTag)) { p.dimensionOrdering.put("L", dimensionTag); } else if (tag == CHANNEL_PROPERTIES && foundChannelTag && !p.dimensionOrdering.containsValue(dimensionTag)) { p.dimensionOrdering.put("C", dimensionTag); } else if (tag == CHANNEL_PROPERTIES) { foundChannelTag = true; } } if (nextField == 0 || tag == -494804095) { return; } if (fp + nextField < vsi.length() && fp + nextField >= 0) { vsi.seek(fp + nextField); } else break; } } catch (Exception e) { LOGGER.debug("Failed to read all tags", e); } } private String getVolumeName(int tag) { switch (tag) { case COLLECTION_VOLUME: case MULTIDIM_IMAGE_VOLUME: case IMAGE_FRAME_VOLUME: case DIMENSION_SIZE: case IMAGE_COLLECTION_PROPERTIES: case MULTIDIM_STACK_PROPERTIES: case FRAME_PROPERTIES: case DIMENSION_DESCRIPTION_VOLUME: case CHANNEL_PROPERTIES: case DISPLAY_MAPPING_VOLUME: case LAYER_INFO_PROPERTIES: return ""; case OPTICAL_PATH: return "Microscope "; case 2417: return "Channel Wavelength "; case WORKING_DISTANCE: return "Objective Working Distance "; } LOGGER.debug("Unhandled volume {}", tag); return ""; } private String getTagName(int tag) { switch (tag) { case Y_PLANE_DIMENSION_UNIT: return "Image plane rectangle unit (Y dimension)"; case Y_DIMENSION_UNIT: return "Y dimension unit"; case CHANNEL_OVERFLOW: return "Channel under/overflow"; case SLIDE_SPECIMEN: return "Specimen"; case SLIDE_TISSUE: return "Tissue"; case SLIDE_PREPARATION: return "Preparation"; case SLIDE_STAINING: return "Staining"; case SLIDE_INFO: return "Slide Info"; case SLIDE_NAME: return "Slide Name"; case EXPOSURE_TIME: return "Exposure time (microseconds)"; case CAMERA_GAIN: return "Camera gain"; case CAMERA_OFFSET: return "Camera offset"; case CAMERA_GAMMA: return "Gamma"; case SHARPNESS: return "Sharpness"; case RED_GAIN: return "Red channel gain"; case GREEN_GAIN: return "Green channel gain"; case BLUE_GAIN: return "Blue channel gain"; case RED_OFFSET: return "Red channel offset"; case GREEN_OFFSET: return "Green channel offset"; case BLUE_OFFSET: return "Blue channel offset"; case SHADING_SUB: return "Shading sub"; case SHADING_MUL: return "Shading mul"; case X_BINNING: return "Binning (X)"; case Y_BINNING: return "Binning (Y)"; case CLIPPING: return "Clipping"; case MIRROR_H: return "Mirror (horizontal)"; case MIRROR_V: return "Mirror (vertical)"; case CLIPPING_STATE: return "Clipping state"; case ICC_ENABLED: return "ICC enabled"; case BRIGHTNESS: return "Brightness"; case CONTRAST: return "Contrast"; case CONTRAST_TARGET: return "Contrast reference"; case ACCUMULATION: return "Camera accumulation"; case AVERAGING: return "Camera averaging"; case ISO_SENSITIVITY: return "ISO sensitivity"; case ACCUMULATION_MODE: return "Camera accumulation mode"; case AUTOEXPOSURE: return "Autoexposure enabled"; case EXPOSURE_METERING_MODE: return "Autoexposure metering mode"; case Z_START: return "Z stack start"; case Z_INCREMENT: return "Z stack increment"; case Z_VALUE: return "Z position"; case TIME_START: return "Timelapse start"; case TIME_INCREMENT: return "Timelapse increment"; case TIME_VALUE: return "Timestamp"; case LAMBDA_START: return "Lambda start"; case LAMBDA_INCREMENT: return "Lambda increment"; case LAMBDA_VALUE: return "Lambda value"; case DIMENSION_NAME: return "Dimension name"; case DIMENSION_MEANING: return "Dimension description"; case DIMENSION_START_ID: return "Dimension start ID"; case DIMENSION_INCREMENT_ID: return "Dimension increment ID"; case DIMENSION_VALUE_ID: return "Dimension value ID"; case IMAGE_BOUNDARY: return "Image size"; case TILE_SYSTEM: return "Tile system"; case HAS_EXTERNAL_FILE: return "External file present"; case EXTERNAL_DATA_VOLUME: return "External file volume"; case TILE_ORIGIN: return "Origin of tile coordinate system"; case DISPLAY_LIMITS: return "Display limits"; case STACK_DISPLAY_LUT: return "Stack display LUT"; case GAMMA_CORRECTION: return "Gamma correction"; case FRAME_ORIGIN: return "Frame origin (plane coordinates)"; case FRAME_SCALE: return "Frame scale (plane coordinates)"; case DISPLAY_COLOR: return "Display color"; case CREATION_TIME: return "Creation time (UTC)"; case RWC_FRAME_ORIGIN: return "Origin"; case RWC_FRAME_SCALE: return "Calibration"; case RWC_FRAME_UNIT: return "Calibration units"; case STACK_NAME: return "Layer"; case CHANNEL_DIM: return "Channel dimension"; case STACK_TYPE: return "Image Type"; case LIVE_OVERFLOW: return "Live overflow"; case IS_TRANSMISSION: return "IS transmission mask"; case CONTRAST_BRIGHTNESS: return "Contrast and brightness"; case ACQUISITION_PROPERTIES: return "Acquisition properties"; case GRADIENT_LUT: return "Gradient LUT"; case DISPLAY_PROCESSOR_TYPE: return "Display processor type"; case RENDER_OPERATION_ID: return "Render operation ID"; case DISPLAY_STACK_ID: return "Displayed stack ID"; case TRANSPARENCY_ID: return "Transparency ID"; case THIRD_ID: return "Display third ID"; case DISPLAY_VISIBLE: return "Display visible"; case TRANSPARENCY_VALUE: return "Transparency value"; case DISPLAY_LUT: return "Display LUT"; case DISPLAY_STACK_INDEX: return "Display stack index"; case CHANNEL_TRANSPARENCY_VALUE: return "Channel transparency value"; case CHANNEL_VISIBLE: return "Channel visible"; case SELECTED_CHANNELS: return "List of selected channels"; case DISPLAY_GAMMA_CORRECTION: return "Display gamma correction"; case CHANNEL_GAMMA_CORRECTION: return "Channel gamma correction"; case DISPLAY_CONTRAST_BRIGHTNESS: return "Display contrast and brightness"; case CHANNEL_CONTRAST_BRIGHTNESS: return "Channel contrast and brightness"; case ACTIVE_STACK_DIMENSION: return "Active stack dimension"; case SELECTED_FRAMES: return "Selected frames"; case DISPLAYED_LUT_ID: return "Displayed LUT ID"; case HIDDEN_LAYER: return "Hidden layer"; case LAYER_XY_FIXED: return "Layer fixed in XY"; case ACTIVE_LAYER_VECTOR: return "Active layer vector"; case ACTIVE_LAYER_INDEX_VECTOR: return "Active layer index vector"; case CHAINED_LAYERS: return "Chained layers"; case LAYER_SELECTION: return "Layer selection"; case LAYER_SELECTION_INDEX: return "Layer selection index"; case CANVAS_COLOR_1: return "Canvas background color 1"; case CANVAS_COLOR_2: return "Canvas background color 2"; case ORIGINAL_FRAME_RATE: return "Original frame rate (ms)"; case USE_ORIGINAL_FRAME_RATE: return "Use original frame rate"; case ACTIVE_CHANNEL: return "Active channel"; case PLANE_UNIT: return "Plane unit"; case PLANE_ORIGIN_RWC: return "Origin"; case PLANE_SCALE_RWC: return "Physical pixel size"; case MAGNIFICATION: return "Original magnification"; case DOCUMENT_NAME: return "Document Name"; case DOCUMENT_NOTE: return "Document Note"; case DOCUMENT_TIME: return "Document Creation Time"; case DOCUMENT_AUTHOR: return "Document Author"; case DOCUMENT_COMPANY: return "Document Company"; case DOCUMENT_CREATOR_NAME: return "Document creator name"; case DOCUMENT_CREATOR_MAJOR_VERSION: return "Document creator major version"; case DOCUMENT_CREATOR_MINOR_VERSION: return "Document creator minor version"; case DOCUMENT_CREATOR_SUB_VERSION: return "Document creator sub version"; case DOCUMENT_CREATOR_BUILD_NUMBER: return "Product Build Number"; case DOCUMENT_CREATOR_PACKAGE: return "Document creator package"; case DOCUMENT_PRODUCT: return "Document product"; case DOCUMENT_PRODUCT_NAME: return "Document product name"; case DOCUMENT_PRODUCT_VERSION: return "Document product version"; case DOCUMENT_TYPE_HINT: return "Document type hint"; case DOCUMENT_THUMB: return "Document thumbnail"; case COARSE_PYRAMID_LEVEL: return "Coarse pyramid level"; case EXTRA_SAMPLES: return "Extra samples"; case DEFAULT_BACKGROUND_COLOR: return "Default background color"; case VERSION_NUMBER: return "Version number"; case CHANNEL_NAME: return "Channel name"; case OBJECTIVE_MAG: return "Magnification"; case NUMERICAL_APERTURE: return "Numerical Aperture"; case WORKING_DISTANCE: return "Objective Working Distance"; case OBJECTIVE_NAME: return "Objective Name"; case OBJECTIVE_TYPE: return "Objective Type"; case 120065: return "Objective Description"; case 120066: return "Objective Subtype"; case 120069: return "Brightness Correction"; case 120070: return "Objective Lens"; case 120075: return "Objective X Shift"; case 120076: return "Objective Y Shift"; case 120077: return "Objective Z Shift"; case 120078: return "Objective Gear Setting"; case 120635: return "Slide Bar Code"; case 120638: return "Tray No."; case 120637: return "Slide No."; case 34: return "Product Name"; case 35: return "Product Version"; case DEVICE_NAME: return "Device Name"; case BIT_DEPTH: return "Camera Actual Bit Depth"; case 120001: return "Device Position"; case 120050: return "TV Adapter Magnification"; case REFRACTIVE_INDEX: return "Objective Refractive Index"; case 120117: return "Device Type"; case DEVICE_ID: return "Device Unit ID"; case DEVICE_SUBTYPE: return "Device Subtype"; case 120132: return "Device Model"; case DEVICE_MANUFACTURER: return "Device Manufacturer"; case 121102: return "Stage Insert Position"; case 121131: return "Laser/Lamp Intensity"; case 268435456: return "Units"; case VALUE: return "Value"; case 175208: return "Snapshot Count"; case 175209: return "Scanning Time (seconds)"; case 120210: return "Device Configuration Position"; case 120211: return "Device Configuration Index"; case 124000: return "Aperture Max Mode"; case FRAME_SIZE: return "Camera Maximum Frame Size"; case HDRI_ON: return "Camera HDRI Enabled"; case HDRI_FRAMES: return "Camera Images per HDRI image"; case HDRI_EXPOSURE_RANGE: return "Camera HDRI Exposure Ratio"; case HDRI_MAP_MODE: return "Camera HDRI Mapping Mode"; case CUSTOM_GRAYSCALE: return "Camera Custom Grayscale Value"; case SATURATION: return "Camera Saturation"; case WB_PRESET_ID: return "Camera White Balance Preset ID"; case WB_PRESET_NAME: return "Camera White Balance Preset Name"; case WB_MODE: return "Camera White Balance Mode"; case CCD_SENSITIVITY: return "Camera CCD Sensitivity"; case ENHANCED_DYNAMIC_RANGE: return "Camera Enhanced Dynamic Range"; case PIXEL_CLOCK: return "Camera Pixel Clock (MHz)"; case COLORSPACE: return "Camera Colorspace"; case COOLING_ON: return "Camera Cooling Enabled"; case FAN_SPEED: return "Camera Cooling Fan Speed"; case TEMPERATURE_TARGET: return "Camera Cooling Temperature Target"; case GAIN_UNIT: return "Camera Gain Unit"; case EM_GAIN: return "Camera EM Gain"; case PHOTON_IMAGING_MODE: return "Camera Photon Imaging Mode"; case FRAME_TRANSFER: return "Camera Frame Transfer Enabled"; case ANDOR_SHIFT_SPEED: return "Camera iXon Shift Speed"; case VCLOCK_AMPLITUDE: return "Camera Vertical Clock Amplitude"; case SPURIOUS_NOISE_REMOVAL: return "Camera Spurious Noise Removal Enabled"; case SIGNAL_OUTPUT: return "Camera Signal Output"; case BASELINE_OFFSET_CLAMP: return "Camera Baseline Offset Clamp"; case DP80_FRAME_CENTERING: return "Camera DP80 Frame Centering"; case HOT_PIXEL_CORRECTION: return "Camera Hot Pixel Correction Enabled"; case NOISE_REDUCTION: return "Camera Noise Reduction"; case WIDER: return "Camera WiDER"; case PHOTOBLEACHING: return "Camera Photobleaching Enabled"; case PREAMP_GAIN_VALUE: return "Camera Preamp Gain"; case WIDER_ENABLED: return "Camera WiDER Enabled"; } LOGGER.debug("Unhandled tag {}", tag); return null; } private String getDeviceSubtype(String type) { int deviceType = Integer.parseInt(type); switch (deviceType) { case 0: return "Camera"; case 10000: return "Stage"; case 20000: return "Objective revolver"; case 20001: return "TV Adapter"; case 20002: return "Filter Wheel"; case 20003: return "Lamp"; case 20004: return "Aperture Stop"; case 20005: return "Shutter"; case 20006: return "Objective"; case 20007: return "Objective Changer"; case 20008: return "TopLens"; case 20009: return "Prism"; case 20010: return "Zoom"; case 20011: return "DSU"; case 20012: return "ZDC"; case 20050: return "Stage Insert"; case 30000: return "Slide Loader"; case 40000: return "Manual Control"; case 40500: return "Microscope Frame"; } return type; } private String getStackType(String type) { int stackType = Integer.parseInt(type); switch (stackType) { case DEFAULT_IMAGE: return "Default image"; case OVERVIEW_IMAGE: return "Overview image"; case SAMPLE_MASK: return "Sample mask"; case FOCUS_IMAGE: return "Focus image"; case EFI_SHARPNESS_MAP: return "EFI sharpness map"; case EFI_HEIGHT_MAP: return "EFI height map"; case EFI_TEXTURE_MAP: return "EFI texture map"; case EFI_STACK: return "EFI stack"; case MACRO_IMAGE: return "Macro image"; } return type; } // -- Helper class -- class TileCoordinate { public int[] coordinate; public TileCoordinate(int nDimensions) { coordinate = new int[nDimensions]; } @Override public boolean equals(Object o) { if (!(o instanceof TileCoordinate)) { return false; } TileCoordinate t = (TileCoordinate) o; if (coordinate.length != t.coordinate.length) { return false; } for (int i=0; i<coordinate.length; i++) { if (coordinate[i] != t.coordinate[i]) { return false; } } return true; } @Override public String toString() { StringBuffer b = new StringBuffer("{"); for (int p : coordinate) { b.append(p); b.append(", "); } b.append("}"); return b.toString(); } } class Pyramid { public String name; public Double magnification; public Double numericalAperture; public String objectiveName; public Double refractiveIndex; public Double workingDistance; public Integer width; public Integer height; public Double originX; public Double originY; public Double physicalSizeX; public Double physicalSizeY; public Long acquisitionTime; public Integer bitDepth; public Integer binningX; public Integer binningY; public Double gain; public Double offset; public Double redGain; public Double greenGain; public Double blueGain; public Double redOffset; public Double greenOffset; public Double blueOffset; public ArrayList<String> channelNames = new ArrayList<String>(); public ArrayList<Double> channelWavelengths = new ArrayList<Double>(); public ArrayList<Long> exposureTimes = new ArrayList<Long>(); public Long defaultExposureTime; public ArrayList<String> objectiveNames = new ArrayList<String>(); public ArrayList<Integer> objectiveTypes = new ArrayList<Integer>(); public ArrayList<String> deviceNames = new ArrayList<String>(); public ArrayList<String> deviceTypes = new ArrayList<String>(); public ArrayList<String> deviceIDs = new ArrayList<String>(); public ArrayList<String> deviceManufacturers = new ArrayList<String>(); public Hashtable<String, Object> originalMetadata = new Hashtable<String, Object>(); public HashMap<String, Integer> dimensionOrdering = new HashMap<String, Integer>(); } }