//
// FormatWriter.java
//
/*
OME Bio-Formats package for reading and converting biological file formats.
Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
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.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/FormatWriter.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/FormatWriter.java;hb=HEAD">Gitweb</a></dd></dl>
*/
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();
/**
* Next plane index for each series. This is only used by deprecated methods.
*/
private HashMap<Integer, Integer> planeIndices =
new HashMap<Integer, Integer>();
/** 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) */
public void changeOutputFile(String id) throws FormatException, IOException {
setId(id);
}
/* @see IFormatWriter#saveBytes(int, byte[]) */
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) */
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) */
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) */
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) */
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) */
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() */
public int getSeries() {
return series;
}
/* @see IFormatWriter#setInterleaved(boolean) */
public void setInterleaved(boolean interleaved) {
this.interleaved = interleaved;
}
/* @see IFormatWriter#isInterleaved() */
public boolean isInterleaved() {
return interleaved;
}
/* @see IFormatWriter#setValidBitsPerPixel(int) */
public void setValidBitsPerPixel(int bits) {
validBits = bits;
}
/* @see IFormatWriter#canDoStacks() */
public boolean canDoStacks() { return false; }
/* @see IFormatWriter#setMetadataRetrieve(MetadataRetrieve) */
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() */
public MetadataRetrieve getMetadataRetrieve() {
return metadataRetrieve;
}
/* @see IFormatWriter#setColorModel(ColorModel) */
public void setColorModel(ColorModel model) { cm = model; }
/* @see IFormatWriter#getColorModel() */
public ColorModel getColorModel() { return cm; }
/* @see IFormatWriter#setFramesPerSecond(int) */
public void setFramesPerSecond(int rate) { fps = rate; }
/* @see IFormatWriter#getFramesPerSecond() */
public int getFramesPerSecond() { return fps; }
/* @see IFormatWriter#getCompressionTypes() */
public String[] getCompressionTypes() { return compressionTypes; }
/* @see IFormatWriter#setCompression(compress) */
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) */
public void setCodecOptions(CodecOptions options) {
this.options = options;
}
/* @see IFormatWriter#getCompression() */
public String getCompression() {
return compression;
}
/* @see IFormatWriter#getPixelTypes() */
public int[] getPixelTypes() {
return getPixelTypes(getCompression());
}
/* @see IFormatWriter#getPixelTypes(String) */
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) */
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) */
public void setWriteSequentially(boolean sequential) {
this.sequential = sequential;
}
// -- Deprecated IFormatWriter API methods --
/**
* @deprecated
* @see IFormatWriter#saveBytes(byte[], boolean)
*/
public void saveBytes(byte[] bytes, boolean last)
throws FormatException, IOException
{
saveBytes(bytes, 0, last, last);
}
/**
* @deprecated
* @see IFormatWriter#saveBytes(byte[], int, boolean, boolean)
*/
public void saveBytes(byte[] bytes, int series, boolean lastInSeries,
boolean last) throws FormatException, IOException
{
setSeries(series);
Integer planeIndex = planeIndices.get(series);
if (planeIndex == null) planeIndex = 0;
saveBytes(planeIndex, bytes);
planeIndex++;
planeIndices.put(series, planeIndex);
}
/**
* @deprecated
* @see IFormatWriter#savePlane(Object, boolean)
*/
public void savePlane(Object plane, boolean last)
throws FormatException, IOException
{
savePlane(plane, 0, last, last);
}
/**
* @deprecated
* @see IFormatWriter#savePlane(Object, int, boolean, boolean)
*/
public void savePlane(Object plane, int series, boolean lastInSeries,
boolean last) 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((byte[]) plane, series, lastInSeries, last);
}
// -- IFormatHandler API methods --
/* @see IFormatHandler#setId(String) */
public void setId(String id) throws FormatException, IOException {
if (id.equals(currentId)) return;
close();
currentId = id;
out = new RandomAccessOutputStream(currentId);
MetadataRetrieve r = getMetadataRetrieve();
initialized = new boolean[r.getImageCount()][];
int oldSeries = series;
for (int i=0; i<r.getImageCount(); i++) {
setSeries(i);
initialized[i] = new boolean[getPlaneCount()];
}
setSeries(oldSeries);
}
/* @see IFormatHandler#close() */
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() {
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() {
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;
}
}