/*
* ------------------------------------------------------------------------
*
* 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.groups;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.imagej.ImgPlus;
import net.imagej.ops.OpEnvironment;
import net.imagej.ops.special.computer.AbstractUnaryComputerOp;
import net.imglib2.img.Img;
import net.imglib2.roi.labeling.LabelRegion;
import net.imglib2.type.logic.BitType;
import net.imglib2.type.numeric.RealType;
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.DataTableSpec;
import org.knime.core.data.DataType;
import org.knime.core.data.container.DataContainer;
import org.knime.core.data.def.DoubleCell;
import org.knime.core.data.def.StringCell;
import org.knime.core.util.UniqueNameGenerator;
import org.knime.knip.base.data.img.ImgPlusCell;
import org.knime.knip.base.data.img.ImgPlusCellFactory;
import org.knime.knip.core.KNIPGateway;
import org.knime.knip.features.FeatureSetGroup;
import org.knime.knip.features.LabelRegionToBitmaskConverter;
import org.knime.knip.features.sets.FeatureSet;
import org.knime.knip.features.sets.NamedFeature;
import org.knime.knip.features.sets.RequireNumDimensions;
/**
* FIXME: Design of FeatureGroups is really weak. However, we can redesign it
* whenever we have more time, without destroying backwards compatibility.
*
* @author Christian Dietz, University of Konstanz
*
* @param <L>
* @param <T>
* @param <O>
*/
public abstract class AbstractFeatureSetGroup implements FeatureSetGroup {
protected <O, R extends RealType<R>> List<DataColumnSpec> createColumnSpec(final DataTableSpec inSpec,
final List<FeatureSet<O, R>> featureSets, final boolean append,
final boolean appendOverlappingSegmentsColumnSpec, final boolean appendSegmentInformation,
final int expectedDims) {
return createColumnSpec(inSpec, new HashMap<>(), featureSets, append, appendOverlappingSegmentsColumnSpec,
appendSegmentInformation, expectedDims);
}
protected <O, R extends RealType<R>> List<DataColumnSpec> createColumnSpec(final DataTableSpec inSpec,
final HashMap<String, Integer> prefixMap, final List<FeatureSet<O, R>> featureSets, final boolean append,
final boolean appendOverlappingSegmentsColumnSpec, final boolean appendSegmentInformation,
final int expectedDims) {
final UniqueNameGenerator colNameGenerator;
if (append) {
colNameGenerator = new UniqueNameGenerator(inSpec);
} else {
colNameGenerator = new UniqueNameGenerator(new DataTableSpec());
}
final List<DataColumnSpec> specs = new ArrayList<>();
if (appendSegmentInformation) {
specs.addAll(createSegmentInformationColumnSpec(colNameGenerator));
}
if (appendOverlappingSegmentsColumnSpec) {
specs.add(createOverlappingSegmentsColumnSpec(colNameGenerator));
}
for (final FeatureSet<O, R> featureSet : featureSets) {
if (featureSet instanceof RequireNumDimensions) {
((RequireNumDimensions) featureSet).setNumDimensions(expectedDims);
}
final String featureSetName = KNIPGateway.cs().getCommand(featureSet.getClass()).getLabel() + "";
final String suffix;
if (prefixMap.containsKey(featureSetName)) {
final int ctr = prefixMap.get(featureSetName);
suffix = " [#" + ctr + "]";
prefixMap.put(featureSetName, ctr + 1);
} else {
prefixMap.put(featureSetName, 1);
suffix = "";
}
for (final NamedFeature featureName : featureSet.getFeatures()) {
specs.add(new DataColumnSpecCreator(
colNameGenerator.newName(featureName.getName() + " (" + featureSetName + ")" + suffix),
DoubleCell.TYPE).createSpec());
}
}
return specs;
}
private DataColumnSpec createOverlappingSegmentsColumnSpec(final UniqueNameGenerator colNameGenerator) {
return colNameGenerator.newColumn("OverlappingLabels", StringCell.TYPE);
}
private List<DataColumnSpec> createSegmentInformationColumnSpec(final UniqueNameGenerator colNameGenerator) {
List<DataColumnSpec> specs = new ArrayList<DataColumnSpec>();
specs.add(new DataColumnSpecCreator(colNameGenerator.newName("Bitmask"), ImgPlusCell.TYPE).createSpec());
specs.add(new DataColumnSpecCreator(colNameGenerator.newName("Label"), StringCell.TYPE).createSpec());
return specs;
}
public abstract class FeatureSetGroupComputer<O extends RealType<O>>
extends AbstractUnaryComputerOp<DataRow, DataContainer> {
protected <I> boolean initFeatureSet(final List<FeatureSet<I, O>> sets, final I in) {
boolean allValid = true;
for (final FeatureSet<I, O> fs : sets) {
fs.setInput(in);
if (fs.conforms()) {
fs.initialize();
} else {
allValid = false;
}
}
return allValid;
}
protected <I> List<DataCell> computeOnFeatureSets(final List<FeatureSet<I, O>> sets, final I in) {
final List<DataCell> cells = new ArrayList<>();
for (final FeatureSet<I, O> fs : sets) {
fs.setInput(in);
if (fs.conforms()) {
final Map<NamedFeature, O> fsRes = fs.calculate(in);
for (final NamedFeature namedFeature : fs.getFeatures()) {
cells.add(new DoubleCell(fsRes.get(namedFeature).getRealDouble()));
}
} else {
for (int i = 0; i < fs.getFeatures().size(); i++) {
cells.add(DataType.getMissingCell());
}
KNIPGateway.log()
.warn("Selected FeatureSet " + fs.getClass()
+ " can't handle the provided input. Please select "
+ "feature sets suitable for input dimensions. Inserting missing cell.");
}
}
return cells;
}
}
protected <L> void appendRegionOptions(final LabelRegion<L> region, final List<DataCell> cells,
final ImgPlusCellFactory imgPlusCellFactory, final Map<L, List<L>> dependencies,
final boolean appendOverlappingLabels, final OpEnvironment ops) {
if (imgPlusCellFactory != null) {
@SuppressWarnings("unchecked")
Img<BitType> bitmask = (Img<BitType>) ops.run(LabelRegionToBitmaskConverter.class, region);
try {
cells.add(imgPlusCellFactory.createCell(new ImgPlus<BitType>(bitmask)));
} catch (IOException exc) {
KNIPGateway.log()
.warn("Could not create bitmask for label " + region.toString() + ". Inserted missing cell.");
cells.add(DataType.getMissingCell());
}
cells.add(new StringCell(region.getLabel().toString()));
}
if (appendOverlappingLabels) {
final StringBuffer buf = new StringBuffer();
if (!dependencies.isEmpty()) {
List<L> list = dependencies.get(region.getLabel());
if (list != null) {
for (final L s : list) {
buf.append(s.toString());
buf.append(";");
}
}
} else {
KNIPGateway.log()
.warn("No overlapping segment found for segment with label: " + region.getLabel().toString());
}
cells.add(new StringCell(buf.toString()));
}
}
}