/*
* #%L
* Bio-Formats command line tools for reading and converting files
* %%
* 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.tools;
import java.awt.image.IndexColorModel;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.HashMap;
import loci.common.Constants;
import loci.common.DataTools;
import loci.common.DebugTools;
import loci.common.Location;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.common.services.ServiceFactory;
import loci.formats.ChannelFiller;
import loci.formats.ChannelMerger;
import loci.formats.ChannelSeparator;
import loci.formats.FilePattern;
import loci.formats.FileStitcher;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.IFormatReader;
import loci.formats.IFormatWriter;
import loci.formats.ImageReader;
import loci.formats.ImageTools;
import loci.formats.ImageWriter;
import loci.formats.MetadataTools;
import loci.formats.MinMaxCalculator;
import loci.formats.MissingLibraryException;
import loci.formats.UpgradeChecker;
import loci.formats.gui.Index16ColorModel;
import loci.formats.meta.IMetadata;
import loci.formats.meta.MetadataRetrieve;
import loci.formats.meta.MetadataStore;
import loci.formats.out.TiffWriter;
import loci.formats.services.OMEXMLService;
import loci.formats.services.OMEXMLServiceImpl;
import loci.formats.tiff.IFD;
import ome.xml.meta.OMEXMLMetadataRoot;
import ome.xml.model.Image;
import ome.xml.model.enums.PixelType;
import ome.xml.model.primitives.PositiveInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ImageConverter is a utility class for converting a file between formats.
*/
public final class ImageConverter {
// -- Constants --
private static final Logger LOGGER =
LoggerFactory.getLogger(ImageConverter.class);
private static final String NO_UPGRADE_CHECK = "-no-upgrade";
// -- Fields --
private String in = null, out = null;
private String map = null;
private String compression = null;
private boolean stitch = false, separate = false, merge = false, fill = false;
private boolean bigtiff = false, group = true;
private boolean printVersion = false;
private boolean autoscale = false;
private Boolean overwrite = null;
private int series = -1;
private int firstPlane = 0;
private int lastPlane = Integer.MAX_VALUE;
private int channel = -1, zSection = -1, timepoint = -1;
private int xCoordinate = 0, yCoordinate = 0, width = 0, height = 0;
private int saveTileWidth = 0, saveTileHeight = 0;
private IFormatReader reader;
private MinMaxCalculator minMax;
private HashMap<String, Integer> nextOutputIndex = new HashMap<String, Integer>();
private boolean firstTile = true;
// -- Constructor --
private ImageConverter() { }
/**
* Parse the given argument list to determine how to perform file conversion.
* @param args the list of command line arguments
* @return whether or not the argument list is valid
*/
private boolean parseArgs(String[] args) {
if (args == null) {
return true;
}
for (int i=0; i<args.length; i++) {
if (args[i].startsWith("-") && args.length > 1) {
if (args[i].equals("-debug")) {
DebugTools.enableLogging("DEBUG");
}
else if (args[i].equals("-stitch")) stitch = true;
else if (args[i].equals("-separate")) separate = true;
else if (args[i].equals("-merge")) merge = true;
else if (args[i].equals("-expand")) fill = true;
else if (args[i].equals("-bigtiff")) bigtiff = true;
else if (args[i].equals("-map")) map = args[++i];
else if (args[i].equals("-compression")) compression = args[++i];
else if (args[i].equals("-nogroup")) group = false;
else if (args[i].equals("-autoscale")) autoscale = true;
else if (args[i].equals("-overwrite")) {
overwrite = true;
}
else if (args[i].equals("-nooverwrite")) {
overwrite = false;
}
else if (args[i].equals("-channel")) {
channel = Integer.parseInt(args[++i]);
}
else if (args[i].equals("-z")) {
zSection = Integer.parseInt(args[++i]);
}
else if (args[i].equals("-timepoint")) {
timepoint = Integer.parseInt(args[++i]);
}
else if (args[i].equals("-series")) {
try {
series = Integer.parseInt(args[++i]);
}
catch (NumberFormatException exc) { }
}
else if (args[i].equals("-range")) {
try {
firstPlane = Integer.parseInt(args[++i]);
lastPlane = Integer.parseInt(args[++i]) + 1;
}
catch (NumberFormatException exc) { }
}
else if (args[i].equals("-crop")) {
String[] tokens = args[++i].split(",");
xCoordinate = Integer.parseInt(tokens[0]);
yCoordinate = Integer.parseInt(tokens[1]);
width = Integer.parseInt(tokens[2]);
height = Integer.parseInt(tokens[3]);
}
else if (args[i].equals("-tilex")) {
try {
saveTileWidth = Integer.parseInt(args[++i]);
}
catch (NumberFormatException e) { }
}
else if (args[i].equals("-tiley")) {
try {
saveTileHeight = Integer.parseInt(args[++i]);
}
catch (NumberFormatException e) { }
}
else if (!args[i].equals(NO_UPGRADE_CHECK)) {
LOGGER.error("Found unknown command flag: {}; exiting.", args[i]);
return false;
}
}
else {
if (args[i].equals("-version")) printVersion = true;
else if (in == null) in = args[i];
else if (out == null) out = args[i];
else {
LOGGER.error("Found unknown argument: {}; exiting.", args[i]);
LOGGER.error("You should specify exactly one input file and " +
"exactly one output file.");
return false;
}
}
}
return true;
}
/**
* Output usage information, using log4j.
*/
private void printUsage() {
String[] s = {
"To convert a file between formats, run:",
" bfconvert [-debug] [-stitch] [-separate] [-merge] [-expand]",
" [-bigtiff] [-compression codec] [-series series] [-map id]",
" [-range start end] [-crop x,y,w,h] [-channel channel] [-z Z]",
" [-timepoint timepoint] [-nogroup] [-autoscale] [-version]",
" [-no-upgrade] in_file out_file",
"",
" -version: print the library version and exit",
" -no-upgrade: do not perform the upgrade check",
" -debug: turn on debugging output",
" -stitch: stitch input files with similar names",
" -separate: split RGB images into separate channels",
" -merge: combine separate channels into RGB image",
" -expand: expand indexed color to RGB",
" -bigtiff: force BigTIFF files to be written",
"-compression: specify the codec to use when saving images",
" -series: specify which image series to convert",
" -map: specify file on disk to which name should be mapped",
" -range: specify range of planes to convert (inclusive)",
" -nogroup: force multi-file datasets to be read as individual" +
" files",
" -autoscale: automatically adjust brightness and contrast before",
" converting; this may mean that the original pixel",
" values are not preserved",
" -overwrite: always overwrite the output file, if it already exists",
"-nooverwrite: never overwrite the output file, if it already exists",
" -crop: crop images before converting; argument is 'x,y,w,h'",
" -channel: only convert the specified channel (indexed from 0)",
" -z: only convert the specified Z section (indexed from 0)",
" -timepoint: only convert the specified timepoint (indexed from 0)",
"",
"If any of the following patterns are present in out_file, they will",
"be replaced with the indicated metadata value from the input file.",
"",
" Pattern:\tMetadata value:",
" ---------------------------",
" " + FormatTools.SERIES_NUM + "\t\tseries index",
" " + FormatTools.SERIES_NAME + "\t\tseries name",
" " + FormatTools.CHANNEL_NUM + "\t\tchannel index",
" " + FormatTools.CHANNEL_NAME +"\t\tchannel name",
" " + FormatTools.Z_NUM + "\t\tZ index",
" " + FormatTools.T_NUM + "\t\tT index",
" " + FormatTools.TIMESTAMP + "\t\tacquisition timestamp",
"",
"If any of these patterns are present, then the images to be saved",
"will be split into multiple files. For example, if the input file",
"contains 5 Z sections and 3 timepoints, and out_file is",
"",
" converted_Z" + FormatTools.Z_NUM + "_T" +
FormatTools.T_NUM + ".tiff",
"",
"then 15 files will be created, with the names",
"",
" converted_Z0_T0.tiff",
" converted_Z0_T1.tiff",
" converted_Z0_T2.tiff",
" converted_Z1_T0.tiff",
" ...",
" converted_Z4_T2.tiff",
"",
"Each file would have a single image plane."
};
for (int i=0; i<s.length; i++) LOGGER.info(s[i]);
}
// -- Utility methods --
/** A utility method for converting a file from the command line. */
public boolean testConvert(IFormatWriter writer, String[] args)
throws FormatException, IOException
{
nextOutputIndex.clear();
firstTile = true;
DebugTools.enableLogging("INFO");
boolean success = parseArgs(args);
if (!success) {
return false;
}
if (printVersion) {
LOGGER.info("Version: {}", FormatTools.VERSION);
LOGGER.info("VCS revision: {}", FormatTools.VCS_REVISION);
LOGGER.info("Build date: {}", FormatTools.DATE);
return true;
}
if (in == null || out == null) {
printUsage();
return false;
}
if (new Location(out).exists()) {
if (overwrite == null) {
LOGGER.warn("Output file {} exists.", out);
LOGGER.warn("Do you want to overwrite it? ([y]/n)");
BufferedReader r = new BufferedReader(
new InputStreamReader(System.in, Constants.ENCODING));
String choice = r.readLine().trim().toLowerCase();
overwrite = !choice.startsWith("n");
}
if (!overwrite) {
LOGGER.warn("Exiting; next time, please specify an output file that " +
"does not exist.");
return false;
}
else {
new Location(out).delete();
}
}
if (map != null) Location.mapId(in, map);
long start = System.currentTimeMillis();
LOGGER.info(in);
reader = new ImageReader();
if (stitch) {
reader = new FileStitcher(reader);
Location f = new Location(in);
String pat = null;
if (!f.exists()) {
pat = in;
}
else {
pat = FilePattern.findPattern(f);
}
if (pat != null) in = pat;
}
if (separate) reader = new ChannelSeparator(reader);
if (merge) reader = new ChannelMerger(reader);
if (fill) reader = new ChannelFiller(reader);
minMax = null;
if (autoscale) {
reader = new MinMaxCalculator(reader);
minMax = (MinMaxCalculator) reader;
}
reader.setGroupFiles(group);
reader.setMetadataFiltered(true);
reader.setOriginalMetadataPopulated(true);
OMEXMLService service = null;
try {
ServiceFactory factory = new ServiceFactory();
service = factory.getInstance(OMEXMLService.class);
reader.setMetadataStore(service.createOMEXMLMetadata());
}
catch (DependencyException de) {
throw new MissingLibraryException(OMEXMLServiceImpl.NO_OME_XML_MSG, de);
}
catch (ServiceException se) {
throw new FormatException(se);
}
reader.setId(in);
MetadataStore store = reader.getMetadataStore();
MetadataTools.populatePixels(store, reader, false, false);
boolean dimensionsSet = true;
if (width == 0 || height == 0) {
// only switch series if the '-series' flag was used;
// otherwise default to series 0
if (series >= 0) {
reader.setSeries(series);
}
width = reader.getSizeX();
height = reader.getSizeY();
dimensionsSet = false;
}
if (channel >= reader.getEffectiveSizeC()) {
throw new FormatException("Invalid channel '" + channel + "' (" +
reader.getEffectiveSizeC() + " channels in source file)");
}
if (timepoint >= reader.getSizeT()) {
throw new FormatException("Invalid timepoint '" + timepoint + "' (" +
reader.getSizeT() + " timepoints in source file)");
}
if (zSection >= reader.getSizeZ()) {
throw new FormatException("Invalid Z section '" + zSection + "' (" +
reader.getSizeZ() + " Z sections in source file)");
}
if (store instanceof MetadataRetrieve) {
if (series >= 0) {
try {
String xml = service.getOMEXML(service.asRetrieve(store));
OMEXMLMetadataRoot root = (OMEXMLMetadataRoot) store.getRoot();
Image exportImage = root.getImage(series);
IMetadata meta = service.createOMEXMLMetadata(xml);
OMEXMLMetadataRoot newRoot = (OMEXMLMetadataRoot) meta.getRoot();
while (newRoot.sizeOfImageList() > 0) {
newRoot.removeImage(newRoot.getImage(0));
}
newRoot.addImage(exportImage);
meta.setRoot(newRoot);
meta.setPixelsSizeX(new PositiveInteger(width), 0);
meta.setPixelsSizeY(new PositiveInteger(height), 0);
if (autoscale) {
store.setPixelsType(PixelType.UINT8, 0);
}
if (channel >= 0) {
meta.setPixelsSizeC(new PositiveInteger(1), 0);
}
if (zSection >= 0) {
meta.setPixelsSizeZ(new PositiveInteger(1), 0);
}
if (timepoint >= 0) {
meta.setPixelsSizeT(new PositiveInteger(1), 0);
}
writer.setMetadataRetrieve((MetadataRetrieve) meta);
}
catch (ServiceException e) {
throw new FormatException(e);
}
}
else {
for (int i=0; i<reader.getSeriesCount(); i++) {
store.setPixelsSizeX(new PositiveInteger(width), 0);
store.setPixelsSizeY(new PositiveInteger(height), 0);
if (autoscale) {
store.setPixelsType(PixelType.UINT8, i);
}
if (channel >= 0) {
store.setPixelsSizeC(new PositiveInteger(1), 0);
}
if (zSection >= 0) {
store.setPixelsSizeZ(new PositiveInteger(1), 0);
}
if (timepoint >= 0) {
store.setPixelsSizeT(new PositiveInteger(1), 0);
}
}
writer.setMetadataRetrieve((MetadataRetrieve) store);
}
}
writer.setWriteSequentially(true);
if (writer instanceof TiffWriter) {
((TiffWriter) writer).setBigTiff(bigtiff);
}
else if (writer instanceof ImageWriter) {
IFormatWriter w = ((ImageWriter) writer).getWriter(out);
if (w instanceof TiffWriter) {
((TiffWriter) w).setBigTiff(bigtiff);
}
}
String format = writer.getFormat();
LOGGER.info("[{}] -> {} [{}]",
new Object[] {reader.getFormat(), out, format});
long mid = System.currentTimeMillis();
int total = 0;
int num = writer.canDoStacks() ? reader.getSeriesCount() : 1;
long read = 0, write = 0;
int first = series == -1 ? 0 : series;
int last = series == -1 ? num : series + 1;
long timeLastLogged = System.currentTimeMillis();
for (int q=first; q<last; q++) {
reader.setSeries(q);
firstTile = true;
if (!dimensionsSet) {
width = reader.getSizeX();
height = reader.getSizeY();
}
int writerSeries = series == -1 ? q : 0;
writer.setSeries(writerSeries);
writer.setInterleaved(reader.isInterleaved() && !autoscale);
writer.setValidBitsPerPixel(reader.getBitsPerPixel());
int numImages = writer.canDoStacks() ? reader.getImageCount() : 1;
int startPlane = (int) Math.max(0, firstPlane);
int endPlane = (int) Math.min(numImages, lastPlane);
numImages = endPlane - startPlane;
if (channel >= 0) {
numImages /= reader.getEffectiveSizeC();
}
if (zSection >= 0) {
numImages /= reader.getSizeZ();
}
if (timepoint >= 0) {
numImages /= reader.getSizeT();
}
total += numImages;
int count = 0;
for (int i=startPlane; i<endPlane; i++) {
int[] coords = reader.getZCTCoords(i);
if ((zSection >= 0 && coords[0] != zSection) || (channel >= 0 &&
coords[1] != channel) || (timepoint >= 0 && coords[2] != timepoint))
{
continue;
}
String outputName = FormatTools.getFilename(q, i, reader, out);
if (outputName.equals(FormatTools.getTileFilename(0, 0, 0, outputName))) {
writer.setId(outputName);
if (compression != null) writer.setCompression(compression);
}
else {
int tileNum = outputName.indexOf(FormatTools.TILE_NUM);
int tileX = outputName.indexOf(FormatTools.TILE_X);
int tileY = outputName.indexOf(FormatTools.TILE_Y);
if (tileNum < 0 && (tileX < 0 || tileY < 0)) {
throw new FormatException("Invalid file name pattern; " +
FormatTools.TILE_NUM + " or both of " + FormatTools.TILE_X +
" and " + FormatTools.TILE_Y + " must be specified.");
}
}
int outputIndex = 0;
if (nextOutputIndex.containsKey(outputName)) {
outputIndex = nextOutputIndex.get(outputName);
}
long s = System.currentTimeMillis();
long m = convertPlane(writer, i, outputIndex, outputName);
long e = System.currentTimeMillis();
read += m - s;
write += e - m;
nextOutputIndex.put(outputName, outputIndex + 1);
if (i == endPlane - 1) {
nextOutputIndex.remove(outputName);
}
// log number of planes processed every second or so
if (count == numImages - 1 || (e - timeLastLogged) / 1000 > 0) {
int current = (count - startPlane) + 1;
int percent = 100 * current / numImages;
StringBuilder sb = new StringBuilder();
sb.append("\t");
int numSeries = last - first;
if (numSeries > 1) {
sb.append("Series ");
sb.append(q);
sb.append(": converted ");
}
else sb.append("Converted ");
LOGGER.info(sb.toString() + "{}/{} planes ({}%)",
new Object[] {current, numImages, percent});
timeLastLogged = e;
}
count++;
}
}
writer.close();
long end = System.currentTimeMillis();
LOGGER.info("[done]");
// output timing results
float sec = (end - start) / 1000f;
long initial = mid - start;
float readAvg = (float) read / total;
float writeAvg = (float) write / total;
LOGGER.info("{}s elapsed ({}+{}ms per plane, {}ms overhead)",
new Object[] {sec, readAvg, writeAvg, initial});
return true;
}
// -- Helper methods --
/**
* Convert the specified plane using the given writer.
* @param writer the {@link loci.formats.IFormatWriter} to use for writing the plane
* @param index the index of the plane to convert in the input file
* @param outputIndex the index of the plane to convert in the output file
* @param currentFile the file name or pattern being written to
* @return the time at which conversion started, in milliseconds
* @throws FormatException
* @throws IOException
*/
private long convertPlane(IFormatWriter writer, int index, int outputIndex,
String currentFile)
throws FormatException, IOException
{
if (DataTools.safeMultiply64(width, height) >=
DataTools.safeMultiply64(4096, 4096) ||
saveTileWidth > 0 || saveTileHeight > 0)
{
// this is a "big image" or an output tile size was set, so we will attempt
// to convert it one tile at a time
if ((writer instanceof TiffWriter) || ((writer instanceof ImageWriter) &&
(((ImageWriter) writer).getWriter(out) instanceof TiffWriter)))
{
return convertTilePlane(writer, index, outputIndex, currentFile);
}
}
byte[] buf =
reader.openBytes(index, xCoordinate, yCoordinate, width, height);
autoscalePlane(buf, index);
applyLUT(writer);
long m = System.currentTimeMillis();
writer.saveBytes(outputIndex, buf);
return m;
}
/**
* Convert the specified plane as a set of tiles, using the specified writer.
* @param writer the {@link loci.formats.IFormatWriter} to use for writing the plane
* @param index the index of the plane to convert in the input file
* @param outputIndex the index of the plane to convert in the output file
* @param currentFile the file name or pattern being written to
* @return the time at which conversion started, in milliseconds
* @throws FormatException
* @throws IOException
*/
private long convertTilePlane(IFormatWriter writer, int index, int outputIndex,
String currentFile)
throws FormatException, IOException
{
int w = reader.getOptimalTileWidth();
int h = reader.getOptimalTileHeight();
if (saveTileWidth > 0 && saveTileWidth <= width) {
w = saveTileWidth;
}
if (saveTileHeight > 0 && saveTileHeight <= height) {
h = saveTileHeight;
}
if (firstTile) {
LOGGER.info("Tile size = {} x {}", w, h);
firstTile = false;
}
int nXTiles = width / w;
int nYTiles = height / h;
if (nXTiles * w != width) {
nXTiles++;
}
if (nYTiles * h != height) {
nYTiles++;
}
IFD ifd = new IFD();
ifd.put(IFD.TILE_WIDTH, w);
ifd.put(IFD.TILE_LENGTH, h);
Long m = null;
for (int y=0; y<nYTiles; y++) {
for (int x=0; x<nXTiles; x++) {
int tileX = xCoordinate + x * w;
int tileY = yCoordinate + y * h;
int tileWidth = x < nXTiles - 1 ? w : width - (w * x);
int tileHeight = y < nYTiles - 1 ? h : height - (h * y);
byte[] buf =
reader.openBytes(index, tileX, tileY, tileWidth, tileHeight);
String tileName =
FormatTools.getTileFilename(x, y, y * nXTiles + x, currentFile);
if (!currentFile.equals(tileName)) {
int nTileRows = getTileRows(currentFile);
int nTileCols = getTileColumns(currentFile);
int sizeX = nTileCols == 1 ? width : tileWidth;
int sizeY = nTileRows == 1 ? height : tileHeight;
MetadataRetrieve retrieve = writer.getMetadataRetrieve();
if (retrieve instanceof MetadataStore) {
((MetadataStore) retrieve).setPixelsSizeX(
new PositiveInteger(sizeX), reader.getSeries());
((MetadataStore) retrieve).setPixelsSizeY(
new PositiveInteger(sizeY), reader.getSeries());
}
writer.close();
writer.setMetadataRetrieve(retrieve);
writer.setId(tileName);
if (compression != null) writer.setCompression(compression);
outputIndex = 0;
if (nextOutputIndex.containsKey(tileName)) {
outputIndex = nextOutputIndex.get(tileName);
}
nextOutputIndex.put(tileName, outputIndex + 1);
if (nTileRows > 1) {
tileY = 0;
}
if (nTileCols > 1) {
tileX = 0;
}
}
autoscalePlane(buf, index);
applyLUT(writer);
if (m == null) {
m = System.currentTimeMillis();
}
if (writer instanceof TiffWriter) {
((TiffWriter) writer).saveBytes(outputIndex, buf,
ifd, tileX, tileY, tileWidth, tileHeight);
}
else if (writer instanceof ImageWriter) {
IFormatWriter baseWriter = ((ImageWriter) writer).getWriter(out);
if (baseWriter instanceof TiffWriter) {
((TiffWriter) baseWriter).saveBytes(outputIndex, buf, ifd,
tileX, tileY, tileWidth, tileHeight);
}
}
}
}
return m;
}
/**
* Calculate the number of vertical tiles represented by the given file name pattern.
* @param outputName the output file name pattern
* @return the number of vertical tiles (rows)
*/
private int getTileRows(String outputName) {
if (outputName.indexOf(FormatTools.TILE_Y) >= 0 ||
outputName.indexOf(FormatTools.TILE_NUM) >= 0)
{
int h = reader.getOptimalTileHeight();
if (saveTileHeight > 0 && saveTileHeight <= height) {
h = saveTileHeight;
}
int nYTiles = height / h;
if (nYTiles * h != height) {
nYTiles++;
}
return nYTiles;
}
return 1;
}
/**
* Calculate the number of horizontal tiles represented by the given file name pattern.
* @param outputName the output file name pattern
* @return the number of horizontal tiles (columns)
*/
public int getTileColumns(String outputName) {
if (outputName.indexOf(FormatTools.TILE_X) >= 0 ||
outputName.indexOf(FormatTools.TILE_NUM) >= 0)
{
int w = reader.getOptimalTileWidth();
if (saveTileWidth > 0 && saveTileWidth <= width) {
w = saveTileWidth;
}
int nXTiles = width / w;
if (nXTiles * w != width) {
nXTiles++;
}
return nXTiles;
}
return 1;
}
/**
* Perform in-place autoscaling on the given plane data.
* @param buf the raw pixel data for the plane
* @param index the index of the plane in the input file
* @throws FormatException
* @throws IOException
*/
private void autoscalePlane(byte[] buf, int index)
throws FormatException, IOException
{
if (autoscale) {
Double min = null;
Double max = null;
Double[] planeMin = minMax.getPlaneMinimum(index);
Double[] planeMax = minMax.getPlaneMaximum(index);
if (planeMin != null && planeMax != null) {
min = planeMin[0];
max = planeMax[0];
for (int j=1; j<planeMin.length; j++) {
if (planeMin[j].doubleValue() < min.doubleValue()) {
min = planeMin[j];
}
if (planeMax[j].doubleValue() < max.doubleValue()) {
max = planeMax[j];
}
}
}
int pixelType = reader.getPixelType();
int bpp = FormatTools.getBytesPerPixel(pixelType);
boolean floatingPoint = FormatTools.isFloatingPoint(pixelType);
Object pix = DataTools.makeDataArray(buf, bpp, floatingPoint,
reader.isLittleEndian());
byte[][] b = ImageTools.make24Bits(pix, width, height,
reader.isInterleaved(), false, min, max);
int channelCount = reader.getRGBChannelCount();
int copyComponents = (int) Math.min(channelCount, b.length);
buf = new byte[channelCount * b[0].length];
for (int j=0; j<copyComponents; j++) {
System.arraycopy(b[j], 0, buf, b[0].length * j, b[0].length);
}
}
}
/**
* Use the lookup table from the reader (if present) to set
* the color model in the given writer
* @param writer the {@link loci.formats.IFormatWriter} on which to set a color model
* @throws FormatException
* @throws IOException
*/
private void applyLUT(IFormatWriter writer)
throws FormatException, IOException
{
byte[][] lut = reader.get8BitLookupTable();
if (lut != null) {
IndexColorModel model = new IndexColorModel(8, lut[0].length,
lut[0], lut[1], lut[2]);
writer.setColorModel(model);
}
else {
short[][] lut16 = reader.get16BitLookupTable();
if (lut16 != null) {
Index16ColorModel model = new Index16ColorModel(16, lut16[0].length,
lut16, reader.isLittleEndian());
writer.setColorModel(model);
}
}
}
// -- Main method --
public static void main(String[] args) throws FormatException, IOException {
if (DataTools.indexOf(args, NO_UPGRADE_CHECK) == -1) {
UpgradeChecker checker = new UpgradeChecker();
boolean canUpgrade =
checker.newVersionAvailable(UpgradeChecker.DEFAULT_CALLER);
if (canUpgrade) {
LOGGER.info("*** A new stable version is available. ***");
LOGGER.info("*** Install the new version using: ***");
LOGGER.info("*** 'upgradechecker -install' ***");
}
}
ImageConverter converter = new ImageConverter();
if (!converter.testConvert(new ImageWriter(), args)) System.exit(1);
System.exit(0);
}
}