/*
* #%L
* BSD implementations of Bio-Formats readers and writers
* %%
* Copyright (C) 2005 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package loci.formats;
import java.awt.image.ColorModel;
import java.io.IOException;
import java.util.HashMap;
import ome.xml.model.primitives.PositiveInteger;
import loci.common.DataTools;
import loci.common.RandomAccessOutputStream;
import loci.common.Region;
import loci.formats.codec.CodecOptions;
import loci.formats.meta.DummyMetadata;
import loci.formats.meta.MetadataRetrieve;
/**
* Abstract superclass of all biological file format writers.
*/
public abstract class FormatWriter extends FormatHandler
implements IFormatWriter
{
// -- Fields --
/** Frame rate to use when writing in frames per second, if applicable. */
protected int fps = 10;
/** Default color model. */
protected ColorModel cm;
/** Available compression types. */
protected String[] compressionTypes;
/** Current compression type. */
protected String compression;
/** The options if required. */
protected CodecOptions options;
/**
* Whether each plane in each series of the current file has been
* prepped for writing.
*/
protected boolean[][] initialized;
/** Whether the channels in an RGB image are interleaved. */
protected boolean interleaved;
/** The number of valid bits per pixel. */
protected int validBits;
/** Current series. */
protected int series;
/** Whether or not we are writing planes sequentially. */
protected boolean sequential;
/**
* Current metadata retrieval object. Should <b>never</b> be accessed
* directly as the semantics of {@link #getMetadataRetrieve()}
* prevent "null" access.
*/
protected MetadataRetrieve metadataRetrieve = new DummyMetadata();
/** Current file. */
protected RandomAccessOutputStream out;
// -- Constructors --
/** Constructs a format writer with the given name and default suffix. */
public FormatWriter(String format, String suffix) { super(format, suffix); }
/** Constructs a format writer with the given name and default suffixes. */
public FormatWriter(String format, String[] suffixes) {
super(format, suffixes);
}
// -- IFormatWriter API methods --
/* @see IFormatWriter#changeOutputFile(String) */
@Override
public void changeOutputFile(String id) throws FormatException, IOException {
setId(id);
}
/* @see IFormatWriter#saveBytes(int, byte[]) */
@Override
public void saveBytes(int no, byte[] buf) throws FormatException, IOException
{
int width = metadataRetrieve.getPixelsSizeX(getSeries()).getValue();
int height = metadataRetrieve.getPixelsSizeY(getSeries()).getValue();
saveBytes(no, buf, 0, 0, width, height);
}
/* @see IFormatWriter#saveBytes(int, byte[], Region) */
@Override
public void saveBytes(int no, byte[] buf, Region tile)
throws FormatException, IOException
{
saveBytes(no, buf, tile.x, tile.y, tile.width, tile.height);
}
/* @see IFormatWriter#savePlane(int, Object) */
@Override
public void savePlane(int no, Object plane)
throws FormatException, IOException
{
int width = metadataRetrieve.getPixelsSizeX(getSeries()).getValue();
int height = metadataRetrieve.getPixelsSizeY(getSeries()).getValue();
savePlane(no, plane, 0, 0, width, height);
}
/* @see IFormatWriter#savePlane(int, Object, int, int, int, int) */
@Override
public void savePlane(int no, Object plane, int x, int y, int w, int h)
throws FormatException, IOException
{
// NB: Writers use byte arrays by default as the native type.
if (!(plane instanceof byte[])) {
throw new IllegalArgumentException("Object to save must be a byte[]");
}
saveBytes(no, (byte[]) plane, x, y, w, h);
}
/* @see IFormatWriter#savePlane(int, Object, Region) */
@Override
public void savePlane(int no, Object plane, Region tile)
throws FormatException, IOException
{
savePlane(no, plane, tile.x, tile.y, tile.width, tile.height);
}
/* @see IFormatWriter#setSeries(int) */
@Override
public void setSeries(int series) throws FormatException {
if (series < 0) throw new FormatException("Series must be > 0.");
if (series >= metadataRetrieve.getImageCount()) {
throw new FormatException("Series is '" + series +
"' but MetadataRetrieve only defines " +
metadataRetrieve.getImageCount() + " series.");
}
this.series = series;
}
/* @see IFormatWriter#getSeries() */
@Override
public int getSeries() {
return series;
}
/* @see IFormatWriter#setInterleaved(boolean) */
@Override
public void setInterleaved(boolean interleaved) {
this.interleaved = interleaved;
}
/* @see IFormatWriter#isInterleaved() */
@Override
public boolean isInterleaved() {
return interleaved;
}
/* @see IFormatWriter#setValidBitsPerPixel(int) */
@Override
public void setValidBitsPerPixel(int bits) {
validBits = bits;
}
/* @see IFormatWriter#canDoStacks() */
@Override
public boolean canDoStacks() { return false; }
/* @see IFormatWriter#setMetadataRetrieve(MetadataRetrieve) */
@Override
public void setMetadataRetrieve(MetadataRetrieve retrieve) {
FormatTools.assertId(currentId, false, 1);
if (retrieve == null) {
throw new IllegalArgumentException("Metadata object is null");
}
metadataRetrieve = retrieve;
}
/* @see IFormatWriter#getMetadataRetrieve() */
@Override
public MetadataRetrieve getMetadataRetrieve() {
return metadataRetrieve;
}
/* @see IFormatWriter#setColorModel(ColorModel) */
@Override
public void setColorModel(ColorModel model) { cm = model; }
/* @see IFormatWriter#getColorModel() */
@Override
public ColorModel getColorModel() { return cm; }
/* @see IFormatWriter#setFramesPerSecond(int) */
@Override
public void setFramesPerSecond(int rate) { fps = rate; }
/* @see IFormatWriter#getFramesPerSecond() */
@Override
public int getFramesPerSecond() { return fps; }
/* @see IFormatWriter#getCompressionTypes() */
@Override
public String[] getCompressionTypes() { return compressionTypes; }
/* @see IFormatWriter#setCompression(compress) */
@Override
public void setCompression(String compress) throws FormatException {
// check that this is a valid type
for (int i=0; i<compressionTypes.length; i++) {
if (compressionTypes[i].equals(compress)) {
compression = compress;
return;
}
}
throw new FormatException("Invalid compression type: " + compress);
}
/* @see IFormatWriter#setCodecOptions(CodecOptions) */
@Override
public void setCodecOptions(CodecOptions options) {
this.options = options;
}
/* @see IFormatWriter#getCompression() */
@Override
public String getCompression() {
return compression;
}
/* @see IFormatWriter#getPixelTypes() */
@Override
public int[] getPixelTypes() {
return getPixelTypes(getCompression());
}
/* @see IFormatWriter#getPixelTypes(String) */
@Override
public int[] getPixelTypes(String codec) {
return new int[] {FormatTools.INT8, FormatTools.UINT8, FormatTools.INT16,
FormatTools.UINT16, FormatTools.INT32, FormatTools.UINT32,
FormatTools.FLOAT};
}
/* @see IFormatWriter#isSupportedType(int) */
@Override
public boolean isSupportedType(int type) {
int[] types = getPixelTypes();
for (int i=0; i<types.length; i++) {
if (type == types[i]) return true;
}
return false;
}
/* @see IFormatWriter#setWriteSequentially(boolean) */
@Override
public void setWriteSequentially(boolean sequential) {
this.sequential = sequential;
}
// -- IFormatHandler API methods --
/**
* Initializes a writer from the input file name.
*
* Initializes a {@link RandomAccessOutputStream} for the output
* file and initializes the metadata for all the series using
* {@link #setSeries(int)}.
*
* @param id a {@link String} specifying the path to the file
*/
@Override
public void setId(String id) throws FormatException, IOException {
if (id.equals(currentId)) return;
currentId = id;
if (out != null) {
out.close();
}
out = new RandomAccessOutputStream(currentId);
MetadataRetrieve r = getMetadataRetrieve();
initialized = new boolean[r.getImageCount()][];
for (int i=0; i<r.getImageCount(); i++) {
initialized[i] = new boolean[getPlaneCount(i)];
}
}
/* @see IFormatHandler#close() */
@Override
public void close() throws IOException {
if (out != null) out.close();
out = null;
currentId = null;
initialized = null;
}
// -- Helper methods --
/**
* Ensure that the arguments that are being passed to saveBytes(...) are
* valid.
* @throws FormatException if any of the arguments is invalid.
*/
protected void checkParams(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException
{
MetadataRetrieve r = getMetadataRetrieve();
MetadataTools.verifyMinimumPopulated(r, series);
if (buf == null) throw new FormatException("Buffer cannot be null.");
int z = r.getPixelsSizeZ(series).getValue().intValue();
int t = r.getPixelsSizeT(series).getValue().intValue();
int c = r.getChannelCount(series);
int planes = z * c * t;
if (no < 0) throw new FormatException(String.format(
"Plane index:%d must be >= 0", no));
if (no >= planes) {
throw new FormatException(String.format(
"Plane index:%d must be < %d", no, planes));
}
int sizeX = r.getPixelsSizeX(series).getValue().intValue();
int sizeY = r.getPixelsSizeY(series).getValue().intValue();
if (x < 0) throw new FormatException(String.format("X:%d must be >= 0", x));
if (y < 0) throw new FormatException(String.format("Y:%d must be >= 0", y));
if (x >= sizeX) {
throw new FormatException(String.format(
"X:%d must be < %d", x, sizeX));
}
if (y >= sizeY) {
throw new FormatException(String.format("Y:%d must be < %d", y, sizeY));
}
if (w <= 0) throw new FormatException(String.format(
"Width:%d must be > 0", w));
if (h <= 0) throw new FormatException(String.format(
"Height:%d must be > 0", h));
if (x + w > sizeX) throw new FormatException(String.format(
"(w:%d + x:%d) must be <= %d", w, x, sizeX));
if (y + h > sizeY) throw new FormatException(String.format(
"(h:%d + y:%d) must be <= %d", h, y, sizeY));
int pixelType =
FormatTools.pixelTypeFromString(r.getPixelsType(series).toString());
int bpp = FormatTools.getBytesPerPixel(pixelType);
PositiveInteger samples = r.getChannelSamplesPerPixel(series, 0);
if (samples == null) samples = new PositiveInteger(1);
int minSize = bpp * w * h * samples.getValue();
if (buf.length < minSize) {
throw new FormatException("Buffer is too small; expected " + minSize +
" bytes, got " + buf.length + " bytes.");
}
if (!DataTools.containsValue(getPixelTypes(compression), pixelType)) {
throw new FormatException("Unsupported image type '" +
FormatTools.getPixelTypeString(pixelType) + "'.");
}
}
/**
* Seek to the given (x, y) coordinate of the image that starts at
* the given offset.
*/
protected void seekToPlaneOffset(long baseOffset, int x, int y)
throws IOException
{
out.seek(baseOffset);
MetadataRetrieve r = getMetadataRetrieve();
int samples = getSamplesPerPixel();
int pixelType =
FormatTools.pixelTypeFromString(r.getPixelsType(series).toString());
int bpp = FormatTools.getBytesPerPixel(pixelType);
if (interleaved) bpp *= samples;
int sizeX = r.getPixelsSizeX(series).getValue().intValue();
out.skipBytes(bpp * (y * sizeX + x));
}
/**
* Returns true if the given rectangle coordinates correspond to a full
* image in the given series.
*/
protected boolean isFullPlane(int x, int y, int w, int h) {
MetadataRetrieve r = getMetadataRetrieve();
int sizeX = r.getPixelsSizeX(series).getValue().intValue();
int sizeY = r.getPixelsSizeY(series).getValue().intValue();
return x == 0 && y == 0 && w == sizeX && h == sizeY;
}
/** Retrieve the number of samples per pixel for the current series. */
protected int getSamplesPerPixel() {
return getSamplesPerPixel(series);
}
/** Retrieve the number of samples per pixel for given series. */
protected int getSamplesPerPixel(int series) {
MetadataRetrieve r = getMetadataRetrieve();
PositiveInteger samples = r.getChannelSamplesPerPixel(series, 0);
if (samples == null) {
LOGGER.warn("SamplesPerPixel #0 is null. It is assumed to be 1.");
}
return samples == null ? 1 : samples.getValue();
}
/** Retrieve the total number of planes in the current series. */
protected int getPlaneCount() {
return getPlaneCount(series);
}
/** Retrieve the total number of planes in given series. */
protected int getPlaneCount(int series) {
MetadataRetrieve r = getMetadataRetrieve();
int z = r.getPixelsSizeZ(series).getValue().intValue();
int t = r.getPixelsSizeT(series).getValue().intValue();
int c = r.getPixelsSizeC(series).getValue().intValue();
c /= r.getChannelSamplesPerPixel(series, 0).getValue().intValue();
return z * c * t;
}
}