/*
* ------------------------------------------------------------------------
*
* Copyright (C) 2003 - 2013
* University of Konstanz, Germany and
* KNIME GmbH, Konstanz, Germany
* Website: http://www.knime.org; Email: contact@knime.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, Version 3, as
* published by the Free Software Foundation.
*
* 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, see <http://www.gnu.org/licenses>.
*
* Additional permission under GNU GPL version 3 section 7:
*
* KNIME interoperates with ECLIPSE solely via ECLIPSE's plug-in APIs.
* Hence, KNIME and ECLIPSE are both independent programs and are not
* derived from each other. Should, however, the interpretation of the
* GNU GPL Version 3 ("License") under any applicable laws result in
* KNIME and ECLIPSE being a combined program, KNIME GMBH herewith grants
* you the additional permission to use and propagate KNIME together with
* ECLIPSE with only the license terms in place for ECLIPSE applying to
* ECLIPSE and the GNU GPL Version 3 applying for KNIME, provided the
* license terms of ECLIPSE themselves allow for the respective use and
* propagation of ECLIPSE together with KNIME.
*
* Additional permission relating to nodes for KNIME that extend the Node
* Extension (and in particular that are based on subclasses of NodeModel,
* NodeDialog, and NodeView) and that only interoperate with KNIME through
* standard APIs ("Nodes"):
* Nodes are deemed to be separate and independent programs and to not be
* covered works. Notwithstanding anything to the contrary in the
* License, the License does not apply to Nodes, you are not required to
* license Nodes under the License, and you are granted a license to
* prepare and propagate Nodes, in each case even if such Nodes are
* propagated with or for interoperation with KNIME. The owner of a Node
* may freely choose the license terms applicable to such Node, including
* when such Node is propagated with or for interoperation with KNIME.
* --------------------------------------------------------------------- *
*
*/
package org.knime.knip.io;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Level;
import org.knime.core.node.NodeLogger;
import org.knime.knip.base.exceptions.KNIPRuntimeException;
import org.knime.knip.core.KNIPLogService;
import org.knime.knip.core.types.NativeTypes;
import org.knime.knip.core.util.MiscViews;
import org.scijava.Context;
import io.scif.Format;
import io.scif.FormatException;
import io.scif.Metadata;
import io.scif.Parser;
import io.scif.Plane;
import io.scif.Reader;
import io.scif.config.SCIFIOConfig;
import io.scif.filters.ChannelFiller;
import io.scif.filters.PlaneSeparator;
import io.scif.filters.ReaderFilter;
import io.scif.gui.AWTImageTools;
import io.scif.img.IO;
import io.scif.img.ImageRegion;
import io.scif.img.ImgOpener;
import io.scif.img.ImgUtilityService;
import io.scif.img.Range;
import io.scif.img.SCIFIOImgPlus;
import io.scif.ome.OMEMetadata;
import io.scif.refs.RefManagerService;
import loci.formats.FormatHandler;
import net.imagej.ImgPlus;
import net.imagej.axis.Axes;
import net.imagej.axis.AxisType;
import net.imagej.axis.CalibratedAxis;
import net.imagej.axis.TypedAxis;
import net.imglib2.Cursor;
import net.imglib2.RandomAccess;
import net.imglib2.img.Img;
import net.imglib2.img.ImgFactory;
import net.imglib2.img.array.ArrayImgFactory;
import net.imglib2.img.cell.CellImgFactory;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.util.Pair;
import ome.xml.model.OMEModelImpl;
import ome.xml.model.enums.handlers.DetectorTypeEnumHandler;
/**
* TODO Auto-generated
*
* @author <a href="mailto:dietzc85@googlemail.com">Christian Dietz</a>
* @author <a href="mailto:horn_martin@gmx.de">Martin Horn</a>
* @author <a href="mailto:michael.zinsmaier@googlemail.com">Michael
* Zinsmaier</a>
*/
public class ScifioImgSource implements ImgSource {
private static final NodeLogger LOGGER = NodeLogger.getLogger(ScifioImgSource.class);
/* ID of the source */
private static final String SOURCE_ID = "Scifio Image Source";
private UnclosableReaderFilter m_reader;
/* The currently used file by the reader */
private String m_currentFile;
private final ImgOpener m_imgOpener;
@SuppressWarnings("rawtypes")
private final ImgFactory m_imgFactory;
private ImgUtilityService m_imgUtilsService;
private boolean m_checkFileFormat;
private SCIFIOConfig m_scifioConfig;
/*
* helps do decide if the checkFileFormat option could have been set to
* false.
*/
private boolean m_usedDifferentReaders;
private Level m_rootLvl;
public ScifioImgSource() {
this(true);
}
@SuppressWarnings("rawtypes")
public ScifioImgSource(final boolean checkFileFormat) {
this(new ArrayImgFactory(), checkFileFormat, true);
}
/**
* Creates a new ScifioImgSource.
*
* @param imgFactory
* the image factory to be used
* @param checkFileFormat
* if for each new file to be read a new reader should be created
* or the old one can be re-used
* @param isGroupFiles
* if a file points to a collection of files
*
*/
public ScifioImgSource(@SuppressWarnings("rawtypes") final ImgFactory imgFactory, final boolean checkFileFormat,
final boolean isGroupFiles) {
this(imgFactory, checkFileFormat, new SCIFIOConfig().groupableSetGroupFiles(isGroupFiles));
}
/**
* Creates a new ScifioImgSource.
*
* @param imgFactory
* the image factory to be used
* @param checkFileFormat
* if for each new file to be read a new reader should be created
* or the old one can be re-used
* @param config
* additional scifio-specific settings
*
*
*/
public ScifioImgSource(@SuppressWarnings("rawtypes") final ImgFactory imgFactory, final boolean checkFileFormat,
final SCIFIOConfig config) {
m_scifioConfig = config;
m_checkFileFormat = checkFileFormat;
m_imgOpener = new ImgOpener(ScifioGateway.getSCIFIO().getContext());
m_imgFactory = imgFactory;
m_usedDifferentReaders = false;
// TODO Workaround to suppress BioFormats info message flooding
org.apache.log4j.Logger.getLogger(OMEModelImpl.class).setLevel(Level.ERROR);
org.apache.log4j.Logger.getLogger(FormatHandler.class).setLevel(Level.ERROR);
org.apache.log4j.Logger.getLogger(DetectorTypeEnumHandler.class).setLevel(Level.ERROR);
m_rootLvl = org.apache.log4j.Logger.getLogger(KNIPLogService.class.getSimpleName()).getLevel();
}
@Override
public void close() {
if (m_reader != null) {
try {
m_reader.closeNow();
} catch (final IOException e) {
}
}
}
@Override
public String getSource(final String imgRef) throws Exception {
return SOURCE_ID;
}
/**
* @param ref
* @return number of images contained in the specified file
* @throws Exception
*/
public int getSeriesCount(final String imgRef) throws Exception {
return getReader(imgRef).getImageCount();
}
/**
* @param ref
* @return omexml metadata string
* @throws Exception
*/
public String getOMEXMLMetadata(final String imgRef) throws Exception {
final Metadata meta = getReader(imgRef).getMetadata();
final OMEMetadata omexml = new OMEMetadata(ScifioGateway.getSCIFIO().getContext());
try {
ScifioGateway.getSCIFIO().translator().translate(meta, omexml, false);
} catch (Exception e) {
throw new IOException("Could not read the OMEXML-metadata! : " + e);
}
final String xml = omexml.getRoot().dumpXML();
return xml;
}
@SuppressWarnings("rawtypes")
@Override
public ImgPlus<RealType> getImg(final String imgRef, final int currentSeries) throws Exception {
return getImg(imgRef, currentSeries, null);
}
// TODO: Use new SCIFIO API
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public ImgPlus<RealType> getImg(final String imgRef, final int currentSeries,
final Pair<TypedAxis, long[]>[] axisSelectionConstraints) throws Exception {
org.apache.log4j.Logger.getLogger(KNIPLogService.class.getSimpleName()).setLevel(Level.ERROR);
// FIXME: WORKAROUND till bug 3391 is fixed (support for
// SCIFIOImgPlus)
if (m_imgFactory instanceof CellImgFactory) {
LOGGER.warn(
"Cell images are not fully supported, yet. Images are read but potential subset selection is ignored.");
// copy scifio img plus to an ordinary cell img
final SCIFIOImgPlus<RealType> scifio = (SCIFIOImgPlus<RealType>) IO.open(imgRef);
final Img<RealType> tmp = new CellImgFactory().create(scifio, scifio.firstElement().createVariable());
final Cursor<RealType> inCur = scifio.cursor();
final RandomAccess<RealType> outRA = tmp.randomAccess();
while (inCur.hasNext()) {
inCur.fwd();
outRA.setPosition(inCur);
outRA.get().set(inCur.get());
}
final ImgPlus<RealType> res = new ImgPlus<RealType>(tmp, scifio);
// register
final Context ctx = m_imgOpener.getContext();
final RefManagerService refManagerService = ctx.getService(RefManagerService.class);
refManagerService.manage(res, ctx);
return res;
}
final SCIFIOConfig options = new SCIFIOConfig();
options.imgOpenerSetComputeMinMax(false);
options.imgOpenerSetIndex(currentSeries);
// boolean withCropping = false;
if (axisSelectionConstraints != null && axisSelectionConstraints.length > 0) {
// withCropping = true;
// TODO: Still WRONG WRONG WRONG only 5d support?
final Range[] ranges = new Range[axisSelectionConstraints.length];
final AxisType[] axes = new AxisType[axisSelectionConstraints.length];
for (int i = 0; i < ranges.length; i++) {
ranges[i] = new Range(axisSelectionConstraints[i].getB());
axes[i] = axisSelectionConstraints[i].getA().type();
}
options.imgOpenerSetRegion(new ImageRegion(axes, ranges));
}
try {
final ImgPlus<RealType> ret = MiscViews.cleanImgPlus(
m_imgOpener.openImg(getReader(imgRef), getPixelType(imgRef, currentSeries), m_imgFactory, options));
org.apache.log4j.Logger.getLogger(KNIPLogService.class.getSimpleName()).setLevel(m_rootLvl);
return ret;
} catch(Exception e) {
m_reader.closeNow();
m_reader = null;
throw e;
}
}
@Override
public <T extends RealType<T> & NativeType<T>> ImgPlus<T> getTypedImg(final String imgRef, final int currentSeries,
T type) throws Exception {
return getTypedImg(imgRef, currentSeries, null, type);
}
// TODO: Use new SCIFIO API
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <T extends RealType<T> & NativeType<T>> ImgPlus<T> getTypedImg(final String imgRef, final int currentSeries,
final Pair<TypedAxis, long[]>[] axisSelectionConstraints, T type) throws Exception {
org.apache.log4j.Logger.getLogger(KNIPLogService.class.getSimpleName()).setLevel(Level.ERROR);
// FIXME: WORKAROUND till bug 3391 is fixed (support for
// SCIFIOImgPlus)
if (m_imgFactory instanceof CellImgFactory) {
LOGGER.warn(
"Cell images are not fully supported, yet. Images are read but potential subset selection is ignored.");
// copy scifio img plus to an ordinary cell img
final SCIFIOImgPlus<T> scifio = (SCIFIOImgPlus<T>) IO.openImg(imgRef, type);
final Img<T> tmp = new CellImgFactory().create(scifio, type);
final Cursor<T> inCur = scifio.cursor();
final RandomAccess<T> outRA = tmp.randomAccess();
while (inCur.hasNext()) {
inCur.fwd();
outRA.setPosition(inCur);
outRA.get().set(inCur.get());
}
final ImgPlus<T> res = new ImgPlus<T>(tmp, scifio);
// register
final Context ctx = m_imgOpener.getContext();
final RefManagerService refManagerService = ctx.getService(RefManagerService.class);
refManagerService.manage(res, ctx);
return res;
}
final SCIFIOConfig options = new SCIFIOConfig();
options.imgOpenerSetComputeMinMax(false);
options.imgOpenerSetIndex(currentSeries);
// boolean withCropping = false;
if (axisSelectionConstraints != null && axisSelectionConstraints.length > 0) {
// withCropping = true;
// TODO: Still WRONG WRONG WRONG only 5d support?
final Range[] ranges = new Range[axisSelectionConstraints.length];
final AxisType[] axes = new AxisType[axisSelectionConstraints.length];
for (int i = 0; i < ranges.length; i++) {
ranges[i] = new Range(axisSelectionConstraints[i].getB());
axes[i] = axisSelectionConstraints[i].getA().type();
}
options.imgOpenerSetRegion(new ImageRegion(axes, ranges));
}
final ImgPlus<T> ret = MiscViews
.cleanImgPlus(m_imgOpener.openImg(getReader(imgRef), type, m_imgFactory, options));
org.apache.log4j.Logger.getLogger(KNIPLogService.class.getSimpleName()).setLevel(m_rootLvl);
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public BufferedImage getThumbnail(final String imgRef, final int planeNo) throws Exception {
final Reader r = getReader(imgRef);
final int sizeX = (int) r.getMetadata().get(0).getThumbSizeX();
final int sizeY = (int) r.getMetadata().get(0).getThumbSizeY();
// image index / plane index
final Plane pl = r.openThumbPlane(0, 0);
return AWTImageTools.makeImage(pl.getBytes(), sizeX, sizeY,
NativeTypes.getPixelType(getPixelType(imgRef, 0)).isSigned());
}
// META DATA
/**
* {@inheritDoc}
*/
@Override
public List<CalibratedAxis> getAxes(final String imgRef, final int currentSeries) throws Exception {
return getReader(imgRef).getMetadata().get(currentSeries).getAxes();
}
/**
* {@inheritDoc}
*/
@Override
public long[] getDimensions(final String imgRef, final int currentSeries) throws Exception {
final long[] tmp = getReader(imgRef).getMetadata().get(currentSeries).getAxesLengths();
final long[] ret = new long[tmp.length];
for (int i = 0; i < tmp.length; i++) {
ret[i] = tmp[i];
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public String getName(final String imgRef) throws Exception {
return getReader(imgRef).getMetadata().getDatasetName();
}
public boolean usedDifferentReaders() {
return m_usedDifferentReaders;
}
/**
* {@inheritDoc}
*
* @throws IOException
* @throws FormatException
*/
@SuppressWarnings("rawtypes")
@Override
public RealType getPixelType(final String imgRef, final int currentSeries) throws IOException, FormatException {
if (m_imgUtilsService == null) {
m_imgUtilsService = ScifioGateway.getSCIFIO().getContext().getService(ImgUtilityService.class);
}
final RealType type = m_imgUtilsService
.makeType(getReader(imgRef).getMetadata().get(currentSeries).getPixelType());
return type;
}
// -- private helper methods --
private UnclosableReaderFilter getReader(final String imgRef) throws FormatException, IOException {
org.apache.log4j.Logger.getLogger(KNIPLogService.class.getSimpleName()).setLevel(Level.ERROR);
if (m_reader == null || (!m_currentFile.equals(imgRef) && m_checkFileFormat)) {
final Format format = ScifioGateway.getSCIFIO().format().getFormat(imgRef,
new SCIFIOConfig().checkerSetOpen(true));
final UnclosableReaderFilter r = new UnclosableReaderFilter(format.createReader());
final Parser p = format.createParser();
r.setMetadata(p.parse(imgRef, m_scifioConfig));
// check if the current file really contains images
if (r.getMetadata().getImageCount() == 0) {
throw new KNIPRuntimeException("No images available in file " + imgRef);
}
// without the "separate"-stuff the images will not be split
// correctly for some types. This fixes the bug if, for instance,
// only Channel 1 is desired and Channel 0 was returned every time.
r.enable(ChannelFiller.class);
r.enable(PlaneSeparator.class).separate(axesToSplit(r));
if (m_reader != null && !(m_reader.getFormat().getClass().equals(r.getFormat().getClass()))) {
// more than one reader (class) has been used
m_usedDifferentReaders = true;
}
if (m_reader != null) {
m_reader.closeNow();
}
m_reader = r;
} else if (!m_currentFile.equals(imgRef) && !m_checkFileFormat) {
// re-use the last reader, set the new image reference (i.e. id) and
// parse the metadata
m_reader.closeNow();
final Parser p = m_reader.getFormat().createParser();
m_reader.setMetadata(p.parse(imgRef, m_scifioConfig));
// TODO maybe this is a bug and we shouldn't have to do this.
m_reader.enable(ChannelFiller.class);
m_reader.enable(PlaneSeparator.class).separate(axesToSplit(m_reader));
}
// sets the file the reader currently points to
m_currentFile = imgRef;
org.apache.log4j.Logger.getLogger(KNIPLogService.class.getSimpleName()).setLevel(m_rootLvl);
return m_reader;
}
/*
* Returns a list of all AxisTypes that should be split out. This is a list
* of all non-X,Y planar axes. Always tries to split {@link Axes#CHANNEL}.
*
* Code taken from ImgOpener!
*/
private AxisType[] axesToSplit(final ReaderFilter r) {
final Set<AxisType> axes = new HashSet<AxisType>();
final Metadata meta = r.getTail().getMetadata();
// Split any non-X,Y axis
for (final CalibratedAxis t : meta.get(0).getAxesPlanar()) {
final AxisType type = t.type();
if (!(type == Axes.X || type == Axes.Y)) {
axes.add(type);
}
}
// Ensure channel is attempted to be split
axes.add(Axes.CHANNEL);
return axes.toArray(new AxisType[axes.size()]);
}
/* Helper class to prevent a reader from being closed. */
private class UnclosableReaderFilter extends ReaderFilter {
public UnclosableReaderFilter(final Reader r) {
super(r);
}
@Override
public void close() throws IOException {
// do nothing here to prevent the reader from being closed
}
@Override
public void close(final boolean fileOnly) throws IOException {
// do nothing here to prevent the reader from being closed
}
/*
* Closes the underlying reader, called by the
* ScifioImgsource.close()-method.
*/
private void closeNow() throws IOException {
super.close(false);
}
}
}