/*
* ------------------------------------------------------------------------
*
* Copyright (C) 2003 - 2011
* 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.
* ------------------------------------------------------------------------
*
* History
* Feb 5, 2013 (hornm): created
*/
package org.knime.knip.base.nodes.seg.compare;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.knime.base.node.parallel.appender.AppendColumn;
import org.knime.base.node.parallel.appender.ColumnDestination;
import org.knime.base.node.parallel.appender.ExtendedCellFactory;
import org.knime.base.node.parallel.appender.ThreadedColAppenderNodeModel;
import org.knime.core.data.DataCell;
import org.knime.core.data.DataColumnSpec;
import org.knime.core.data.DataColumnSpecCreator;
import org.knime.core.data.DataRow;
import org.knime.core.data.DataTable;
import org.knime.core.data.DataTableSpec;
import org.knime.core.data.collection.CollectionCellFactory;
import org.knime.core.data.collection.SetCell;
import org.knime.core.data.def.DoubleCell;
import org.knime.core.data.def.IntCell;
import org.knime.core.data.def.StringCell;
import org.knime.core.node.BufferedDataTable;
import org.knime.core.node.CanceledExecutionException;
import org.knime.core.node.ExecutionMonitor;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.knime.core.node.defaultnodesettings.SettingsModelBoolean;
import org.knime.core.node.defaultnodesettings.SettingsModelString;
import org.knime.knip.base.data.img.ImgPlusValue;
import org.knime.knip.base.node.NodeUtils;
import net.imglib2.Interval;
import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessibleInterval;
import net.imglib2.img.Img;
import net.imglib2.iterator.IntervalIterator;
import net.imglib2.type.logic.BitType;
import net.imglib2.util.Intervals;
import net.imglib2.view.Views;
/**
* Node to estimate the maximum relative pixel agreement of a list of segments according to a list of reference
* segments.
*
* @author Martin Horn, University of Konstanz
*/
public class CompareSegmentsNodeModel extends ThreadedColAppenderNodeModel {
final static SettingsModelString createReferenceColumnModel() {
return new SettingsModelString("reference_column", "");
}
final static SettingsModelString createTargetColumnModel() {
return new SettingsModelString("target_column", "");
}
final static SettingsModelBoolean createAppendNumOverlapsModel() {
return new SettingsModelBoolean("num_overlaps", false);
}
final static SettingsModelBoolean createAppendOverlapRowkeysModel() {
return new SettingsModelBoolean("overlap_rowkeys", false);
}
private SettingsModelString m_smRefCol = createReferenceColumnModel();
private SettingsModelString m_smTarCol = createTargetColumnModel();
private SettingsModelBoolean m_smAppendNumOverlaps = createAppendNumOverlapsModel();
private SettingsModelBoolean m_smAppendOverlapRowkeys = createAppendOverlapRowkeysModel();
/**
* @param nrInDataPorts
* @param nrOutDataPorts
*/
protected CompareSegmentsNodeModel() {
super(2, 1);
}
/**
* {@inheritDoc}
*/
@Override
protected DataTableSpec[] configure(final DataTableSpec[] inSpecs) throws InvalidSettingsException {
NodeUtils.autoColumnSelection(inSpecs[1], m_smRefCol, ImgPlusValue.class, this.getClass());
NodeUtils.autoColumnSelection(inSpecs[0], m_smTarCol, ImgPlusValue.class, this.getClass());
return new DataTableSpec[]{createOutputSpec(inSpecs[0], createCellFactory(inSpecs[0], null, null, null, -1))};
}
/**
* {@inheritDoc}
*/
@Override
protected ExtendedCellFactory[] prepareExecute(final DataTable[] inData) throws Exception {
int refColIdx =
NodeUtils.autoColumnSelection(inData[1].getDataTableSpec(), m_smRefCol, ImgPlusValue.class,
this.getClass());
int tarColIdx =
NodeUtils.autoColumnSelection(inData[0].getDataTableSpec(), m_smTarCol, ImgPlusValue.class,
this.getClass());
ImgPlusValue<BitType>[] refSegs = new ImgPlusValue[((BufferedDataTable)inData[1]).getRowCount()];
String[] refKeys = null;
int[] numPix = new int[refSegs.length];
if (m_smAppendOverlapRowkeys.getBooleanValue()) {
refKeys = new String[refSegs.length];
}
// collect reference segments and row keys
int i = 0;
for (DataRow row : inData[1]) {
refSegs[i] = (ImgPlusValue<BitType>)row.getCell(refColIdx);
numPix[i] = numPix(refSegs[i]);
if (refKeys != null) {
refKeys[i] = row.getKey().toString();
}
i++;
}
return new ExtendedCellFactory[]{createCellFactory(inData[0].getDataTableSpec(), refSegs, refKeys, numPix, tarColIdx)};
}
private ExtendedCellFactory createCellFactory(final DataTableSpec inSpec, final ImgPlusValue<BitType>[] refSegs, final String[] refKeys,
final int[] numPix, final int tarColIdx) {
return new ExtendedCellFactory() {
@Override
public DataColumnSpec[] getColumnSpecs() {
ArrayList<DataColumnSpec> colSpecs = new ArrayList<DataColumnSpec>(3);
colSpecs.add(new DataColumnSpecCreator(DataTableSpec.getUniqueColumnName(inSpec, "maximum relative pixel agreement"), DoubleCell.TYPE)
.createSpec());
if (m_smAppendNumOverlaps.getBooleanValue()) {
colSpecs.add(new DataColumnSpecCreator("number of overlaps", IntCell.TYPE).createSpec());
}
if (m_smAppendOverlapRowkeys.getBooleanValue()) {
colSpecs.add(new DataColumnSpecCreator("overlap keys", SetCell.getCollectionType(StringCell.TYPE))
.createSpec());
}
return colSpecs.toArray(new DataColumnSpec[colSpecs.size()]);
}
@Override
public DataCell[] getCells(final DataRow row) {
ImgPlusValue<BitType> tarVal = (ImgPlusValue<BitType>)row.getCell(tarColIdx);
double overlap;
int count = 0;
double numPix1 = numPix(tarVal);
double res = Double.MIN_VALUE;
ArrayList<StringCell> keys = null;
if (refKeys != null) {
keys = new ArrayList<StringCell>();
}
for (int i = 0; i < refSegs.length; i++) {
overlap = overlap(refSegs[i], tarVal);
if (overlap > 0) {
count++;
if (keys != null) {
keys.add(new StringCell(refKeys[i]));
}
}
res = Math.max(res, overlap / (numPix1 + numPix[i] - overlap));
}
ArrayList<DataCell> cells = new ArrayList<DataCell>(3);
cells.add(new DoubleCell(res));
if (m_smAppendNumOverlaps.getBooleanValue()) {
cells.add(new IntCell(count));
}
if (m_smAppendOverlapRowkeys.getBooleanValue()) {
cells.add(CollectionCellFactory.createSetCell(keys));
}
return cells.toArray(new DataCell[cells.size()]);
}
@Override
public ColumnDestination[] getColumnDestinations() {
ColumnDestination[] cd =
new ColumnDestination[1 + (m_smAppendNumOverlaps.getBooleanValue() ? 1 : 0)
+ (m_smAppendOverlapRowkeys.getBooleanValue() ? 1 : 0)];
Arrays.fill(cd, new AppendColumn());
return cd;
}
};
}
private int overlap(final ImgPlusValue<BitType> val1, final ImgPlusValue<BitType> val2) {
Img<BitType> img1 = val1.getImgPlus();
Img<BitType> img2 = val2.getImgPlus();
if (!val1.getMetadata().getSource().equals(val2.getMetadata().getSource())) {
return 0;
}
RandomAccessibleInterval<BitType> iv1 = img1;
RandomAccessibleInterval<BitType> iv2 = img2;
//adopt image dimension if they are different
int dimDiff = img1.numDimensions() - img2.numDimensions();
if (dimDiff < 0) {
for (int i = 0; i < Math.abs(dimDiff); i++) {
iv1 = Views.addDimension(iv1, 0, 0);
}
} else if (dimDiff > 0) {
for (int i = 0; i < Math.abs(dimDiff); i++) {
iv2 = Views.addDimension(iv2, 0, 0);
}
}
Interval intersect = Intervals.intersect(iv1, iv2);
for (int i = 0; i < intersect.numDimensions(); i++) {
if (intersect.dimension(i) <= 0) {
return 0;
}
}
RandomAccess<BitType> ra1 = iv1.randomAccess();
RandomAccess<BitType> ra2 = iv2.randomAccess();
IntervalIterator ii = new IntervalIterator(intersect);
int numPix = 0;
while (ii.hasNext()) {
ii.fwd();
ra1.setPosition(ii);
if (ra1.get().get()) {
ra2.setPosition(ii);
if (ra2.get().get()) {
numPix++;
}
}
}
return numPix;
}
private int numPix(final ImgPlusValue<BitType> val) {
int numPix = 0;
for (BitType t : val.getImgPlus()) {
if (t.get()) {
numPix++;
}
}
return numPix;
}
/**
* {@inheritDoc}
*/
@Override
protected void loadInternals(final File nodeInternDir, final ExecutionMonitor exec) throws IOException,
CanceledExecutionException {
//
}
/**
* {@inheritDoc}
*/
@Override
protected void saveInternals(final File nodeInternDir, final ExecutionMonitor exec) throws IOException,
CanceledExecutionException {
//
}
/**
* {@inheritDoc}
*/
@Override
protected void saveSettingsTo(final NodeSettingsWO settings) {
m_smRefCol.saveSettingsTo(settings);
m_smTarCol.saveSettingsTo(settings);
m_smAppendNumOverlaps.saveSettingsTo(settings);
m_smAppendOverlapRowkeys.saveSettingsTo(settings);
}
/**
* {@inheritDoc}
*/
@Override
protected void validateSettings(final NodeSettingsRO settings) throws InvalidSettingsException {
m_smRefCol.validateSettings(settings);
m_smTarCol.validateSettings(settings);
m_smAppendNumOverlaps.validateSettings(settings);
m_smAppendOverlapRowkeys.validateSettings(settings);
}
/**
* {@inheritDoc}
*/
@Override
protected void loadValidatedSettingsFrom(final NodeSettingsRO settings) throws InvalidSettingsException {
m_smRefCol.loadSettingsFrom(settings);
m_smTarCol.loadSettingsFrom(settings);
m_smAppendNumOverlaps.loadSettingsFrom(settings);
m_smAppendOverlapRowkeys.loadSettingsFrom(settings);
}
/**
* {@inheritDoc}
*/
@Override
protected void reset() {
}
}