![JAR search and dependency download from the Maven repository](/logo.png)
net.sf.ij_plugins.imageio.IJImageIO Maven / Gradle / Ivy
Show all versions of ijp_imageio Show documentation
/*
* Image/J Plugins
* Copyright (C) 2002-2016 Jarek Sacha
* Author's email: jpsacha at gmail.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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
*
* Latest release available at http://sourceforge.net/projects/ij-plugins/
*/
package net.sf.ij_plugins.imageio;
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.process.ImageProcessor;
import javax.imageio.*;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.List;
/**
* Helper class that for easy reading of images using {@code javax.imageio} into ImageJ representation.
*
* For example:
*
* ImagePlus[] imps = IJImageIO.read(file);
*
*
* @author Jarek Sacha
*/
public class IJImageIO {
private final static boolean useOneBitCompressionDefault = false;
public static final String PREFERRED_SPI_VENDOR = "github.com/jai-imageio";
static class ImageAndMetadata {
public final BufferedImage image;
public final IIOMetadata metadata;
public ImageAndMetadata(BufferedImage bi, IIOMetadata md) {
this.image = bi;
this.metadata = md;
}
}
// TODO: Simplify API of this class, there are too many very similar looking methods for 'write'
static {
// Try to register all available ImageIO SPIs
IIORegistry.getDefaultInstance().registerApplicationClasspathSpis();
}
/**
* Default constructor intentionally made private to prevent instantiation of the class.
*/
private IJImageIO() {
}
/**
* Return array of strings representing all supported image file extension that this data set can read.
*
* @return array of supported file extension.
*/
public static String[] supportedImageReaderExtensions() {
final String[] formatNames = ImageIO.getReaderFormatNames();
final Set extensions = new TreeSet<>();
for (final String formatName : formatNames) {
final Iterator readers = ImageIO.getImageReadersByFormatName(formatName);
while (readers.hasNext()) {
final ImageReader reader = readers.next();
final String[] suffixes = reader.getOriginatingProvider().getFileSuffixes();
if (suffixes != null) {
for (final String suffix : suffixes) {
if (suffix != null && suffix.trim().length() > 0) {
extensions.add(suffix);
}
}
}
}
}
return extensions.toArray(new String[extensions.size()]);
}
public static String[] supportedImageWriterExtensions() {
final String[] formatNames = ImageIO.getWriterFormatNames();
final Set extensions = new TreeSet<>();
for (final String formatName : formatNames) {
final Iterator writers = ImageIO.getImageWritersByFormatName(formatName);
while (writers.hasNext()) {
final ImageWriter writer = writers.next();
final String[] suffixes = writer.getOriginatingProvider().getFileSuffixes();
if (suffixes != null) {
for (final String suffix : suffixes) {
if (suffix != null && suffix.trim().length() > 0) {
extensions.add(suffix);
}
}
}
}
}
return extensions.toArray(new String[extensions.size()]);
}
/**
* Read image from file using using {@code javax.imageio} and convert it to ImageJ representation. All
* images contained in the file ill be read.
*
* @param file input image file.
* @param combineStacks if {@code true} series of images of the same type and size will be combined into stacks (single ImagePlus).
* @return Array of images read from the file. If images are of the same type and size they will
* be combined into a stack and the returned ImagePlus array will have a single element
* with stack size equal to the number of images in the input file.
* @throws IJImageIOException when images cannot be read or represented as ImagePlus.
*/
public static ImagePlus[] read(final File file,
final boolean combineStacks) throws IJImageIOException {
return read(file, combineStacks, null);
}
/**
* Read image from file using using {@code javax.imageio} and convert it to ImageJ representation. All
* images contained in the file ill be read.
*
* @param file input image file.
* @param combineStacks if {@code true} series of images of the same type and size will be combined into stacks (single ImagePlus).
* @param pageIndex index of pages to read from the file. if {@code null} all pages will be read.
* @return Array of images read from the file. If images are of the same type and size they will
* be combined into a stack and the returned ImagePlus array will have a single element
* with stack size equal to the number of images in the input file.
* @throws IJImageIOException when images cannot be read or represented as ImagePlus.
*/
public static ImagePlus[] read(final File file,
final boolean combineStacks,
final int[] pageIndex) throws IJImageIOException {
// FIXME: for TIFF images read description and decode stored information, like calibration, etc.
// Load images
final List ims = readAsBufferedImages(file, pageIndex);
// Convert to ImageJ representation
final List images = new ArrayList<>();
for (final ImageAndMetadata im : ims) {
final ImagePlus imp;
try {
imp = ImagePlusFactory.create(file.getName(), im);
} catch (final IJImageIOException e) {
throw new IJImageIOException("Unable to convert loaded image to ImagePlus. " + e.getMessage(), e);
}
// Add converted to the list
images.add(imp);
}
return combineStacks
? attemptToCombineStacks(images)
: images.toArray(new ImagePlus[images.size()]);
}
/**
* Read image from file using using javax.imageio and convert it to ImageJ representation. All
* images contained in the file will be read, and stacks combined.
* Convenience call to read(file, true) ({@link #read(java.io.File, boolean)} ).
*
* @param file input image file.
* @return Array of images read from the file. If images are of the same type and size they will
* be combined into a stack and the returned ImagePlus array will have a single element
* with stack size equal to the number of images in the input file.
* @throws IJImageIOException when images cannot be read or represented as ImagePlus.
* @see #read(java.io.File, boolean)
*/
public static ImagePlus[] read(final File file) throws IJImageIOException {
return read(file, true);
}
/**
* Read image from file using using {@code javax.imageio} and convert it to ImageJ representation. All
* images contained in the file ill be read.
*
* @param file input image file.
* @return Array of images read from the file. If images are of the same type and size they will
* be combined into a stack and the returned ImagePlus array will have a single element
* with stack size equal to the number of images in the input file.
* @throws IJImageIOException when I/O error occurs.
*/
public static List readAsBufferedImages(final File file) throws IJImageIOException {
return readAsBufferedImages(file, null);
}
/**
* Read image from file using using {@code javax.imageio} and convert it to ImageJ representation. All
* images contained in the file ill be read.
*
* @param file input image file.
* @param pageIndex index of pages to read from the file. if {@code null} all pages will be read.
* @return Array of images read from the file. If images are of the same type and size they will
* be combined into a stack and the returned ImagePlus array will have a single element
* with stack size equal to the number of images in the input file.
* @throws IJImageIOException when I/O error occurs.
*/
public static List readAsBufferedImages(final File file,
final int[] pageIndex) throws IJImageIOException {
if (file == null) {
throw new IllegalArgumentException("Argument 'file' cannot be null.");
}
final ImageInputStream iis = createImageInputStream(file);
try {
// Locate all available readers
final List readerList = getImageReaders(iis);
// Try available readers till one of them reads images with no errors
final StringBuilder errorBuffer = new StringBuilder();
List bufferedImages = null;
for (int i = 0; bufferedImages == null && i < readerList.size(); i++) {
final ImageReader reader = readerList.get(i);
IJImageIO.logDebug("Using reader: " + reader.getClass().getName());
try {
bufferedImages = read(reader, iis, pageIndex);
} catch (final Exception ex) {
errorBuffer.append(reader.getClass().getName()).append(": ").append(ex.getMessage()).append("\n");
}
}
if (bufferedImages != null) {
return bufferedImages;
} else {
throw new IJImageIOException("Unable to read images from file: " + file.getAbsoluteFile() + ". " + errorBuffer.toString());
}
} finally {
try {
iis.close();
} catch (final IOException e) {
final String message = "Failed to close image input stream. " + e.getMessage();
e.printStackTrace();
logDebug(message);
}
}
}
/**
* IJImageIOException
* Read only the first image in the file
.
*
* @param file Image file.
* @return ImageInfo object.
* @throws IJImageIOException In case of I/O error.
*/
public static ImageInfo readPreviewAndInfo(final File file) throws IJImageIOException {
if (file == null) {
throw new IllegalArgumentException("Argument 'file' cannot be null.");
}
final ImageInputStream iis = createImageInputStream(file);
try {
// Locate all available readers
final List readerList = getImageReaders(iis);
// Try available readers till one of them reads images with no errors
final StringBuilder errorBuffer = new StringBuilder();
List bufferedImages = null;
ImageInfo imageInfo = null;
for (int i = 0; bufferedImages == null && i < readerList.size(); i++) {
final ImageReader reader = readerList.get(i);
IJImageIO.logDebug("Using reader: " + reader.getClass().getName());
try {
imageInfo = readInfo(reader, iis);
} catch (final Exception ex) {
errorBuffer.append(reader.getClass().getName()).append(": ").append(ex.getMessage()).append("\n");
}
}
if (imageInfo != null) {
return imageInfo;
} else {
throw new IJImageIOException("Unable to read images from file: " + file.getAbsoluteFile() + ". " + errorBuffer.toString());
}
} finally {
try {
iis.close();
} catch (final IOException e) {
final String message = "Failed to close image input stream. " + e.getMessage();
e.printStackTrace();
logDebug(message);
}
}
}
public static void write(final ImagePlus imp,
final File file,
final ImageWriterSpi imageWriterSpi) throws IJImageIOException {
write(imp, file, imageWriterSpi, useOneBitCompressionDefault);
}
public static void write(final ImagePlus imp,
final File file,
final ImageWriterSpi imageWriterSpi,
final boolean useOneBitCompression) throws IJImageIOException {
final ImageStack stack = imp.getStack();
final BufferedImage[] images = new BufferedImage[stack.getSize()];
for (int i = 0; i < images.length; ++i) {
images[i] = BufferedImageFactory.createFrom(stack.getProcessor(i + 1), useOneBitCompression);
}
write(images, file, imageWriterSpi, null);
}
public static void write(final ImagePlus imp,
final File file,
final ImageWriter writer,
final IIOMetadata metadata,
final ImageWriteParam parameters) throws IJImageIOException {
final ImageStack stack = imp.getStack();
final BufferedImage[] images = new BufferedImage[stack.getSize()];
for (int i = 0; i < images.length; ++i) {
images[i] = BufferedImageFactory.createFrom(stack.getProcessor(i + 1));
}
write(images, file, writer, metadata, parameters);
}
public static void write(final ImagePlus imp,
final File file,
final ImageWriter writer,
final IIOMetadata metadata,
final ImageWriteParam parameters,
final boolean useOneBitCompression) throws IJImageIOException {
final BufferedImage[] bis = new BufferedImage[imp.getNSlices()];
for (int s = 0; s < imp.getNSlices(); s++) {
bis[s] = BufferedImageFactory.createFrom(imp, s, useOneBitCompression);
}
write(bis, file, writer, metadata, parameters);
}
public static void write(ImagePlus imp,
File file,
String format) throws IJImageIOException {
write(imp, file, format, useOneBitCompressionDefault);
}
public static void write(ImagePlus imp,
File file,
String format,
final boolean useOneBitCompression) throws IJImageIOException {
List spis = IJImageOUtils.writerSpiByFormatName(format);
write(imp, file, spis.get(0), useOneBitCompression);
}
/**
* Write image to a file using specified format.
* Supported formats can be obtained calling {@link #supportedImageWriterExtensions()}.
*
* TIFF images will be saved with LZW compression.
*
* @param image image to be saved.
* @param file file where to save the image.
* @param format image format (extension)
* @throws IJImageIOException writing fails or file format is not supported.
*/
public static void write(final BufferedImage image,
final File file,
final String format) throws IJImageIOException {
write(image, file, format, null);
}
/**
* Write image to a file using specified format and also save the metadata if provided.
* Supported formats can be obtained calling {@link #supportedImageWriterExtensions()}.
*
* TIFF images will be saved with LZW compression.
*
* @param images images to be saved.
* @param file file where to save the image.
* @param format image format (extension)
* @param metadata image meta data
* @throws IJImageIOException writing fails or file format is not supported.
*/
public static void write(final BufferedImage[] images,
final File file,
final String format,
final IIOMetadata metadata)
throws IJImageIOException {
Validate.notEmpty(images, "Argument 'image' cannot be null");
Validate.notNull(file, "Argument 'file' cannot be null");
Validate.notNull(format, "Argument 'format' cannot be null");
List spis = IJImageOUtils.writerSpiByFormatName(format);
if (spis.isEmpty()) {
throw new IJImageIOException("Cannot find writer for format: '" + format + "'.");
}
write(images, file, spis.get(0), metadata);
}
public static void write(final BufferedImage[] images,
final File file,
final ImageWriterSpi imageWriterSpi,
final IIOMetadata metadata)
throws IJImageIOException {
Validate.notEmpty(images, "Argument 'image' cannot be null");
Validate.notNull(file, "Argument 'file' cannot be null");
Validate.notNull(imageWriterSpi, "Argument 'format' cannot be null");
final ImageWriter imageWriter;
try {
imageWriter = imageWriterSpi.createWriterInstance();
} catch (IOException e) {
throw new IJImageIOException("Failed to create image writer. " + e.getMessage(), e);
}
final ImageWriteParam parameters = imageWriter.getDefaultWriteParam();
write(images, file, imageWriter, metadata, parameters);
}
public static void write(final BufferedImage[] images,
final File file,
final ImageWriter writer,
final IIOMetadata metadata,
final ImageWriteParam parameters)
throws IJImageIOException {
Validate.notEmpty(images, "Argument 'image' cannot be null");
Validate.notNull(file, "Argument 'file' cannot be null");
Validate.notNull(writer, "Argument 'format' cannot be null");
try (ImageOutputStream outputStream = new FileImageOutputStream(file)) {
writer.setOutput(outputStream);
if (images.length <= 0) {
throw new IllegalArgumentException("There are no input images to write");
}
if (images.length == 1) {
final IIOImage iioImage = new IIOImage(images[0], null, metadata);
writer.write(null, iioImage, parameters);
} else {
writer.prepareWriteSequence(metadata);
for (BufferedImage image : images) {
final IIOImage iioImage = new IIOImage(image, null, metadata);
// Write image
writer.writeToSequence(iioImage, parameters);
}
writer.endWriteSequence();
}
} catch (final FileNotFoundException ex) {
throw new IJImageIOException("Error creating file output stream '" + file.getAbsolutePath() + ". "
+ Objects.toString(ex.getMessage()), ex);
} catch (final IOException ex) {
throw new IJImageIOException("Error writing image to file '" + file.getAbsolutePath() + ". "
+ Objects.toString(ex.getMessage()), ex);
}
}
/**
* Write image to a file using specified format and also save the metadata if provided.
* Supported formats can be obtained calling {@link #supportedImageWriterExtensions()}.
*
* TIFF images will be saved with LZW compression.
*
* @param image image to be saved.
* @param file file where to save the image.
* @param format image format (extension)
* @param metadata image meta data
* @throws IJImageIOException writing fails or file format is not supported.
*/
public static void write(final BufferedImage image,
final File file,
final String format,
final IIOMetadata metadata)
throws IJImageIOException {
write(new BufferedImage[]{image}, file, format, metadata);
}
/**
* Write image in TIFF format with LZW compression using ImageIO
*
* @param file File to save to.
* @param image Image to save.
* @throws IJImageIOException writing fails or file format is not supported.
*/
public static void writeAsTiff(final ImagePlus image,
final File file) throws IJImageIOException {
final String format = "tif";
final IIOMetadata metadata = TiffMetaDataFactory.createFrom(image);
if (image.getStackSize() == 1) {
write(BufferedImageFactory.createFrom(image.getProcessor()), file, format, metadata);
} else {
final ImageStack stack = image.getStack();
final BufferedImage[] images = new BufferedImage[stack.getSize()];
for (int i = 0; i < images.length; ++i) {
images[i] = BufferedImageFactory.createFrom(stack.getProcessor(i + 1));
}
write(images, file, format, metadata);
}
}
/**
* Write image in TIFF format with LZW compression using ImageIO
*
* @param file File to save to.
* @param ip Image to save.
* @throws IJImageIOException writing fails or file format is not supported.
*/
public static void writeAsTiff(final ImageProcessor ip, final File file) throws IJImageIOException {
final String format = "tif";
write(BufferedImageFactory.createFrom(ip), file, format);
}
private static ImageInputStream createImageInputStream(File file) throws IJImageIOException {
final ImageInputStream iis;
try {
iis = ImageIO.createImageInputStream(file);
} catch (final IOException e) {
throw new IJImageIOException("Failed to create image input stream for file: " + file.getAbsolutePath() + ". "
+ e.getMessage(), e);
}
if (iis == null) {
throw new IJImageIOException("Failed to create image input stream for file: " + file.getAbsolutePath() + ".");
}
return iis;
}
/**
* Return list of all currently registered readers that that claim to be able to decode the supplied ImageInputStream.
* Preferred readers are returned at the beginning on=f the list.
*
* @param iis input stream.
* @return list of readers claiming to be able to decode the input stream.
* @throws IJImageIOException if no readers are found.
*/
public static List getImageReaders(ImageInputStream iis) throws IJImageIOException {
final Iterator readers = ImageIO.getImageReaders(iis);
final List preferredReaders = new ArrayList<>();
final List otherReaders = new ArrayList<>();
while (readers.hasNext()) {
final ImageReader reader = readers.next();
ImageReaderSpi spi = reader.getOriginatingProvider();
if (spi != null && spi.getVendorName().toLowerCase().contains(PREFERRED_SPI_VENDOR)) {
preferredReaders.add(reader);
} else {
otherReaders.add(reader);
}
}
preferredReaders.addAll(otherReaders);
// Verify that there is at least one reader available.
if (preferredReaders.isEmpty()) {
throw new IJImageIOException("Input file format not supported: Cannot find proper image reader.");
}
return preferredReaders;
}
private static List read(final ImageReader reader,
final ImageInputStream iis,
int[] pageIndex)
throws IJImageIOException {
// iis.reset();
try {
iis.seek(0);
} catch (final IOException e) {
throw new IJImageIOException("Unable to reset input stream to position 0. ", e);
}
reader.setInput(iis, false, false);
// How many images are in the file and what is the first image index
final int numImages;
// reader.addIIOReadProgressListener(new ProgressListener(numImages));
try {
numImages = reader.getNumImages(true);
} catch (final IOException e) {
throw new IJImageIOException("Failed to retrieve number of images in the file. ", e);
}
final int minIndex = reader.getMinIndex();
if (pageIndex == null) {
pageIndex = new int[numImages - minIndex];
for (int i = minIndex; i < numImages; ++i) {
pageIndex[i] = i;
}
}
// Read each image and add it to list 'images'
final List images = new ArrayList<>();
for (int i = 0; i < pageIndex.length; i++) {
IJ.showProgress(i, pageIndex.length);
final BufferedImage bi;
final IIOMetadata md;
try {
bi = reader.read(i);
md = reader.getImageMetadata(i);
} catch (final IOException e) {
throw new IJImageIOException("Error reading image with internal index " + i
+ ". Min internal index is " + minIndex + ". ", e);
}
// // Read metadata for this image
// final ImageReadParam imageReadParam = reader.getDefaultReadParam();
// final IIOImage a = reader.readAll(j, imageReadParam);
// final IIOMetadata metadata = a.getMetadata();
md.getController();
images.add(new ImageAndMetadata(bi, md));
IJ.showProgress(i + 1, pageIndex.length);
}
return images;
}
private static ImageInfo readInfo(final ImageReader reader,
final ImageInputStream iis)
throws IJImageIOException {
// iis.reset();
try {
iis.seek(0);
} catch (final IOException e) {
throw new IJImageIOException("Unable to reset input stream to position 0. ", e);
}
reader.setInput(iis, false, false);
final ImageInfo imageInfo = new ImageInfo();
try {
imageInfo.numberOfPages = reader.getNumImages(true);
imageInfo.codecName = reader.getFormatName();
if (reader.hasThumbnails(0)) {
imageInfo.previewImage = reader.readThumbnail(0, 0);
} else {
imageInfo.previewImage = reader.read(0);
}
} catch (final IOException ex) {
throw new IJImageIOException(ex);
}
return imageInfo;
}
/**
* Attempts to combine images on the list into a stack.
* Images cannot be combined if they are of different types or different sizes.
*
* @param imageList List of images to combine into a stack.
* @return Combined image stacks.
*/
private static ImagePlus[] attemptToCombineStacks(final List imageList) {
final List result = new ArrayList<>();
int sourceIndex = 0;
while (sourceIndex < imageList.size()) {
// Test how many images can be combined
final int chainLength = stackableChain(imageList, sourceIndex);
// Combine
final ImagePlus imp = imageList.get(sourceIndex);
if (chainLength > 1) {
final ImageStack stack = imp.getStack();
for (int i = sourceIndex + 1; i < sourceIndex + chainLength; ++i) {
final ImageStack s2 = imageList.get(i).getStack();
for (int s2i = 1; s2i <= s2.getSize(); s2i++) {
stack.addSlice(s2.getSliceLabel(s2i), s2.getProcessor(s2i));
}
}
imp.setStack(imp.getTitle(), stack);
}
// Add to output
result.add(imp);
sourceIndex += chainLength;
}
return result.toArray(new ImagePlus[result.size()]);
}
private static int stackableChain(final List imageList, final int startIndex) {
if (imageList.size() <= startIndex || startIndex < 0) {
return 0;
}
if (imageList.size() == startIndex + 1) {
return 1;
}
final ImagePlus firstImage = imageList.get(startIndex);
final int fileType = firstImage.getFileInfo().fileType;
final int w = firstImage.getWidth();
final int h = firstImage.getHeight();
int count = 1;
for (int i = startIndex + 1; i < imageList.size(); i++) {
final ImagePlus imp = imageList.get(i);
if (fileType == imp.getFileInfo().fileType && w == imp.getWidth() && h == imp.getHeight()) {
count++;
} else {
break;
}
}
return count;
}
/**
* Helper method to print log message using {@link ij.IJ#log} when {@link ij.IJ#debugMode} is
* set to true
.
*
* @param message log message
*/
private static void logDebug(final String message) {
if (IJ.debugMode) {
IJ.log(message);
}
}
private static final class ProgressListener implements IIOReadProgressListener {
final private int numberOfImages;
private ProgressListener(int numberOfImages) {
this.numberOfImages = numberOfImages;
}
@Override
public void sequenceStarted(ImageReader source, int minIndex) {
System.out.println("IJImageIO.sequenceStarted");
}
@Override
public void sequenceComplete(ImageReader source) {
System.out.println("IJImageIO.sequenceComplete");
}
@Override
public void imageStarted(ImageReader source, int imageIndex) {
System.out.println("IJImageIO.imageStarted: " + imageIndex);
}
@Override
public void imageProgress(ImageReader source, float percentageDone) {
System.out.println("IJImageIO.imageProgress: " + percentageDone);
}
@Override
public void imageComplete(ImageReader source) {
System.out.println("IJImageIO.imageComplete");
}
@Override
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
System.out.println("IJImageIO.thumbnailStarted");
}
@Override
public void thumbnailProgress(ImageReader source, float percentageDone) {
System.out.println("IJImageIO.thumbnailProgress: " + percentageDone);
}
@Override
public void thumbnailComplete(ImageReader source) {
System.out.println("IJImageIO.thumbnailComplete");
}
@Override
public void readAborted(ImageReader source) {
System.out.println("IJImageIO.readAborted");
}
}
/*
* Basic image information including first image in the file.
*/
public static class ImageInfo {
public Image previewImage;
public int numberOfPages;
public String codecName;
}
}