/* ------------------------------------------------------------------
* This source code, its documentation and all appendant files
* are protected by copyright law. All rights reserved.
*
* Copyright, 2008 - 2012
* KNIME.com, Zurich, Switzerland
*
* You may not modify, publish, transmit, transfer or sell, reproduce,
* create derivative works from, distribute, perform, display, or in
* any way exploit any of the content, in whole or in part, except as
* otherwise expressly permitted in writing by the copyright owner or
* as specified in the license file distributed with this product.
*
* If you have any questions please contact the copyright holder:
* website: www.knime.com
* email: contact@knime.com
* ---------------------------------------------------------------------
*
* History
* Jun 18, 2012 (hornm): created
*/
package org.knime.knip.base.data.aggregation;
import java.awt.Component;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import org.knime.base.data.aggregation.AggregationOperator;
import org.knime.base.data.aggregation.GlobalSettings;
import org.knime.base.data.aggregation.OperatorColumnSettings;
import org.knime.core.data.DataCell;
import org.knime.core.data.DataRow;
import org.knime.core.data.DataTableSpec;
import org.knime.core.data.DataType;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.knime.core.node.NotConfigurableException;
import org.knime.core.node.defaultnodesettings.DialogComponentString;
import org.knime.core.node.defaultnodesettings.SettingsModelString;
import org.knime.knip.base.data.img.ImgPlusCell;
import org.knime.knip.base.data.img.ImgPlusValue;
import org.knime.knip.core.data.img.DefaultImgMetadata;
import net.imagej.ImgPlus;
import net.imagej.ImgPlusMetadata;
import net.imagej.axis.Axes;
import net.imagej.axis.CalibratedAxis;
import net.imagej.axis.DefaultLinearAxis;
import net.imagej.space.DefaultCalibratedSpace;
import net.imglib2.Cursor;
import net.imglib2.img.Img;
import net.imglib2.img.ImgView;
import net.imglib2.img.array.ArrayImg;
import net.imglib2.img.array.ArrayImgFactory;
import net.imglib2.img.basictypeaccess.array.ArrayDataAccess;
import net.imglib2.img.basictypeaccess.array.ByteArray;
import net.imglib2.img.basictypeaccess.array.DoubleArray;
import net.imglib2.img.basictypeaccess.array.FloatArray;
import net.imglib2.img.basictypeaccess.array.IntArray;
import net.imglib2.img.basictypeaccess.array.LongArray;
import net.imglib2.img.basictypeaccess.array.ShortArray;
import net.imglib2.img.planar.PlanarImg;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.RealType;
import net.imglib2.type.numeric.integer.ByteType;
import net.imglib2.type.numeric.integer.IntType;
import net.imglib2.type.numeric.integer.LongType;
import net.imglib2.type.numeric.integer.ShortType;
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.integer.UnsignedShortType;
import net.imglib2.type.numeric.real.DoubleType;
import net.imglib2.type.numeric.real.FloatType;
import net.imglib2.util.Fraction;
import net.imglib2.view.Views;
/**
* Aggregation operator which merges images.
*
* @author Martin Horn, University of Konstanz
*
*/
public class ImgMergeOperator<T extends RealType<T> & NativeType<T>, A, ADA extends ArrayDataAccess<ADA>>
extends ImgAggregrationOperation {
private class ByteTypeHandler implements RealTypeHandler<ByteType, byte[], ByteArray> {
/**
* {@inheritDoc}
*/
@Override
public int copyData(final byte[] srcArray, final byte[] resArray, final int fromIndex) {
System.arraycopy(srcArray, 0, resArray, fromIndex, srcArray.length);
return fromIndex + srcArray.length;
}
/**
* {@inheritDoc}
*/
@Override
public byte[] createArray(final int size) {
return new byte[size];
}
/**
* {@inheritDoc}
*/
@Override
public ByteType createLinkedType(final PlanarImg img) {
return new ByteType(img);
}
/**
* {@inheritDoc}
*/
@Override
public void setType(final byte[] dataArray, final double val, final int index) {
dataArray[index] = (byte)val;
}
/**
* {@inheritDoc}
*/
@Override
public ByteArray wrap(final byte[] dataArray) {
return new ByteArray(dataArray);
}
}
private class CustomPlanarImg extends PlanarImg<T, ADA> {
/**
* @param dim
* @param entitiesPerPixel
*/
public CustomPlanarImg(final ArrayList<ADA> mirror, final long[] dim, final Fraction entitiesPerPixel) {
super(dim, entitiesPerPixel);
for (int i = 0; i < super.mirror.size(); i++) {
super.mirror.set(i, mirror.get(i));
}
}
}
private class DoubleTypeHandler implements RealTypeHandler<DoubleType, double[], DoubleArray> {
/**
* {@inheritDoc}
*/
@Override
public int copyData(final double[] srcArray, final double[] resArray, final int fromIndex) {
System.arraycopy(srcArray, 0, resArray, fromIndex, srcArray.length);
return fromIndex + srcArray.length;
}
/**
* {@inheritDoc}
*/
@Override
public double[] createArray(final int size) {
return new double[size];
}
/**
* {@inheritDoc}
*/
@Override
public DoubleType createLinkedType(final PlanarImg img) {
return new DoubleType(img);
}
/**
* {@inheritDoc}
*/
@Override
public void setType(final double[] dataArray, final double val, final int index) {
dataArray[index] = val;
}
/**
* {@inheritDoc}
*/
@Override
public DoubleArray wrap(final double[] dataArray) {
return new DoubleArray(dataArray);
}
}
private class FloatTypeHandler implements RealTypeHandler<FloatType, float[], FloatArray> {
/**
* {@inheritDoc}
*/
@Override
public int copyData(final float[] srcArray, final float[] resArray, final int fromIndex) {
System.arraycopy(srcArray, 0, resArray, fromIndex, srcArray.length);
return fromIndex + srcArray.length;
}
/**
* {@inheritDoc}
*/
@Override
public float[] createArray(final int size) {
return new float[size];
}
/**
* {@inheritDoc}
*/
@Override
public FloatType createLinkedType(final PlanarImg img) {
return new FloatType(img);
}
/**
* {@inheritDoc}
*/
@Override
public void setType(final float[] dataArray, final double val, final int index) {
dataArray[index] = (float)val;
}
/**
* {@inheritDoc}
*/
@Override
public FloatArray wrap(final float[] dataArray) {
return new FloatArray(dataArray);
}
}
private class IntTypeHandler implements RealTypeHandler<IntType, int[], IntArray> {
/**
* {@inheritDoc}
*/
@Override
public int copyData(final int[] srcArray, final int[] resArray, final int fromIndex) {
System.arraycopy(srcArray, 0, resArray, fromIndex, srcArray.length);
return fromIndex + srcArray.length;
}
/**
* {@inheritDoc}
*/
@Override
public int[] createArray(final int size) {
return new int[size];
}
/**
* {@inheritDoc}
*/
@Override
public IntType createLinkedType(final PlanarImg img) {
return new IntType(img);
}
/**
* {@inheritDoc}
*/
@Override
public void setType(final int[] dataArray, final double val, final int index) {
dataArray[index] = (int)val;
}
/**
* {@inheritDoc}
*/
@Override
public IntArray wrap(final int[] dataArray) {
return new IntArray(dataArray);
}
}
private class LongTypeHandler implements RealTypeHandler<LongType, long[], LongArray> {
/**
* {@inheritDoc}
*/
@Override
public int copyData(final long[] srcArray, final long[] resArray, final int fromIndex) {
System.arraycopy(srcArray, 0, resArray, fromIndex, srcArray.length);
return fromIndex + srcArray.length;
}
/**
* {@inheritDoc}
*/
@Override
public long[] createArray(final int size) {
return new long[size];
}
/**
* {@inheritDoc}
*/
@Override
public LongType createLinkedType(final PlanarImg img) {
return new LongType(img);
}
/**
* {@inheritDoc}
*/
@Override
public void setType(final long[] dataArray, final double val, final int index) {
dataArray[index] = (long)val;
}
/**
* {@inheritDoc}
*/
@Override
public LongArray wrap(final long[] dataArray) {
return new LongArray(dataArray);
}
}
private interface RealTypeHandler<T, A, ADA> {
/**
* @param srcArray
* @param resArray
* @param fromIndex
* @return the new index
*/
int copyData(A srcArray, A resArray, int fromIndex);
A createArray(int size);
T createLinkedType(PlanarImg img);
void setType(A dataArray, double val, int index);
ADA wrap(A dataArray);
}
private class ShortTypeHandler implements RealTypeHandler<ShortType, short[], ShortArray> {
/**
* {@inheritDoc}
*/
@Override
public int copyData(final short[] srcArray, final short[] resArray, final int fromIndex) {
System.arraycopy(srcArray, 0, resArray, fromIndex, srcArray.length);
return fromIndex + srcArray.length;
}
/**
* {@inheritDoc}
*/
@Override
public short[] createArray(final int size) {
return new short[size];
}
/**
* {@inheritDoc}
*/
@Override
public ShortType createLinkedType(final PlanarImg img) {
return new ShortType(img);
}
/**
* {@inheritDoc}
*/
@Override
public void setType(final short[] dataArray, final double val, final int index) {
dataArray[index] = (short)val;
}
/**
* {@inheritDoc}
*/
@Override
public ShortArray wrap(final short[] dataArray) {
return new ShortArray(dataArray);
}
}
private class UnsignedByteTypeHandler implements RealTypeHandler<UnsignedByteType, byte[], ByteArray> {
/**
* {@inheritDoc}
*/
@Override
public int copyData(final byte[] srcArray, final byte[] resArray, final int fromIndex) {
System.arraycopy(srcArray, 0, resArray, fromIndex, srcArray.length);
return fromIndex + srcArray.length;
}
/**
* {@inheritDoc}
*/
@Override
public byte[] createArray(final int size) {
return new byte[size];
}
/**
* {@inheritDoc}
*/
@Override
public UnsignedByteType createLinkedType(final PlanarImg img) {
return new UnsignedByteType(img);
}
/**
* {@inheritDoc}
*/
@Override
public void setType(final byte[] dataArray, final double val, final int index) {
dataArray[index] = (byte)val;
}
@Override
public ByteArray wrap(final byte[] dataArray) {
return new ByteArray(dataArray);
};
}
private class UnsignedShortTypeHandler implements RealTypeHandler<UnsignedShortType, short[], ShortArray> {
/**
* {@inheritDoc}
*/
@Override
public int copyData(final short[] srcArray, final short[] resArray, final int fromIndex) {
System.arraycopy(srcArray, 0, resArray, fromIndex, resArray.length);
return fromIndex + srcArray.length;
}
/**
* {@inheritDoc}
*/
@Override
public short[] createArray(final int size) {
return new short[size];
}
/**
* {@inheritDoc}
*/
@Override
public UnsignedShortType createLinkedType(final PlanarImg img) {
return new UnsignedShortType(img);
}
/**
* {@inheritDoc}
*/
@Override
public void setType(final short[] dataArray, final double val, final int index) {
dataArray[index] = (short)val;
}
/**
* {@inheritDoc}
*/
@Override
public ShortArray wrap(final short[] dataArray) {
return new ShortArray(dataArray);
}
}
private static SettingsModelString createAxisLabelModel() {
return new SettingsModelString("axis_label", "UNKNOWN");
}
private String m_axisLabel;
/* aggregated pixel data (in case of a 2D input)*/
private ArrayList<A> m_data = null;
// dialog components
private DialogComponentString m_dcAxisLabel;
private long[] m_dims;
private ImgPlusMetadata m_metadata;
// settings models
private final SettingsModelString m_smAxisLabel = createAxisLabelModel();
private T m_type;
private RealTypeHandler<T, A, ADA> m_typeHandler;
/**
* Required to determine the size of the result image for the nD case (n > 2)
* and keep track of the current position in the result image
*/
private long m_rowCount;
private long m_rowIdx;
/**
* Result image in case of an input >2D
*/
private Img<T> m_resImg;
public ImgMergeOperator() {
super("Merge Image", "Merge Image", "Merge Image");
}
public ImgMergeOperator(final GlobalSettings globalSettings) {
this(globalSettings, null);
}
public ImgMergeOperator(final GlobalSettings globalSettings, final String axisLabel) {
super("Merge Image", "Merge Image", globalSettings);
if (axisLabel != null) {
m_smAxisLabel.setStringValue(axisLabel);
}
m_axisLabel = m_smAxisLabel.getStringValue();
m_rowCount = globalSettings.getNoOfRows();
}
/**
* {@inheritDoc}
*/
@Override
protected boolean computeInternal(final DataCell cell) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
protected boolean computeInternal(final DataRow row, final DataCell cell) {
final ImgPlus<T> imgPlus = ((ImgPlusValue<T>)cell).getImgPlus();
if ((m_type != null) && !imgPlus.firstElement().getClass().isAssignableFrom(m_type.getClass())) {
throw new IllegalArgumentException(
"Image " + imgPlus.getName() + " not compatible with first-row image. Different type!");
}
if (m_dims != null) {
for (int i = 0; i < m_dims.length - 1; i++) {
if (imgPlus.dimension(i) != m_dims[i]) {
throw new IllegalArgumentException(
"Image " + imgPlus.getName() + " not compatible with first-row image. Different dimension!");
}
}
}
if (m_data == null && m_resImg == null) {
//first iteration -> create data structures
final CalibratedAxis[] axes = new CalibratedAxis[imgPlus.numDimensions()];
imgPlus.axes(axes);
final CalibratedAxis[] newAxes = new CalibratedAxis[axes.length + 1];
for (int i = 0; i < axes.length; i++) {
newAxes[i] = axes[i];
}
//TODO: How to support different types of calibrates spaces/axis.
newAxes[newAxes.length - 1] = new DefaultLinearAxis(Axes.get(m_axisLabel));
m_metadata = new DefaultImgMetadata(new DefaultCalibratedSpace(newAxes), imgPlus, imgPlus, imgPlus);
m_dims = new long[newAxes.length];
imgPlus.dimensions(m_dims);
m_type = imgPlus.firstElement().createVariable();
if (imgPlus.numDimensions() == 2) {
if (m_type instanceof ByteType) {
m_typeHandler = (RealTypeHandler<T, A, ADA>)new ByteTypeHandler();
} else if (m_type instanceof UnsignedByteType) {
m_typeHandler = (RealTypeHandler<T, A, ADA>)new UnsignedByteTypeHandler();
} else if (m_type instanceof UnsignedShortType) {
m_typeHandler = (RealTypeHandler<T, A, ADA>)new UnsignedShortTypeHandler();
} else if (m_type instanceof ShortType) {
m_typeHandler = (RealTypeHandler<T, A, ADA>)new ShortTypeHandler();
} else if (m_type instanceof IntType) {
m_typeHandler = (RealTypeHandler<T, A, ADA>)new IntTypeHandler();
} else if (m_type instanceof LongType) {
m_typeHandler = (RealTypeHandler<T, A, ADA>)new LongTypeHandler();
} else if (m_type instanceof FloatType) {
m_typeHandler = (RealTypeHandler<T, A, ADA>)new FloatTypeHandler();
} else if (m_type instanceof DoubleType) {
m_typeHandler = (RealTypeHandler<T, A, ADA>)new DoubleTypeHandler();
} else {
throw new IllegalArgumentException(
"Pixel type " + m_type.getClass().getSimpleName() + " not supported for merging.");
}
m_data = new ArrayList<A>();
} else {
//create res img
m_dims[m_dims.length - 1] = m_rowCount;
m_resImg = new ArrayImgFactory().create(m_dims, m_type);
m_rowIdx = 0;
}
}
//in case of a previous virtual operation (img is wrapped in a ImgView), first execute the operation in order to get the 'physical' pixel data
Img<T> img;
if(imgPlus.getImg() instanceof ImgView) {
img = imgPlus.getImg().copy();
} else {
img = imgPlus.getImg();
}
if (imgPlus.numDimensions() == 2) {
int hyperPlaneSize = 1;
for (int i = 0; i < imgPlus.numDimensions(); i++) {
hyperPlaneSize *=imgPlus.dimension(i);
}
int numHyperPlanes = (int)(imgPlus.size() / hyperPlaneSize);
m_dims[m_dims.length - 1] += numHyperPlanes;
//in case of 2D we can efficiently copy the data arrays
// copy data
if (img instanceof ArrayImg) {
for (int i = 0; i < numHyperPlanes; i++) {
final A plane = m_typeHandler.createArray(hyperPlaneSize);
m_typeHandler
.copyData((A)((ArrayDataAccess<A>)((ArrayImg)img).update(null)).getCurrentStorageArray(),
plane, 0);
m_data.add(plane);
}
} else if (imgPlus.getImg() instanceof PlanarImg) {
for (int i = 0; i < ((PlanarImg)img).numSlices(); i++) {
final A plane = m_typeHandler.createArray(hyperPlaneSize);
m_typeHandler
.copyData((A)((ArrayDataAccess<A>)((PlanarImg)img).getPlane(i)).getCurrentStorageArray(),
plane, 0);
m_data.add(plane);
}
} else {
if (imgPlus.numDimensions() != (m_dims.length - 1)) {
throw new IllegalArgumentException("Image type not supported, yet.");
}
}
} else {
//just transfer the pixel values to the already created result image
//TODO guarantee same iteration order
Cursor<T> dest = Views.hyperSlice(m_resImg, m_resImg.numDimensions() - 1, m_rowIdx).cursor();
for(T t : imgPlus) {
dest.fwd();
dest.get().set(t);
}
m_rowIdx++;
}
return false;
}
private void createDCs() {
if (m_dcAxisLabel == null) {
m_dcAxisLabel = new DialogComponentString(createAxisLabelModel(), "New axis label");
}
}
/**
* {@inheritDoc}
*/
@Override
public AggregationOperator createInstance(final GlobalSettings globalSettings,
final OperatorColumnSettings opColSettings) {
return new ImgMergeOperator(globalSettings, m_smAxisLabel.getStringValue());
}
/**
* {@inheritDoc}
*/
@Override
protected DataType getDataType(final DataType origType) {
return ImgPlusCell.TYPE;
}
/**
* {@inheritDoc}
*/
@Override
public String getDescription() {
return "Merges the n-dimension to one (n+1) dimensional image object. The images to be merged must have exactly the same dimensions and same pixel type as the first image in the group. If not, they will be skipped.";
}
/**
* {@inheritDoc}
*/
@Override
protected DataCell getResultInternal() {
if (m_data != null) {
//in case 2D images have been merged
final ArrayList<ADA> mirror = new ArrayList<ADA>(m_data.size());
for (int i = 0; i < m_data.size(); i++) {
mirror.add(m_typeHandler.wrap(m_data.get(i)));
}
final CustomPlanarImg img = new CustomPlanarImg(mirror, m_dims, new Fraction(1, 1));
img.setLinkedType(m_typeHandler.createLinkedType(img));
try {
return getImgPlusCellFactory().createCell(new ImgPlus(img, m_metadata));
} catch (final IOException e) {
//TODO better error handling
throw new RuntimeException(e);
}
} else {
assert m_resImg != null;
try {
return getImgPlusCellFactory().createCell(new ImgPlus(m_resImg, m_metadata));
} catch (IOException e) {
//TODO better error handling
throw new RuntimeException(e);
}
}
}
@Override
public Component getSettingsPanel() {
createDCs();
final JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(m_dcAxisLabel.getComponentPanel());
return panel;
}
@Override
public boolean hasOptionalSettings() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void loadSettingsFrom(final NodeSettingsRO settings, final DataTableSpec spec)
throws NotConfigurableException {
createDCs();
m_dcAxisLabel.loadSettingsFrom(settings, new DataTableSpec[]{spec});
}
/**
* {@inheritDoc}
*/
@Override
public void loadValidatedSettings(final NodeSettingsRO settings) throws InvalidSettingsException {
m_smAxisLabel.loadSettingsFrom(settings);
if (m_smAxisLabel.getStringValue() == null) {
m_smAxisLabel.setStringValue("UNKNOWN");
}
}
/**
* {@inheritDoc}
*/
@Override
protected void resetInternal() {
m_data = null;
}
@Override
public void saveSettingsTo(final NodeSettingsWO settings) {
if (m_dcAxisLabel != null) {
try {
m_dcAxisLabel.saveSettingsTo(settings);
} catch (final InvalidSettingsException e) {
throw new RuntimeException(e.getMessage());
}
} else {
m_smAxisLabel.saveSettingsTo(settings);
}
}
/**
* {@inheritDoc}
*/
@Override
public void validateSettings(final NodeSettingsRO settings) throws InvalidSettingsException {
m_smAxisLabel.validateSettings(settings);
}
}