/*
* 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.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.SampleModel;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
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.DatasetUtils;
import org.eclipse.january.dataset.IDataset;
import org.eclipse.january.dataset.ILazyDataset;
import org.eclipse.january.dataset.LazyDataset;
import org.eclipse.january.dataset.RGBDataset;
import org.eclipse.january.dataset.SliceND;
import org.eclipse.january.metadata.Metadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that loads in data from an image file using native Java ImageIO
* library with built-in image reader/writer.
* <p>
* A Raster object comprises a SampleModel and a DataBuffer where we have the
* model of an image comprising bands of samples so that each pixel is a tuple
* of samples (e.g. R, G, B) and the SampleModel maps to/from a sample (of a
* pixel) to information held by the DataBuffer. A BufferedImage object
* comprises a Raster and a ColorModel. The reader/writer handles BufferImages
* so access the image data via the BufferedImage's Raster attribute.
*/
public class JavaImageLoader extends AbstractFileLoader {
protected static final Logger logger = LoggerFactory.getLogger(JavaImageLoader.class);
private String fileType = "";
protected boolean asGrey;
protected boolean keepBitWidth = false;
/**
* @return true if loader keeps bit width of pixels
*/
public boolean isKeepBitWidth() {
return keepBitWidth;
}
/**
* set loader to keep bit width of pixels
* @param keepBitWidth
*/
public void setKeepBitWidth(boolean keepBitWidth) {
this.keepBitWidth = keepBitWidth;
}
/**
* @param FileName
* which is the name of the file being passed to this class.
* @param FileType
* which is the type of image being passed to the class
*/
public JavaImageLoader(String FileName, String FileType) {
this(FileName, FileType, false);
}
/**
* @param FileName
* which is the name of the file being passed to this class.
* @param FileType
* which is the type of image being passed to the class
* @param convertToGrey
* interpret colour image as a greyscale image (rather than taking the first colour channel)
*/
public JavaImageLoader(String FileName, String FileType, boolean convertToGrey) {
this(FileName, FileType, convertToGrey, false);
}
/**
* @param FileName
* which is the name of the file being passed to this class.
* @param FileType
* which is the type of image being passed to the class
* @param convertToGrey
* interpret colour image as a greyscale image (rather than taking the first colour channel)
* @param keepBitWidth
* true if loader keeps bit width of pixels
*/
public JavaImageLoader(String FileName, String FileType, boolean convertToGrey, boolean keepBitWidth) {
fileName = FileName;
fileType = FileType; // format name
asGrey = convertToGrey;
this.keepBitWidth = keepBitWidth;
}
@Override
protected void clearMetadata() {
metadata = null;
}
@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();
}
DataHolder output = new DataHolder();
// test to see if the filename passed will load
f = new File(fileName);
ImageInputStream iis = null;
try {
iis = ImageIO.createImageInputStream(f);
} catch (Exception e) {
logger.error("Problem creating input stream for file " + fileName, e);
throw new ScanFileHolderException("Problem creating input stream for file " + fileName, e);
}
if (iis == null) {
logger.error("File format in '{}' cannot be read", fileName);
throw new ScanFileHolderException("File format in '" + fileName + "' cannot be read");
}
Iterator<ImageReader> it = ImageIO.getImageReaders(iis);
boolean loaded = false;
while (it.hasNext()) {
ImageReader reader = it.next();
reader.setInput(iis, false, loadMetadata);
if (loadLazily) {
loaded = createLazyDatasets(output, reader);
} else {
loaded = createDatasets(output, reader);
}
if (loaded) {
if (loadMetadata) {
createMetadata(output, reader);
}
break;
}
}
if (!loaded) {
logger.error("File format in '{}' cannot be read", fileName);
throw new ScanFileHolderException("File format in '" + fileName + "' cannot be read");
}
return output;
}
private boolean createDatasets(DataHolder output, ImageReader reader) {
int j = 1; // start at 1
BufferedImage input = null;
for (int i = 0; true; i++) {
try {
String name = String.format(IMAGE_NAME_FORMAT, j);
input = reader.read(i);
Dataset data = createDataset(input);
data.setName(name);
output.addDataset(name, data);
} catch (IOException e) {
return false;
} catch (IndexOutOfBoundsException e) {
break;
} catch (ScanFileHolderException e) {
logger.error("Problem with creating dataset from image", e);
return false;
}
j++;
}
return true;
}
protected boolean createLazyDatasets(DataHolder output, ImageReader reader) {
for (int i = 0; true; i++) {
try {
int[] shape = new int[] {reader.getHeight(i), reader.getWidth(i)};
Iterator<ImageTypeSpecifier> it = reader.getImageTypes(i);
SampleModel sm = it.next().getSampleModel();
int dtype = AWTImageUtils.getDTypeFromImage(sm, keepBitWidth)[0];
final String name = String.format(IMAGE_NAME_FORMAT, i + 1);
final int num = i;
LazyDataset lazy = createLazyDataset(name, dtype, shape, new LazyLoaderStub() {
@Override
public IDataset getDataset(IMonitor mon, SliceND slice) throws IOException {
try {
Dataset data = loadDataset(fileName, name, num, asGrey, keepBitWidth);
return data == null ? null : data.getSliceView(slice);
} catch (ScanFileHolderException e) {
throw new IOException(e);
}
}
});
output.addDataset(name, lazy);
// IIOMetadata imd = reader.getImageMetadata(i);
// imd.getAsTree(imd.getNativeMetadataFormatName()).toString();
} catch (IndexOutOfBoundsException e) {
break;
} catch (Exception e) {
logger.warn("Could not get height or width for image {}", i);
continue;
}
}
return output.getNames().length > 0;
}
private static Dataset loadDataset(String path, String name, int num, boolean asGrey, boolean keepBitWidth) throws ScanFileHolderException {
IDataHolder holder = LoaderFactory.fetchData(path, false, num);
if (holder != null) {
IDataset data = holder.getDataset(name);
if (data != null) {
return DatasetUtils.convertToDataset(data);
}
}
File f = new File(path);
ImageInputStream iis = null;
try {
iis = ImageIO.createImageInputStream(f);
} catch (Exception e) {
logger.error("Problem creating input stream for file " + path, e);
throw new ScanFileHolderException("Problem creating input stream for file " + path, e);
}
if (iis == null) {
logger.error("File format in '{}' cannot be read", path);
throw new ScanFileHolderException("File format in '" + path + "' cannot be read");
}
Iterator<ImageReader> it = ImageIO.getImageReaders(iis);
while (it.hasNext()) {
ImageReader reader = it.next();
reader.setInput(iis, false, true);
Dataset data;
try {
data = createDataset(reader.read(num), asGrey, keepBitWidth);
data.setName(name);
if (holder == null) {
holder = new DataHolder();
holder.setLoaderClass(JavaImageLoader.class);
holder.setFilePath(path);
LoaderFactory.cacheData(holder, num);
}
holder.addDataset(name, data);
return data;
} catch (IndexOutOfBoundsException e) {
throw new ScanFileHolderException("Image number is incorrect");
} catch (IOException e) {
logger.error("Problem reading file", e);
} catch (ScanFileHolderException e) {
logger.error("Problem creating dataset", e);
}
}
return null;
}
protected void createMetadata(DataHolder output, @SuppressWarnings("unused") ImageReader reader) {
if (metadata == null) {
metadata = new Metadata();
}
metadata.setFilePath(fileName);
for (String n : output.getNames()) {
ILazyDataset lazy = output.getLazyDataset(n);
metadata.addDataInfo(n, lazy.getShape());
}
}
protected Dataset createDataset(BufferedImage input) throws ScanFileHolderException {
return createDataset(input, asGrey, keepBitWidth);
}
protected static Dataset createDataset(BufferedImage input, boolean asGrey, boolean keepBitWidth) throws ScanFileHolderException {
Dataset data = null;
try {
Dataset[] channels = AWTImageUtils.makeDatasets(input, keepBitWidth);
// final int bands = input.getData().getNumBands();
final int bands = channels.length;
if (bands == 1) {
data = channels[0];
} else {
if (input.getColorModel().getColorSpace().getType() != ColorSpace.TYPE_RGB) {
throw new ScanFileHolderException("File does not contain RGB data");
}
if (bands < 3) {
throw new ScanFileHolderException("Number of colour channels is less than three so cannot load and convert");
}
data = DatasetUtils.createCompoundDataset(Dataset.RGB, channels);
if (asGrey)
data = ((RGBDataset) data).createGreyDataset(channels[0].getDType());
}
} catch (Exception e) {
throw new ScanFileHolderException("There was a problem loading the image", e);
}
return data;
}
protected File findCorrectSuffix() throws ScanFileHolderException {
String[] suffixes = ImageIO.getReaderFileSuffixes();
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
File f = null;
testforsuffix: {
if (!extension.equals(fileName)) { // there is a suffix
for (String s : suffixes) {
if (extension.equalsIgnoreCase(s)) {
break testforsuffix;
}
}
}
// try standard suffix first then all supported suffixes
String name = fileName + "." + fileType;
f = new File(name);
if (f.exists()) {
fileName = name;
break testforsuffix;
}
for (String s : suffixes) {
name = fileName + "." + s;
f = new File(name);
if (f.exists()) {
fileName = name;
break testforsuffix;
}
}
}
if (f == null || !f.exists()) {
throw new ScanFileHolderException("Does not exist",
new FileNotFoundException(fileName));
}
return f;
}
}