/*
* Copyright (c) 2012 Diamond Light Source Ltd.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package uk.ac.diamond.scisoft.analysis.io;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;
import org.eclipse.dawnsci.analysis.api.io.IDataHolder;
import org.eclipse.dawnsci.analysis.api.io.ScanFileHolderException;
import org.eclipse.january.IMonitor;
import org.eclipse.january.dataset.Dataset;
import org.eclipse.january.dataset.DatasetFactory;
import org.eclipse.january.dataset.DatasetUtils;
import org.eclipse.january.dataset.IDataset;
import org.eclipse.january.dataset.ILazyDataset;
import org.eclipse.january.dataset.SliceND;
import org.eclipse.january.metadata.IMetadata;
import com.sun.media.imageio.plugins.tiff.TIFFDirectory;
import com.sun.media.imageio.plugins.tiff.TIFFField;
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReader;
import com.sun.media.imageioimpl.plugins.tiff.TIFFImageReaderSpi;
import uk.ac.diamond.scisoft.analysis.io.tiff.Grey12bitTIFFReader;
import uk.ac.diamond.scisoft.analysis.io.tiff.Grey12bitTIFFReaderSpi;
/**
* This class loads a TIFF image file
*/
public class TIFFImageLoader extends JavaImageLoader {
protected Map<String, Serializable> metadataMap = null;
private boolean loadData = true;
private int height = -1;
private int width = -1;
public TIFFImageLoader() {
this(null, false);
}
/**
* @param FileName
*/
public TIFFImageLoader(String FileName) {
this(FileName, false);
}
/**
* @param FileName
* @param convertToGrey
*/
public TIFFImageLoader(String FileName, boolean convertToGrey) {
super(FileName, "tiff", convertToGrey);
}
/**
* @param FileName
* @param convertToGrey
* @param keepBitWidth
*/
public TIFFImageLoader(String FileName, boolean convertToGrey, boolean keepBitWidth) {
super(FileName, "tiff", convertToGrey, keepBitWidth);
}
@Override
public DataHolder loadFile() throws ScanFileHolderException {
File f = null;
// Check for file
f = new File(fileName);
if (!f.exists()) {
logger.warn("File, {}, did not exist. Now trying to replace suffix", fileName);
f = findCorrectSuffix();
}
// TODO cope with multiple images (tiff)
DataHolder output = new DataHolder();
ImageReader reader = null;
try {
// test to see if the filename passed will load
f = new File(fileName);
ImageInputStream iis = new FileImageInputStream(f);
try {
reader = new TIFFImageReader(new TIFFImageReaderSpi());
reader.setInput(iis);
readImages(output, reader); // this raises an exception for 12-bit images when using standard reader
} catch (Exception e) {
logger.debug("Using alternative 12-bit TIFF reader: {}", fileName);
reader = new Grey12bitTIFFReader(new Grey12bitTIFFReaderSpi());
reader.setInput(iis);
readImages(output, reader);
}
} catch (IOException e) {
throw new ScanFileHolderException("IOException loading file '" + fileName + "'", e);
} catch (IllegalArgumentException e) {
throw new ScanFileHolderException("IllegalArgumentException interpreting file '" + fileName + "'", e);
} catch (NullPointerException e) {
throw new ScanFileHolderException("NullPointerException interpreting file '" + fileName + "'", e);
} finally {
if (reader != null)
reader.dispose();
}
if (!loadData) {
return null;
}
return output;
}
private void readImages(DataHolder output, ImageReader reader) throws IOException, ScanFileHolderException {
int n = reader.getNumImages(true);
if (n == 0) {
return;
}
if (loadMetadata && metadataMap == null)
metadataMap = createMetadataMap(reader.getImageMetadata(0));
if (!loadData)
return;
boolean allSame = true;
if (height < 0 || width < 0) {
height = reader.getHeight(0); // this can throw NPE when using 12-bit reader
width = reader.getWidth(0);
for (int i = 1; i < n; i++) {
if (height != reader.getHeight(i) || width != reader.getWidth(i)) {
allSame = false;
break;
}
}
}
final ImageTypeSpecifier its = reader.getRawImageType(0); // this raises an exception for 12-bit images when using standard reader
if (allSame) {
for (int i = 1; i < n; i++) {
if (!its.equals(reader.getRawImageType(i))) {
throw new ScanFileHolderException("Type of image in stack does not match first");
}
}
}
int dtype = AWTImageUtils.getDTypeFromImage(its.getSampleModel(), keepBitWidth)[0];
if (n == 1) {
ILazyDataset image;
if (loadLazily) {
image = createLazyDataset(dtype, height, width);
} else {
image = createDataset(reader.read(0));
}
image.setName(DEF_IMAGE_NAME);
if (metadata != null)
mergeMetadata(image);
output.addDataset(DEF_IMAGE_NAME, image);
} else if (allSame) {
ILazyDataset ld = createLazyDataset(dtype, n, height, width);
ld.setMetadata(metadata);
output.addDataset(STACK_NAME, ld);
} else {
createLazyDatasets(output, reader);
}
if (loadMetadata) {
createMetadata(output, reader);
metadata.setMetadata(metadataMap);
output.setMetadata(metadata);
}
}
private void mergeMetadata(ILazyDataset image) {
IMetadata imd;
try {
imd = image.getMetadata();
if (imd == null)
return;
HashMap<String, Serializable> map = new HashMap<String, Serializable>();
for (String m : imd.getMetaNames()) {
map.put(m, imd.getMetaValue(m));
}
for (String m : metadata.getMetaNames()) {
map.put(m, imd.getMetaValue(m));
}
metadata.setMetadata(map);
image.clearMetadata(IMetadata.class);
image.setMetadata(metadata);
} catch (Exception e) {
}
}
private ILazyDataset createLazyDataset(final int dtype, final int... trueShape) {
LazyLoaderStub l = new LazyLoaderStub() {
@Override
public IDataset getDataset(IMonitor mon, SliceND slice) throws IOException {
int[] lstart = slice.getStart();
int[] lstep = slice.getStep();
int[] newShape = slice.getShape();
int[] shape = slice.getSourceShape();
final int rank = shape.length;
Dataset d = null;
try {
if (!Arrays.equals(trueShape, shape)) {
final int trank = trueShape.length;
int[] tstart = new int[trank];
int[] tsize = new int[trank];
int[] tstep = new int[trank];
if (rank > trank) { // shape was extended (from left) then need to translate to true slice
int j = 0;
for (int i = 0; i < trank; i++) {
if (trueShape[i] == 1) {
tstart[i] = 0;
tsize[i] = 1;
tstep[i] = 1;
} else {
while (shape[j] == 1 && (rank - j) > (trank - i))
j++;
tstart[i] = lstart[j];
tsize[i] = newShape[j];
tstep[i] = lstep[j];
j++;
}
}
} else { // shape was squeezed then need to translate to true slice
int j = 0;
for (int i = 0; i < trank; i++) {
if (trueShape[i] == 1) {
tstart[i] = 0;
tsize[i] = 1;
tstep[i] = 1;
} else {
tstart[i] = lstart[j];
tsize[i] = newShape[j];
tstep[i] = lstep[j];
j++;
}
}
}
d = loadData(mon, fileName, asGrey, keepBitWidth, dtype, shape, tstart, tsize, tstep);
d.setShape(newShape); // squeeze shape back
} else {
d = loadData(mon, fileName, asGrey, keepBitWidth, dtype, shape, lstart, newShape, lstep);
}
} catch (ScanFileHolderException e) {
throw new IOException("Problem with TIFF loading", e);
}
return d;
}
};
return createLazyDataset(STACK_NAME, dtype, trueShape.clone(), l);
}
private static Dataset loadData(IMonitor mon, String filename, boolean asGrey, boolean keepBitWidth,
int dtype, int[] oshape, int[] start, int[] count, int[] step) throws ScanFileHolderException {
ImageInputStream iis = null;
ImageReader reader = null;
int rank = start.length;
boolean is2D = rank == 2;
int num = is2D ? 0 : start[0];
int off = rank - 2;
int[] nshape = Arrays.copyOfRange(oshape, off, rank);
int[] nstart = Arrays.copyOfRange(start, off, rank);
int[] nstep = Arrays.copyOfRange(step, off, rank);
SliceND iSlice = new SliceND(nshape, nstart,
new int[] {nstart[0] + count[off] * nstep[0], nstart[1] + count[off + 1] * nstep[1]},
nstep);
SliceND dSlice = new SliceND(count);
Dataset d = is2D || count[0] == 1 ? null : DatasetFactory.zeros(count, dtype);
try {
// test to see if the filename passed will load
iis = new FileImageInputStream(new File(filename));
int[] dataStart = dSlice.getStart();
int[] dataStop = dSlice.getStop();
Dataset image;
try {
reader = new TIFFImageReader(new TIFFImageReaderSpi());
reader.setInput(iis);
image = readImage(filename, reader, asGrey, keepBitWidth, num);
} catch (IllegalArgumentException e) { // catch bad number of bits
logger.debug("Using alternative 12-bit TIFF reader: {}", filename);
reader = new Grey12bitTIFFReader(new Grey12bitTIFFReaderSpi());
reader.setInput(iis);
image = readImage(filename, reader, asGrey, keepBitWidth, num);
}
while (dataStart[0] < count[0]) {
if (image == null)
image = readImage(filename, reader, asGrey, keepBitWidth, num);
image = image.getSliceView(iSlice);
if (d == null) {
d = image;
d.setShape(count);
break;
}
d.setSlice(image, dSlice);
if (monitorIncrement(mon)) {
break;
}
num += step[0];
dataStart[0]++;
dataStop[0] = dataStart[0] + 1;
image = null;
}
} catch (IOException e) {
throw new ScanFileHolderException("IOException loading file '" + filename + "'", e);
} catch (IllegalArgumentException e) {
throw new ScanFileHolderException("IllegalArgumentException interpreting file '" + filename + "'", e);
} finally {
if (reader != null)
reader.dispose();
if (iis != null) {
try {
iis.close();
} catch (IOException e) {
}
}
}
return d;
}
private static Dataset readImage(String filename, ImageReader reader, boolean asGrey, boolean keepBitWidth, int num) throws IOException, ScanFileHolderException {
String imageName = String.format(IMAGE_NAME_FORMAT, num + 1);
IDataHolder holder = LoaderFactory.fetchData(filename, false, num);
if (holder != null) {
IDataset data = holder.getDataset(imageName);
if (data != null) {
return DatasetUtils.convertToDataset(data);
}
}
int n = reader.getNumImages(true);
if (num >= n) {
throw new ScanFileHolderException("Number exceeds images found in '" + filename + "'");
}
BufferedImage input = reader.read(num);
if (input == null) {
throw new ScanFileHolderException("File format in '" + filename + "' cannot be read");
}
Dataset d = createDataset(input, asGrey, keepBitWidth);
if (holder == null) {
holder = new DataHolder();
holder.setLoaderClass(TIFFImageLoader.class);
holder.setFilePath(filename);
LoaderFactory.cacheData(holder, num);
}
holder.addDataset(imageName, d);
return d;
}
/**
* This can be overridden to add metadata
* @param imageMetadata
* @return metadata map
*/
@SuppressWarnings("unused")
protected Map<String, Serializable> createMetadataMap(IIOMetadata imageMetadata) throws ScanFileHolderException {
try {
Map<String, Serializable> metadataTable = new HashMap<String, Serializable>();
TIFFDirectory tiffDir;
try {
tiffDir = TIFFDirectory.createFromMetadata(imageMetadata);
} catch (IIOInvalidTreeException e) {
throw new ScanFileHolderException("Problem creating TIFF directory from header", e);
}
TIFFField[] tiffField = tiffDir.getTIFFFields();
for (int i = 0; i < tiffField.length; i++) {
TIFFField field = tiffField[i];
metadataTable.put(field.getTag().getName(), field.getValueAsString(0));
}
return metadataTable;
} catch (Throwable ne) {
return null;
}
}
@Override
protected void clearMetadata() {
super.clearMetadata();
metadataMap.clear();
}
}