ome.io.bioformats.BfPyramidPixelBuffer.java Source code

Java tutorial

Introduction

Here is the source code for ome.io.bioformats.BfPyramidPixelBuffer.java

Source

/*
 *   $Id$
 *
 *   Copyright 2009 Glencoe Software, Inc. All rights reserved.
 *   Use is subject to license terms supplied in LICENSE.txt
 */
package ome.io.bioformats;

import java.awt.Dimension;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.List;

import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.in.TiffReader;
import loci.formats.meta.IMetadata;
import loci.formats.out.TiffWriter;
import loci.formats.services.OMEXMLService;
import loci.formats.tiff.IFD;
import loci.formats.tiff.IFDList;
import loci.formats.tiff.TiffCompression;
import ome.conditions.ApiUsageException;
import ome.conditions.LockTimeout;
import ome.io.nio.ConfiguredTileSizes;
import ome.io.nio.DimensionsOutOfBoundsException;
import ome.io.nio.PixelBuffer;
import ome.io.nio.TileSizes;
import ome.model.core.Pixels;
import ome.util.PixelData;
import ome.xml.model.enums.DimensionOrder;
import ome.xml.model.enums.EnumerationException;
import ome.xml.model.primitives.PositiveInteger;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * {@link PixelBuffer} implementation which uses Bio-Formats to
 * read pixels data directly from original files.
 *
 * @since OMERO-Beta4.3
 */
public class BfPyramidPixelBuffer implements PixelBuffer {

    private final static Log log = LogFactory.getLog(BfPyramidPixelBuffer.class);

    private BfPixelBuffer delegate;

    /** Bio-Formats implementation used to write to the backing TIFF. */
    protected OmeroPixelsPyramidWriter writer;

    /**
     * Bio-Formats implementation the delegate uses to read the backing TIFF.
     */
    protected OmeroPixelsPyramidReader reader;

    /**
     * File's who absolute path will be passed to
     * {@link TiffReader#setId(String)} for reading.
     *
     * @see {@link #writePath}
     */
    private final File readerFile;

    /** Description of tile sizes */
    private final TileSizes sizes;

    /** The OMERO pixels set we're backing. */
    private final Pixels pixels;

    /** Last IFD we used during a tile write operation. */
    private IFD lastIFD;

    /** Last z-section offset we used during a tile write operation. */
    private int lastZ = -1;

    /** Last channel offset we used during a tile write operation. */
    private int lastC = -1;

    /** Last timepoint offset  we used during a tile write operation. */
    private int lastT = -1;

    /** Metadata implementation used when writing. */
    private IMetadata metadata;

    // LOCKING. See ticket #5083

    /**
     * File whose absolute path will be given to the {@link TiffWriter}.
     *
     * This prevents that a partially written file can be accessed, if some
     * other process does not attempt to acquire the lock. On close, if this is
     * non-null, then a move from this location to the {@link #filePath} (the
     * reader path) will be attempted.
     */
    private File writerFile;

    /**
     * Lock file used both for the {@link TiffReader} and {@link TiffWriter}
     * process.
     */
    private File lockFile;

    /**
     * {@link RandomAccessFile} opened for the {@link #lockFile} path.
     */
    private RandomAccessFile lockRaf;

    /**
     * If not null, {@link FileLock} instance acquired from the {@link #lockRaf}
     */
    private FileLock fileLock;

    /** The byte order of the compressed pyramid. */
    private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;

    public static final String PYR_LOCK_EXT = ".pyr_lock";

    /**
     * We may want a constructor that takes the id of an imported file
     * or that takes a File object?
     * There should ultimately be some sort of check here that the
     * file is in a/the repository.
     *
     * Upon construction, the pixel buffer is available for reading or writing.
     * However, on the first read, writing will be subsequently disabled.
     *
     * @see ticket:5083
     */
    public BfPyramidPixelBuffer(Pixels pixels, String filePath, boolean write) throws IOException, FormatException {
        this(new ConfiguredTileSizes(), pixels, filePath, write);
    }

    /**
     * Full constructor taking a {@link TileSizes} implementation which defines
     * how large the pyramid tiles will be.
     *
     * @param sizes
     * @param pixels
     * @param filePath
     * @param write
     * @throws IOException
     * @throws FormatException
     */
    public BfPyramidPixelBuffer(TileSizes sizes, Pixels pixels, String filePath, boolean write)
            throws IOException, FormatException {
        this.sizes = sizes;
        this.readerFile = new File(filePath);
        this.pixels = pixels;

        if (!write || readerFile.exists()) {
            if (write) {
                log.debug("Initialized in a write-context; setting read-only for " + filePath);
            }

            if (!readerFile.exists() && !readerFile.canRead()) {
                throw new IOException("Cannot access " + filePath);
            }
            initializeReader();
        }

        else {
            final File readerDir = readerFile.getParentFile();
            writerFile = File.createTempFile("." + readerFile.getName(), ".tmp", readerDir);
            writerFile.deleteOnExit();
            acquireLock();
        }
    }

    private synchronized void initializeReader() throws IOException, FormatException {
        if (isLockedByOthers()) {
            throw new LockTimeout(String.format("%s is locked by others", readerFile.getAbsolutePath()), 15 * 1000,
                    0);
        }
        reader = new OmeroPixelsPyramidReader();
        delegate = new BfPixelBuffer(readerFile.getAbsolutePath(), reader);
        byteOrder = delegate.isLittleEndian() ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
    }

    /**
     * Initializes the writer. Since the reader location is not present until
     * this instance is closed, other {@link ByPyramidPixelBuffer} instances
     * may try to also call this method in which case {@link #acquireLock()}
     * will throw a {@link LockTimeout}.
     *
     * @param output The file where to write the compressed data.
     * @param compression The compression to use.
     * @param bigTiff Pass <code>true</code> to set the <code>bigTiff</code>
     * flag, <code>false</code> otherwise.
     * @throws Exception Thrown if an error occurred.
     */
    private synchronized void initializeWriter(String output, String compression, boolean bigTiff, int tileWidth,
            int tileLength) throws FormatException {
        try {
            loci.common.services.ServiceFactory lociServiceFactory = new loci.common.services.ServiceFactory();
            OMEXMLService service = lociServiceFactory.getInstance(OMEXMLService.class);
            metadata = service.createOMEXMLMetadata();
            addSeries(tileWidth, tileLength);
            writer = new OmeroPixelsPyramidWriter();
            writer.setMetadataRetrieve(metadata);
            writer.setCompression(compression);
            writer.setWriteSequentially(true);
            writer.setInterleaved(true);
            writer.setBigTiff(bigTiff);
            writer.setId(output);
        } catch (Exception e) {
            throw new FormatException("Error instantiating service.", e);
        }
    }

    /**
     * Creates a new series for the destination metadata store.
     * @param metadata Metadata store and retrieve implementation.
     * @param pixels Source pixels set.
     * @param series Destination series.
     * @param sizeX Destination X width. Not necessarily
     * <code>Pixels.SizeX</code>.
     * @param sizeY Destination Y height. Not necessarily
     * <code>Pixels.SizeY</code>.
     * @throws EnumerationException
     */
    private void createSeries(int series, int sizeX, int sizeY) throws EnumerationException {
        metadata.setImageID("Image:" + series, series);
        metadata.setPixelsID("Pixels: " + series, series);
        metadata.setPixelsBinDataBigEndian(byteOrder == ByteOrder.BIG_ENDIAN ? true : false, series, 0);
        metadata.setPixelsDimensionOrder(DimensionOrder.XYZCT, series);
        metadata.setPixelsType(ome.xml.model.enums.PixelType.fromString(pixels.getPixelsType().getValue()), series);
        metadata.setPixelsSizeX(new PositiveInteger(sizeX), series);
        metadata.setPixelsSizeY(new PositiveInteger(sizeY), series);
        metadata.setPixelsSizeZ(new PositiveInteger(1), series);
        metadata.setPixelsSizeC(new PositiveInteger(1), series);
        int totalPlanes = pixels.getSizeZ() * pixels.getSizeC() * pixels.getSizeT();
        metadata.setPixelsSizeT(new PositiveInteger(totalPlanes), series);
        metadata.setChannelID("Channel:" + series, series, 0);
        metadata.setChannelSamplesPerPixel(new PositiveInteger(1), series, 0);
        if (log.isDebugEnabled()) {
            log.debug(String.format("Added series %d %dx%dx%d", series, sizeX, sizeY, totalPlanes));
        }
    }

    /**
     * During tile writing, adds additional all series.
     * @param tileWidth Tile width of full resolution tiles.
     * @param tileLength Tile length of full resolution tiles.
     * @throws EnumerationException
     */
    private void addSeries(int tileWidth, int tileLength) throws EnumerationException {
        int series = 0;
        for (int level : new int[] { 0, 5, 4 }) {
            long imageWidth = pixels.getSizeX();
            long imageLength = pixels.getSizeY();
            long factor = (long) Math.pow(2, level);
            long newTileWidth = Math.round((double) tileWidth / factor);
            newTileWidth = newTileWidth < 1 ? 1 : newTileWidth;
            long newTileLength = Math.round((double) tileLength / factor);
            newTileLength = newTileLength < 1 ? 1 : newTileLength;
            long evenTilesPerRow = imageWidth / tileWidth;
            long evenTilesPerColumn = imageLength / tileLength;
            double remainingWidth = ((double) (imageWidth - (evenTilesPerRow * tileWidth))) / factor;
            remainingWidth = remainingWidth < 1 ? Math.ceil(remainingWidth) : Math.round(remainingWidth);
            double remainingLength = ((double) imageLength - (evenTilesPerColumn * tileLength)) / factor;
            remainingLength = remainingLength < 1 ? Math.ceil(remainingLength) : Math.round(remainingLength);
            int newImageWidth = (int) ((evenTilesPerRow * newTileWidth) + remainingWidth);
            int newImageLength = (int) ((evenTilesPerColumn * newTileLength) + remainingLength);

            createSeries(series, newImageWidth, newImageLength);
            series++;
        }
    }

    private void acquireLock() {
        try {
            lockFile = lockFile();
            lockRaf = new RandomAccessFile(lockFile, "rw");
            fileLock = lockRaf.getChannel().lock(); // THROWS!
        } catch (OverlappingFileLockException overlap) {
            closeRaf();
            throw new LockTimeout("Already locked! " + lockFile.getAbsolutePath(), 15 * 1000, 0);
        } catch (IOException e) {
            closeRaf();
            throw new LockTimeout("IOException while locking " + lockFile.getAbsolutePath(), 15 * 1000, 0);
        }
    }

    private void closeRaf() {
        if (lockRaf != null) {
            try {
                lockRaf.close();
            } catch (Exception e) {
                log.warn("Failed to close " + lockFile, e);
            } finally {
                lockRaf = null;
            }
        }
    }

    private boolean isLockedByOthers() {

        if (fileLock != null) {
            return false; // We control the lock.
        }

        // Since we don't control the lock here, we will try to
        // obtain it an release it immediately.

        try {
            lockFile = lockFile();
            lockRaf = new RandomAccessFile(lockFile, "rw");
            try {
                fileLock = lockRaf.getChannel().tryLock();
            } catch (OverlappingFileLockException ofle) {
                // Another object in this JVM controls the lock.
            }
            if (fileLock == null) {
                // If we don't control the fileLock, then we
                // also don't have the right to delete the
                // lockFile. #5655
                lockFile = null;
                return true;
            } else {
                return false;
            }
        } catch (IOException e) {
            lockFile = null;
            throw new RuntimeException(e);
        } finally {
            releaseLock();
        }
    }

    private File lockFile() {
        File parent = readerFile.getParentFile();
        String name = "." + readerFile.getName() + PYR_LOCK_EXT;
        File lock = new File(parent, name);
        return lock;
    }

    private void releaseLock() {
        try {
            if (fileLock != null) {
                fileLock.release();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            fileLock = null;
            closeRaf();
            if (lockFile != null) {
                lockFile.delete();
                lockFile = null;
            }
        }
    }

    /**
     * This method should never exit without releasing the lock.
     */
    private void closeWriter() throws IOException {
        try {
            if (writer != null) {
                writer.close();
                writer = null;
            }
        } finally {
            try {
                if (writerFile != null) {
                    try {
                        FileUtils.moveFile(writerFile, readerFile);
                    } finally {
                        writerFile = null;
                    }
                }
            } finally {
                releaseLock();
            }
        }
    }

    /**
     * Wether or not this instance is in writing-mode. Any of the calls to reader
     * methods called while this method returns true will close the writer,
     * saving it to disk and preventing any further write methods.
     */
    public boolean isWrite() {
        return writerFile != null;
    }

    private BfPixelBuffer delegate() {
        if (isWrite()) {
            try {
                closeWriter();
                try {
                    initializeReader();
                } catch (FormatException e) {
                    throw new RuntimeException(e);
                }
            } catch (IOException e1) {
                throw new RuntimeException(e1);
            }
        } else if (delegate == null) {
            try {
                initializeReader();
            } catch (IOException e) {
                throw new RuntimeException(e);
            } catch (FormatException e) {
                throw new RuntimeException(e);
            }
        }
        return delegate;
    }

    /* (non-Javadoc)
     * @see ome.io.bioformats.BfPixelBuffer#setTile(byte[], java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized void setTile(byte[] buffer, Integer z, Integer c, Integer t, Integer x, Integer y,
            Integer w, Integer h) throws IOException, BufferOverflowException {
        if (!isWrite()) {
            throw new ApiUsageException("In read-only mode!");
        }
        try {
            int planeCount = getSizeZ() * getSizeC() * getSizeT();
            int planeNumber = FormatTools.getIndex("XYZCT", getSizeZ(), getSizeC(), getSizeT(), planeCount, z, c,
                    t);
            IFD ifd = getIFD(z, c, t, w, h);
            if (log.isDebugEnabled()) {
                log.debug(String.format("Writing tile planeNumber:%d bufferSize:%d ifd:%s " + "x:%d y:%d w:%d h:%d",
                        planeNumber, buffer.length, ifd.toString(), x, y, w, h));
            }
            writer.saveBytes(planeNumber, buffer, ifd, x, y, w, h);
        } catch (FormatException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Retrieves the IFD that should be used for a given planar offset.
     * @param z Z-section offset requested.
     * @param c Channel offset requested.
     * @param t Timepoint offset requested.
     * @param w Tile width requested.
     * @param h Tile height requested.
     * @return A new or already allocated IFD for use when writing tiles.
     */
    private synchronized IFD getIFD(int z, int c, int t, int w, int h) {
        if (lastT == -1 && lastC == -1 && lastZ == -1) {
            try {
                initializeWriter(writerFile.getAbsolutePath(), TiffCompression.JPEG_2000.getCodecName(), true, w,
                        h);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        if (lastT != t || lastC != c || lastZ != z) {
            lastIFD = new IFD();
            lastIFD.put(IFD.IMAGE_DESCRIPTION, OmeroPixelsPyramidWriter.IMAGE_DESCRIPTION);
            lastIFD.put(IFD.TILE_WIDTH, w);
            lastIFD.put(IFD.TILE_LENGTH, h);
            if (log.isDebugEnabled()) {
                log.debug(
                        String.format("Creating new IFD z:%d c:%d t:%d w:%d: h:%d -- %s", z, c, t, w, h, lastIFD));
            }
        }
        lastT = t;
        lastC = c;
        lastZ = z;
        return lastIFD;
    }

    /**
     * Retrieves the rasterized timepoint offset based on the linearization of
     * the z-section, channel and timepoint offsets.
     * @param z Z-section offset requested.
     * @param c Channel offset requested.
     * @param t Timepoint offset requested.
     * @return
     */
    private int getRasterizedT(int z, int c, int t) {
        int rasterizedT = (t * pixels.getSizeC() * pixels.getSizeZ()) // T
                + (c * pixels.getSizeZ()) // C
                + z; // Z
        if (log.isDebugEnabled()) {
            log.debug(String.format("Rasterizing z:%d c:%d t:%d to t:%d", z, c, t, rasterizedT));
        }
        return rasterizedT;
    }

    /**
     * Checks that the tile parameters are not weirdly offset and do not have
     * odd sizes.
     * @param x X offset to the tile request.
     * @param y Y offset to the tile request.
     * @param w Width of the tile request.
     * @param h Height of the tile request.
     * @throws IOException If there is a problem with the parameters or a
     * problem checking them.
     */
    private synchronized void checkTileParameters(int x, int y, int w, int h) throws IOException {
        // No-op.
    }

    /**
     * Returns the current pixel byte order.
     * @return See above.
     */
    public ByteOrder getByteOrder() {
        return byteOrder;
    }

    /**
     * Sets the pixel byte order.
     * @param byteOrder The pixel byte order to set.
     */
    public void setByteOrder(ByteOrder byteOrder) {
        this.byteOrder = byteOrder;
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#calculateMessageDigest()
     */
    public synchronized byte[] calculateMessageDigest() throws IOException {
        return delegate().calculateMessageDigest();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#checkBounds(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized void checkBounds(Integer x, Integer y, Integer z, Integer c, Integer t)
            throws DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        delegate().checkBounds(x, y, z, c, t);
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#close()
     */
    public synchronized void close() throws IOException {
        try {
            if (delegate != null) {
                delegate.close();
            }
        } catch (IOException e) {
            log.error("Failure to close delegate.", e);
        }
        delegate = null;

        if (reader != null) {
            try {
                reader.close();
            } catch (Exception e) {
                log.warn("Failed to close reader", e);
            } finally {
                reader = null;
            }
        }

        closeWriter();

    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getByteWidth()
     */
    public synchronized int getByteWidth() {
        return delegate().getByteWidth();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getCol(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized PixelData getCol(Integer x, Integer z, Integer c, Integer t)
            throws IOException, DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        PixelData data = delegate().getCol(x, z, c, t);
        data.setOrder(byteOrder);
        return data;
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getColDirect(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, byte[])
     */
    public synchronized byte[] getColDirect(Integer x, Integer z, Integer c, Integer t, byte[] buffer)
            throws IOException, DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        return delegate().getColDirect(x, z, c, t, buffer);
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getColSize()
     */
    public synchronized Integer getColSize() {
        return delegate().getColSize();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getHypercube(java.util.List, java.util.List, java.util.List)
     */
    public synchronized PixelData getHypercube(List<Integer> offset, List<Integer> size, List<Integer> step)
            throws IOException, DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not yet implemented.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getHypercubeDirect(java.util.List, java.util.List, java.util.List, byte[])
     */
    public synchronized byte[] getHypercubeDirect(List<Integer> offset, List<Integer> size, List<Integer> step,
            byte[] buffer) throws IOException, DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not yet implemented.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getHypercubeSize(java.util.List, java.util.List, java.util.List)
     */
    public synchronized Integer getHypercubeSize(List<Integer> offset, List<Integer> size, List<Integer> step)
            throws DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not yet implemented.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getId()
     */
    public synchronized long getId() {
        return delegate().getId();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getPath()
     */
    public synchronized String getPath() {
        return delegate().getPath();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getPlane(java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized PixelData getPlane(Integer z, Integer c, Integer t)
            throws IOException, DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        PixelData data = delegate().getPlane(z, c, t);
        data.setOrder(byteOrder);
        return data;
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getPlaneDirect(java.lang.Integer, java.lang.Integer, java.lang.Integer, byte[])
     */
    public synchronized byte[] getPlaneDirect(Integer z, Integer c, Integer t, byte[] buffer)
            throws IOException, DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        return delegate().getPlaneDirect(z, c, t, buffer);
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getPlaneOffset(java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized Long getPlaneOffset(Integer z, Integer c, Integer t) throws DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        return delegate().getPlaneOffset(z, c, t);
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getPlaneRegion(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized PixelData getPlaneRegion(Integer x, Integer y, Integer width, Integer height, Integer z,
            Integer c, Integer t, Integer stride) throws IOException, DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        PixelData data = delegate().getPlaneRegion(x, y, width, height, z, c, t, stride);
        data.setOrder(byteOrder);
        return data;
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getPlaneRegionDirect(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, byte[])
     */
    public synchronized byte[] getPlaneRegionDirect(Integer z, Integer c, Integer t, Integer count, Integer offset,
            byte[] buffer) throws IOException, DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        return getPlaneRegionDirect(z, c, t, count, offset, buffer);
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getPlaneSize()
     */
    public synchronized Integer getPlaneSize() {
        return delegate().getPlaneSize();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getRegion(java.lang.Integer, java.lang.Long)
     */
    public PixelData getRegion(Integer size, Long offset) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getRegionDirect(java.lang.Integer, java.lang.Long, byte[])
     */
    public byte[] getRegionDirect(Integer size, Long offset, byte[] buffer) throws IOException {
        throw new UnsupportedOperationException("Not supported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getRow(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized PixelData getRow(Integer y, Integer z, Integer c, Integer t)
            throws IOException, DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        PixelData data = delegate().getRow(y, z, c, t);
        data.setOrder(byteOrder);
        return data;
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getRowDirect(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, byte[])
     */
    public synchronized byte[] getRowDirect(Integer y, Integer z, Integer c, Integer t, byte[] buffer)
            throws IOException, DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        return delegate().getRowDirect(y, z, c, t, buffer);
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getRowOffset(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized Long getRowOffset(Integer y, Integer z, Integer c, Integer t)
            throws DimensionsOutOfBoundsException {
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        return delegate().getRowOffset(y, z, c, t);
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getRowSize()
     */
    public synchronized Integer getRowSize() {
        return delegate().getRowSize();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getSizeC()
     */
    public int getSizeC() {
        // Not delegating due to the timepoint rasterization of dimensions
        // that's happening below us.
        return pixels.getSizeC();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getSizeT()
     */
    public int getSizeT() {
        // Not delegating due to the timepoint rasterization of dimensions
        // that's happening below us.
        return pixels.getSizeT();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getSizeX()
     */
    public synchronized int getSizeX() {
        if (delegate == null || delegate.reader.get() == null) {
            // The downstream reader has not been initialized, we don't need to
            // delegate and can't even if we wanted to because no data has
            // actually been written yet.
            return pixels.getSizeX();
        }
        return delegate.getSizeX();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getSizeY()
     */
    public synchronized int getSizeY() {
        if (delegate == null || delegate.reader.get() == null) {
            // The downstream reader has not been initialized, we don't need to
            // delegate and can't even if we wanted to because no data has
            // actually been written yet.
            return pixels.getSizeY();
        }
        return delegate.getSizeY();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getSizeZ()
     */
    public int getSizeZ() {
        // Not delegating due to the timepoint rasterization of dimensions
        // that's happening below us.
        return pixels.getSizeZ();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getStack(java.lang.Integer, java.lang.Integer)
     */
    public PixelData getStack(Integer c, Integer t) throws IOException, DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not supported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getStackDirect(java.lang.Integer, java.lang.Integer, byte[])
     */
    public byte[] getStackDirect(Integer c, Integer t, byte[] buffer)
            throws IOException, DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not supported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getStackOffset(java.lang.Integer, java.lang.Integer)
     */
    public Long getStackOffset(Integer c, Integer t) throws DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not supported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getStackSize()
     */
    public synchronized Integer getStackSize() {
        return delegate().getStackSize();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getTile(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public synchronized PixelData getTile(Integer z, Integer c, Integer t, Integer x, Integer y, Integer w,
            Integer h) throws IOException {
        checkTileParameters(x, y, w, h);
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        PixelData data = delegate().getTile(z, c, t, x, y, w, h);
        data.setOrder(ByteOrder.LITTLE_ENDIAN);
        return data;
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getTileDirect(java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer, byte[])
     */
    public synchronized byte[] getTileDirect(Integer z, Integer c, Integer t, Integer x, Integer y, Integer w,
            Integer h, byte[] buffer) throws IOException {
        checkTileParameters(x, y, w, h);
        t = getRasterizedT(z, c, t);
        c = 0;
        z = 0;
        return delegate().getTileDirect(z, c, t, x, y, w, h, buffer);
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getTimepoint(java.lang.Integer)
     */
    public synchronized PixelData getTimepoint(Integer t) throws IOException, DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not supported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getTimepointDirect(java.lang.Integer, byte[])
     */
    public synchronized byte[] getTimepointDirect(Integer t, byte[] buffer)
            throws IOException, DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not supported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getTimepointOffset(java.lang.Integer)
     */
    public Long getTimepointOffset(Integer t) throws DimensionsOutOfBoundsException {
        throw new UnsupportedOperationException("Not supported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getTimepointSize()
     */
    public synchronized Integer getTimepointSize() {
        return delegate().getTimepointSize();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getTotalSize()
     */
    public synchronized Integer getTotalSize() {
        return delegate().getTotalSize();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#isFloat()
     */
    public synchronized boolean isFloat() {
        return delegate().isFloat();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#isSigned()
     */
    public synchronized boolean isSigned() {
        return delegate().isSigned();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setPlane(java.nio.ByteBuffer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public void setPlane(ByteBuffer buffer, Integer z, Integer c, Integer t)
            throws IOException, DimensionsOutOfBoundsException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setPlane(byte[], java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public void setPlane(byte[] buffer, Integer z, Integer c, Integer t)
            throws IOException, DimensionsOutOfBoundsException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setRegion(java.lang.Integer, java.lang.Long, byte[])
     */
    public void setRegion(Integer size, Long offset, byte[] buffer) throws IOException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setRegion(java.lang.Integer, java.lang.Long, java.nio.ByteBuffer)
     */
    public void setRegion(Integer size, Long offset, ByteBuffer buffer)
            throws IOException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setRow(java.nio.ByteBuffer, java.lang.Integer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public void setRow(ByteBuffer buffer, Integer y, Integer z, Integer c, Integer t)
            throws IOException, DimensionsOutOfBoundsException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setStack(java.nio.ByteBuffer, java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public void setStack(ByteBuffer buffer, Integer z, Integer c, Integer t)
            throws IOException, DimensionsOutOfBoundsException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setStack(byte[], java.lang.Integer, java.lang.Integer, java.lang.Integer)
     */
    public void setStack(byte[] buffer, Integer z, Integer c, Integer t)
            throws IOException, DimensionsOutOfBoundsException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setTimepoint(java.nio.ByteBuffer, java.lang.Integer)
     */
    public void setTimepoint(ByteBuffer buffer, Integer t)
            throws IOException, DimensionsOutOfBoundsException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setTimepoint(byte[], java.lang.Integer)
     */
    public void setTimepoint(byte[] buffer, Integer t)
            throws IOException, DimensionsOutOfBoundsException, BufferOverflowException {
        throw new UnsupportedOperationException("Non-tile based writing unsupported.");
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getResolutionLevel()
     */
    public synchronized int getResolutionLevel() {
        if (isWrite()) {
            throw new ApiUsageException("In write mode!");
        }
        return delegate().getResolutionLevel();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getResolutionLevels()
     */
    public synchronized int getResolutionLevels() {
        if (isWrite()) {
            throw new ApiUsageException("In write mode!");
        }
        return delegate().getResolutionLevels();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#getTileSize()
     */
    public synchronized Dimension getTileSize() {
        if (isWrite()) {
            return new Dimension(sizes.getTileWidth(), sizes.getTileHeight());
        }
        return delegate().getTileSize();
    }

    /* (non-Javadoc)
     * @see ome.io.nio.PixelBuffer#setResolutionLevel(int)
     */
    public synchronized void setResolutionLevel(int resolutionLevel) {
        if (isWrite()) {
            throw new ApiUsageException("In write mode!");
        }
        delegate().setResolutionLevel(resolutionLevel);
    }
}