package org.knime.knip.tracking.nodes.trackmate; 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.jgrapht.graph.SimpleWeightedGraph; 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.DataValue; import org.knime.core.data.DoubleValue; import org.knime.core.data.RowKey; import org.knime.core.data.StringValue; import org.knime.core.data.def.DefaultRow; import org.knime.core.data.def.DoubleCell; import org.knime.core.data.def.StringCell; 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.SettingsModel; import org.knime.core.node.defaultnodesettings.SettingsModelBoolean; import org.knime.core.node.defaultnodesettings.SettingsModelColumnFilter2; import org.knime.core.node.defaultnodesettings.SettingsModelDouble; import org.knime.core.node.defaultnodesettings.SettingsModelInteger; import org.knime.core.node.defaultnodesettings.SettingsModelString; import org.knime.knip.base.data.img.ImgPlusCell; import org.knime.knip.base.data.img.ImgPlusCellFactory; 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.DefaultImgMetadata; import org.knime.knip.core.data.img.LabelingMetadata; import org.knime.knip.tracking.data.TrackedNode; import org.knime.knip.tracking.nodes.trackmate.TrackmateTrackerSettingsModels.TrackMateTrackFeature; import org.knime.knip.tracking.util.NodeTools; import fiji.plugin.trackmate.FeatureModel; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.TrackmateConstants; import fiji.plugin.trackmate.features.track.TrackAnalyzer; import fiji.plugin.trackmate.tracking.DefaultTOCollection; import fiji.plugin.trackmate.tracking.TrackableObjectCollection; import fiji.plugin.trackmate.tracking.sparselap.SparseLAPTracker; import fiji.plugin.trackmate.util.LAPUtils; import fiji.plugin.trackmate.util.TrackableObjectUtils; import net.imagej.ImgPlus; import net.imagej.ImgPlusMetadata; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; import net.imglib2.Cursor; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.Img; import net.imglib2.img.array.ArrayImgFactory; import net.imglib2.ops.operation.iterableinterval.unary.Centroid; import net.imglib2.ops.util.MetadataUtil; import net.imglib2.roi.Regions; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegions; import net.imglib2.roi.labeling.LabelingType; import net.imglib2.type.logic.BitType; /** * Node Model for the Trackmate Tracker Node. * * @author Gabriel Einsdorf, University of Konstanz * @author Christian Dietz, University of Konstanz * */ public class TrackmateTrackerNodeModel extends NodeModel implements BufferedDataTableHolder { /* * KNIME SETTINGS MODELS */ private final SettingsModelString m_sourceLabelingColumn = TrackmateTrackerSettingsModels.createSourceLabelingSettingsModel(); private final SettingsModelColumnFilter2 m_columnFilterModel = TrackmateTrackerSettingsModels.createColumnFilterModel(); private final SettingsModelString m_timeAxisModel = TrackmateTrackerSettingsModels.createTimeAxisModel(); private final SettingsModelString m_bitMaskColumnModel = TrackmateTrackerSettingsModels.createBitMaskModel(); private final SettingsModelString m_labelColumnModel = TrackmateTrackerSettingsModels.createLabelModel(); private final SettingsModelBoolean m_attachSourceLabelings = TrackmateTrackerSettingsModels.createAttachSourceLabelingsModel(); private final SettingsModelBoolean m_useCustomTrackPrefix = TrackmateTrackerSettingsModels.createUseCustomTrackPrefixModel(); private final SettingsModelString m_customTrackPrefix = TrackmateTrackerSettingsModels.createCustomTrackPrefixModel(); private final SettingsModelBoolean m_calculateTrackFeaturesModel = TrackmateTrackerSettingsModels.createCalculateTrackFeaturesModel(); /* * TRACKMATE SETTINGS MODELS */ private final SettingsModelBoolean m_allowGapClosingModel = TrackmateTrackerSettingsModels.createAllowGapClosingModel(); private final SettingsModelBoolean m_allowMergingModel = TrackmateTrackerSettingsModels.createAllowMergingModel(); private final SettingsModelBoolean m_allowSplittingModel = TrackmateTrackerSettingsModels.createAllowSplittingModel(); private final SettingsModelInteger m_gapClosingMaxFrameModel = TrackmateTrackerSettingsModels.createMaxFrameGapClosingModel(); private final SettingsModelDouble m_alternativeLinkingCostFactor = TrackmateTrackerSettingsModels.createAlternativeLinkingCostFactor(); private final SettingsModelDouble m_cutoffPercentileModel = TrackmateTrackerSettingsModels.createCutoffPercentileModel(); private final SettingsModelDouble m_gapClosingMaxDistanceModel = TrackmateTrackerSettingsModels.createGapClosingMaxDistanceModel(); private final SettingsModelDouble m_linkingMaxDistanceModel = TrackmateTrackerSettingsModels.createLinkingMaxDistanceModel(); private final SettingsModelDouble m_mergingMaxDistanceModel = TrackmateTrackerSettingsModels.createMergingMaxDistance(); private final SettingsModelDouble m_splittingMaxDistance = TrackmateTrackerSettingsModels.createSplittingMaxDistance(); /* * GLOBAL MEMBERS */ private BufferedDataTable m_labelingTable; private BufferedDataTable m_trackFeatureTable; private List<SettingsModel> m_settingsModels; /********************** * NODE SETUP METHODS * **********************/ /** * Constructor. */ protected TrackmateTrackerNodeModel() { // SECOND OUT PORT FOR FEATURES super(1, 2); // for state consistency: m_customTrackPrefix.setEnabled(false); } /** * {@inheritDoc} */ @Override protected DataTableSpec[] configure(final DataTableSpec[] inSpecs) throws InvalidSettingsException { // simply to check whether the input changed NodeTools.getIndicesFromFilter(inSpecs[0], m_columnFilterModel, DoubleValue.class, this.getClass()); getColIndices(m_labelColumnModel, StringValue.class, inSpecs[0], getColIndices(m_bitMaskColumnModel, ImgPlusValue.class, inSpecs[0]), getColIndices(m_sourceLabelingColumn, LabelingValue.class, inSpecs[0])); return createOutSpec(); } /** * @return The output DataTableSpecs for this node. */ private DataTableSpec[] createOutSpec() { final DataTableSpec[] dataTableSpecs; // Create the outspec depending on the calculate track features setting. if (m_calculateTrackFeaturesModel.getBooleanValue()) { final TrackMateTrackFeature[] values = TrackMateTrackFeature.values(); final List<DataColumnSpec> colSpecs = new ArrayList<>(values.length + 2); colSpecs.add(new DataColumnSpecCreator("TrackID", StringCell.TYPE) .createSpec()); colSpecs.add(new DataColumnSpecCreator("Bitmask", ImgPlusCell.TYPE) .createSpec()); for (final TrackMateTrackFeature feature : values) { colSpecs.add(new DataColumnSpecCreator(feature.toString(), DoubleCell.TYPE).createSpec()); } dataTableSpecs = new DataTableSpec[] { new DataTableSpec(new DataColumnSpecCreator("Tracking", LabelingCell.TYPE).createSpec()), new DataTableSpec(colSpecs .toArray(new DataColumnSpec[colSpecs.size()])) }; } else { dataTableSpecs = new DataTableSpec[] { new DataTableSpec(new DataColumnSpecCreator("Tracking", LabelingCell.TYPE).createSpec()), new DataTableSpec() }; } return dataTableSpecs; } /********************* * DATA FLOW METHODS * *********************/ /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override protected BufferedDataTable[] execute(final BufferedDataTable[] inData, final ExecutionContext exec) throws Exception { // Select the name prefix for the Tracks. final String trackPrefix = m_useCustomTrackPrefix.getBooleanValue() ? m_customTrackPrefix.getStringValue() : TrackmateTrackerSettingsModels.DEFAULT_TRACK_PREFIX; // set the source labeling, only one source is allowed since now final int labelingIndex = getColIndices(m_sourceLabelingColumn, LabelingValue.class, inData[0].getSpec()); if (inData[0].size() < 1) { throw new IllegalArgumentException("Input Table is empty!"); } final LabelingValue<String> srcLabelingValue; try { srcLabelingValue = (LabelingValue<String>) inData[0].iterator() .next().getCell(labelingIndex); } catch (final ClassCastException e) { throw new IllegalArgumentException( "Invalid labeling type in the Labeling Column: " + e.getMessage()); } // create a list of all nodes final TrackableObjectCollection<TrackedNode<String>> trackedNodes = createTrackedNodes(inData, exec, srcLabelingValue.getLabelingMetadata().getName()); // Do the tracking final SparseLAPTracker<TrackedNode<String>> tracker = new SparseLAPTracker<>(trackedNodes, initTrackMateSettings()); tracker.setNumThreads(Runtime.getRuntime().availableProcessors()); final boolean success = tracker.process(); if (!success) { throw new CanceledExecutionException(tracker.getErrorMessage()); } // get the tracks from the tracker final List<SortedSet<TrackedNode<String>>> tracks = retrieveTrackSegments(tracker); // create the result labeling final ImgLabeling<String, ?> resultLabeling = createResultLabeling( srcLabelingValue.getLabeling(), tracks, trackPrefix); // Calculate the track features (if needed) Map<Integer, Map<String, Double>> featureValues = null; if (m_calculateTrackFeaturesModel.getBooleanValue()) { featureValues = calculateTrackFeatures(trackedNodes, tracker.getResult(), exec).getAllTrackFeatureValues(); } // create the output tables final BufferedDataTable[] resultTables = createResultTables(exec, srcLabelingValue, resultLabeling, tracks, trackPrefix, featureValues); m_labelingTable = resultTables[0]; m_trackFeatureTable = resultTables[1]; return resultTables; } /** * @param inData * @param exec * @param sourceLabelingName * @return A {@link TrackableObjectCollection} containing all trackable * objects in the input data. * @throws CanceledExecutionException * @throws InvalidSettingsException */ @SuppressWarnings("unchecked") private TrackableObjectCollection<TrackedNode<String>> createTrackedNodes( final BufferedDataTable[] inData, final ExecutionContext exec, final String sourceLabelingName) throws CanceledExecutionException, InvalidSettingsException { // get all information needed from table final DataTableSpec spec = inData[0].getSpec(); final String[] columnNames = spec.getColumnNames(); // get the feature indices final List<Integer> featureIndices = NodeTools.getIndicesFromFilter( spec, m_columnFilterModel, DoubleValue.class, this.getClass()); // get bitmask index final int sourceLabelingIdx = getColIndices(m_sourceLabelingColumn, LabelingValue.class, inData[0].getSpec()); // time axis final AxisType timeAxis = Axes.get(m_timeAxisModel.getStringValue()); // get bitmask index final int bitMaskColumnIdx = getColIndices(m_bitMaskColumnModel, ImgPlusValue.class, spec); // get label index final int labelIdx = getColIndices(m_labelColumnModel, StringValue.class, spec, bitMaskColumnIdx); // create the nodes from the input data final TrackableObjectCollection<TrackedNode<String>> trackedNodes = new DefaultTOCollection<>(); for (final DataRow row : inData[0]) { exec.checkCanceled(); // get the spot ImgPlus<BitType> bitMask = null; ImgPlusValue<BitType> bitMaskValue = null; try { bitMaskValue = (ImgPlusValue<BitType>) row.getCell(bitMaskColumnIdx); bitMask = bitMaskValue.getImgPlus(); } catch (final ClassCastException e) { handleMissingValue(row.getKey(), columnNames[bitMaskColumnIdx]); } String label = null; try { label = ((StringValue) row.getCell(labelIdx)).getStringValue(); } catch (final ClassCastException e) { handleMissingValue(row.getKey(), columnNames[bitMaskColumnIdx]); } // 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!"); } 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) { try { featureMap.put(columnNames[idx], ((DoubleValue) row.getCell(idx)).getDoubleValue()); } catch (final ClassCastException e) { handleMissingValue(row.getKey(), columnNames[idx]); } } final Centroid centroid = new Centroid(); double[] pos = centroid.compute(bitMask, new double[bitMask.numDimensions()]); // the TrackLocationAnalyzer calculators only works with 3D // images, so we extend the 2D ones. if (pos.length == 3) { pos = new double[] { pos[0], pos[1], pos[2], 0.0 }; } // set correct meta data featureMap.put(TrackmateConstants.FRAME, pos[timeIdx]); featureMap.put(TrackmateConstants.POSITION_T, pos[timeIdx]); // add the node final TrackedNode<String> trackedNode = new TrackedNode<>(bitMask, pos, label, timeIdx, featureMap); trackedNodes.add(trackedNode, trackedNode.frame()); } return trackedNodes; } /** * Throws the appropriate Exception for a missing value. * * @param rowkey * @param columnName */ private void handleMissingValue(final RowKey rowkey, final String columnName) { throw new IllegalArgumentException("Missing values in the row: '" + rowkey + "' in the column: '" + columnName + "'"); } /** * Retrieves the calculated track segments in a harmonized form from the * Tracker. * * @param tracker * the tracker to retrieve the tracks from. * @return the retrieved track segments */ private List<SortedSet<TrackedNode<String>>> retrieveTrackSegments( final SparseLAPTracker<TrackedNode<String>> tracker) { // get the tracks from the tracker and final ConnectivityInspector<TrackedNode<String>, DefaultWeightedEdge> inspector = new ConnectivityInspector<>(tracker.getResult()); final List<Set<TrackedNode<String>>> unsortedSegments = inspector.connectedSets(); final List<SortedSet<TrackedNode<String>>> trackSegments = new ArrayList<>(unsortedSegments.size()); // sort the track nodes by adding all segments to a sorted TreeSet for (final Set<TrackedNode<String>> segmentSet : unsortedSegments) { final SortedSet<TrackedNode<String>> sortedSet = new TreeSet<>(TrackableObjectUtils.frameComparator()); sortedSet.addAll(segmentSet); trackSegments.add(sortedSet); } return trackSegments; } /** * Creates a Labeling which contains all tracks. * * @param sourceLabeling * the source labeling. * @param tracks * the calculated tracks. * @param trackPrefix * the string prefix for the tracks. * @return */ private ImgLabeling<String, ?> createResultLabeling( final RandomAccessibleInterval<LabelingType<String>> sourceLabeling, final List<SortedSet<TrackedNode<String>>> tracks, final String trackPrefix) { final RandomAccess<?> srcAccess = sourceLabeling.randomAccess(); final ImgLabeling<String, ?> resultLabeling = KNIPGateway.ops().create().imgLabeling(sourceLabeling); final RandomAccess<LabelingType<String>> resAccess = resultLabeling.randomAccess(); // loop invariants final boolean attachSourceLabelings = m_attachSourceLabelings.getBooleanValue(); final int numDims = resAccess.numDimensions(); final long[] pos = new long[numDims]; int trackCtr = 0; for (final SortedSet<TrackedNode<String>> track : tracks) { 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; } bitMaskCursor.localize(pos); resAccess.setPosition(pos); // set all the important information final Set<String> labeling = new HashSet<>(resAccess.get()); labeling.add(trackPrefix + trackCtr); // add original labelings 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++; } return resultLabeling; } /** * Calculates the Track Features. * * @param spots * A TrackableObjectCollection containing the tracked spots * * @param trackingResult * the result of the TrackMate tracker * @param exec * @throws CanceledExecutionException */ private FeatureModel<TrackedNode<String>> calculateTrackFeatures( final TrackableObjectCollection<TrackedNode<String>> spots, final SimpleWeightedGraph<TrackedNode<String>, DefaultWeightedEdge> trackingResult, final ExecutionContext exec) throws CanceledExecutionException { final Model<TrackedNode<String>> model = new Model<>(); model.setTracks(trackingResult, false); model.setSpots(spots, false); final Set<Integer> trackIDs = model.getTrackModel().trackIDs(false); for (final TrackAnalyzer<TrackedNode<String>> analyzer : TrackmateTrackerSettingsModels.TRACK_ANALYZERS) { exec.checkCanceled(); if (analyzer.isLocal()) { analyzer.process(trackIDs, model); } else { analyzer.process(model.getTrackModel().trackIDs(false), model); } } return model.getFeatureModel(); } /** * Creates the tables holding the results of the node. * * @param exec * the execution context. * @param srcLabelingValue * the {@link LabelingValue} of the source Labeling. * @param resultLabeling * the labeling to return as the result of the tracking. * @param tracks * the Tracks to write out. * @param trackPrefix * the prefix for the tracks. * @param featureValues * A map containing the calculated feature values. * @return BufferedDataTable[] the Tables containing the results * @throws IOException * @throws CanceledExecutionException */ private BufferedDataTable[] createResultTables(final ExecutionContext exec, final LabelingValue<String> srcLabelingValue, final ImgLabeling<String, ?> resultLabeling, final List<SortedSet<TrackedNode<String>>> tracks, final String trackPrefix, final Map<Integer, Map<String, Double>> featureValues) throws IOException, CanceledExecutionException { final DataTableSpec[] outSpec = createOutSpec(); // create the labeling result table final LabelingCellFactory labelingCellFactory = new LabelingCellFactory(exec); final BufferedDataContainer labelingContainer = exec.createDataContainer(outSpec[0]); final LabelingMetadata sourceLabelingMetadata = srcLabelingValue.getLabelingMetadata(); final String sourceLabelingName = sourceLabelingMetadata.getName(); labelingContainer.addRowToTable( new DefaultRow(sourceLabelingName, labelingCellFactory .createCell(resultLabeling, sourceLabelingMetadata))); labelingContainer.close(); // (optionally) create the track features result table final BufferedDataContainer featureContainer = exec.createDataContainer(outSpec[1]); if (m_calculateTrackFeaturesModel.getBooleanValue()) { final LabelRegions<String> regions = KNIPGateway.regions().regions(resultLabeling); // create the metadata for the bitmasks final DefaultImgMetadata mdata = new DefaultImgMetadata( srcLabelingValue.getLabeling().numDimensions()); MetadataUtil.copyTypedSpace(sourceLabelingMetadata, mdata); MetadataUtil.copyName(sourceLabelingMetadata, mdata); MetadataUtil.copySource(srcLabelingValue.getLabelingMetadata(), mdata); mdata.setSource(sourceLabelingMetadata.getName()); // create a row for each track containing its features for (int i = 0, n = tracks.size(); i < n; i++) { final String trackName = trackPrefix + i; featureContainer.addRowToTable(createTrackFeatureRow( sourceLabelingName, featureValues.get(i), trackName, mdata, regions, exec)); } } featureContainer.close(); return new BufferedDataTable[] { labelingContainer.getTable(), featureContainer.getTable() }; } /**************************** * DATA FLOW HELPER METHODS * ****************************/ /** * Creates the a row for a given track and its features for the track * features table. * * @param sourceLabelingName * the name of the source labeling. * @param featureMap * the map containing the features * @param exec * @return a DataRow containing all the feature values for the specified * track. * @throws IOException */ private DataRow createTrackFeatureRow(final String sourceLabelingName, final Map<String, Double> featureMap, final String trackName, final ImgPlusMetadata mdata, final LabelRegions<String> regions, final ExecutionContext exec) throws IOException { final List<DataCell> cells = new ArrayList<>(TrackMateTrackFeature.values().length); // TrackID Column cells.add(new StringCell(trackName)); // Bitmask Column final Img<BitType> bitMask = createBinaryMask(regions, trackName); cells.add(new ImgPlusCellFactory(exec) .createCell(new ImgPlus<>(bitMask, mdata))); // Features for (final TrackMateTrackFeature feature : TrackMateTrackFeature .values()) { cells.add(new DoubleCell(featureMap.get(feature.name()))); } return new DefaultRow(sourceLabelingName + '#' + trackName, cells .toArray(new DataCell[TrackMateTrackFeature.values().length])); } /** * Helper to create a binary mask from a region of interest. The mask has * the size given in the dimensions. * * @param roi * the region of interest. * @param dims * the dimensions of the original labeling. * @param resultLabeling * @param trackName * @returns */ private Img<BitType> createBinaryMask(LabelRegions<String> regions, String trackName) { final IterableInterval<Void> labelII = Regions.iterable(regions.getLabelRegion(trackName)); final long[] dimensions = new long[labelII.numDimensions()]; labelII.dimensions(dimensions); final Img<BitType> mask = new ArrayImgFactory<BitType>().create(labelII, new BitType()); final RandomAccess<BitType> maskRA = mask.randomAccess(); final Cursor<Void> cur = labelII.localizingCursor(); while (cur.hasNext()) { cur.fwd(); for (int d = 0; d < cur.numDimensions(); d++) { maskRA.setPosition(cur.getLongPosition(d) - labelII.min(d), d); } maskRA.get().set(true); } return mask; } /** * @return A Map containing the settings for the TrackMateTracker, obtained * from the node settings models. */ private Map<String, Object> initTrackMateSettings() { // 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; } /******************************************* * COLUMN SELECTION AND VALIDATION METHODS * *******************************************/ /** * Gets the column index associated with a given settings model and class. */ private 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; } /****************************** * DEFAULT KNIME NODE METHODS * ******************************/ /* * Helper to collect all settings models and add them to one list (if not * already done) */ private void collectSettingsModels() { if (m_settingsModels == null) { m_settingsModels = new ArrayList<>(); m_settingsModels.add(m_bitMaskColumnModel); m_settingsModels.add(m_columnFilterModel); m_settingsModels.add(m_labelColumnModel); m_settingsModels.add(m_timeAxisModel); m_settingsModels.add(m_allowGapClosingModel); m_settingsModels.add(m_allowMergingModel); m_settingsModels.add(m_alternativeLinkingCostFactor); m_settingsModels.add(m_cutoffPercentileModel); m_settingsModels.add(m_linkingMaxDistanceModel); m_settingsModels.add(m_gapClosingMaxFrameModel); m_settingsModels.add(m_mergingMaxDistanceModel); m_settingsModels.add(m_splittingMaxDistance); m_settingsModels.add(m_allowSplittingModel); m_settingsModels.add(m_gapClosingMaxDistanceModel); m_settingsModels.add(m_sourceLabelingColumn); m_settingsModels.add(m_useCustomTrackPrefix); m_settingsModels.add(m_customTrackPrefix); m_settingsModels.add(m_attachSourceLabelings); } } /** * {@inheritDoc} */ @Override protected void loadInternals(final File nodeInternDir, final ExecutionMonitor exec) throws IOException, CanceledExecutionException { // nothing to do here } /** * {@inheritDoc} */ @Override protected void saveInternals(final File nodeInternDir, final ExecutionMonitor exec) throws IOException, CanceledExecutionException { // nothing to do here } /** * {@inheritDoc} */ @Override protected void saveSettingsTo(final NodeSettingsWO settings) { collectSettingsModels(); for (final SettingsModel s : m_settingsModels) { s.saveSettingsTo(settings); } } /** * {@inheritDoc} */ @Override protected void validateSettings(final NodeSettingsRO settings) throws InvalidSettingsException { collectSettingsModels(); for (final SettingsModel s : m_settingsModels) { s.validateSettings(settings); } } /** * {@inheritDoc} */ @Override protected void loadValidatedSettingsFrom(final NodeSettingsRO settings) throws InvalidSettingsException { collectSettingsModels(); for (final SettingsModel s : m_settingsModels) { s.loadSettingsFrom(settings); } } /** * {@inheritDoc} */ @Override protected void reset() { m_labelingTable = null; m_trackFeatureTable = null; } /** * {@inheritDoc} */ @Override public BufferedDataTable[] getInternalTables() { return new BufferedDataTable[] { m_labelingTable, m_trackFeatureTable }; } /** * {@inheritDoc} */ @Override public void setInternalTables(final BufferedDataTable[] tables) { m_labelingTable = tables[0]; m_trackFeatureTable = tables[1]; } }