/*
* ------------------------------------------------------------------------
*
* 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.features.node;
import java.io.File;
import java.io.IOException;
import net.imagej.ops.special.computer.UnaryComputerOp;
import net.imglib2.type.NativeType;
import net.imglib2.type.numeric.RealType;
import org.knime.core.data.DataRow;
import org.knime.core.data.DataTableSpec;
import org.knime.core.data.DataValue;
import org.knime.core.data.container.DataContainer;
import org.knime.core.node.BufferedDataContainer;
import org.knime.core.node.BufferedDataTable;
import org.knime.core.node.CanceledExecutionException;
import org.knime.core.node.ExecutionContext;
import org.knime.core.node.ExecutionMonitor;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NodeModel;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.knime.core.node.defaultnodesettings.SettingsModel;
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.data.labeling.LabelingValue;
import org.knime.knip.base.node.NodeUtils;
import org.knime.knip.base.node.nodesettings.SettingsModelDimSelection;
import org.knime.knip.base.node.nodesettings.SettingsModelFilterSelection;
import org.knime.knip.core.KNIPGateway;
import org.knime.knip.features.FeatureSetGroup;
import org.knime.knip.features.FeaturesGateway;
import org.knime.knip.features.node.model.SettingsModelFeatureSet;
/**
* This is the model implementation of FeatureNode.
*
* @author Daniel Seebacher, University of Konstanz
* @author Tim-Oliver Buchholz, University of Konstanz
* @author Christian Dietz, University of Konstanz
*/
public class FeatureCalculatorModel<T extends RealType<T> & NativeType<T>, L extends Comparable<L>> extends NodeModel {
static final String[] COL_CREATION_MODES = new String[] { "Append", "New Table" };
/**
* Helper to create various {@link SettingsModel}s
*/
public static SettingsModelFeatureSet createFeatureSetsModel() {
return new SettingsModelFeatureSet("feature_node_featuresets");
}
public static SettingsModelString createImgColumnModel() {
return new SettingsModelString("feature_node_img_column_selection", "");
}
public static SettingsModelString createLabelingColumnModel() {
return new SettingsModelString("feature_node_labeling_column_selection", "");
}
public static SettingsModelString createColumnCreationModeModel() {
return new SettingsModelString("feature_node_column_creation_mode", "");
}
public static SettingsModelDimSelection createDimSelectionModel() {
return new SettingsModelDimSelection("feature_node_dim_selection", "X", "Y");
}
public static SettingsModelBoolean createAppendLabelsOfOverlappingSegments() {
return new SettingsModelBoolean("m_appendLabelsOfOverlappingSegmentsModel", false);
}
public static SettingsModelBoolean createIntersectionModeModel() {
return new SettingsModelBoolean("m_labelIntersectionModel", false);
}
public static SettingsModelBoolean createAppendSegmentInfoModel() {
return new SettingsModelBoolean("m_appendSegmentInfoModel", false);
}
public static <K extends Comparable<K>> SettingsModelFilterSelection<K> createIncludeLabelModel() {
return new SettingsModelFilterSelection<K>("segment_filter");
}
public static <L extends Comparable<L>> SettingsModelFilterSelection<L> createFilterOverlappingLabelModel() {
return new SettingsModelFilterSelection<L>("overlapping_segment_filter");
}
/**
* Setting Models
*/
private final SettingsModelString m_imgColumn = createImgColumnModel();
private final SettingsModelString m_labelingColumn = createLabelingColumnModel();
private final SettingsModelString m_columnCreationModeModel = createColumnCreationModeModel();
private final SettingsModelDimSelection m_dimselectionModel = createDimSelectionModel();
private final SettingsModelBoolean m_appendLabelsOfOverlappingSegmentsModel = createAppendLabelsOfOverlappingSegments();
private final SettingsModelBoolean m_labelIntersectionModeModel = createIntersectionModeModel();
private final SettingsModelBoolean m_appendSegmentInfoModel = createAppendSegmentInfoModel();
private final SettingsModelFilterSelection<L> m_includeLabelModel = createIncludeLabelModel();
private final SettingsModelFilterSelection<L> m_filterOverlappingLabelsModeModel = createFilterOverlappingLabelModel();
private final SettingsModelFeatureSet m_featureSetsModel = createFeatureSetsModel();
/**
* Constructor for the node model.
*/
protected FeatureCalculatorModel() {
super(1, 1);
}
/**
* {@inheritDoc}
*/
@Override
protected BufferedDataTable[] execute(final BufferedDataTable[] inData, final ExecutionContext exec)
throws Exception {
final int imgColIndex = getColIdx(inData[0].getDataTableSpec(), ImgPlusValue.class, m_imgColumn);
final int labColIdx = getColIdx(inData[0].getDataTableSpec(), LabelingValue.class, m_labelingColumn);
final FeatureSetGroup group = FeaturesGateway.fs().getFeatureGroup(m_featureSetsModel.getFeatureSetInfos(),
imgColIndex, labColIdx, doAppend(), doAppendOverlappingSegments(), doAppendSegmentInformation(),
getIntersectionMode(), m_includeLabelModel.getRulebasedFilter(), m_filterOverlappingLabelsModeModel.getRulebasedFilter(), exec, m_dimselectionModel);
final DataTableSpec outSpec = group.createSpec(inData[0].getDataTableSpec());
final BufferedDataContainer container = exec.createDataContainer(outSpec);
// check for empty table
if (inData[0].size() == 0) {
KNIPGateway.log().warn("Empty input table. Creating empty output table.");
container.close();
return new BufferedDataTable[] { container.getTable() };
}
// iterate over input table
double currentRow = 0;
final UnaryComputerOp<DataRow, DataContainer> computer = group.createComputerOp();
for (final DataRow row : inData[0]) {
if (imgColIndex != -1 && row.getCell(imgColIndex).isMissing()) {
logMissingCell(imgColIndex, row);
} else if (labColIdx != -1 && row.getCell(labColIdx).isMissing()) {
logMissingCell(labColIdx, row);
} else {
exec.setProgress(
"Computing " + m_featureSetsModel.getFeatureSetNames() + " on row " + row.getKey() + ".");
// magic happens here ;-)
computer.compute(row, container);
}
exec.checkCanceled();
exec.setProgress(currentRow++ / inData[0].size());
}
container.close();
return new BufferedDataTable[] { container.getTable() };
}
private void logMissingCell(final int imgColIndex, final DataRow row) {
KNIPGateway.log().warn(
"Row " + row.getKey() + " was ignored because the cell in column " + imgColIndex + " was missing.");
}
/**
* {@inheritDoc}
*/
@Override
protected void reset() {
// Nothing to do here
}
/**
* {@inheritDoc}
*/
@Override
protected DataTableSpec[] configure(final DataTableSpec[] inSpecs) throws InvalidSettingsException {
final int imgColIndex = getColIdx(inSpecs[0], ImgPlusValue.class, m_imgColumn);
final int labelingColIdx = getColIdx(inSpecs[0], LabelingValue.class, m_labelingColumn);
if ((-1 == imgColIndex) && (-1 == labelingColIdx)) {
throw new InvalidSettingsException("At least one image or labeling column must be selected!");
}
final FeatureSetGroup group = FeaturesGateway.fs().getFeatureGroup(m_featureSetsModel.getFeatureSetInfos(),
imgColIndex, labelingColIdx, doAppend(), doAppendOverlappingSegments(), doAppendSegmentInformation(),
getIntersectionMode(), m_includeLabelModel.getRulebasedFilter(), m_filterOverlappingLabelsModeModel.getRulebasedFilter(), null, m_dimselectionModel);
final DataTableSpec outSpec = group.createSpec(inSpecs[0]);
return new DataTableSpec[] { outSpec };
}
/**
* {@inheritDoc}
*/
@Override
protected void saveSettingsTo(final NodeSettingsWO settings) {
this.m_imgColumn.saveSettingsTo(settings);
this.m_labelingColumn.saveSettingsTo(settings);
this.m_columnCreationModeModel.saveSettingsTo(settings);
this.m_dimselectionModel.saveSettingsTo(settings);
this.m_featureSetsModel.saveSettingsTo(settings);
this.m_appendLabelsOfOverlappingSegmentsModel.saveSettingsTo(settings);
this.m_labelIntersectionModeModel.saveSettingsTo(settings);
this.m_appendSegmentInfoModel.saveSettingsTo(settings);
this.m_includeLabelModel.saveSettingsTo(settings);
this.m_filterOverlappingLabelsModeModel.saveSettingsTo(settings);
}
/**
* {@inheritDoc}
*/
@Override
protected void loadValidatedSettingsFrom(final NodeSettingsRO settings) throws InvalidSettingsException {
this.m_imgColumn.loadSettingsFrom(settings);
this.m_labelingColumn.loadSettingsFrom(settings);
this.m_columnCreationModeModel.loadSettingsFrom(settings);
this.m_dimselectionModel.loadSettingsFrom(settings);
this.m_featureSetsModel.loadSettingsFrom(settings);
this.m_appendLabelsOfOverlappingSegmentsModel.loadSettingsFrom(settings);
this.m_labelIntersectionModeModel.loadSettingsFrom(settings);
this.m_appendSegmentInfoModel.loadSettingsFrom(settings);
this.m_includeLabelModel.loadSettingsFrom(settings);
try {
this.m_filterOverlappingLabelsModeModel.loadSettingsFrom(settings);
} catch (InvalidSettingsException ise) {
// do nothing
}
}
/**
* {@inheritDoc}
*/
@Override
protected void validateSettings(final NodeSettingsRO settings) throws InvalidSettingsException {
this.m_imgColumn.validateSettings(settings);
this.m_labelingColumn.validateSettings(settings);
this.m_columnCreationModeModel.validateSettings(settings);
this.m_dimselectionModel.validateSettings(settings);
this.m_featureSetsModel.validateSettings(settings);
this.m_appendLabelsOfOverlappingSegmentsModel.validateSettings(settings);
this.m_labelIntersectionModeModel.validateSettings(settings);
this.m_appendSegmentInfoModel.validateSettings(settings);
this.m_includeLabelModel.validateSettings(settings);
try {
this.m_filterOverlappingLabelsModeModel.loadSettingsFrom(settings);
} catch (InvalidSettingsException ise) {
// do nothing
}
}
/**
* {@inheritDoc}
*/
@Override
protected void loadInternals(final File internDir, final ExecutionMonitor exec)
throws IOException, CanceledExecutionException {
// do nothing
}
/**
* {@inheritDoc}
*/
@Override
protected void saveInternals(final File internDir, final ExecutionMonitor exec)
throws IOException, CanceledExecutionException {
// do nothing
}
/**
* HELPER METHODS
*/
private int getColIdx(final DataTableSpec inSpec, final Class<? extends DataValue> type,
final SettingsModelString colModel) throws InvalidSettingsException {
int colIndex = -1;
if (colModel.getStringValue() != null && colModel.getStringValue().equals("")) {
colIndex = NodeUtils.silentOptionalAutoColumnSelection(inSpec, colModel, type);
return colIndex;
}
colIndex = inSpec.findColumnIndex(colModel.getStringValue());
if (-1 != colIndex && !inSpec.getColumnSpec(colIndex).getType().isCompatible(type)) {
throw new InvalidSettingsException("No column selected!");
}
return colIndex;
}
private boolean doAppend() {
return COL_CREATION_MODES[0].equalsIgnoreCase(this.m_columnCreationModeModel.getStringValue());
}
private boolean doAppendSegmentInformation() {
return m_appendSegmentInfoModel.getBooleanValue();
}
private boolean doAppendOverlappingSegments() {
return m_appendLabelsOfOverlappingSegmentsModel.getBooleanValue();
}
private boolean getIntersectionMode() {
return m_labelIntersectionModeModel.getBooleanValue();
}
}