package org.knime.knip.tracking.nodes.laptracker; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_ALLOW_GAP_CLOSING; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_ALLOW_TRACK_MERGING; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_ALLOW_TRACK_SPLITTING; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_ALTERNATIVE_LINKING_COST_FACTOR; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_CUTOFF_PERCENTILE; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_GAP_CLOSING_MAX_DISTANCE; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_GAP_CLOSING_MAX_FRAME_GAP; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_MAX_DISTANCE; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_MERGING_MAX_DISTANCE; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_SPLITTING_MAX_DISTANCE; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.jgrapht.alg.ConnectivityInspector; import org.jgrapht.graph.DefaultWeightedEdge; 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.DataValue; import org.knime.core.data.DoubleValue; import org.knime.core.data.StringValue; import org.knime.core.data.def.DefaultRow; import org.knime.core.node.BufferedDataContainer; import org.knime.core.node.BufferedDataTable; import org.knime.core.node.BufferedDataTableHolder; 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.SettingsModelBoolean; import org.knime.core.node.defaultnodesettings.SettingsModelDouble; import org.knime.core.node.defaultnodesettings.SettingsModelFilterString; import org.knime.core.node.defaultnodesettings.SettingsModelInteger; import org.knime.core.node.defaultnodesettings.SettingsModelString; import org.knime.knip.base.data.img.ImgPlusValue; import org.knime.knip.base.data.labeling.LabelingCell; import org.knime.knip.base.data.labeling.LabelingCellFactory; import org.knime.knip.base.data.labeling.LabelingValue; import org.knime.knip.base.node.NodeUtils; import org.knime.knip.core.KNIPGateway; import org.knime.knip.core.data.img.LabelingMetadata; import org.knime.knip.core.util.EnumUtils; import org.knime.knip.tracking.data.TrackedNode; import fiji.plugin.trackmate.tracking.DefaultTOCollection; import fiji.plugin.trackmate.tracking.TrackableObjectCollection; import fiji.plugin.trackmate.util.LAPUtils; import fiji.plugin.trackmate.util.TrackableObjectUtils; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; import net.imglib2.Cursor; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.ops.operation.iterableinterval.unary.Centroid; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.type.logic.BitType; @Deprecated public class LAPTrackerNodeModel extends NodeModel implements BufferedDataTableHolder { public enum LAPTrackerAlgorithm { HUNGARIAN("Hungarian"), MUNKRESKUHN("Munkres Kuhn"), JONKERVOLGENANT( "Jonker Volgenant"); private String name; private LAPTrackerAlgorithm(final String describingName) { name = describingName; } @Override public String toString() { return name; } } /* * KNIME SETTINGS MODELS */ private final SettingsModelString m_sourceLabelingColumn = LAPTrackerSettingsModels.createSourceLabelingSettingsModel(); private final SettingsModelFilterString m_columns = LAPTrackerSettingsModels.createColumnSelectionModel(); private final SettingsModelString m_timeAxisModel = LAPTrackerSettingsModels.createTimeAxisModel(); private final SettingsModelString m_bitMaskColumnModel = LAPTrackerSettingsModels.createBitMaskModel(); private final SettingsModelString m_labelColumnModel = LAPTrackerSettingsModels.createLabelModel(); private final SettingsModelBoolean m_attachSourceLabelings = LAPTrackerSettingsModels.createAttachSourceLabelingsModel(); private final SettingsModelBoolean m_useCustomTrackPrefix = LAPTrackerSettingsModels.createUseCustomTrackPrefixModel(); private final SettingsModelString m_customTrackPrefix = LAPTrackerSettingsModels.createCustomTrackPrefixModel(); /* * TRACKMATE SETTINGS */ private final SettingsModelString m_trackingAlgorithmModel = LAPTrackerSettingsModels.createTrackingAlgorithmModel(); private final SettingsModelBoolean m_allowGapClosingModel = LAPTrackerSettingsModels.createAllowGapClosingModel(); private final SettingsModelBoolean m_allowMergingModel = LAPTrackerSettingsModels.createAllowMergingModel(); private final SettingsModelBoolean m_allowSplittingModel = LAPTrackerSettingsModels.createAllowSplittingModel(); private final SettingsModelInteger m_gapClosingMaxFrameModel = LAPTrackerSettingsModels.createMaxFrameGapClosingModel(); private final SettingsModelDouble m_alternativeLinkingCostFactor = LAPTrackerSettingsModels.createAlternativeLinkingCostFactor(); private final SettingsModelDouble m_cutoffPercentileModel = LAPTrackerSettingsModels.createCutoffPercentileModel(); private final SettingsModelDouble m_gapClosingMaxDistanceModel = LAPTrackerSettingsModels.createGapClosingMaxDistanceModel(); private final SettingsModelDouble m_linkingMaxDistanceModel = LAPTrackerSettingsModels.createLinkingMaxDistanceModel(); private final SettingsModelDouble m_mergingMaxDistanceModel = LAPTrackerSettingsModels.createMergingMaxDistance(); private final SettingsModelDouble m_splittingMaxDistance = LAPTrackerSettingsModels.createSplittingMaxDistance(); private BufferedDataTable m_resultTable; /* * Node Begins */ protected LAPTrackerNodeModel() { super(1, 1); // for state consistency: m_customTrackPrefix.setEnabled(false); } @Override protected DataTableSpec[] configure(final DataTableSpec[] inSpecs) throws InvalidSettingsException { // simply to check whether the input changed getSelectedColumnIndices(inSpecs[0]); getColIndices(m_labelColumnModel, StringValue.class, inSpecs[0], getColIndices(m_bitMaskColumnModel, ImgPlusValue.class, inSpecs[0]), getColIndices(m_sourceLabelingColumn, LabelingValue.class, inSpecs[0])); return createOutSpec(); } private DataTableSpec[] createOutSpec() { return new DataTableSpec[] { new DataTableSpec( new DataColumnSpecCreator("Tracking", LabelingCell.TYPE) .createSpec()) }; } @SuppressWarnings("unchecked") @Override protected BufferedDataTable[] execute(final BufferedDataTable[] inData, final ExecutionContext exec) throws Exception { // get all information needed from table final DataTableSpec spec = inData[0].getSpec(); final String[] columnNames = spec.getColumnNames(); final int[] featureIndices = getSelectedColumnIndices(spec); // get bitmask & time final int bitMaskColumnIdx = getColIndices(m_bitMaskColumnModel, ImgPlusValue.class, spec); final int labelIdx = getColIndices(m_labelColumnModel, StringValue.class, spec, bitMaskColumnIdx); final int sourceLabelingIdx = getColIndices(m_sourceLabelingColumn, LabelingValue.class, spec); // time axis final AxisType timeAxis = Axes.get(m_timeAxisModel.getStringValue()); // Source labeling. Important: Since now only one labeling is allowed. RandomAccessibleInterval<?> sourceLabeling = null; String sourceLabelingName = ""; LabelingMetadata sourceLabelingMetadata = null; final TrackableObjectCollection<TrackedNode<String>> trackedNodes = new DefaultTOCollection<>(); for (final DataRow row : inData[0]) { exec.checkCanceled(); final ImgPlusValue<BitType> bitMaskValue = ((ImgPlusValue<BitType>) row.getCell(bitMaskColumnIdx)); final ImgPlus<BitType> bitMask = bitMaskValue.getImgPlus(); final String label = ((StringValue) row.getCell(labelIdx)).getStringValue(); // get time dimension final int timeIdx = bitMask.dimensionIndex(timeAxis); if (timeIdx == -1) { throw new IllegalArgumentException( "Tracking dimension doesn't exist in your BitMask. Please choose the correct tracking dimension!"); } // here: if source labeling is null set it. only one source is // allowed since now if (sourceLabeling == null) { final LabelingValue<?> labValue = (LabelingValue<?>) row.getCell(sourceLabelingIdx); sourceLabeling = labValue.getLabeling(); sourceLabelingName = labValue.getLabelingMetadata().getName(); sourceLabelingMetadata = labValue.getLabelingMetadata(); } else if (!sourceLabelingName.equalsIgnoreCase( ((LabelingValue<?>) row.getCell(sourceLabelingIdx)) .getLabelingMetadata().getName())) { throw new IllegalArgumentException( "Since now only labels from one Labeling are allowed. Use KNIME Loops!"); } final Map<String, Double> featureMap = new HashMap<>(); for (final int idx : featureIndices) { featureMap.put(columnNames[idx], ((DoubleValue) row.getCell(idx)).getDoubleValue()); } final Centroid centroid = new Centroid(); final double[] pos = centroid.compute(bitMask, new double[bitMask.numDimensions()]); // add the node final TrackedNode<String> trackedNode = new TrackedNode<>(bitMask, pos, label, timeIdx, featureMap); trackedNodes.add(trackedNode, trackedNode.frame()); } // Set-Up the tracker final GenericLapTracker<String> tracker = new GenericLapTracker<>( EnumUtils.valueForName( m_trackingAlgorithmModel.getStringValue(), LAPTrackerAlgorithm.values()), trackedNodes, initSettings()); // Start tracking tracker.setNumThreads(Runtime.getRuntime().availableProcessors()); tracker.process(); // use the results and create output labeling // create tracks final ConnectivityInspector<TrackedNode<String>, DefaultWeightedEdge> inspector = new ConnectivityInspector<>(tracker.getResult()); final List<Set<TrackedNode<String>>> unsortedSegments = inspector.connectedSets(); final ArrayList<SortedSet<TrackedNode<String>>> trackSegments = new ArrayList<>(unsortedSegments.size()); for (final Set<TrackedNode<String>> set : unsortedSegments) { final SortedSet<TrackedNode<String>> sortedSet = new TreeSet<>(TrackableObjectUtils.frameComparator()); sortedSet.addAll(set); trackSegments.add(sortedSet); } int trackCtr = 0; final ImgLabeling<String, ?> res = KNIPGateway.ops().create().imgLabeling(sourceLabeling); final RandomAccess<net.imglib2.roi.labeling.LabelingType<String>> resAccess = res.randomAccess(); final RandomAccess<?> srcAccess = sourceLabeling.randomAccess(); final boolean useCustomPrefix = m_useCustomTrackPrefix.getBooleanValue(); final String customPrefix = m_customTrackPrefix.getStringValue(); final boolean attachSourceLabelings = m_attachSourceLabelings.getBooleanValue(); final int numDims = resAccess.numDimensions(); for (final SortedSet<TrackedNode<String>> track : trackSegments) { for (final TrackedNode<String> node : track) { final ImgPlus<BitType> bitMask = node.bitMask(); final Cursor<BitType> bitMaskCursor = bitMask.cursor(); while (bitMaskCursor.hasNext()) { if (!bitMaskCursor.next().get()) { continue; } for (int d = 0; d < numDims; d++) { resAccess.setPosition(bitMaskCursor.getLongPosition(d), d); } // set all the important information final Set<String> labeling = new HashSet<>(resAccess.get()); // add custom track prefix if selected if (useCustomPrefix) { labeling.add(customPrefix + trackCtr); } else { labeling.add( LAPTrackerSettingsModels.DEFAULT_TRACK_PREFIX + trackCtr); } // add original labeling if selected by the user if (attachSourceLabelings) { srcAccess.setPosition(resAccess); final Set<?> localLabelings = (Set<?>) srcAccess.get(); for (final Object o : localLabelings) { labeling.add(o.toString()); } } resAccess.get().clear(); resAccess.get().addAll(labeling); } } trackCtr++; } final LabelingCellFactory labelingCellFactory = new LabelingCellFactory(exec); final BufferedDataContainer container = exec.createDataContainer(createOutSpec()[0]); container.addRowToTable(new DefaultRow(sourceLabelingName, labelingCellFactory.createCell(res, sourceLabelingMetadata))); container.close(); return new BufferedDataTable[] { m_resultTable = container.getTable() }; } private Map<String, Object> initSettings() { // Set the tracking settings final Map<String, Object> settings = LAPUtils.getDefaultLAPSettingsMap(); settings.put(KEY_LINKING_MAX_DISTANCE, m_linkingMaxDistanceModel.getDoubleValue()); settings.put(KEY_ALLOW_GAP_CLOSING, m_allowGapClosingModel.getBooleanValue()); settings.put(KEY_ALLOW_TRACK_MERGING, m_allowMergingModel.getBooleanValue()); settings.put(KEY_ALLOW_TRACK_SPLITTING, m_allowSplittingModel.getBooleanValue()); settings.put(KEY_ALTERNATIVE_LINKING_COST_FACTOR, m_alternativeLinkingCostFactor.getDoubleValue()); settings.put(KEY_CUTOFF_PERCENTILE, m_cutoffPercentileModel.getDoubleValue()); settings.put(KEY_GAP_CLOSING_MAX_FRAME_GAP, m_gapClosingMaxFrameModel.getIntValue()); settings.put(KEY_GAP_CLOSING_MAX_DISTANCE, m_gapClosingMaxDistanceModel.getDoubleValue()); settings.put(KEY_LINKING_MAX_DISTANCE, m_linkingMaxDistanceModel.getDoubleValue()); settings.put(KEY_MERGING_MAX_DISTANCE, m_mergingMaxDistanceModel.getDoubleValue()); settings.put(KEY_SPLITTING_MAX_DISTANCE, m_splittingMaxDistance.getDoubleValue()); return settings; } @Override protected void loadInternals(final File nodeInternDir, final ExecutionMonitor exec) throws IOException, CanceledExecutionException { // TODO Auto-generated method stub } @Override protected void saveInternals(final File nodeInternDir, final ExecutionMonitor exec) throws IOException, CanceledExecutionException { // TODO Auto-generated method stub } @Override protected void saveSettingsTo(final NodeSettingsWO settings) { m_bitMaskColumnModel.saveSettingsTo(settings); m_columns.saveSettingsTo(settings); m_labelColumnModel.saveSettingsTo(settings); m_timeAxisModel.saveSettingsTo(settings); m_allowGapClosingModel.saveSettingsTo(settings); m_allowMergingModel.saveSettingsTo(settings); m_alternativeLinkingCostFactor.saveSettingsTo(settings); m_cutoffPercentileModel.saveSettingsTo(settings); m_linkingMaxDistanceModel.saveSettingsTo(settings); m_gapClosingMaxFrameModel.saveSettingsTo(settings); m_mergingMaxDistanceModel.saveSettingsTo(settings); m_splittingMaxDistance.saveSettingsTo(settings); m_trackingAlgorithmModel.saveSettingsTo(settings); m_allowSplittingModel.saveSettingsTo(settings); m_gapClosingMaxDistanceModel.saveSettingsTo(settings); m_sourceLabelingColumn.saveSettingsTo(settings); m_attachSourceLabelings.saveSettingsTo(settings); m_useCustomTrackPrefix.saveSettingsTo(settings); m_customTrackPrefix.saveSettingsTo(settings); } @Override protected void validateSettings(final NodeSettingsRO settings) throws InvalidSettingsException { m_bitMaskColumnModel.validateSettings(settings); m_columns.validateSettings(settings); m_labelColumnModel.validateSettings(settings); m_timeAxisModel.validateSettings(settings); m_allowGapClosingModel.validateSettings(settings); m_allowMergingModel.validateSettings(settings); m_alternativeLinkingCostFactor.validateSettings(settings); m_cutoffPercentileModel.validateSettings(settings); m_linkingMaxDistanceModel.validateSettings(settings); m_gapClosingMaxFrameModel.validateSettings(settings); m_mergingMaxDistanceModel.validateSettings(settings); m_splittingMaxDistance.validateSettings(settings); m_trackingAlgorithmModel.validateSettings(settings); m_allowSplittingModel.validateSettings(settings); m_gapClosingMaxDistanceModel.validateSettings(settings); m_sourceLabelingColumn.validateSettings(settings); try { m_useCustomTrackPrefix.validateSettings(settings); m_customTrackPrefix.validateSettings(settings); m_attachSourceLabelings.validateSettings(settings); } catch (final Exception e) { // backwards compatibility } } @Override protected void loadValidatedSettingsFrom(final NodeSettingsRO settings) throws InvalidSettingsException { m_bitMaskColumnModel.loadSettingsFrom(settings); m_columns.loadSettingsFrom(settings); m_labelColumnModel.loadSettingsFrom(settings); m_timeAxisModel.loadSettingsFrom(settings); m_allowGapClosingModel.loadSettingsFrom(settings); m_allowMergingModel.loadSettingsFrom(settings); m_alternativeLinkingCostFactor.loadSettingsFrom(settings); m_cutoffPercentileModel.loadSettingsFrom(settings); m_linkingMaxDistanceModel.loadSettingsFrom(settings); m_gapClosingMaxFrameModel.loadSettingsFrom(settings); m_mergingMaxDistanceModel.loadSettingsFrom(settings); m_splittingMaxDistance.loadSettingsFrom(settings); m_trackingAlgorithmModel.loadSettingsFrom(settings); m_allowSplittingModel.loadSettingsFrom(settings); m_gapClosingMaxDistanceModel.loadSettingsFrom(settings); m_sourceLabelingColumn.loadSettingsFrom(settings); try { m_useCustomTrackPrefix.loadSettingsFrom(settings); m_customTrackPrefix.loadSettingsFrom(settings); m_attachSourceLabelings.loadSettingsFrom(settings); } catch (final Exception e) { // backwards compatibility } } @Override protected void reset() { // TODO Auto-generated method stub } /* Helper to collect all columns of DoubleType */ private void collectAllColumns(final List<String> colNames, final DataTableSpec spec) { colNames.clear(); for (final DataColumnSpec c : spec) { if (c.getType().isCompatible(DoubleValue.class)) { colNames.add(c.getName()); } } if (colNames.isEmpty()) { return; } } /* * Retrieves the selected column indices from the given DataTableSpec and * the column selection. If the selection turned out to be invalid, all * columns are selected. */ protected int[] getSelectedColumnIndices(final DataTableSpec inSpec) { final List<String> colNames; if ((m_columns.getIncludeList().size() == 0) || m_columns.isKeepAllSelected()) { colNames = new ArrayList<>(); collectAllColumns(colNames, inSpec); m_columns.setIncludeList(colNames); } else { colNames = new ArrayList<>(); colNames.addAll(m_columns.getIncludeList()); if (!validateColumnSelection(colNames, inSpec)) { setWarningMessage( "Invalid column selection. All columns are selected!"); collectAllColumns(colNames, inSpec); } } // get column indices final List<Integer> colIndices = new ArrayList<>(colNames.size()); for (int i = 0; i < colNames.size(); i++) { final int colIdx = inSpec.findColumnIndex(colNames.get(i)); if (colIdx == -1) { // can not occur, actually throw new IllegalStateException( "this should really not happen"); } colIndices.add(colIdx); } final int[] colIdx = new int[colIndices.size()]; for (int i = 0; i < colIdx.length; i++) { colIdx[i] = colIndices.get(i); } return colIdx; } /* Checks if a column is not present in the DataTableSpec */ private boolean validateColumnSelection(final List<String> colNames, final DataTableSpec spec) { for (int i = 0; i < colNames.size(); i++) { final int colIdx = spec.findColumnIndex(colNames.get(i)); if (colIdx == -1) { return false; } } return true; } protected int getColIndices(final SettingsModelString model, final Class<? extends DataValue> clazz, final DataTableSpec inSpec, final Integer... excludeCols) throws InvalidSettingsException { int colIdx = -1; if (model.getStringValue() != null) { colIdx = NodeUtils.autoColumnSelection(inSpec, model, clazz, this.getClass(), excludeCols); } return colIdx; } /** * {@inheritDoc} */ @Override public BufferedDataTable[] getInternalTables() { return new BufferedDataTable[] { m_resultTable }; } /** * {@inheritDoc} */ @Override public void setInternalTables(final BufferedDataTable[] tables) { m_resultTable = tables[0]; } }