Search in sources :

Example 21 with ByteArrayHandle

use of loci.common.ByteArrayHandle in project bioformats by openmicroscopy.

the class TiffSaver method writeIFD.

public void writeIFD(IFD ifd, long nextOffset) throws FormatException, IOException {
    TreeSet<Integer> keys = new TreeSet<Integer>(ifd.keySet());
    int keyCount = keys.size();
    if (ifd.containsKey(new Integer(IFD.LITTLE_ENDIAN)))
        keyCount--;
    if (ifd.containsKey(new Integer(IFD.BIG_TIFF)))
        keyCount--;
    if (ifd.containsKey(new Integer(IFD.REUSE)))
        keyCount--;
    long fp = out.getFilePointer();
    int bytesPerEntry = bigTiff ? TiffConstants.BIG_TIFF_BYTES_PER_ENTRY : TiffConstants.BYTES_PER_ENTRY;
    int ifdBytes = (bigTiff ? 16 : 6) + bytesPerEntry * keyCount;
    if (bigTiff)
        out.writeLong(keyCount);
    else
        out.writeShort(keyCount);
    ByteArrayHandle extra = new ByteArrayHandle();
    RandomAccessOutputStream extraStream = new RandomAccessOutputStream(extra);
    for (Integer key : keys) {
        if (key.equals(IFD.LITTLE_ENDIAN) || key.equals(IFD.BIG_TIFF) || key.equals(IFD.REUSE))
            continue;
        Object value = ifd.get(key);
        writeIFDValue(extraStream, ifdBytes + fp, key.intValue(), value);
    }
    if (bigTiff)
        out.seek(out.getFilePointer());
    writeIntValue(out, nextOffset);
    out.write(extra.getBytes(), 0, (int) extra.length());
    extraStream.close();
}
Also used : TreeSet(java.util.TreeSet) RandomAccessOutputStream(loci.common.RandomAccessOutputStream) ByteArrayHandle(loci.common.ByteArrayHandle)

Example 22 with ByteArrayHandle

use of loci.common.ByteArrayHandle in project bioformats by openmicroscopy.

the class NativeND2Reader method initFile.

/* @see loci.formats.FormatReader#initFile(String) */
@Override
protected void initFile(String id) throws FormatException, IOException {
    super.initFile(id);
    // using a 32KB buffer instead of the default 1MB gives
    // better performance with the seek/skip pattern used here
    in = new RandomAccessInputStream(id, BUFFER_SIZE);
    boolean useChunkMap = useChunkMap();
    LOGGER.debug("Attempting to use chunk map = {}", useChunkMap);
    channelColors = new HashMap<String, Integer>();
    if (in.read() == -38 && in.read() == -50) {
        // newer version of ND2 - doesn't use JPEG2000
        LOGGER.info("Searching for blocks");
        isJPEG = false;
        in.seek(0);
        in.order(true);
        // assemble offsets to each block
        ArrayList<String> imageNames = new ArrayList<String>();
        ArrayList<Long> imageOffsets = new ArrayList<Long>();
        ArrayList<int[]> imageLengths = new ArrayList<int[]>();
        ArrayList<Long> customDataOffsets = new ArrayList<Long>();
        ArrayList<int[]> customDataLengths = new ArrayList<int[]>();
        // order matters when working with the text blocks, which is
        // why two ArrayLists are used instead of a HashMap
        ArrayList<String> textStrings = new ArrayList<String>();
        ArrayList<Boolean> validDimensions = new ArrayList<Boolean>();
        ByteArrayHandle xml = new ByteArrayHandle();
        final StringBuilder name = new StringBuilder();
        int extraZDataCount = 0;
        boolean foundMetadata = false;
        boolean foundAttributes = false;
        boolean useLastText = false;
        int blockCount = 0;
        TreeMap<Long, ChunkMapEntry> allChunkPositions = new TreeMap<Long, ChunkMapEntry>();
        if (useChunkMap) {
            /* In modern ND2 files, the chunk map is stored near the end, and contains
         * a list of blocks and their offsets. By using these offsets instead of
         * scanning through the whole file, an enormous speed up can be achieved.
         *
         * Implementation: Read the chunk map beforehand, process the file as normally,
         * once the first ImageDataSeq block is reached, add all images and skip past the
         * image data to process remaining metadata.
         * I haven't read through all of NativeND2Reader, but I hope to have the least
         * chance of inadvertedly breaking something by this approach.
         */
            String chunkMapSignature = "ND2 CHUNK MAP SIGNATURE 0000001";
            in.seek(in.length() - 40);
            if (!in.readString(chunkMapSignature.length()).equals(chunkMapSignature)) {
                useChunkMap = false;
                LOGGER.info("ND2 Warning: No chunk map found!");
            } else {
                in.skipBytes(1);
                long chunkMapPosition = in.readLong();
                in.seek(chunkMapPosition);
                int tmpLenOne = in.readInt();
                int tmpLenTwo = in.readInt();
                long chunkMapLength = in.readLong();
                chunkMapPosition += 16 + tmpLenTwo;
                in.seek(chunkMapPosition);
                long chunkMapEnd = chunkMapPosition + chunkMapLength;
                int imageDataCount = 0;
                int maxImageIndex = -1;
                while (in.getFilePointer() + 1 + 16 < chunkMapEnd) {
                    char b = (char) in.readByte();
                    while (b != '!') {
                        name.append(b);
                        b = (char) in.readByte();
                    }
                    ChunkMapEntry entry = new ChunkMapEntry();
                    entry.name = name.toString();
                    name.delete(0, name.length());
                    if (entry.name.equals(chunkMapSignature))
                        break;
                    entry.position = in.readLong();
                    entry.length = in.readLong();
                    if (entry.name.startsWith("ImageDataSeq|")) {
                        imageDataCount++;
                        int imageIndex = -1;
                        try {
                            imageIndex = Integer.parseInt(entry.name.substring("ImageDataSeq|".length()));
                            if (imageIndex > maxImageIndex) {
                                maxImageIndex = imageIndex;
                            }
                        } catch (NumberFormatException e) {
                            LOGGER.trace(entry.name, e);
                        }
                    }
                    allChunkPositions.put(entry.position, entry);
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("ND2 {}", entry.toString());
                    }
                }
                if (imageDataCount != maxImageIndex + 1) {
                    LOGGER.warn("Discarding chunk map; image data count = {}, max index = {}", imageDataCount, maxImageIndex);
                    useChunkMap = false;
                }
            }
            in.seek(0);
        }
        // search for blocks
        // 0xDACEBE0A
        byte[] sigBytes = { -38, -50, -66, 10 };
        byte[] buf = new byte[BUFFER_SIZE];
        if (useChunkMap) {
            long checkEvery = in.length() / 10;
            long nextCheck = 0;
            for (ChunkMapEntry entry : allChunkPositions.values()) {
                if (!entry.name.startsWith("ImageDataSeq")) {
                    continue;
                }
                if (entry.position > nextCheck) {
                    in.seek(entry.position);
                    in.read(buf, 0, sigBytes.length);
                    if (!(buf[0] == sigBytes[0] && buf[1] == sigBytes[1] && buf[2] == sigBytes[2] && buf[3] == sigBytes[3])) {
                        LOGGER.warn("Broken ND2 File detected! Disabling chunk map processing.");
                        useChunkMap = false;
                        break;
                    }
                    nextCheck = entry.position + checkEvery;
                }
            }
        }
        int chunkmapSkips = 0;
        in.seek(0);
        while (in.getFilePointer() < in.length() - 1 && in.getFilePointer() >= 0) {
            int foundIndex = -1;
            in.read(buf, 0, sigBytes.length);
            while (foundIndex == -1 && in.getFilePointer() < in.length()) {
                int n = in.read(buf, sigBytes.length, buf.length - sigBytes.length);
                for (int i = 0; i < buf.length - sigBytes.length; i++) {
                    for (int j = 0; j < sigBytes.length; j++) {
                        if (buf[i + j] != sigBytes[j])
                            break;
                        if (j == sigBytes.length - 1)
                            foundIndex = i;
                    }
                    if (foundIndex != -1)
                        break;
                }
                if (foundIndex == -1) {
                    // likely faster than System.arraycopy
                    buf[0] = buf[buf.length - 4];
                    buf[1] = buf[buf.length - 3];
                    buf[2] = buf[buf.length - 2];
                    buf[3] = buf[buf.length - 1];
                } else if (in.getFilePointer() - n + foundIndex < in.length()) {
                    in.seek(in.getFilePointer() - n + foundIndex);
                }
            }
            if (in.getFilePointer() > in.length() - 24 || foundIndex == -1) {
                break;
            }
            // Remember starting position
            Long helper = in.getFilePointer();
            // Length of the block name
            int nameLength = in.readInt();
            // Length of the data
            long dataLength = in.readLong();
            // Read the name
            String nameAttri = in.readString(nameLength).trim();
            // Where this block ends
            Long stop = helper + (dataLength + nameLength);
            // Only 1 MetadataSeq is needed
            boolean seq = false;
            // (we are interested only in xxxxLV - LV = light variant)
            if (nameAttri.contains("MetadataLV") || nameAttri.contains("CalibrationLV") || (nameAttri.contains("MetadataSeqLV") && !seq)) {
                // prevent position count from being doubled
                if (nameAttri.equals("ImageMetadataLV!")) {
                    positionCount = 0;
                }
                iterateIn(in, stop);
            }
            // (others should be same, and time is saved elsewhere)
            if (nameAttri.contains("MetadataSeqLV")) {
                seq = true;
            }
            // Return to starting position
            in.seek(helper + 12);
            int len = (int) (nameLength + dataLength);
            long fp = in.getFilePointer();
            String blockType = in.readString(12);
            int percent = (int) (100 * fp / in.length());
            LOGGER.info("Parsing block '{}' {}%", blockType, percent);
            blockCount++;
            int skip = len - 12 - nameLength * 2;
            if (skip <= 0)
                skip += nameLength * 2;
            if (blockType.endsWith("Calibra")) {
                long veryStart = in.getFilePointer();
                // ImageCalibra|tionLV
                in.skipBytes(12);
                long endFP = in.getFilePointer() + len - 24;
                while (in.read() == 0) ;
                while (in.getFilePointer() < endFP) {
                    int nameLen = in.read();
                    if (nameLen == 0) {
                        in.seek(in.getFilePointer() - 3);
                        nameLen = in.read();
                    }
                    if (nameLen < 0) {
                        break;
                    }
                    // Get data
                    String attributeName = DataTools.stripString(in.readString(nameLen * 2));
                    double valueOrLength = in.readDouble();
                    if (attributeName.equals("dCalibration")) {
                        if (valueOrLength > 0) {
                            addGlobalMeta(attributeName, valueOrLength);
                            if (trueSizeX == 0) {
                                trueSizeX = valueOrLength;
                            } else if (trueSizeY == 0) {
                                trueSizeY = valueOrLength;
                            }
                        }
                        // Done with calibration
                        break;
                    }
                }
                // For old nd2 files
                in.seek(veryStart);
            }
            if (blockType.startsWith("ImageDataSeq")) {
                if (foundMetadata && foundAttributes) {
                    imageOffsets.clear();
                    imageNames.clear();
                    imageLengths.clear();
                    customDataOffsets.clear();
                    customDataLengths.clear();
                    foundMetadata = false;
                    foundAttributes = false;
                    extraZDataCount = 0;
                    useLastText = true;
                }
                if (useChunkMap && chunkmapSkips == 0) {
                    ChunkMapEntry lastImage = null;
                    // sanity check: see if the chunk we just found is actually in the chunkmap ...
                    long lookupPosition = in.getFilePointer() - 28;
                    Long lookupResult = allChunkPositions.floorKey(lookupPosition);
                    if (lookupResult == null || lookupResult != lookupPosition) {
                        // if not, deactivate chunkmap processing and try classic
                        useChunkMap = false;
                        in.seek(lookupPosition);
                        continue;
                    }
                    for (ChunkMapEntry entry : allChunkPositions.values()) {
                        if ((entry.position + 28) < in.getFilePointer()) {
                            continue;
                        }
                        if (!entry.name.startsWith("ImageDataSeq")) {
                            break;
                        }
                        if (lastImage != null) {
                            chunkmapSkips = (int) (((entry.position - lastImage.position) / entry.length) - 1);
                            if (chunkmapSkips > 0) {
                                break;
                            }
                        }
                        lastImage = entry;
                        imageOffsets.add(new Long(entry.position + 16));
                        int realLength = (int) Math.max(entry.name.length() + 1, nameLength);
                        imageLengths.add(new int[] { realLength, (int) (entry.length - nameLength - 16), getSizeX() * getSizeY() });
                        imageNames.add(entry.name.substring(12));
                        blockCount++;
                        percent = (int) (100 * entry.position / in.length());
                        LOGGER.info("Parsing block '{}' {}%", "ImageDataSeq", percent);
                    }
                    // one was already added by the outer blockCount ++;
                    blockCount--;
                    if (lastImage.position + lastImage.length >= in.length()) {
                        in.seek(lastImage.position + 16);
                    } else {
                        in.seek(lastImage.position + lastImage.length);
                    }
                    continue;
                }
                if (chunkmapSkips > 0) {
                    chunkmapSkips -= 1;
                }
                dataLength -= 31;
                LOGGER.debug("Adding non-chunkmap offset {}, nameLength = {}, dataLength = {}", fp, nameLength, dataLength);
                imageOffsets.add(fp);
                imageLengths.add(new int[] { nameLength, (int) dataLength, getSizeX() * getSizeY() });
                char b = (char) in.readByte();
                while (b != '!') {
                    name.append(b);
                    b = (char) in.readByte();
                }
                imageNames.add(name.toString());
                name.setLength(0);
            } else if (blockType.startsWith("ImageText")) {
                foundMetadata = true;
                in.skipBytes(6);
                while (in.read() == 0) ;
                long startFP = in.getFilePointer();
                in.seek(startFP - 1);
                String textString = DataTools.stripString(in.readString((int) dataLength));
                textStrings.add(textString);
                validDimensions.add(blockCount > 2);
                if (!textString.startsWith("<")) {
                    skip = 0;
                }
            } else if (blockType.startsWith("Image") || blockType.startsWith("CustomDataVa")) {
                if (blockType.equals("ImageAttribu")) {
                    foundAttributes = true;
                    in.skipBytes(6);
                    long endFP = in.getFilePointer() + len - 18;
                    while (in.read() == 0) ;
                    boolean canBeLossless = true;
                    while (in.getFilePointer() < endFP) {
                        int nameLen = in.read();
                        if (nameLen == 0) {
                            in.seek(in.getFilePointer() - 3);
                            nameLen = in.read();
                        }
                        if (nameLen < 0) {
                            break;
                        }
                        long start = in.getFilePointer();
                        String attributeName = DataTools.stripString(in.readString(nameLen * 2));
                        if (attributeName.startsWith("xml ") || attributeName.startsWith("ml version") || attributeName.startsWith("l version") || attributeName.startsWith("version")) {
                            if (attributeName.startsWith("xml ")) {
                                in.seek(start - 2);
                            } else if (attributeName.startsWith("ml version")) {
                                in.seek(start - 3);
                            } else if (attributeName.startsWith("l version")) {
                                in.seek(start - 4);
                            } else {
                                in.seek(start - 6);
                            }
                            attributeName = in.readCString();
                            String xmlString = XMLTools.sanitizeXML(attributeName.trim());
                            xmlString = xmlString.substring(0, xmlString.lastIndexOf(">") + 1);
                            if (xmlString.startsWith("<?xml")) {
                                xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
                            }
                            if (!xmlString.endsWith("</variant>")) {
                                xmlString += "</variant>";
                            }
                            if (getDimensionOrder() == null) {
                                core.get(0).dimensionOrder = "";
                            }
                            try {
                                ND2Handler handler = new ND2Handler(core, imageOffsets.size());
                                XMLTools.parseXML(xmlString, handler);
                                xmlString = null;
                                core = handler.getCoreMetadataList();
                                if (backupHandler == null) {
                                    backupHandler = handler;
                                }
                            } catch (IOException e) {
                                LOGGER.debug("Could not parse XML", e);
                            }
                            in.seek(in.getFilePointer() - 8);
                            break;
                        }
                        int valueOrLength = in.readInt();
                        addGlobalMeta(attributeName, valueOrLength);
                        if (attributeName.equals("uiWidth")) {
                            core.get(0).sizeX = valueOrLength;
                        } else if (attributeName.equals("uiHeight")) {
                            core.get(0).sizeY = valueOrLength;
                        } else if (attributeName.equals("uiComp")) {
                            core.get(0).sizeC = valueOrLength;
                        } else if (attributeName.equals("uiBpcInMemory")) {
                            core.get(0).pixelType = FormatTools.pixelTypeFromBytes(valueOrLength / 8, false, false);
                        } else if (attributeName.equals("uiBpcSignificant")) {
                            core.get(0).bitsPerPixel = valueOrLength;
                        } else if (attributeName.equals("dCompressionParam")) {
                            isLossless = valueOrLength >= 0;
                        } else if (attributeName.equals("eCompression")) {
                            canBeLossless = valueOrLength <= 0;
                        } else if (attributeName.equals("SLxImageAttributes")) {
                            int toSkip = valueOrLength - 5;
                            if ((toSkip % 2) == 1) {
                                toSkip++;
                            }
                            in.skipBytes(toSkip);
                        } else if (attributeName.endsWith("Desc")) {
                            in.seek(in.getFilePointer() - 2);
                        } else if (attributeName.equals("SLxExperiment")) {
                            in.skipBytes(8);
                        } else if (attributeName.equals("wsCameraName")) {
                            in.seek(in.getFilePointer() - 4);
                            byte[] b = new byte[2];
                            in.read(b);
                            StringBuilder value = new StringBuilder();
                            while (b[0] != 0) {
                                value.append(b[0]);
                                in.read(b);
                            }
                            addGlobalMeta(attributeName, value.toString());
                        } else if (attributeName.equals("uLoopPars")) {
                            int v2 = in.readInt();
                            int v3 = in.readInt();
                            addGlobalMeta(attributeName, valueOrLength + ", " + v2 + ", " + v3);
                        } else if (attributeName.equals("pPeriod")) {
                            in.skipBytes(22);
                        } else if (attributeName.equals("dPeriod") || attributeName.equals("dDuration")) {
                            in.skipBytes(4);
                        } else if (attributeName.equals("bDurationPref")) {
                            in.seek(in.getFilePointer() - 3);
                        }
                        in.skipBytes(1);
                    }
                    if (in.getFilePointer() > endFP) {
                        in.seek(endFP);
                    }
                    isLossless = isLossless && canBeLossless;
                } else {
                    if (blockType.startsWith("ImageMetadat")) {
                        foundMetadata = true;
                    }
                    int length = len - 12;
                    byte[] b = new byte[length];
                    in.read(b);
                    // strip out invalid characters
                    int off = 0;
                    for (int j = 0; j < length; j++) {
                        char c = (char) b[j];
                        if ((off == 0 && c == '!') || c == 0)
                            off = j + 1;
                        if (Character.isISOControl(c) || !Character.isDefined(c)) {
                            b[j] = (byte) ' ';
                        }
                    }
                    if (length - off >= 5 && b[off] == '<' && b[off + 1] == '?' && b[off + 2] == 'x' && b[off + 3] == 'm' && b[off + 4] == 'l') {
                        boolean endBracketFound = false;
                        while (!endBracketFound) {
                            if (b[off++] == '>') {
                                endBracketFound = true;
                            }
                        }
                        xml.write(b, off, b.length - off);
                    }
                }
                skip = 0;
            } else if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
                int nDoubles = len / 8;
                int nInts = len / 4;
                long doubleOffset = fp + 8 * (nDoubles - imageOffsets.size());
                long intOffset = fp + 4 * (nInts - imageOffsets.size());
                if (blockType.startsWith("CustomData|A")) {
                    customDataOffsets.add(fp);
                    customDataLengths.add(new int[] { nameLength, (int) dataLength });
                } else if (blockType.startsWith("CustomData|Z")) {
                    if (zOffset == 0) {
                        zOffset = doubleOffset;
                    }
                    extraZDataCount++;
                } else if (blockType.startsWith("CustomData|X")) {
                    xOffset = doubleOffset;
                } else if (blockType.startsWith("CustomData|Y")) {
                    yOffset = doubleOffset;
                } else if (blockType.startsWith("CustomData|P")) {
                    if (pfsOffset == 0) {
                        pfsOffset = intOffset;
                    } else if (pfsStateOffset == 0) {
                        pfsStateOffset = intOffset;
                    }
                }
            }
            if (skip > 0 && skip + in.getFilePointer() <= in.length()) {
                in.skipBytes(skip);
            }
        }
        // parse text blocks
        int nChannelNames = textChannelNames.size();
        for (int i = 0; i < textStrings.size(); i++) {
            parseText(textStrings.get(i), imageOffsets.size(), validDimensions.get(i));
        }
        if (textChannelNames.size() > nChannelNames) {
            int diff = textChannelNames.size() - nChannelNames;
            while (textChannelNames.size() > diff) {
                textChannelNames.remove(0);
            }
        }
        // parse XML blocks
        String xmlString = new String(xml.getBytes(), 0, (int) xml.length(), Constants.ENCODING);
        xml = null;
        xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><ND2>" + xmlString + "</ND2>";
        xmlString = XMLTools.sanitizeXML(xmlString);
        core.get(0).dimensionOrder = "";
        ND2Handler handler = new ND2Handler(core, getSizeX() == 0, imageOffsets.size());
        XMLTools.parseXML(xmlString, handler);
        xmlString = null;
        if (handler.getChannelColors().size() > 0) {
            channelColors = handler.getChannelColors();
        }
        if (!isLossless) {
            isLossless = handler.isLossless();
        }
        fieldIndex = handler.getFieldIndex();
        core = handler.getCoreMetadataList();
        final Map<String, Object> globalMetadata = handler.getMetadata();
        nXFields = handler.getXFields();
        if (nXFields > 6) {
            nXFields = 0;
        }
        for (String key : globalMetadata.keySet()) {
            addGlobalMeta(key, globalMetadata.get(key));
            if (key.equals("ChannelCount")) {
                for (int i = 0; i < getSeriesCount(); i++) {
                    CoreMetadata ms = core.get(i);
                    if (ms.sizeC == 0) {
                        ms.sizeC = Integer.parseInt(globalMetadata.get(key).toString());
                        if (ms.sizeC > 1) {
                            ms.rgb = true;
                        }
                    }
                }
            } else if (key.equals("uiBpcInMemory")) {
                int bpc = Integer.parseInt(globalMetadata.get(key).toString());
                core.get(0).pixelType = FormatTools.pixelTypeFromBytes(bpc / 8, false, false);
            }
        }
        int planeCount = core.size() * getSizeZ() * getSizeT();
        if (!textData && planeCount < imageOffsets.size() && planeCount > 0 && (imageOffsets.size() % (planeCount / core.size())) == 0) {
            int seriesCount = imageOffsets.size() / (planeCount / core.size());
            core = new ArrayList<CoreMetadata>();
            for (int i = 0; i < seriesCount; i++) {
                core.add(handler.getCoreMetadataList().get(0));
            }
        }
        int numSeries = core.size();
        if (numSeries == 0)
            numSeries = 1;
        else if (numSeries == 1 && positionCount > 1) {
            for (int i = 1; i < positionCount; i++) {
                core.add(core.get(0));
            }
            numSeries = core.size();
        }
        if (getSizeZ() == 0) {
            for (int i = 0; i < getSeriesCount(); i++) {
                core.get(i).sizeZ = 1;
            }
            if (getSizeT() == 0) {
                for (int i = 0; i < getSeriesCount(); i++) {
                    core.get(i).sizeT = imageOffsets.size() / getSeriesCount();
                }
            }
        }
        if (getSizeT() == 0) {
            for (int i = 0; i < getSeriesCount(); i++) {
                core.get(i).sizeT = 1;
            }
        }
        if (getSizeC() == 0) {
            for (int i = 0; i < getSeriesCount(); i++) {
                core.get(i).sizeC = 1;
            }
        }
        if (getSizeZ() * getSizeT() == imageOffsets.size() && core.size() > 1) {
            CoreMetadata ms0 = core.get(0);
            core = new ArrayList<CoreMetadata>();
            core.add(ms0);
        }
        if (positionCount != getSeriesCount() && (getSizeZ() == imageOffsets.size() || (extraZDataCount > 1 && getSizeZ() == 1 && (extraZDataCount == getSizeC())) || (handler.getXPositions().size() == 0 && (xOffset == 0 && getSizeZ() != getSeriesCount()))) && getSeriesCount() > 1) {
            CoreMetadata ms0 = core.get(0);
            if (getSeriesCount() > ms0.sizeZ) {
                ms0.sizeZ = getSeriesCount();
            }
            core = new ArrayList<CoreMetadata>();
            core.add(ms0);
        }
        // make sure the channel count is reasonable
        // sometimes the XML will indicate that there are multiple channels,
        // when in fact there is only one channel
        long firstOffset = imageOffsets.get(0);
        long secondOffset = imageOffsets.size() > 1 ? imageOffsets.get(1) : in.length();
        long availableBytes = secondOffset - firstOffset;
        // make sure that we have the compression setting correct
        // it's not always easy to tell from the metadata
        isLossless = true;
        long fp = in.getFilePointer();
        int[] firstLengths = imageLengths.get(0);
        in.seek(firstOffset + firstLengths[0] + 8);
        if (codec == null)
            codec = createCodec(false);
        try {
            CodecOptions options = new CodecOptions();
            options.littleEndian = isLittleEndian();
            options.interleaved = true;
            options.maxBytes = (int) secondOffset;
            byte[] t = codec.decompress(in, options);
            if (t.length == 2 * getSizeX() * getSizeY() && getPixelType() == FormatTools.INT8) {
                core.get(0).pixelType = FormatTools.UINT16;
            }
            availableBytes = t.length;
        } catch (IOException e) {
            isLossless = false;
        }
        boolean allEqual = true;
        long offsetDiff = imageOffsets.get(0) - imageLengths.get(0)[1];
        for (int i = 1; i < imageOffsets.size(); i++) {
            long nextOffsetDiff = imageOffsets.get(i) - imageLengths.get(i)[1];
            if (imageLengths.get(i)[1] != imageLengths.get(0)[1] && offsetDiff != nextOffsetDiff) {
                allEqual = false;
                break;
            }
        }
        if (!allEqual && !isLossless && imageOffsets.size() > 1) {
            int plane = (getSizeX() + getScanlinePad()) * getSizeY();
            boolean fixByteCounts = false;
            if (plane > 0) {
                for (int i = 0; i < imageOffsets.size(); i++) {
                    int check = imageLengths.get(i)[2];
                    int length = imageLengths.get(i)[1] - 8;
                    if ((length % plane != 0 && length % (getSizeX() * getSizeY()) != 0) || (check > 0 && plane != check)) {
                        if (imageOffsets.get(i) - length != offsetDiff + 8) {
                            if (i == 0) {
                                fixByteCounts = true;
                            }
                            imageOffsets.remove(i);
                            imageLengths.remove(i);
                            i--;
                        }
                    }
                }
            }
            if (fixByteCounts) {
                firstOffset = imageOffsets.get(0);
                secondOffset = imageOffsets.size() > 1 ? imageOffsets.get(1) : in.length();
                availableBytes = secondOffset - firstOffset;
                if (isLossless) {
                    firstLengths = imageLengths.get(0);
                    in.seek(firstOffset + firstLengths[0] + 8);
                    CodecOptions options = new CodecOptions();
                    options.littleEndian = isLittleEndian();
                    options.interleaved = true;
                    options.maxBytes = (int) secondOffset;
                    byte[] t = codec.decompress(in, options);
                    availableBytes = t.length;
                }
            }
        }
        in.seek(fp);
        long planeSize = getSizeX() * getSizeY() * getSizeC() * FormatTools.getBytesPerPixel(getPixelType());
        if (availableBytes < planeSize) {
            LOGGER.debug("Correcting SizeC: was {}", getSizeC());
            LOGGER.debug("plane size = {}", planeSize);
            LOGGER.debug("available bytes = {}", availableBytes);
            core.get(0).sizeC = (int) (availableBytes / (planeSize / getSizeC()));
            if (getSizeC() == 0) {
                core.get(0).sizeC = 1;
            }
            planeSize = getSizeX() * getSizeY() * getSizeC() * FormatTools.getBytesPerPixel(getPixelType());
        }
        if (planeSize > 0 && availableBytes % planeSize != 0) {
            // an extra 4K block of zeros may have been appended
            if ((availableBytes - 4096) % planeSize == 0) {
                availableBytes -= 4096;
            }
        }
        if (planeSize > 0 && imageOffsets.size() > 1 && availableBytes > DataTools.safeMultiply64(planeSize, 3)) {
            if (availableBytes < DataTools.safeMultiply64(planeSize, 6)) {
                core.get(0).sizeC = 3;
                core.get(0).rgb = true;
                if (getPixelType() == FormatTools.INT8) {
                    core.get(0).pixelType = availableBytes > planeSize * 5 ? FormatTools.UINT16 : FormatTools.UINT8;
                }
            }
        } else if (((planeSize > 0 && availableBytes >= DataTools.safeMultiply64(planeSize, 2)) || getSizeC() > 3) && getPixelType() == FormatTools.INT8) {
            core.get(0).pixelType = FormatTools.UINT16;
            planeSize *= 2;
            if (getSizeC() > 3 && availableBytes % planeSize != 0 && planeSize > availableBytes) {
                core.get(0).sizeC = 3;
                core.get(0).rgb = true;
            }
        } else if (getSizeC() == 2 && getPixelType() == FormatTools.INT8 && availableBytes >= planeSize * 2) {
            core.get(0).pixelType = FormatTools.UINT16;
        } else if (getPixelType() == FormatTools.INT8) {
            core.get(0).pixelType = FormatTools.UINT8;
        }
        if (getSizeX() == 0) {
            core.get(0).sizeX = (int) Math.sqrt(availableBytes / (getSizeC() * FormatTools.getBytesPerPixel(getPixelType())));
            core.get(0).sizeY = getSizeX();
        }
        int rowSize = getSizeX() * FormatTools.getBytesPerPixel(getPixelType()) * getSizeC();
        long sizeY = availableBytes / rowSize;
        if (sizeY < getSizeY()) {
            core.get(0).sizeY = (int) sizeY;
        }
        if (getSizeT() == imageOffsets.size() && getSeriesCount() > 1) {
            CoreMetadata firstCore = core.get(0);
            core = new ArrayList<CoreMetadata>();
            core.add(firstCore);
        }
        // calculate the image count
        for (int i = 0; i < getSeriesCount(); i++) {
            CoreMetadata ms = core.get(i);
            ms.imageCount = getSizeZ() * getSizeT() * getSizeC();
            if (imageOffsets.size() / getSeriesCount() < ms.imageCount) {
                ms.imageCount /= getSizeC();
            }
            if (ms.imageCount > imageOffsets.size() / getSeriesCount()) {
                int diff = imageOffsets.size() - ms.imageCount;
                if (diff >= 0 && diff < ms.sizeZ && diff < ms.sizeT) {
                    CoreMetadata ms0 = core.get(0);
                    core = new ArrayList<CoreMetadata>();
                    core.add(ms0);
                    numSeries = 1;
                    break;
                } else if (imageOffsets.size() % ms.sizeT == 0) {
                    ms.imageCount = imageOffsets.size() / getSeriesCount();
                    ms.sizeZ = ms.imageCount / ms.sizeT;
                    ms.dimensionOrder = "CZT";
                } else {
                    ms.imageCount = imageOffsets.size() / getSeriesCount();
                    ms.sizeZ = 1;
                    ms.sizeT = ms.imageCount;
                }
            }
        }
        if (numSeries * getImageCount() == 1 && imageOffsets.size() > 1) {
            for (int i = 0; i < getSeriesCount(); i++) {
                core.get(i).imageCount = imageOffsets.size() / getSeriesCount();
                core.get(i).sizeZ = getImageCount();
                core.get(i).sizeT = 1;
            }
        }
        if (getSizeZ() * getSizeT() * (split ? 1 : getSizeC()) < imageOffsets.size() / getSeriesCount()) {
            split = getSizeC() > 1;
            int count = imageOffsets.size() / getSeriesCount();
            if (!split && count >= getSizeC()) {
                count /= getSizeC();
            }
            int diff = count - getSizeZ() * getSizeT();
            if (diff == getSizeZ()) {
                core.get(0).sizeT++;
            } else if (getSizeT() > getSizeZ()) {
                core.get(0).sizeZ = 1;
                core.get(0).sizeT = count;
            } else {
                core.get(0).sizeT = 1;
                core.get(0).sizeZ = count;
            }
            if (useZ != null && !useZ) {
                CoreMetadata original = core.get(0);
                int nSeries = imageOffsets.size() / (getSizeZ() * getSizeT());
                for (int i = 1; i < nSeries; i++) {
                    core.add(original);
                }
                numSeries = core.size();
            }
            if (getSizeZ() * getSizeT() * (split ? 1 : getSizeC()) < imageOffsets.size() / getSeriesCount() && getSizeC() > 4) {
                core.get(0).sizeZ = 1;
                core.get(0).sizeT = imageOffsets.size() / getSeriesCount();
            }
            core.get(0).imageCount = getSizeZ() * getSizeT() * getSizeC();
        }
        if (getDimensionOrder().equals("T")) {
            fieldIndex = 0;
        } else if (getDimensionOrder().equals("ZT") && fieldIndex == 2) {
            fieldIndex--;
        }
        if (getSizeC() > 1 && getDimensionOrder().indexOf('C') == -1) {
            core.get(0).dimensionOrder = "C" + getDimensionOrder();
            fieldIndex++;
        }
        core.get(0).dimensionOrder = "XY" + getDimensionOrder();
        if (getDimensionOrder().indexOf('Z') == -1)
            core.get(0).dimensionOrder += 'Z';
        if (getDimensionOrder().indexOf('C') == -1)
            core.get(0).dimensionOrder += 'C';
        if (getDimensionOrder().indexOf('T') == -1)
            core.get(0).dimensionOrder += 'T';
        if (getSizeZ() == 0) {
            core.get(0).sizeZ = 1;
        }
        if (getSizeT() == 0) {
            core.get(0).sizeT = 1;
        }
        if (getSizeC() == 0) {
            core.get(0).sizeC = 1;
        }
        core.get(0).imageCount = getSizeZ() * getSizeT();
        if (!isRGB()) {
            core.get(0).imageCount *= getSizeC();
        }
        posX = handler.getXPositions();
        posY = handler.getYPositions();
        posZ = handler.getZPositions();
        int uniqueX = 0, uniqueY = 0, uniqueZ = 0;
        if (posX.size() == 0 && xOffset != 0) {
            in.seek(xOffset);
            for (int i = 0; i < imageOffsets.size(); i++) {
                final Double number = Double.valueOf(in.readDouble());
                final Length x = new Length(number, UNITS.REFERENCEFRAME);
                if (!posX.contains(x)) {
                    uniqueX++;
                }
                posX.add(x);
            }
        }
        if (posY.size() == 0 && yOffset != 0) {
            in.seek(yOffset);
            for (int i = 0; i < imageOffsets.size(); i++) {
                final Double number = Double.valueOf(in.readDouble());
                final Length y = new Length(number, UNITS.REFERENCEFRAME);
                if (!posY.contains(y)) {
                    uniqueY++;
                }
                posY.add(y);
            }
        }
        if (posZ.size() == 0 && zOffset != 0) {
            in.seek(zOffset);
            for (int i = 0; i < imageOffsets.size(); i++) {
                final Double number = Double.valueOf(in.readDouble());
                final Length z = new Length(number, UNITS.REFERENCEFRAME);
                if (!posZ.contains(z)) {
                    boolean unique = true;
                    for (int q = 0; q < posZ.size(); q++) {
                        // account for potential stage drift
                        final double z1 = z.value(UNITS.REFERENCEFRAME).doubleValue();
                        final double z2 = posZ.get(q).value(UNITS.REFERENCEFRAME).doubleValue();
                        if (Math.abs(z1 - z2) <= 0.05) {
                            unique = false;
                            break;
                        }
                    }
                    if (unique) {
                        uniqueZ++;
                    }
                }
                posZ.add(z);
            }
        }
        if (pfsOffset != 0) {
            in.seek(pfsOffset);
            for (int i = 0; i < imageOffsets.size(); i++) {
                addGlobalMetaList("PFS Offset", in.readInt());
            }
        }
        if (pfsStateOffset != 0) {
            in.seek(pfsStateOffset);
            for (int i = 0; i < imageOffsets.size(); i++) {
                addGlobalMetaList("PFS Status", in.readInt());
            }
        }
        if (core.size() == 1 && ((uniqueX == getSizeT() && uniqueY == getSizeT()) || uniqueZ == getSizeT())) {
            int count = getSizeT();
            core.get(0).imageCount /= count;
            core.get(0).sizeT = 1;
            for (int i = 1; i < count; i++) {
                core.add(core.get(0));
            }
            numSeries = core.size();
        }
        // when the offsets array is allocated
        if (getImageCount() == imageOffsets.size() && numSeries > 1 && getSizeC() == 1) {
            CoreMetadata first = core.get(0);
            core.clear();
            core.add(first);
            numSeries = 1;
        }
        offsets = new long[numSeries][getImageCount()];
        int[] lengths = new int[4];
        int nextChar = 2;
        for (int i = 0; i < lengths.length; i++) {
            if (i == fieldIndex)
                lengths[i] = core.size();
            else {
                char axis = getDimensionOrder().charAt(nextChar++);
                if (axis == 'Z')
                    lengths[i] = getSizeZ();
                else if (axis == 'C')
                    lengths[i] = 1;
                else if (axis == 'T')
                    lengths[i] = getSizeT();
            }
        }
        int[] zctLengths = new int[4];
        System.arraycopy(lengths, 0, zctLengths, 0, lengths.length);
        zctLengths[fieldIndex] = 1;
        boolean oneIndexed = false;
        for (int i = 0; i < imageOffsets.size(); i++) {
            long offset = imageOffsets.get(i).longValue();
            int[] p = imageLengths.get(i);
            int length = p[0] + p[1];
            if (getSizeC() == 0) {
                int sizeC = length / (getSizeX() * getSizeY() * FormatTools.getBytesPerPixel(getPixelType()));
                for (int q = 0; q < getSeriesCount(); q++) {
                    core.get(q).sizeC = sizeC;
                }
            }
            String imageName = imageNames.get(i);
            int ndx = Integer.parseInt(imageName.replaceAll("\\D", ""));
            if (ndx == 1 && i == 0) {
                oneIndexed = true;
            }
            if (oneIndexed) {
                ndx--;
            }
            int[] pos = FormatTools.rasterToPosition(lengths, ndx);
            int seriesIndex = pos[fieldIndex];
            pos[fieldIndex] = 0;
            int plane = FormatTools.positionToRaster(zctLengths, pos);
            if (seriesIndex < offsets.length && plane < offsets[seriesIndex].length) {
                offsets[seriesIndex][plane] = offset + p[0] + 8;
            }
        }
        ArrayList<long[]> tmpOffsets = new ArrayList<long[]>();
        for (int i = 0; i < offsets.length; i++) {
            if (offsets[i].length > 0 && offsets[i][0] > 0) {
                tmpOffsets.add(offsets[i]);
            }
        }
        offsets = new long[tmpOffsets.size()][];
        for (int i = 0; i < tmpOffsets.size(); i++) {
            offsets[i] = tmpOffsets.get(i);
        }
        if (offsets.length != getSeriesCount()) {
            int x = getSizeX();
            int y = getSizeY();
            int c = getSizeC();
            int pixelType = getPixelType();
            int bitsPerPixel = getBitsPerPixel();
            boolean rgb = isRGB();
            String order = getDimensionOrder();
            core = new ArrayList<CoreMetadata>();
            for (int i = 0; i < offsets.length; i++) {
                CoreMetadata ms = new CoreMetadata();
                core.add(ms);
                ms.sizeX = x;
                ms.sizeY = y;
                ms.sizeC = c == 0 ? 1 : c;
                ms.pixelType = pixelType;
                ms.bitsPerPixel = bitsPerPixel;
                ms.rgb = rgb;
                ms.sizeZ = 1;
                ms.dimensionOrder = order;
                int invalid = 0;
                for (int q = 0; q < offsets[i].length; q++) {
                    if (offsets[i][q] == 0)
                        invalid++;
                }
                ms.imageCount = offsets[i].length - invalid;
                ms.sizeT = ms.imageCount / (rgb ? 1 : ms.sizeC);
                if (ms.sizeT == 0)
                    ms.sizeT = 1;
            }
        } else {
            for (int i = 0; i < getSeriesCount(); i++) {
                CoreMetadata ms = core.get(i);
                ms.sizeX = getSizeX();
                ms.sizeY = getSizeY();
                ms.sizeC = getSizeC() == 0 ? 1 : getSizeC();
                ms.sizeZ = getSizeZ() == 0 ? 1 : getSizeZ();
                ms.sizeT = getSizeT() == 0 ? 1 : getSizeT();
                ms.imageCount = getImageCount();
                ms.pixelType = getPixelType();
                ms.bitsPerPixel = getBitsPerPixel();
                ms.dimensionOrder = getDimensionOrder();
            }
        }
        boolean hasColor = false;
        for (String ch : channelColors.keySet()) {
            Integer color = channelColors.get(ch);
            // look for a color that is neither black nor white
            if (color != 0xffffff && color != 0) {
                hasColor = true;
                break;
            }
        }
        split = getSizeC() > 1;
        for (int i = 0; i < getSeriesCount(); i++) {
            CoreMetadata ms = core.get(i);
            ms.rgb = false;
            ms.littleEndian = true;
            ms.interleaved = false;
            ms.indexed = channelColors.size() > 0 && hasColor;
            ms.falseColor = true;
            ms.metadataComplete = true;
            ms.imageCount = ms.sizeZ * ms.sizeT * ms.sizeC;
        }
        if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) {
            if (customDataOffsets.size() > 0) {
                in.seek(customDataOffsets.get(0).longValue());
                int[] p = customDataLengths.get(0);
                int len = p[0] + p[1];
                int timestampBytes = imageOffsets.size() * 8;
                in.skipBytes(len - timestampBytes);
                for (int series = 0; series < getSeriesCount(); series++) {
                    setSeries(series);
                    int count = split ? getImageCount() / getSizeC() : getImageCount();
                    for (int plane = 0; plane < count; plane++) {
                        // timestamps are stored in ms; we want them in seconds
                        double time = in.readDouble() / 1000;
                        tsT.add(time);
                        addSeriesMetaList("timestamp", time);
                    }
                }
                setSeries(0);
            }
        }
        populateMetadataStore(handler);
        return;
    } else
        in.seek(0);
    // older version of ND2 - uses JPEG 2000 compression
    isJPEG = true;
    LOGGER.info("Calculating image offsets");
    ArrayList<Long> vs = new ArrayList<Long>();
    long pos = in.getFilePointer();
    boolean lastBoxFound = false;
    int length = 0;
    int box = 0;
    // assemble offsets to each plane
    int x = 0, y = 0, c = 0, type = 0;
    while (!lastBoxFound) {
        pos = in.getFilePointer();
        length = in.readInt();
        long nextPos = pos + length;
        if (nextPos < 0 || nextPos >= in.length() || length == 0) {
            lastBoxFound = true;
        }
        box = in.readInt();
        pos = in.getFilePointer();
        length -= 8;
        if (box == 0x6a703263) {
            vs.add(pos);
        } else if (box == 0x6a703268) {
            in.skipBytes(4);
            String s = in.readString(4);
            if (s.equals("ihdr")) {
                y = in.readInt();
                x = in.readInt();
                c = in.readShort();
                type = in.readInt();
                if (type == 0xf070100 || type == 0xf070000)
                    type = FormatTools.UINT16;
                else
                    type = FormatTools.UINT8;
            }
        }
        if (!lastBoxFound && box != 0x6a703268)
            in.skipBytes(length);
    }
    LOGGER.info("Finding XML metadata");
    // read XML metadata from the end of the file
    in.seek(vs.get(vs.size() - 1).longValue());
    boolean found = false;
    long off = -1;
    byte[] buf = new byte[8192];
    while (!found && in.getFilePointer() < in.length()) {
        int read = 0;
        if (in.getFilePointer() == vs.get(vs.size() - 1).longValue()) {
            read = in.read(buf);
        } else {
            System.arraycopy(buf, buf.length - 10, buf, 0, 10);
            read = in.read(buf, 10, buf.length - 10);
        }
        if (read == buf.length)
            read -= 10;
        for (int i = 0; i < read + 9; i++) {
            if (buf[i] == (byte) 0xff && buf[i + 1] == (byte) 0xd9) {
                found = true;
                off = in.getFilePointer() - (read + 10) + i;
                i = buf.length;
                break;
            }
        }
    }
    buf = null;
    LOGGER.info("Parsing XML");
    ArrayList<Long> zs = new ArrayList<Long>();
    ArrayList<Long> ts = new ArrayList<Long>();
    int numSeries = 0;
    ND2Handler handler = null;
    if (off > 0 && off < in.length() - 5 && (in.length() - off - 5) > 14) {
        in.seek(off + 4);
        StringBuilder sb = new StringBuilder();
        // stored XML doesn't have a root node - add one, so that we can parse
        // using SAX
        sb.append("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><NIKON>");
        String s = null;
        int blockLength = 0;
        while (in.getFilePointer() < in.length()) {
            blockLength = in.readShort();
            if (blockLength < 2)
                break;
            blockLength -= 2;
            if (blockLength + in.getFilePointer() >= in.length()) {
                blockLength = (int) (in.length() - in.getFilePointer());
            }
            s = in.readString(blockLength);
            // remove comments
            s = s.replaceAll("<!--.+?>", "");
            int openBracket = s.indexOf('<');
            if (openBracket == -1)
                continue;
            int closedBracket = s.lastIndexOf(">") + 1;
            if (closedBracket < openBracket)
                continue;
            s = s.substring(openBracket, closedBracket).trim();
            if (s.indexOf("CalibrationSeq") == -1 && s.indexOf("VCAL") == -1 && s.indexOf("jp2cLUNK") == -1) {
                sb.append(s);
            }
        }
        s = null;
        sb.append("</NIKON>");
        LOGGER.info("Finished assembling XML string");
        // strip out invalid characters
        int offset = 0;
        int len = sb.length();
        for (int i = 0; i < len; i++) {
            char ch = sb.charAt(i);
            if (offset == 0 && ch == '!')
                offset = i + 1;
            if (Character.isISOControl(ch) || !Character.isDefined(ch)) {
                sb.setCharAt(i, ' ');
            }
        }
        core.get(0).dimensionOrder = "";
        if (len - offset < offset) {
            offset = 0;
        }
        String xml = sb.substring(offset, len);
        sb = null;
        handler = new ND2Handler(core, vs.size());
        try {
            xml = XMLTools.sanitizeXML(xml);
            XMLTools.parseXML(xml, handler);
        } catch (IOException e) {
        }
        xml = null;
        isLossless = handler.isLossless();
        fieldIndex = handler.getFieldIndex();
        zs = handler.getZSections();
        ts = handler.getTimepoints();
        numSeries = handler.getSeriesCount();
        core = handler.getCoreMetadataList();
        final Map<String, Object> globalMetadata = handler.getMetadata();
        for (final Map.Entry<String, Object> entry : globalMetadata.entrySet()) {
            addGlobalMeta(entry.getKey(), entry.getValue());
        }
    }
    LOGGER.info("Populating metadata");
    core.get(0).pixelType = FormatTools.UINT8;
    offsets = new long[1][2];
    offsets[0][0] = vs.get(0).longValue();
    if (offsets[0].length > 1 && vs.size() > 1) {
        offsets[0][1] = vs.get(1).longValue();
    }
    in.seek(offsets[0][0]);
    if (getSizeC() == 0)
        core.get(0).sizeC = 1;
    int numBands = c;
    c = numBands > 1 ? numBands : getSizeC();
    if (numBands == 1 && getImageCount() == 1)
        c = 1;
    for (int i = 0; i < getSeriesCount(); i++) {
        CoreMetadata ms = core.get(i);
        ms.sizeC = c;
        ms.rgb = numBands > 1;
        ms.pixelType = type;
    }
    if (getDimensionOrder() == null)
        core.get(0).dimensionOrder = "";
    if (getSizeC() > 1) {
        core.get(0).dimensionOrder = getDimensionOrder().replaceAll("C", "");
        core.get(0).dimensionOrder = "C" + getDimensionOrder();
        fieldIndex++;
    }
    if (getDimensionOrder().indexOf('Z') == -1)
        core.get(0).dimensionOrder += 'Z';
    if (getDimensionOrder().indexOf('C') == -1)
        core.get(0).dimensionOrder += 'C';
    if (getDimensionOrder().indexOf('T') == -1)
        core.get(0).dimensionOrder += 'T';
    core.get(0).dimensionOrder = "XY" + getDimensionOrder();
    if (getImageCount() == 0) {
        core.get(0).imageCount = vs.size();
        core.get(0).sizeZ = (int) Math.max(zs.size(), 1);
        core.get(0).sizeT = (int) Math.max(ts.size(), 1);
        int channels = isRGB() ? 1 : getSizeC();
        if (channels * getSizeZ() * getSizeT() != getImageCount()) {
            core.get(0).sizeZ = 1;
            core.get(0).sizeT = getImageCount() / channels;
            core.get(0).imageCount = getSizeZ() * getSizeT() * channels;
        }
    }
    if (getSizeZ() == 0)
        core.get(0).sizeZ = 1;
    if (getSizeT() == 0)
        core.get(0).sizeT = 1;
    for (int i = 0; i < getSeriesCount(); i++) {
        CoreMetadata ms = core.get(i);
        ms.sizeZ = getSizeZ();
        ms.sizeT = getSizeT();
        ms.imageCount = getSizeZ() * getSizeT() * (isRGB() ? 1 : getSizeC());
        ms.dimensionOrder = getDimensionOrder();
        ms.sizeX = x;
        ms.sizeY = y;
        ms.interleaved = false;
        ms.littleEndian = false;
        ms.metadataComplete = true;
    }
    int nplanes = getSizeZ() * getEffectiveSizeC();
    if (numSeries == 0)
        numSeries = 1;
    if (numSeries * nplanes * getSizeT() > vs.size()) {
        numSeries = vs.size() / (nplanes * getSizeT());
    }
    offsets = new long[numSeries][getImageCount()];
    for (int i = 0; i < getSizeT(); i++) {
        for (int j = 0; j < numSeries; j++) {
            for (int q = 0; q < nplanes; q++) {
                offsets[j][i * nplanes + q] = vs.remove(0).longValue();
            }
        }
    }
    populateMetadataStore(handler);
}
Also used : CodecOptions(loci.formats.codec.CodecOptions) IOException(java.io.IOException) CoreMetadata(loci.formats.CoreMetadata) Length(ome.units.quantity.Length) RandomAccessInputStream(loci.common.RandomAccessInputStream) ByteArrayHandle(loci.common.ByteArrayHandle)

Example 23 with ByteArrayHandle

use of loci.common.ByteArrayHandle in project bioformats by openmicroscopy.

the class NikonReader method openBytes.

/**
 * @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);
    IFD ifd = ifds.get(no);
    int[] bps = ifd.getBitsPerSample();
    int dataSize = bps[0];
    long[] byteCounts = ifd.getStripByteCounts();
    long totalBytes = 0;
    for (long b : byteCounts) {
        totalBytes += b;
    }
    if (totalBytes == FormatTools.getPlaneSize(this) || bps.length > 1) {
        return super.openBytes(no, buf, x, y, w, h);
    }
    if (lastPlane == null || lastIndex != no) {
        long[] offsets = ifd.getStripOffsets();
        boolean maybeCompressed = ifd.getCompression() == TiffCompression.NIKON;
        boolean compressed = vPredictor != null && curve != null && maybeCompressed;
        if (!maybeCompressed && dataSize == 14)
            dataSize = 16;
        ByteArrayOutputStream src = new ByteArrayOutputStream();
        NikonCodec codec = new NikonCodec();
        NikonCodecOptions options = new NikonCodecOptions();
        options.width = getSizeX();
        options.height = getSizeY();
        options.bitsPerSample = dataSize;
        options.curve = curve;
        if (vPredictor != null) {
            options.vPredictor = new int[vPredictor.length];
        }
        options.lossless = !lossyCompression;
        options.split = split;
        for (int i = 0; i < byteCounts.length; i++) {
            byte[] t = new byte[(int) byteCounts[i]];
            in.seek(offsets[i]);
            in.read(t);
            if (compressed) {
                options.maxBytes = (int) byteCounts[i];
                System.arraycopy(vPredictor, 0, options.vPredictor, 0, vPredictor.length);
                t = codec.decompress(t, options);
            }
            src.write(t);
        }
        RandomAccessInputStream bb = new RandomAccessInputStream(new ByteArrayHandle(src.toByteArray()));
        short[] pix = new short[getSizeX() * getSizeY() * 3];
        src.close();
        // default color map
        int[] colorMap = { 1, 0, 2, 1 };
        short[] ifdColors = (short[]) ifd.get(COLOR_MAP);
        if (ifdColors != null && ifdColors.length >= colorMap.length) {
            boolean colorsValid = true;
            for (int q = 0; q < colorMap.length; q++) {
                if (ifdColors[q] < 0 || ifdColors[q] > 2) {
                    // found invalid channel index, use default color map instead
                    colorsValid = false;
                    break;
                }
            }
            if (colorsValid) {
                for (int q = 0; q < colorMap.length; q++) {
                    colorMap[q] = ifdColors[q];
                }
            }
        }
        boolean interleaveRows = offsets.length == 1 && !maybeCompressed && colorMap[0] != 0;
        for (int row = 0; row < getSizeY(); row++) {
            int realRow = interleaveRows ? (row < (getSizeY() / 2) ? row * 2 : (row - (getSizeY() / 2)) * 2 + 1) : row;
            for (int col = 0; col < getSizeX(); col++) {
                short val = (short) (bb.readBits(dataSize) & 0xffff);
                int mapIndex = (realRow % 2) * 2 + (col % 2);
                int redOffset = realRow * getSizeX() + col;
                int greenOffset = (getSizeY() + realRow) * getSizeX() + col;
                int blueOffset = (2 * getSizeY() + realRow) * getSizeX() + col;
                if (colorMap[mapIndex] == 0) {
                    pix[redOffset] = adjustForWhiteBalance(val, 0);
                } else if (colorMap[mapIndex] == 1) {
                    pix[greenOffset] = adjustForWhiteBalance(val, 1);
                } else if (colorMap[mapIndex] == 2) {
                    pix[blueOffset] = adjustForWhiteBalance(val, 2);
                }
                if (maybeCompressed && !compressed) {
                    int toSkip = 0;
                    if ((col % 10) == 9) {
                        toSkip = 1;
                    }
                    if (col == getSizeX() - 1) {
                        toSkip = 10;
                    }
                    bb.skipBits(toSkip * 8);
                }
            }
        }
        bb.close();
        lastPlane = new byte[FormatTools.getPlaneSize(this)];
        ImageTools.interpolate(pix, lastPlane, colorMap, getSizeX(), getSizeY(), isLittleEndian());
        lastIndex = no;
    }
    int bpp = FormatTools.getBytesPerPixel(getPixelType()) * 3;
    int rowLen = w * bpp;
    int width = getSizeX() * bpp;
    for (int row = 0; row < h; row++) {
        System.arraycopy(lastPlane, (row + y) * width + x * bpp, buf, row * rowLen, rowLen);
    }
    return buf;
}
Also used : IFD(loci.formats.tiff.IFD) ByteArrayOutputStream(java.io.ByteArrayOutputStream) NikonCodecOptions(loci.formats.codec.NikonCodecOptions) NikonCodec(loci.formats.codec.NikonCodec) RandomAccessInputStream(loci.common.RandomAccessInputStream) ByteArrayHandle(loci.common.ByteArrayHandle)

Example 24 with ByteArrayHandle

use of loci.common.ByteArrayHandle in project bioformats by openmicroscopy.

the class OpenlabReader method openBytes.

/**
 * @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;
            }
            RandomAccessInputStream pix = new RandomAccessInputStream(plane);
            readPlane(pix, x, y, w, h, buf);
            pix.close();
            plane = null;
        }
    }
    return buf;
}
Also used : CodecOptions(loci.formats.codec.CodecOptions) IFormatReader(loci.formats.IFormatReader) LZOCodec(loci.formats.codec.LZOCodec) IOException(java.io.IOException) FormatException(loci.formats.FormatException) IOException(java.io.IOException) ChannelSeparator(loci.formats.ChannelSeparator) FormatException(loci.formats.FormatException) RandomAccessInputStream(loci.common.RandomAccessInputStream) ByteArrayHandle(loci.common.ByteArrayHandle)

Example 25 with ByteArrayHandle

use of loci.common.ByteArrayHandle in project bioformats by openmicroscopy.

the class InCell3000Reader method openBytes.

/**
 * @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);
    in.seek(pixelsOffset);
    ByteArrayHandle pixels = new ByteArrayHandle();
    pixels.setOrder(ByteOrder.LITTLE_ENDIAN);
    int count = 0;
    int startValue = 0;
    int totalElements = getSizeX() * getSizeY() * 2;
    while (pixels.length() < totalElements) {
        short pixel = in.readShort();
        if ((pixel & 0xffff) > 32768) {
            count = (pixel & 0xffff) - 32768;
            startValue = in.readShort() & 0xffff;
            long fp = in.getFilePointer();
            for (int i = 0; i < count; i++) {
                in.seek(fp + 2 * (i / 3));
                int intOfs = in.readShort() & 0xffff;
                if ((i % 3) != 0) {
                    intOfs = (intOfs >> 5);
                }
                int tempVal = startValue + (intOfs & 31);
                pixels.writeShort((short) tempVal);
            }
            in.seek(fp + 2 * (int) Math.ceil((double) count / 3));
        } else {
            pixels.writeShort((short) (pixel & 0xffff));
        }
    }
    pixels.seek(0);
    RandomAccessInputStream pix = new RandomAccessInputStream(pixels);
    pix.order(isLittleEndian());
    readPlane(pix, x, y, w, h, buf);
    pix.close();
    return buf;
}
Also used : RandomAccessInputStream(loci.common.RandomAccessInputStream) ByteArrayHandle(loci.common.ByteArrayHandle)

Aggregations

ByteArrayHandle (loci.common.ByteArrayHandle)26 RandomAccessInputStream (loci.common.RandomAccessInputStream)18 RandomAccessOutputStream (loci.common.RandomAccessOutputStream)7 IOException (java.io.IOException)6 FormatException (loci.formats.FormatException)6 CodecOptions (loci.formats.codec.CodecOptions)5 CoreMetadata (loci.formats.CoreMetadata)4 TiffSaver (loci.formats.tiff.TiffSaver)4 ByteArrayOutputStream (java.io.ByteArrayOutputStream)3 Location (loci.common.Location)3 DependencyException (loci.common.services.DependencyException)3 ServiceFactory (loci.common.services.ServiceFactory)3 IFD (loci.formats.tiff.IFD)3 TiffParser (loci.formats.tiff.TiffParser)3 Length (ome.units.quantity.Length)3 Test (org.testng.annotations.Test)3 ArrayList (java.util.ArrayList)2 IFormatReader (loci.formats.IFormatReader)2 Codec (loci.formats.codec.Codec)2 JPEGCodec (loci.formats.codec.JPEGCodec)2