//
// ImageProcessorReader.java
//
/*
LOCI Plugins for ImageJ: a collection of ImageJ plugins including the
Bio-Formats Importer, Bio-Formats Exporter, Bio-Formats Macro Extensions,
Data Browser and Stack Slicer. Copyright (C) 2005-@year@ Melissa Linkert,
Curtis Rueden and Christopher Peterson.
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.plugins.util;
import ij.IJ;
import ij.process.ByteProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import ij.process.LUT;
import ij.process.ShortProcessor;
import java.io.IOException;
import loci.common.DataTools;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.IFormatReader;
import loci.formats.ImageTools;
import loci.formats.ReaderWrapper;
import loci.plugins.BF;
/**
* A low-level reader for {@link ij.process.ImageProcessor} objects.
* For a higher-level reader that returns {@link ij.ImagePlus} objects,
* see {@link loci.plugins.in.ImagePlusReader} instead.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/loci-plugins/src/loci/plugins/util/ImageProcessorReader.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/loci-plugins/src/loci/plugins/util/ImageProcessorReader.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class ImageProcessorReader extends ReaderWrapper {
// -- Utility methods --
/**
* Converts the given reader into a ImageProcessorReader, wrapping if needed.
*/
public static ImageProcessorReader makeImageProcessorReader(IFormatReader r) {
if (r instanceof ImageProcessorReader) return (ImageProcessorReader) r;
return new ImageProcessorReader(r);
}
// -- Constructors --
/** Constructs an ImageProcessorReader around a new image reader. */
public ImageProcessorReader() { super(LociPrefs.makeImageReader()); }
/** Constructs an ImageProcessorReader with the given reader. */
public ImageProcessorReader(IFormatReader r) { super(r); }
// -- ImageProcessorReader methods --
/**
* Creates an ImageJ image processor object
* for the image plane at the given position.
*
* @param no Position of image plane.
*/
public ImageProcessor[] openProcessors(int no)
throws FormatException, IOException
{
return openProcessors(no, 0, 0, getSizeX(), getSizeY());
}
public ImageProcessor[] openThumbProcessors(int no)
throws FormatException, IOException
{
// read byte array
byte[] b = openThumbBytes(no);
int c = getRGBChannelCount();
int type = getPixelType();
int bpp = FormatTools.getBytesPerPixel(type);
boolean interleave = isInterleaved();
int w = getThumbSizeX();
int h = getThumbSizeY();
if (b.length != w * h * c * bpp && b.length != w * h * bpp) {
throw new FormatException("Invalid byte array length: " + b.length +
" (expected w=" + w + ", h=" + h + ", c=" + c + ", bpp=" + bpp + ")");
}
// create a color model for this plane (null means default)
final LUT cm = createColorModel();
// convert byte array to appropriate primitive array type
boolean isFloat = FormatTools.isFloatingPoint(type);
boolean isLittle = isLittleEndian();
boolean isSigned = FormatTools.isSigned(type);
// construct image processors
ImageProcessor[] ip = new ImageProcessor[c];
for (int i=0; i<c; i++) {
byte[] channel =
ImageTools.splitChannels(b, i, c, bpp, false, interleave);
Object pixels = DataTools.makeDataArray(channel, bpp, isFloat, isLittle);
if (pixels instanceof byte[]) {
byte[] q = (byte[]) pixels;
if (q.length != w * h) {
byte[] tmp = q;
q = new byte[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
if (isSigned) q = DataTools.makeSigned(q);
ip[i] = new ByteProcessor(w, h, q, null);
if (cm != null) ip[i].setColorModel(cm);
}
else if (pixels instanceof short[]) {
short[] q = (short[]) pixels;
if (q.length != w * h) {
short[] tmp = q;
q = new short[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
if (isSigned) q = DataTools.makeSigned(q);
ip[i] = new ShortProcessor(w, h, q, cm);
}
else if (pixels instanceof int[]) {
int[] q = (int[]) pixels;
if (q.length != w * h) {
int[] tmp = q;
q = new int[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
ip[i] = new FloatProcessor(w, h, q);
}
else if (pixels instanceof float[]) {
float[] q = (float[]) pixels;
if (q.length != w * h) {
float[] tmp = q;
q = new float[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
ip[i] = new FloatProcessor(w, h, q, null);
}
else if (pixels instanceof double[]) {
double[] q = (double[]) pixels;
if (q.length != w * h) {
double[] tmp = q;
q = new double[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
ip[i] = new FloatProcessor(w, h, q);
}
}
return ip;
}
/**
* Returns an array of ImageProcessors that represent the given slice.
* There is one ImageProcessor per RGB channel;
* i.e., length of returned array == getRGBChannelCount().
*
* @param no Position of image plane.
*/
public ImageProcessor[] openProcessors(int no, int x, int y, int w, int h)
throws FormatException, IOException
{
// read byte array
byte[] b = openBytes(no, x, y, w, h);
int c = getRGBChannelCount();
int type = getPixelType();
int bpp = FormatTools.getBytesPerPixel(type);
boolean interleave = isInterleaved();
if (b.length != w * h * c * bpp && b.length != w * h * bpp) {
throw new FormatException("Invalid byte array length: " + b.length +
" (expected w=" + w + ", h=" + h + ", c=" + c + ", bpp=" + bpp + ")");
}
// create a color model for this plane (null means default)
final LUT cm = createColorModel();
// convert byte array to appropriate primitive array type
boolean isFloat = FormatTools.isFloatingPoint(type);
boolean isLittle = isLittleEndian();
boolean isSigned = FormatTools.isSigned(type);
// construct image processors
ImageProcessor[] ip = new ImageProcessor[c];
for (int i=0; i<c; i++) {
byte[] channel =
ImageTools.splitChannels(b, i, c, bpp, false, interleave);
Object pixels = DataTools.makeDataArray(channel, bpp, isFloat, isLittle);
if (pixels instanceof byte[]) {
byte[] q = (byte[]) pixels;
if (q.length != w * h) {
byte[] tmp = q;
q = new byte[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
if (isSigned) q = DataTools.makeSigned(q);
ip[i] = new ByteProcessor(w, h, q, null);
if (cm != null) ip[i].setColorModel(cm);
}
else if (pixels instanceof short[]) {
short[] q = (short[]) pixels;
if (q.length != w * h) {
short[] tmp = q;
q = new short[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
if (isSigned) q = DataTools.makeSigned(q);
ip[i] = new ShortProcessor(w, h, q, cm);
}
else if (pixels instanceof int[]) {
int[] q = (int[]) pixels;
if (q.length != w * h) {
int[] tmp = q;
q = new int[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
ip[i] = new FloatProcessor(w, h, q);
}
else if (pixels instanceof float[]) {
float[] q = (float[]) pixels;
if (q.length != w * h) {
float[] tmp = q;
q = new float[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
ip[i] = new FloatProcessor(w, h, q, null);
}
else if (pixels instanceof double[]) {
double[] q = (double[]) pixels;
if (q.length != w * h) {
double[] tmp = q;
q = new double[w * h];
System.arraycopy(tmp, 0, q, 0, Math.min(q.length, tmp.length));
}
ip[i] = new FloatProcessor(w, h, q);
}
}
return ip;
}
// -- IFormatReader methods --
@Override
public Class<?> getNativeDataType() {
return ImageProcessor[].class;
}
@Override
public Object openPlane(int no, int x, int y, int w, int h)
throws FormatException, IOException
{
return openProcessors(no, x, y, w, h);
}
// -- Helper methods --
private LUT createColorModel() throws FormatException, IOException {
// NB: If a color table is present, we might as well use it,
// regardless of the value of isIndexed.
//if (!isIndexed()) return null;
byte[][] byteTable = get8BitLookupTable();
if (byteTable == null) byteTable = convertTo8Bit(get16BitLookupTable());
if (byteTable == null || byteTable.length == 0) return null;
// extract red, green and blue elements
final int colors = byteTable.length;
final int samples = byteTable[0].length;
final byte[] r = colors >= 1 ? byteTable[0] : new byte[samples];
final byte[] g = colors >= 2 ? byteTable[1] : new byte[samples];
final byte[] b = colors >= 3 ? byteTable[2] : new byte[samples];
return new LUT(8, samples, r, g, b);
}
private byte[][] convertTo8Bit(short[][] shortTable) {
if (shortTable == null) return null;
byte[][] byteTable = new byte[shortTable.length][256];
double max = Math.pow(2, getBitsPerPixel()) - 1;
for (int c=0; c<byteTable.length; c++) {
int len = Math.min(byteTable[c].length, shortTable[c].length);
int valuesPerBin = shortTable[c].length / byteTable[c].length;
int adjustPerBin = (int) ((max + 1) / byteTable[c].length);
for (int i=0; i<len; i++) {
// NB: you could generate the 8-bit LUT by casting the first 256 samples
// in the 16-bit LUT to bytes. However, this will not produce optimal
// results; in many cases, you will end up with a completely black
// 8-bit LUT even if the original 16-bit LUT contained non-zero samples.
//
// Another option would be to scale every 256th value in the 16-bit LUT;
// this may be a bit faster, but will be less accurate than the
// averaging approach taken below.
// TODO: For non-continuous LUTs, this approach does not work well.
//
// For an example, try:
// 'i16&pixelType=uint16&indexed=true&falseColor=true.fake'
//
// To fully resolve this issue, we would need to redither the image.
//
// At minimum, we should issue a warning to the ImageJ log whenever
// this convertTo8Bit routine is invoked, so the user is informed.
double average = 0;
for (int p=0; p<valuesPerBin; p++) {
final int index = i * valuesPerBin + p;
final int value = 0xffff & shortTable[c][index];
average += value;
}
average /= adjustPerBin;
if (average > max) {
average = (int) Math.min(max, average / valuesPerBin);
}
byteTable[c][i] = (byte) (255 * (average / max));
}
}
if (IJ.debugMode) {
final StringBuilder sb = new StringBuilder();
BF.debug("Downsampled 16-bit LUT to 8-bit:");
BF.debug("shortTable = {");
for (int i=0; i<shortTable.length; i++) {
sb.setLength(0);
sb.append("\t{");
for (int j=0; j<shortTable[i].length; j++) {
sb.append(" ");
sb.append(shortTable[i][j]);
}
sb.append(" }");
BF.debug(sb.toString());
}
BF.debug("}");
BF.debug("byteTable = {");
for (int i=0; i<byteTable.length; i++) {
sb.setLength(0);
sb.append("\t{");
for (int j=0; j<byteTable[i].length; j++) {
sb.append(" ");
sb.append(byteTable[i][j]);
}
sb.append(" }");
BF.debug(sb.toString());
}
BF.debug("}");
}
return byteTable;
}
}