/*
* #%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.out;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import loci.common.RandomAccessInputStream;
import loci.common.RandomAccessOutputStream;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.FormatWriter;
import loci.formats.MetadataTools;
import loci.formats.meta.MetadataRetrieve;
import ome.units.quantity.Time;
import ome.units.UNITS;
/**
* ICSWriter is the file format writer for ICS files. It writes ICS version 1
* and 2 files.
*/
public class ICSWriter extends FormatWriter {
// -- Fields --
private long dimensionOffset;
private int dimensionLength;
private long pixelOffset;
private int lastPlane = -1;
private RandomAccessOutputStream pixels;
// NB: write in ZTC order by default. Certain software (e.g. Volocity)
// lacks the capacity to import files with any other dimension
// ordering. Technically, this is not our problem, but it is
// easy enough to work around and makes life easier for our users.
private String outputOrder = "XYZTC";
// -- Constructor --
public ICSWriter() {
super("Image Cytometry Standard", new String[] {"ids", "ics"});
}
// -- ICSWriter API methods --
/**
* Set the order in which dimensions should be written to the file.
* Valid values are specified in the documentation for
* {@link loci.formats.IFormatReader#getDimensionOrder()}
*
* By default, the ordering is "XYZTC".
*/
public void setOutputOrder(String outputOrder) {
this.outputOrder = outputOrder;
}
// -- IFormatWriter API methods --
/**
* @see loci.formats.IFormatWriter#saveBytes(int, byte[], int, int, int, int)
*/
@Override
public void saveBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
checkParams(no, buf, x, y, w, h);
if (pixels == null) {
pixels = new RandomAccessOutputStream(currentId);
}
MetadataRetrieve meta = getMetadataRetrieve();
int rgbChannels = getSamplesPerPixel();
String order = meta.getPixelsDimensionOrder(series).getValue();
int sizeZ = meta.getPixelsSizeZ(series).getValue().intValue();
int sizeC = meta.getChannelCount(series);
if (rgbChannels <= sizeC) {
sizeC /= rgbChannels;
}
int sizeT = meta.getPixelsSizeT(series).getValue().intValue();
int planes = sizeZ * sizeC * sizeT;
int[] coords =
FormatTools.getZCTCoords(order, sizeZ, sizeC, sizeT, planes, no);
int realIndex =
FormatTools.getIndex(outputOrder, sizeZ, sizeC, sizeT, planes,
coords[0], coords[1], coords[2]);
int sizeX = meta.getPixelsSizeX(series).getValue().intValue();
int sizeY = meta.getPixelsSizeY(series).getValue().intValue();
int pixelType =
FormatTools.pixelTypeFromString(meta.getPixelsType(series).toString());
int bytesPerPixel = FormatTools.getBytesPerPixel(pixelType);
long planeSize = sizeX * sizeY * rgbChannels * bytesPerPixel;
if (!initialized[series][realIndex]) {
initialized[series][realIndex] = true;
if (!isFullPlane(x, y, w, h)) {
// write a dummy plane that will be overwritten in sections
pixels.seek(pixelOffset + (realIndex + 1) * planeSize);
}
}
pixels.seek(pixelOffset + realIndex * planeSize);
if (isFullPlane(x, y, w, h) && (interleaved || rgbChannels == 1)) {
pixels.write(buf);
}
else {
pixels.skipBytes(bytesPerPixel * rgbChannels * sizeX * y);
for (int row=0; row<h; row++) {
ByteArrayOutputStream strip = new ByteArrayOutputStream();
for (int col=0; col<w; col++) {
for (int c=0; c<rgbChannels; c++) {
int index = interleaved ? rgbChannels * (row * w + col) + c :
w * (c * h + row) + col;
strip.write(buf, index * bytesPerPixel, bytesPerPixel);
}
}
pixels.skipBytes(bytesPerPixel * rgbChannels * x);
pixels.write(strip.toByteArray());
pixels.skipBytes(bytesPerPixel * rgbChannels * (sizeX - w - x));
}
}
lastPlane = realIndex;
pixels.close();
pixels = null;
}
/* @see loci.formats.IFormatWriter#canDoStacks() */
@Override
public boolean canDoStacks() { return true; }
/* @see loci.formats.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};
}
// -- IFormatHandler API methods --
/* @see loci.formats.FormatWriter#setId(String) */
@Override
public void setId(String id) throws FormatException, IOException {
super.setId(id);
if (checkSuffix(currentId, "ids")) {
String metadataFile = currentId.substring(0, currentId.lastIndexOf("."));
metadataFile += ".ics";
out.close();
out = new RandomAccessOutputStream(metadataFile);
}
if (out.length() == 0) {
out.writeBytes("\t\n");
if (checkSuffix(id, "ids")) {
out.writeBytes("ics_version\t1.0\n");
}
else {
out.writeBytes("ics_version\t2.0\n");
}
out.writeBytes("filename\t" + currentId + "\n");
out.writeBytes("layout\tparameters\t6\n");
MetadataRetrieve meta = getMetadataRetrieve();
MetadataTools.verifyMinimumPopulated(meta, series);
int pixelType =
FormatTools.pixelTypeFromString(meta.getPixelsType(series).toString());
dimensionOffset = out.getFilePointer();
int[] sizes = overwriteDimensions(meta);
dimensionLength = (int) (out.getFilePointer() - dimensionOffset);
if (validBits != 0) {
out.writeBytes("layout\tsignificant_bits\t" + validBits + "\n");
}
boolean signed = FormatTools.isSigned(pixelType);
boolean littleEndian =
!meta.getPixelsBinDataBigEndian(series, 0).booleanValue();
out.writeBytes("representation\tformat\t" +
(pixelType == FormatTools.FLOAT ? "real\n" : "integer\n"));
out.writeBytes("representation\tsign\t" +
(signed ? "signed\n" : "unsigned\n"));
out.writeBytes("representation\tcompression\tuncompressed\n");
out.writeBytes("representation\tbyte_order\t");
for (int i=0; i<sizes[0]/8; i++) {
if ((littleEndian &&
(sizes[0] < 32 || pixelType == FormatTools.FLOAT)) ||
(!littleEndian && sizes[0] >= 32 && pixelType != FormatTools.FLOAT))
{
out.writeBytes((i + 1) + "\t");
}
else {
out.writeBytes(((sizes[0] / 8) - i) + "\t");
}
}
out.writeBytes("\nparameter\tscale\t1.000000\t");
StringBuffer units = new StringBuffer();
for (int i=0; i<outputOrder.length(); i++) {
char dim = outputOrder.charAt(i);
Number value = 1.0;
if (dim == 'X') {
if (meta.getPixelsPhysicalSizeX(0) != null) {
value = meta.getPixelsPhysicalSizeX(0).value(UNITS.MICROM).doubleValue();
}
units.append("micrometers\t");
}
else if (dim == 'Y') {
if (meta.getPixelsPhysicalSizeY(0) != null) {
value = meta.getPixelsPhysicalSizeY(0).value(UNITS.MICROM).doubleValue();
}
units.append("micrometers\t");
}
else if (dim == 'Z') {
if (meta.getPixelsPhysicalSizeZ(0) != null) {
value = meta.getPixelsPhysicalSizeZ(0).value(UNITS.MICROM).doubleValue();
}
units.append("micrometers\t");
}
else if (dim == 'T') {
Time valueTime = meta.getPixelsTimeIncrement(0);
if (valueTime != null) {
value = valueTime.value(UNITS.S);
units.append("seconds\t");
}
}
out.writeBytes(value + "\t");
}
out.writeBytes("\nparameter\tunits\tbits\t" + units.toString() + "\n");
out.writeBytes("\nend\n");
pixelOffset = out.getFilePointer();
}
else if (checkSuffix(currentId, "ics")) {
RandomAccessInputStream in = new RandomAccessInputStream(currentId);
in.findString("\nend\n");
pixelOffset = in.getFilePointer();
in.close();
}
if (checkSuffix(currentId, "ids")) {
pixelOffset = 0;
}
}
/* @see loci.formats.IFormatHandler#close() */
@Override
public void close() throws IOException {
if (lastPlane != getPlaneCount() - 1 && out != null) {
overwriteDimensions(getMetadataRetrieve());
}
super.close();
pixelOffset = 0;
lastPlane = -1;
dimensionOffset = 0;
dimensionLength = 0;
if (pixels != null) {
pixels.close();
}
pixels = null;
}
// -- Helper methods --
private int[] overwriteDimensions(MetadataRetrieve meta) throws IOException {
out.seek(dimensionOffset);
int sizeX = meta.getPixelsSizeX(series).getValue().intValue();
int sizeY = meta.getPixelsSizeY(series).getValue().intValue();
int z = meta.getPixelsSizeZ(series).getValue().intValue();
int c = meta.getPixelsSizeC(series).getValue().intValue();
int t = meta.getPixelsSizeT(series).getValue().intValue();
int pixelType =
FormatTools.pixelTypeFromString(meta.getPixelsType(series).toString());
int bytesPerPixel = FormatTools.getBytesPerPixel(pixelType);
int rgbChannels = getSamplesPerPixel();
if (lastPlane < 0) lastPlane = z * c * t - 1;
int[] pos =
FormatTools.getZCTCoords(outputOrder, z, c, t, z * c * t, lastPlane);
lastPlane = -1;
StringBuffer dimOrder = new StringBuffer();
int[] sizes = new int[6];
int nextSize = 0;
sizes[nextSize++] = 8 * bytesPerPixel;
if (rgbChannels > 1) {
dimOrder.append("ch\t");
sizes[nextSize++] = pos[1] + 1;
}
for (int i=0; i<outputOrder.length(); i++) {
if (outputOrder.charAt(i) == 'X') sizes[nextSize++] = sizeX;
else if (outputOrder.charAt(i) == 'Y') sizes[nextSize++] = sizeY;
else if (outputOrder.charAt(i) == 'Z') sizes[nextSize++] = pos[0] + 1;
else if (outputOrder.charAt(i) == 'T') sizes[nextSize++] = pos[2] + 1;
else if (outputOrder.charAt(i) == 'C' && dimOrder.indexOf("ch") == -1) {
sizes[nextSize++] = pos[1] + 1;
dimOrder.append("ch");
}
if (outputOrder.charAt(i) != 'C') {
dimOrder.append(String.valueOf(outputOrder.charAt(i)).toLowerCase());
}
dimOrder.append("\t");
}
out.writeBytes("layout\torder\tbits\t" + dimOrder.toString() + "\n");
out.writeBytes("layout\tsizes\t");
for (int i=0; i<sizes.length; i++) {
out.writeBytes(sizes[i] + "\t");
}
while ((out.getFilePointer() - dimensionOffset) < dimensionLength - 1) {
out.writeBytes(" ");
}
out.writeBytes("\n");
return sizes;
}
}