//
// LegacyTiffForm.java
//
/*
VisAD system for interactive analysis and visualization of numerical
data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package visad.data.tiff;
import java.awt.Image;
import java.awt.image.*;
import java.lang.reflect.*;
import java.io.*;
import java.net.URL;
import java.rmi.RemoteException;
import visad.*;
import visad.data.*;
import visad.util.*;
/**
* LegacyTiffForm is the old VisAD data form for the TIFF file format.
* It relies on either ImageJ or JAI being available in the class path,
* and is very inefficient when dealing with large multi-page TIFF files.
* The following table indicates features that the form supports:<p>
*
* <table border=1><tr>
* <td> </td>
* <td><b>uncompressed</b></td>
* <td><b>compressed (LZW)</b></td>
* </tr><tr>
* <td><b>single image</b></td>
* <td>read and write</td>
* <td>read only (with JAI)</td>
* </tr><tr>
* <td><b>multi-page</b></td>
* <td>read and write</td>
* <td>read only (with JAI)</td>
* </tr></table><p>
*
* This form requires ImageJ, available from the
* <a href="http://rsb.info.nih.gov/ij/download.html">ImageJ</a> web site.
*
* Note that features marked with "(with JAI)" also require
* the Java Advanced Imaging (JAI) package, available at Sun's
* <a href="http://java.sun.com/products/java-media/jai/index.html">
* Java Advanced Imaging</a> web site.
*
* Also, no support for reading TIFF data from URLs is provided.
* However, the visad.data.jai package provides limited support for
* importing single-image TIFF data from a URL.
*
* @deprecated Use TiffForm, or visad.data.bio.LociForm
* with loci.formats.in.TiffReader and loci.formats.out.TiffWriter
*/
public class LegacyTiffForm extends Form
implements FormFileInformer, FormBlockReader, FormProgressInformer
{
// -- Static fields --
/** Counter for TIFF form instantiation. */
private static int formCount = 0;
/** Legal TIFF SUFFIXES. */
private static final String[] SUFFIXES = { "tif", "tiff" };
/** Message produced when attempting to use ImageJ without it installed. */
private static final String NO_IJ = "This feature requires ImageJ, " +
"available online at http://rsb.info.nih.gov/ij/download.html";
/** Message produced when attempting to use JAI without it installed. */
private static final String NO_JAI = "This feature requires JAI, " +
"available from Sun at http://java.sun.com/products/java-media/jai/";
// -- Fields --
/** Reflection tool for ImageJ and JAI calls. */
private ReflectedUniverse r;
/** Flag indicating ImageJ is not installed. */
private boolean noImageJ = false;
/** Flag indicating JAI is not installed. */
private boolean noJai = false;
/** Filename of current TIFF stack. */
private String currentId;
/** Number of images in current TIFF stack. */
private int numImages;
/** Flag indicating whether ImageJ supports the current TIFF stack. */
private boolean canUseImageJ;
/** Percent complete with current operation. */
private double percent;
// -- Constructor --
/** Constructs a new TIFF file form. */
public LegacyTiffForm() {
super("LegacyTiffForm" + formCount++);
r = new ReflectedUniverse();
// ImageJ imports
try {
r.exec("import ij.ImagePlus");
r.exec("import ij.ImageStack");
r.exec("import ij.io.FileInfo");
r.exec("import ij.io.FileSaver");
r.exec("import ij.io.Opener");
r.exec("import ij.io.TiffDecoder");
r.exec("import ij.process.ByteProcessor");
r.exec("import ij.process.ColorProcessor");
r.exec("import ij.process.FloatProcessor");
r.exec("import ij.process.ImageProcessor");
r.exec("import ij.process.ShortProcessor");
}
catch (VisADException exc) { noImageJ = true; }
// JAI imports
try {
r.exec("import com.sun.media.jai.codec.ImageDecodeParam");
r.exec("import com.sun.media.jai.codec.ImageDecoder");
r.exec("import com.sun.media.jai.codec.ImageCodec");
}
catch (VisADException exc) { noJai = true; }
}
// -- FormFileInformer methods --
/** Checks if the given string is a valid filename for a TIFF file. */
public boolean isThisType(String name) {
for (int i=0; i<SUFFIXES.length; i++) {
if (name.toLowerCase().endsWith(SUFFIXES[i])) return true;
}
return false;
}
/** Checks if the given block is a valid header for a TIFF file. */
public boolean isThisType(byte[] block) {
return false;
}
/** Returns the default file SUFFIXES for the TIFF file format. */
public String[] getDefaultSuffixes() {
String[] s = new String[SUFFIXES.length];
System.arraycopy(SUFFIXES, 0, s, 0, SUFFIXES.length);
return s;
}
// -- API methods --
/**
* Saves a VisAD Data object to an uncompressed TIFF file.
*
* @param id Filename of TIFF file to save.
* @param data VisAD Data to convert to TIFF format.
* @param replace Whether to overwrite an existing file.
*/
public void save(String id, Data data, boolean replace)
throws BadFormException, IOException, RemoteException, VisADException
{
if (noImageJ) throw new BadFormException(NO_IJ);
percent = 0;
FlatField[] fields = DataUtility.getImageFields(data);
if (fields == null) {
throw new BadFormException(
"Data type must be image or time sequence of images");
}
r.setVar("id", id);
if (fields.length > 1) {
// save as multi-page TIFF
Object is = null;
for (int i=0; i<fields.length; i++) {
r.setVar("ips", extractImage(fields[i]));
if (is == null) {
r.exec("w = ips.getWidth()");
r.exec("h = ips.getHeight()");
r.exec("cm = ips.getColorModel()");
r.exec("is = new ImageStack(w, h, cm)");
is = r.getVar("is");
}
r.setVar("si", "" + i);
// UGLY HACK
//
// There are two methods:
// - ImageStack.addSlice(String, Object)
// - ImageStack.addSlice(String, ImageProcessor)
//
// But since addSlice(String, Object) is declared first,
// ReflectedUniverse always matches it first, and thus it is
// impossible to call addSlice(String, ImageProcessor).
//
// We must fall back to basic Java reflection to accomplish this...
//r.exec("is.addSlice(si, ips)");
try {
Class imageStack = Class.forName("ij.ImageStack");
Class imageProcessor = Class.forName("ij.process.ImageProcessor");
Method addSlice = imageStack.getMethod("addSlice",
new Class[] {String.class, imageProcessor});
addSlice.invoke(is, new Object[] {"" + i, r.getVar("ips")});
}
catch (ClassNotFoundException exc) {
throw new BadFormException(
"Reflection exception: class not found", exc);
}
catch (NoSuchMethodException exc) {
throw new BadFormException(
"Reflection exception: no such method", exc);
}
catch (IllegalAccessException exc) {
throw new BadFormException(
"Reflection exception: illegal access", exc);
}
catch (InvocationTargetException exc) {
throw new BadFormException(
"Reflection exception", exc.getTargetException());
}
percent = (double) (i + 1) / fields.length;
}
r.exec("image = new ImagePlus(id, is)");
r.exec("sav = new FileSaver(image)");
r.exec("sav.saveAsTiffStack(id)");
}
else {
// save as single image TIFF
r.setVar("ip", extractImage(fields[0]));
r.exec("image = new ImagePlus(id, ip)");
r.exec("sav = new FileSaver(image)");
r.exec("sav.saveAsTiff(id)");
}
percent = -1;
}
/**
* Adds data to an existing TIFF file.
*
* @exception BadFormException Always thrown (method is not implemented).
*/
public void add(String id, Data data, boolean replace)
throws BadFormException
{
throw new BadFormException("LegacyTiffForm.add");
}
/**
* Opens an existing TIFF file from the given filename.
*
* @return VisAD Data object containing TIFF data.
*/
public DataImpl open(String id)
throws BadFormException, IOException, VisADException
{
percent = 0;
int nImages = getBlockCount(id);
FieldImpl[] fields = new FieldImpl[nImages];
for (int i=0; i<nImages; i++) {
fields[i] = (FieldImpl) open(id, i);
percent = (double) (i + 1) / nImages;
}
DataImpl data;
if (nImages == 1) data = fields[0];
else {
// combine data stack into time function
RealType time = RealType.getRealType("time");
FunctionType timeFunction = new FunctionType(time, fields[0].getType());
Integer1DSet timeSet = new Integer1DSet(nImages);
FieldImpl timeField = new FieldImpl(timeFunction, timeSet);
timeField.setSamples(fields, false);
data = timeField;
}
close();
percent = -1;
return data;
}
/**
* Opens an existing TIFF file from the given URL.
*
* @return VisAD Data object containing TIFF data.
*
* @exception BadFormException Always thrown (method is not implemented).
*/
public DataImpl open(URL url)
throws BadFormException, IOException, VisADException
{
throw new BadFormException("LegacyTiffForm.open(URL)");
}
public FormNode getForms(Data data) {
return null;
}
// -- FormBlockReader methods --
public DataImpl open(String id, int block_number)
throws BadFormException, IOException, VisADException
{
if (!id.equals(currentId)) initFile(id);
if (block_number < 0 || block_number >= numImages) {
throw new BadFormException("Invalid image number: " + block_number);
}
Image img = null;
if (canUseImageJ) {
if (noImageJ) throw new BadFormException(NO_IJ);
r.exec("stack = image.getStack()");
r.setVar("bn1", block_number + 1);
r.exec("ip = stack.getProcessor(bn1)");
r.exec("img = ip.createImage()");
img = (Image) r.getVar("img");
}
else {
if (noJai) throw new BadFormException(NO_JAI);
try {
r.setVar("i", block_number);
RenderedImage ri =
(RenderedImage) r.exec("id.decodeAsRenderedImage(i)");
WritableRaster wr = ri.copyData(null);
ColorModel cm = ri.getColorModel();
img = new BufferedImage(cm, wr, false, null);
}
catch (VisADException exc) { throw new BadFormException(exc); }
}
return DataUtility.makeField(img);
}
public int getBlockCount(String id)
throws BadFormException, IOException, VisADException
{
if (!id.equals(currentId)) initFile(id);
return numImages;
}
public void close() throws BadFormException, IOException, VisADException { }
// -- FormProgressInformer methods --
public double getPercentComplete() { return percent; }
// -- Helper methods --
/**
* Converts a FlatField of the form <tt>((x, y) -> value)</tt> or
* <tt>((x, y) -> (r, g, b))</tt> to an ImageJ ImageProcessor object.
*/
private Object extractImage(FlatField field) throws VisADException {
GriddedSet set = (GriddedSet) field.getDomainSet();
int[] wh = set.getLengths();
int w = wh[0];
int h = wh[1];
float[][] samples = field.getFloats(false);
r.setVar("w", w);
r.setVar("h", h);
// HACK - detect "fake" 3-color images
boolean fake3 = samples.length == 3 &&
samples[0] == samples[1] && samples[0] == samples[2];
if (samples.length == 3 && !fake3) {
// 24-bit color is the best we can do
int[] pixels = new int[samples[0].length];
for (int i=0; i<pixels.length; i++) {
int red = (int) samples[0][i] & 0x000000ff;
int green = (int) samples[1][i] & 0x000000ff;
int blue = (int) samples[2][i] & 0x000000ff;
pixels[i] = red << 16 | green << 8 | blue;
}
r.setVar("pixels", pixels);
r.exec("proc = new ColorProcessor(w, h, pixels)");
}
else if (samples.length == 1 || fake3) {
// check for 8-bit, 16-bit or 32-bit grayscale
float lo = Float.POSITIVE_INFINITY, hi = Float.NEGATIVE_INFINITY;
for (int i=0; i<samples[0].length; i++) {
float value = samples[0][i];
if (value != (int) value) {
// force 32-bit floats
hi = Float.POSITIVE_INFINITY;
break;
}
if (value < lo) {
lo = value;
if (lo < 0) break; // need 32-bit floats
}
if (value > hi) {
hi = value;
if (hi >= 65536) break; // need 32-bit floats
}
}
if (lo >= 0 && hi < 256) {
// 8-bit grayscale
byte[] pixels = new byte[samples[0].length];
for (int i=0; i<pixels.length; i++) {
int val = (int) samples[0][i] & 0x000000ff;
pixels[i] = (byte) val;
}
r.setVar("pixels", pixels);
r.setVar("cm", null);
r.exec("proc = new ByteProcessor(w, h, pixels, cm)");
}
else if (lo >= 0 && hi < 65536) {
// 16-bit grayscale
short[] pixels = new short[samples[0].length];
for (int i=0; i<pixels.length; i++) {
int val = (int) samples[0][i];
pixels[i] = (short) val;
}
r.setVar("pixels", pixels);
r.setVar("cm", null);
r.exec("proc = new ShortProcessor(w, h, pixels, cm)");
}
else {
// 32-bit floating point grayscale
r.setVar("pixels", samples[0]);
r.setVar("cm", null);
r.exec("proc = new FloatProcessor(w, h, pixels, cm)");
}
}
return r.getVar("proc");
}
private void initFile(String id)
throws BadFormException, IOException, VisADException
{
if (noImageJ) throw new BadFormException(NO_IJ);
// close any currently open files
close();
// determine whether ImageJ can handle the file
r.setVar("id", id);
r.setVar("empty", "");
r.exec("tdec = new TiffDecoder(empty, id)");
canUseImageJ = true;
try {
r.exec("info = tdec.getTiffInfo()");
}
catch (VisADException exc) {
canUseImageJ = false;
}
// determine number of images in TIFF file
if (canUseImageJ) {
r.exec("opener = new Opener()");
r.exec("image = opener.openImage(id)");
r.exec("numImages = image.getStackSize()");
numImages = ((Integer) r.getVar("numImages")).intValue();
}
else {
if (noJai) throw new BadFormException(NO_JAI);
try {
r.setVar("tiff", "tiff");
r.setVar("file", new File(id));
r.exec("id = ImageCodec.createImageDecoder(tiff, file, null)");
numImages = ((Integer) r.exec("id.getNumPages()")).intValue();
}
catch (VisADException exc) { throw new BadFormException(exc); }
}
currentId = id;
}
// -- Main method --
/**
* Run 'java visad.data.visad.LegacyTiffForm in_file out_file' to convert
* in_file to out_file in TIFF data format.
*/
public static void main(String[] args)
throws VisADException, RemoteException, IOException
{
if (args == null || args.length < 1 || args.length > 2) {
System.out.println("To convert a file to TIFF, run:");
System.out.println(" java " +
"visad.data.tiff.LegacyTiffForm in_file out_file");
System.out.println("To test read a TIFF file, run:");
System.out.println(" java visad.data.tiff.LegacyTiffForm in_file");
System.exit(2);
}
if (args.length == 1) {
// Test read TIFF file
LegacyTiffForm form = new LegacyTiffForm();
System.out.print("Reading " + args[0] + " ");
Data data = form.open(args[0]);
System.out.println("[done]");
System.out.println("MathType =\n" + data.getType().prettyString());
}
else if (args.length == 2) {
// Convert file to TIFF format
System.out.print(args[0] + " -> " + args[1] + " ");
DefaultFamily loader = new DefaultFamily("loader");
DataImpl data = loader.open(args[0]);
loader = null;
LegacyTiffForm form = new LegacyTiffForm();
form.save(args[1], data, true);
System.out.println("[done]");
}
System.exit(0);
}
}