diff --git a/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java b/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java index 9cce8b78a..48f4979d0 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java @@ -105,12 +105,24 @@ public class TrackMateRunner extends TrackMatePlugIn */ private static final String ARG_MAX_DISTANCE = "max_distance"; + /** + * The macro parameter to set what is the minimal frame-to-frame linking + * IoU. Accept double values. + */ + private static final String ARG_MIN_IOU = "min_iou"; + /** * The macro parameter to set what is the maximal gap-closing distance. * Accept double values in physical units. */ private static final String ARG_MAX_GAP_DISTANCE = "max_gap_distance"; + /** + * The macro parameter to set what is the minimal gap-closing IoU. + * Accept double values. + */ + private static final String ARG_MIN_GAP_IOU = "min_gap_iou"; + /** * The macro parameter to set what is the maximal acceptable frame gap when * doing gap closing. Accept integer values. @@ -184,7 +196,9 @@ public class TrackMateRunner extends TrackMatePlugIn SUPPORTED_ARGS.add( ARG_INPUT_IMAGE_NAME ); SUPPORTED_ARGS.add( ARG_INPUT_IMAGE_PATH ); SUPPORTED_ARGS.add( ARG_MAX_DISTANCE ); + SUPPORTED_ARGS.add( ARG_MIN_IOU ); SUPPORTED_ARGS.add( ARG_MAX_GAP_DISTANCE ); + SUPPORTED_ARGS.add( ARG_MIN_GAP_IOU ); SUPPORTED_ARGS.add( ARG_MAX_GAP_FRAMES ); SUPPORTED_ARGS.add( ARG_MEDIAN ); SUPPORTED_ARGS.add( ARG_SAVE_TO ); @@ -594,11 +608,21 @@ private Map< String, ValuePair< String, MacroArgumentConverter > > prepareTracke new ValuePair< >( TrackerKeys.KEY_LINKING_MAX_DISTANCE, doubleConverter ); parsers.put( ARG_MAX_DISTANCE, maxDistancePair ); + // Min linking IoU. + final ValuePair< String, MacroArgumentConverter > minIouPair = + new ValuePair< >( TrackerKeys.KEY_LINKING_MIN_IOU, doubleConverter ); + parsers.put( ARG_MIN_IOU, minIouPair ); + // Max gap distance. final ValuePair< String, MacroArgumentConverter > maxGapDistancePair = new ValuePair< >( TrackerKeys.KEY_GAP_CLOSING_MAX_DISTANCE, doubleConverter ); parsers.put( ARG_MAX_GAP_DISTANCE, maxGapDistancePair ); + // Min gap IoU. + final ValuePair< String, MacroArgumentConverter > minGapIoUPair = + new ValuePair< >( TrackerKeys.KEY_GAP_CLOSING_MIN_IOU, doubleConverter ); + parsers.put( ARG_MIN_GAP_IOU, minGapIoUPair ); + // Target channel. final ValuePair< String, MacroArgumentConverter > maxGapFramesPair = new ValuePair< >( TrackerKeys.KEY_GAP_CLOSING_MAX_FRAME_GAP, integerConverter ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/tracker/JPanelLAPOverlapTrackerSettingsMain.java b/src/main/java/fiji/plugin/trackmate/gui/components/tracker/JPanelLAPOverlapTrackerSettingsMain.java new file mode 100644 index 000000000..c7d0f7b5b --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/components/tracker/JPanelLAPOverlapTrackerSettingsMain.java @@ -0,0 +1,405 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2023 TrackMate developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * . + * #L% + */ +package fiji.plugin.trackmate.gui.components.tracker; + +import static fiji.plugin.trackmate.gui.Fonts.BIG_FONT; +import static fiji.plugin.trackmate.gui.Fonts.FONT; +import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; +import static fiji.plugin.trackmate.gui.Fonts.TEXTFIELD_DIMENSION; +import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_LINKING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_LINKING_MIN_IOU; +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_GAP_CLOSING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_GAP_CLOSING_MIN_IOU; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_GAP_CLOSING_MAX_FRAME_GAP; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_MIN_IOU; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_MERGING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_MERGING_MIN_IOU; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_SPLITTING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_SPLITTING_MIN_IOU; + + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.MouseWheelListener; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.text.DecimalFormat; + +import javax.swing.JCheckBox; +import javax.swing.JFormattedTextField; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingConstants; + +import fiji.plugin.trackmate.gui.GuiUtils; +import fiji.plugin.trackmate.tracking.jaqaman.LAPUtils; + +public class JPanelLAPOverlapTrackerSettingsMain extends javax.swing.JPanel +{ + + private static final long serialVersionUID = -1L; + + private final JFormattedTextField txtfldSplittingMinIOU; + + private final JCheckBox chkboxAllowSplitting; + + private final JPanelFeatureSelectionGui panelGapClosing; + + private final JPanelFeatureSelectionGui panelMergingFeatures; + + private final JPanelFeatureSelectionGui panelLinkingFeatures; + + private final JPanelFeatureSelectionGui panelSplittingFeatures; + + private final JScrollPane scrpneMergingFeatures; + + private final JFormattedTextField txtfldMergingMinIOU; + + private final JCheckBox chkboxAllowMerging; + + private final JScrollPane scrpneSplittingFeatures; + + private final JFormattedTextField txtfldGapClosingMinIOU; + + private final JScrollPane scrpneGapClosingFeatures; + + private final JFormattedTextField txtfldGapClosingMaxFrameInterval; + + private final JCheckBox chkboxAllowGapClosing; + + private final JFormattedTextField txtfldLinkingMinIOU; + + private final JLabel lbl6; + + private final JLabel lbl7; + + private final JLabel lbl8; + + private final JLabel lbl10; + + private final JLabel lbl13; + + private final JLabel lbl15; + + private final JLabel lbl16; + + + public JPanelLAPOverlapTrackerSettingsMain( final String trackerName, final String spaceUnits, final Collection< String > features, final Map< String, String > featureNames ) + { + final DecimalFormat decimalFormat = new DecimalFormat( "0.0" ); + + this.setPreferredSize( new Dimension( 280, 1000 ) ); + final GridBagLayout thisLayout = new GridBagLayout(); + thisLayout.columnWidths = new int[] { 180, 50, 50 }; + thisLayout.columnWeights = new double[] { 0.1, 0.8, 0.1 }; + thisLayout.rowHeights = new int[] { 15, 20, 0, 15, 10, 15, 95, 15, 15, 15, 15, 15, 95, 15, 15, 15, 15, 15, 95, 15, 15, 15, 15, 15, 95 }; + thisLayout.rowWeights = new double[] { 0.0, 0.1, 0.25, 0.1, 0.0, 0.0, 0.25, 0.1, 0.0, 0.0, 0.0, 0.0, 0.25, 0.1, 0.0, 0.0, 0.0, 0.0, 0.25, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0 }; + this.setLayout( thisLayout ); + + final JLabel jLabel1 = new JLabel(); + this.add( jLabel1, new GridBagConstraints( 0, 0, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 10, 10, 0, 10 ), 0, 0 ) ); + jLabel1.setText( "Settings for tracker:" ); + jLabel1.setFont( FONT ); + + final JLabel lblTrackerName = new JLabel(); + this.add( lblTrackerName, new GridBagConstraints( 0, 1, 3, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets( 10, 20, 0, 0 ), 0, 0 ) ); + lblTrackerName.setHorizontalTextPosition( SwingConstants.CENTER ); + lblTrackerName.setHorizontalAlignment( SwingConstants.CENTER ); + lblTrackerName.setFont( BIG_FONT ); + lblTrackerName.setText( trackerName ); + + final JLabel lbl2 = new JLabel(); + this.add( lbl2, new GridBagConstraints( 0, 3, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + lbl2.setText( "Frame to frame linking:" ); + lbl2.setFont( BIG_FONT.deriveFont( Font.BOLD ) ); + + final JLabel lbl3 = new JLabel(); + this.add( lbl3, new GridBagConstraints( 0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + lbl3.setText( "Min IoU:" ); + lbl3.setFont( SMALL_FONT ); + + txtfldLinkingMinIOU = new JFormattedTextField( decimalFormat ); + this.add( txtfldLinkingMinIOU, new GridBagConstraints( 1, 4, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + txtfldLinkingMinIOU.setFont( SMALL_FONT ); + txtfldLinkingMinIOU.setSize( TEXTFIELD_DIMENSION ); + txtfldLinkingMinIOU.setHorizontalAlignment( JFormattedTextField.CENTER ); + + final JLabel lbl4 = new JLabel(); + this.add( lbl4, new GridBagConstraints( 0, 5, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + lbl4.setText( "Feature penalties" ); + lbl4.setFont( SMALL_FONT ); + + final JScrollPane scrpneLinkingFeatures = new JScrollPane(); + final MouseWheelListener[] l = scrpneLinkingFeatures.getMouseWheelListeners(); + scrpneLinkingFeatures.removeMouseWheelListener( l[ 0 ] ); + this.add( scrpneLinkingFeatures, new GridBagConstraints( 0, 6, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + scrpneLinkingFeatures.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); + scrpneLinkingFeatures.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS ); + panelLinkingFeatures = new JPanelFeatureSelectionGui(); + panelLinkingFeatures.setDisplayFeatures( features, featureNames ); + scrpneLinkingFeatures.setViewportView( panelLinkingFeatures ); + + // Gap closing + + final JLabel lbl5 = new JLabel(); + this.add( lbl5, new GridBagConstraints( 0, 7, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 20, 10, 0, 10 ), 0, 0 ) ); + lbl5.setText( "Track segment gap closing:" ); + lbl5.setFont( BIG_FONT.deriveFont( Font.BOLD ) ); + + chkboxAllowGapClosing = new JCheckBox(); + this.add( chkboxAllowGapClosing, new GridBagConstraints( 0, 8, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + chkboxAllowGapClosing.setText( "Allow gap closing" ); + chkboxAllowGapClosing.setFont( SMALL_FONT ); + + lbl6 = new JLabel(); + this.add( lbl6, new GridBagConstraints( 0, 9, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + lbl6.setText( "Min IoU:" ); + lbl6.setFont( SMALL_FONT ); + + txtfldGapClosingMinIOU = new JFormattedTextField( decimalFormat ); + this.add( txtfldGapClosingMinIOU, new GridBagConstraints( 1, 9, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + txtfldGapClosingMinIOU.setSize( TEXTFIELD_DIMENSION ); + txtfldGapClosingMinIOU.setFont( SMALL_FONT ); + txtfldGapClosingMinIOU.setHorizontalAlignment( JFormattedTextField.CENTER ); + + lbl7 = new JLabel(); + this.add( lbl7, new GridBagConstraints( 0, 10, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + lbl7.setText( "Max frame gap:" ); + lbl7.setFont( SMALL_FONT ); + + txtfldGapClosingMaxFrameInterval = new JFormattedTextField( Integer.valueOf( 2 ) ); + this.add( txtfldGapClosingMaxFrameInterval, new GridBagConstraints( 1, 10, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + txtfldGapClosingMaxFrameInterval.setSize( TEXTFIELD_DIMENSION ); + txtfldGapClosingMaxFrameInterval.setFont( SMALL_FONT ); + txtfldGapClosingMaxFrameInterval.setHorizontalAlignment( JFormattedTextField.CENTER ); + + lbl8 = new JLabel(); + this.add( lbl8, new GridBagConstraints( 0, 11, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + lbl8.setText( "Feature penalties:" ); + lbl8.setFont( SMALL_FONT ); + + scrpneGapClosingFeatures = new JScrollPane(); + final MouseWheelListener[] l1 = scrpneGapClosingFeatures.getMouseWheelListeners(); + scrpneGapClosingFeatures.removeMouseWheelListener( l1[ 0 ] ); + this.add( scrpneGapClosingFeatures, new GridBagConstraints( 0, 12, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + scrpneGapClosingFeatures.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); + scrpneGapClosingFeatures.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS ); + panelGapClosing = new JPanelFeatureSelectionGui(); + panelGapClosing.setDisplayFeatures( features, featureNames ); + scrpneGapClosingFeatures.setViewportView( panelGapClosing ); + + // Splitting + + final JLabel lbl9 = new JLabel(); + this.add( lbl9, new GridBagConstraints( 0, 13, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 20, 10, 0, 10 ), 0, 0 ) ); + lbl9.setText( "Track segment splitting:" ); + lbl9.setFont( BIG_FONT.deriveFont( Font.BOLD ) ); + + chkboxAllowSplitting = new JCheckBox(); + this.add( chkboxAllowSplitting, new GridBagConstraints( 0, 14, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + chkboxAllowSplitting.setText( "Allow track segment splitting" ); + chkboxAllowSplitting.setFont( SMALL_FONT ); + + lbl10 = new JLabel(); + this.add( lbl10, new GridBagConstraints( 0, 15, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 0 ), 0, 0 ) ); + lbl10.setText( "Min IoU:" ); + lbl10.setFont( SMALL_FONT ); + + txtfldSplittingMinIOU = new JFormattedTextField( decimalFormat ); + this.add( txtfldSplittingMinIOU, new GridBagConstraints( 1, 15, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + txtfldSplittingMinIOU.setSize( TEXTFIELD_DIMENSION ); + txtfldSplittingMinIOU.setFont( SMALL_FONT ); + txtfldSplittingMinIOU.setHorizontalAlignment( JFormattedTextField.CENTER ); + + lbl15 = new JLabel(); + this.add( lbl15, new GridBagConstraints( 0, 17, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + lbl15.setText( "Feature penalties:" ); + lbl15.setFont( SMALL_FONT ); + + scrpneSplittingFeatures = new JScrollPane(); + final MouseWheelListener[] l2 = scrpneSplittingFeatures.getMouseWheelListeners(); + scrpneSplittingFeatures.removeMouseWheelListener( l2[ 0 ] ); + this.add( scrpneSplittingFeatures, new GridBagConstraints( 0, 18, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + scrpneSplittingFeatures.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); + scrpneSplittingFeatures.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS ); + panelSplittingFeatures = new JPanelFeatureSelectionGui(); + panelSplittingFeatures.setDisplayFeatures( features, featureNames ); + scrpneSplittingFeatures.setViewportView( panelSplittingFeatures ); + + // Merging + + final JLabel lbl12 = new JLabel(); + this.add( lbl12, new GridBagConstraints( 0, 19, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 20, 10, 0, 10 ), 0, 0 ) ); + lbl12.setText( "Track segment merging:" ); + lbl12.setFont( BIG_FONT.deriveFont( Font.BOLD ) ); + + chkboxAllowMerging = new JCheckBox(); + this.add( chkboxAllowMerging, new GridBagConstraints( 0, 20, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + chkboxAllowMerging.setText( "Allow track segment merging" ); + chkboxAllowMerging.setFont( SMALL_FONT ); + + lbl13 = new JLabel(); + this.add( lbl13, new GridBagConstraints( 0, 21, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 0 ), 0, 0 ) ); + lbl13.setText( "Min IoU:" ); + lbl13.setFont( SMALL_FONT ); + + txtfldMergingMinIOU = new JFormattedTextField( decimalFormat ); + this.add( txtfldMergingMinIOU, new GridBagConstraints( 1, 21, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + txtfldMergingMinIOU.setSize( TEXTFIELD_DIMENSION ); + txtfldMergingMinIOU.setFont( SMALL_FONT ); + txtfldMergingMinIOU.setHorizontalAlignment( JFormattedTextField.CENTER ); + + lbl16 = new JLabel(); + this.add( lbl16, new GridBagConstraints( 0, 23, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets( 0, 10, 0, 10 ), 0, 0 ) ); + lbl16.setText( "Feature penalties:" ); + lbl16.setFont( SMALL_FONT ); + + scrpneMergingFeatures = new JScrollPane(); + final MouseWheelListener[] l3 = scrpneMergingFeatures.getMouseWheelListeners(); + scrpneMergingFeatures.removeMouseWheelListener( l3[ 0 ] ); + this.add( scrpneMergingFeatures, new GridBagConstraints( 0, 24, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 0 ), 0, 0 ) ); + scrpneMergingFeatures.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); + scrpneMergingFeatures.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS ); + panelMergingFeatures = new JPanelFeatureSelectionGui(); + panelMergingFeatures.setDisplayFeatures( features, featureNames ); + scrpneMergingFeatures.setViewportView( panelMergingFeatures ); + + // Select text-fields content on focus. + GuiUtils.selectAllOnFocus( txtfldGapClosingMinIOU ); + GuiUtils.selectAllOnFocus( txtfldGapClosingMaxFrameInterval ); + GuiUtils.selectAllOnFocus( txtfldLinkingMinIOU ); + GuiUtils.selectAllOnFocus( txtfldMergingMinIOU ); + GuiUtils.selectAllOnFocus( txtfldSplittingMinIOU ); + + // Listeners. + chkboxAllowGapClosing.addActionListener( e -> setEnabled( new Component[] { + lbl6, txtfldGapClosingMinIOU, + lbl7, txtfldGapClosingMaxFrameInterval, txtfldGapClosingMaxFrameInterval, + lbl8, scrpneGapClosingFeatures, panelGapClosing }, + chkboxAllowGapClosing.isSelected() ) ); + + chkboxAllowSplitting.addActionListener( e -> setEnabled( new Component[] { + lbl10, txtfldSplittingMinIOU, + lbl15, scrpneSplittingFeatures, panelSplittingFeatures }, + chkboxAllowSplitting.isSelected() ) ); + + chkboxAllowMerging.addActionListener( e -> setEnabled( new Component[] { + lbl13, txtfldMergingMinIOU, + lbl16, scrpneMergingFeatures, panelMergingFeatures }, + chkboxAllowMerging.isSelected() ) ); + } + + /* + * PUBLIC METHODS + */ + + @SuppressWarnings( "unchecked" ) + void echoSettings( final Map< String, Object > settings ) + { + txtfldLinkingMinIOU.setValue( settings.get( KEY_LINKING_MIN_IOU ) ); + panelLinkingFeatures.setSelectedFeaturePenalties( ( Map< String, Double > ) settings.get( KEY_LINKING_FEATURE_PENALTIES ) ); + + chkboxAllowGapClosing.setSelected( ( Boolean ) settings.get( KEY_ALLOW_GAP_CLOSING ) ); + txtfldGapClosingMinIOU.setValue( settings.get( KEY_GAP_CLOSING_MIN_IOU ) ); + txtfldGapClosingMaxFrameInterval.setValue( settings.get( KEY_GAP_CLOSING_MAX_FRAME_GAP ) ); + panelGapClosing.setSelectedFeaturePenalties( ( Map< String, Double > ) settings.get( KEY_GAP_CLOSING_FEATURE_PENALTIES ) ); + + chkboxAllowSplitting.setSelected( ( Boolean ) settings.get( KEY_ALLOW_TRACK_SPLITTING ) ); + txtfldSplittingMinIOU.setValue( settings.get( KEY_SPLITTING_MIN_IOU ) ); + panelSplittingFeatures.setSelectedFeaturePenalties( ( Map< String, Double > ) settings.get( KEY_SPLITTING_FEATURE_PENALTIES ) ); + + chkboxAllowMerging.setSelected( ( Boolean ) settings.get( KEY_ALLOW_TRACK_MERGING ) ); + txtfldMergingMinIOU.setValue( settings.get( KEY_MERGING_MIN_IOU ) ); + panelMergingFeatures.setSelectedFeaturePenalties( ( Map< String, Double > ) settings.get( KEY_MERGING_FEATURE_PENALTIES ) ); + + setEnabled( new Component[] { + lbl6, txtfldGapClosingMinIOU, + lbl7, txtfldGapClosingMaxFrameInterval, txtfldGapClosingMaxFrameInterval, + lbl8, scrpneGapClosingFeatures, panelGapClosing }, + chkboxAllowGapClosing.isSelected() ); + + setEnabled( new Component[] { + lbl10, txtfldSplittingMinIOU, + lbl15, scrpneSplittingFeatures, panelSplittingFeatures }, + chkboxAllowSplitting.isSelected() ); + + setEnabled( new Component[] { + lbl13, txtfldMergingMinIOU, + lbl16, scrpneMergingFeatures, panelMergingFeatures }, + chkboxAllowMerging.isSelected() ); + } + + /** + * @return a new settings {@link Map} with values taken from this panel. + */ + public Map< String, Object > getSettings() + { + final Map< String, Object > settings = getDefaultLAPSettingsMap(); + + settings.put( KEY_LINKING_MIN_IOU, ( ( Number ) txtfldLinkingMinIOU.getValue() ).doubleValue() ); + settings.put( KEY_LINKING_FEATURE_PENALTIES, panelLinkingFeatures.getFeaturePenalties() ); + + settings.put( KEY_ALLOW_GAP_CLOSING, chkboxAllowGapClosing.isSelected() ); + settings.put( KEY_GAP_CLOSING_MIN_IOU, ( ( Number ) txtfldGapClosingMinIOU.getValue() ).doubleValue() ); + settings.put( KEY_GAP_CLOSING_MAX_FRAME_GAP, ( ( Number ) txtfldGapClosingMaxFrameInterval.getValue() ).intValue() ); + settings.put( KEY_GAP_CLOSING_FEATURE_PENALTIES, panelGapClosing.getFeaturePenalties() ); + + settings.put( KEY_ALLOW_TRACK_SPLITTING, chkboxAllowSplitting.isSelected() ); + settings.put( KEY_SPLITTING_MIN_IOU, ( ( Number ) txtfldSplittingMinIOU.getValue() ).doubleValue() ); + settings.put( KEY_SPLITTING_FEATURE_PENALTIES, panelSplittingFeatures.getFeaturePenalties() ); + + settings.put( KEY_ALLOW_TRACK_MERGING, chkboxAllowMerging.isSelected() ); + settings.put( KEY_MERGING_MIN_IOU, ( ( Number ) txtfldMergingMinIOU.getValue() ).doubleValue() ); + settings.put( KEY_MERGING_FEATURE_PENALTIES, panelMergingFeatures.getFeaturePenalties() ); + + return settings; + } + + public static final Map< String, Object > getDefaultLAPSettingsMap() + { + final Map< String, Object > settings = LAPUtils.getDefaultSegmentSettingsMap(); + // Linking + settings.put( KEY_LINKING_MIN_IOU, DEFAULT_LINKING_MIN_IOU ); + settings.put( KEY_LINKING_FEATURE_PENALTIES, new HashMap<>( DEFAULT_LINKING_FEATURE_PENALTIES ) ); + return settings; + } + + /* + * PRIVATE METHODS + */ + + private void setEnabled( final Component[] components, final boolean enable ) + { + for ( final Component component : components ) + component.setEnabled( enable ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/tracker/LAPOverlapTrackerSettingsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/tracker/LAPOverlapTrackerSettingsPanel.java new file mode 100644 index 000000000..dd1720128 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/components/tracker/LAPOverlapTrackerSettingsPanel.java @@ -0,0 +1,111 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2023 TrackMate developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * . + * #L% + */ +package fiji.plugin.trackmate.gui.components.tracker; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.util.Collection; +import java.util.Map; + +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; + +import fiji.plugin.trackmate.gui.components.ConfigurationPanel; + +public class LAPOverlapTrackerSettingsPanel extends ConfigurationPanel +{ + + private static final long serialVersionUID = 1L; + + private JPanelLAPOverlapTrackerSettingsMain jPanelMain; + + private final String trackerName; + + private final String spaceUnits; + + private final Collection< String > features; + + private final Map< String, String > featureNames; + + /* + * CONSTRUCTOR + */ + + public LAPOverlapTrackerSettingsPanel( final String trackerName, final String spaceUnits, final Collection< String > features, final Map< String, String > featureNames ) + { + this.trackerName = trackerName; + this.spaceUnits = spaceUnits; + this.features = features; + this.featureNames = featureNames; + initGUI(); + } + + /* + * PUBLIC METHODS + */ + + @Override + public Map< String, Object > getSettings() + { + return jPanelMain.getSettings(); + } + + @Override + public void setSettings( final Map< String, Object > settings ) + { + jPanelMain.echoSettings( settings ); + } + + /* + * PRIVATE METHODS + */ + + private void initGUI() + { + try + { + final BorderLayout thisLayout = new BorderLayout(); + setPreferredSize( new Dimension( 300, 500 ) ); + this.setLayout( thisLayout ); + { + final JScrollPane jScrollPaneMain = new JScrollPane(); + this.add( jScrollPaneMain, BorderLayout.CENTER ); + jScrollPaneMain.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS ); + jScrollPaneMain.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); + jScrollPaneMain.getVerticalScrollBar().setUnitIncrement( 24 ); + { + jPanelMain = new JPanelLAPOverlapTrackerSettingsMain( trackerName, spaceUnits, features, featureNames ); + jScrollPaneMain.setViewportView( jPanelMain ); + } + } + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } + + @Override + public void clean() + {} + +} diff --git a/src/main/java/fiji/plugin/trackmate/tracking/TrackerKeys.java b/src/main/java/fiji/plugin/trackmate/tracking/TrackerKeys.java index 5c45912a1..748dbff63 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/TrackerKeys.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/TrackerKeys.java @@ -260,4 +260,51 @@ public class TrackerKeys /** A default value for the {@value #KEY_BLOCKING_VALUE} parameter. */ public static final double DEFAULT_BLOCKING_VALUE = Double.POSITIVE_INFINITY; + + /** + * Key for the parameter specifying the minimum linking IoU. The + * expected value must be a Double. + */ + public static final String KEY_LINKING_MIN_IOU = "LINKING_MIN_IOU"; + + /** A default value for the {@value #KEY_LINKING_MIN_IOU} parameter. */ + public static final double DEFAULT_LINKING_MIN_IOU = 0.0; + + /** + * Key for the parameter specifying the min gap-closing IoU. Expected + * values are {@link Double}s. If two spots, candidate for a gap-closing + * event, are found separated by a IoU smaller than this parameter value, + * gap-closing will not occur. + */ + public static final String KEY_GAP_CLOSING_MIN_IOU = "GAP_CLOSING_MIN_IOU"; + + /** + * A default value for the {@value #KEY_GAP_CLOSING_MIN_IOU} parameter. + */ + public static final double DEFAULT_GAP_CLOSING_MIN_IOU = 0.0; + + /** + * Key for the parameter specifying the min merging IoU. Expected + * values are {@link Double}s. If two spots, candidate for a merging + * event, are found separated by an IoU smaller than this parameter + * value, track merging will not occur. + */ + public static final String KEY_MERGING_MIN_IOU = "MERGING_MIN_IOU"; + + /** A default value for the {@value #KEY_MERGING_MIN_IOU} parameter. */ + public static final double DEFAULT_MERGING_MIN_IOU = 0.0; + + /** + * Key for the parameter specifying the min splitting IoU. Expected + * values are {@link Double}s. If two spots, candidate for a merging event, + * are found separated by an IoU smaller than this parameter value, + * track splitting will not occur. + */ + public static final String KEY_SPLITTING_MIN_IOU = "SPLITTING_MIN_IOU"; + + /** + * A default value for the {@link #KEY_SPLITTING_MIN_IOU} parameter. + */ + public static final double DEFAULT_SPLITTING_MIN_IOU = 0.0; + } diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/LAPUtils.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/LAPUtils.java index 195498918..2e02c8b41 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/LAPUtils.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/LAPUtils.java @@ -29,11 +29,14 @@ import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_CUTOFF_PERCENTILE; import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_GAP_CLOSING_FEATURE_PENALTIES; import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_GAP_CLOSING_MAX_DISTANCE; +import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_GAP_CLOSING_MIN_IOU; import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_GAP_CLOSING_MAX_FRAME_GAP; import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_MERGING_FEATURE_PENALTIES; import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_MERGING_MAX_DISTANCE; +import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_MERGING_MIN_IOU; import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_SPLITTING_FEATURE_PENALTIES; import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_SPLITTING_MAX_DISTANCE; +import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_SPLITTING_MIN_IOU; 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; @@ -42,13 +45,17 @@ import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_CUTOFF_PERCENTILE; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_GAP_CLOSING_FEATURE_PENALTIES; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_GAP_CLOSING_MAX_DISTANCE; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_GAP_CLOSING_MIN_IOU; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_GAP_CLOSING_MAX_FRAME_GAP; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_FEATURE_PENALTIES; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_MAX_DISTANCE; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_MIN_IOU; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_MERGING_FEATURE_PENALTIES; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_MERGING_MAX_DISTANCE; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_MERGING_MIN_IOU; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_SPLITTING_FEATURE_PENALTIES; import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_SPLITTING_MAX_DISTANCE; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_SPLITTING_MIN_IOU; import static fiji.plugin.trackmate.util.TMUtils.checkMapKeys; import static fiji.plugin.trackmate.util.TMUtils.checkParameter; @@ -148,14 +155,17 @@ public static final Map< String, Object > getDefaultSegmentSettingsMap() settings.put( KEY_ALLOW_GAP_CLOSING, DEFAULT_ALLOW_GAP_CLOSING ); settings.put( KEY_GAP_CLOSING_MAX_FRAME_GAP, DEFAULT_GAP_CLOSING_MAX_FRAME_GAP ); settings.put( KEY_GAP_CLOSING_MAX_DISTANCE, DEFAULT_GAP_CLOSING_MAX_DISTANCE ); + settings.put( KEY_GAP_CLOSING_MIN_IOU, DEFAULT_GAP_CLOSING_MIN_IOU ); settings.put( KEY_GAP_CLOSING_FEATURE_PENALTIES, new HashMap<>( DEFAULT_GAP_CLOSING_FEATURE_PENALTIES ) ); // Track splitting settings.put( KEY_ALLOW_TRACK_SPLITTING, DEFAULT_ALLOW_TRACK_SPLITTING ); settings.put( KEY_SPLITTING_MAX_DISTANCE, DEFAULT_SPLITTING_MAX_DISTANCE ); + settings.put( KEY_SPLITTING_MIN_IOU, DEFAULT_SPLITTING_MIN_IOU ); settings.put( KEY_SPLITTING_FEATURE_PENALTIES, new HashMap<>( DEFAULT_SPLITTING_FEATURE_PENALTIES ) ); // Track merging settings.put( KEY_ALLOW_TRACK_MERGING, DEFAULT_ALLOW_TRACK_MERGING ); settings.put( KEY_MERGING_MAX_DISTANCE, DEFAULT_MERGING_MAX_DISTANCE ); + settings.put( KEY_MERGING_MIN_IOU, DEFAULT_MERGING_MIN_IOU ); settings.put( KEY_MERGING_FEATURE_PENALTIES, new HashMap<>( DEFAULT_MERGING_FEATURE_PENALTIES ) ); // Others settings.put( KEY_BLOCKING_VALUE, DEFAULT_BLOCKING_VALUE ); @@ -257,20 +267,24 @@ public static final boolean checkSettingsValidity( final Map< String, Object > s if (linking) { ok = ok & checkParameter( settings, KEY_LINKING_MAX_DISTANCE, Double.class, errorHolder ); + ok = ok & checkParameter( settings, KEY_LINKING_MIN_IOU, Double.class, errorHolder ); ok = ok & checkFeatureMap( settings, KEY_LINKING_FEATURE_PENALTIES, errorHolder ); } // Gap-closing ok = ok & checkParameter( settings, KEY_ALLOW_GAP_CLOSING, Boolean.class, errorHolder ); ok = ok & checkParameter( settings, KEY_GAP_CLOSING_MAX_DISTANCE, Double.class, errorHolder ); + ok = ok & checkParameter( settings, KEY_GAP_CLOSING_MIN_IOU, Double.class, errorHolder ); ok = ok & checkParameter( settings, KEY_GAP_CLOSING_MAX_FRAME_GAP, Integer.class, errorHolder ); ok = ok & checkFeatureMap( settings, KEY_GAP_CLOSING_FEATURE_PENALTIES, errorHolder ); // Splitting ok = ok & checkParameter( settings, KEY_ALLOW_TRACK_SPLITTING, Boolean.class, errorHolder ); ok = ok & checkParameter( settings, KEY_SPLITTING_MAX_DISTANCE, Double.class, errorHolder ); + ok = ok & checkParameter( settings, KEY_SPLITTING_MIN_IOU, Double.class, errorHolder ); ok = ok & checkFeatureMap( settings, KEY_SPLITTING_FEATURE_PENALTIES, errorHolder ); // Merging ok = ok & checkParameter( settings, KEY_ALLOW_TRACK_MERGING, Boolean.class, errorHolder ); ok = ok & checkParameter( settings, KEY_MERGING_MAX_DISTANCE, Double.class, errorHolder ); + ok = ok & checkParameter( settings, KEY_MERGING_MIN_IOU, Double.class, errorHolder ); ok = ok & checkFeatureMap( settings, KEY_MERGING_FEATURE_PENALTIES, errorHolder ); // Others ok = ok & checkParameter( settings, KEY_CUTOFF_PERCENTILE, Double.class, errorHolder ); @@ -282,14 +296,18 @@ public static final boolean checkSettingsValidity( final Map< String, Object > s if ( linking ) { mandatoryKeys.add( KEY_LINKING_MAX_DISTANCE ); + mandatoryKeys.add( KEY_LINKING_MIN_IOU ); } mandatoryKeys.add( KEY_ALLOW_GAP_CLOSING ); mandatoryKeys.add( KEY_GAP_CLOSING_MAX_DISTANCE ); + mandatoryKeys.add( KEY_GAP_CLOSING_MIN_IOU ); mandatoryKeys.add( KEY_GAP_CLOSING_MAX_FRAME_GAP ); mandatoryKeys.add( KEY_ALLOW_TRACK_SPLITTING ); mandatoryKeys.add( KEY_SPLITTING_MAX_DISTANCE ); + mandatoryKeys.add( KEY_SPLITTING_MIN_IOU ); mandatoryKeys.add( KEY_ALLOW_TRACK_MERGING ); mandatoryKeys.add( KEY_MERGING_MAX_DISTANCE ); + mandatoryKeys.add( KEY_MERGING_MIN_IOU ); mandatoryKeys.add( KEY_ALTERNATIVE_LINKING_COST_FACTOR ); mandatoryKeys.add( KEY_CUTOFF_PERCENTILE ); mandatoryKeys.add( KEY_BLOCKING_VALUE ); diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPFrameToFrameOverlapTracker.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPFrameToFrameOverlapTracker.java new file mode 100644 index 000000000..e3422caca --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPFrameToFrameOverlapTracker.java @@ -0,0 +1,259 @@ +package fiji.plugin.trackmate.tracking.jaqaman; + +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_ALTERNATIVE_LINKING_COST_FACTOR; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_MIN_IOU; +import static fiji.plugin.trackmate.tracking.jaqaman.LAPUtils.checkFeatureMap; +import static fiji.plugin.trackmate.util.TMUtils.checkMapKeys; +import static fiji.plugin.trackmate.util.TMUtils.checkParameter; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleWeightedGraph; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.tracking.jaqaman.costfunction.CostFunction; +import fiji.plugin.trackmate.tracking.jaqaman.costfunction.OverlapFeaturePenaltyCostFunction; +import fiji.plugin.trackmate.tracking.jaqaman.costfunction.OverlapCostFunction; +import fiji.plugin.trackmate.util.Threads; +import fiji.plugin.trackmate.tracking.jaqaman.costmatrix.JaqamanLinkingCostMatrixCreator; + +public class SparseLAPFrameToFrameOverlapTracker extends SparseLAPFrameToFrameTracker +{ + + /* + * CONSTRUCTOR + */ + + + public SparseLAPFrameToFrameOverlapTracker( final SpotCollection spots, final Map< String, Object > settings ) + { + super(spots, settings); + } + + /* + * METHODS + */ + + @Override + public boolean process() + { + isCanceled = false; + cancelReason = null; + + /* + * Check input now. + */ + + // Check that the objects list itself isn't null + if ( null == spots ) + { + errorMessage = BASE_ERROR_MESSAGE + "The spot collection is null."; + return false; + } + + // Check that the objects list contains inner collections. + if ( spots.keySet().isEmpty() ) + { + errorMessage = BASE_ERROR_MESSAGE + "The spot collection is empty."; + return false; + } + + // Check that at least one inner collection contains an object. + boolean empty = true; + for ( final int frame : spots.keySet() ) + { + if ( spots.getNSpots( frame, true ) > 0 ) + { + empty = false; + break; + } + } + if ( empty ) + { + errorMessage = BASE_ERROR_MESSAGE + "The spot collection is empty."; + return false; + } + // Check parameters + final StringBuilder errorHolder = new StringBuilder(); + if ( !checkSettingsValidity( settings, errorHolder ) ) + { + errorMessage = BASE_ERROR_MESSAGE + errorHolder.toString(); + return false; + } + + /* + * Process. + */ + + final long start = System.currentTimeMillis(); + + // Prepare frame pairs in order, not necessarily separated by 1. + final ArrayList< int[] > framePairs = new ArrayList<>( spots.keySet().size() - 1 ); + final Iterator< Integer > frameIterator = spots.keySet().iterator(); + int frame0 = frameIterator.next(); + int frame1; + while ( frameIterator.hasNext() ) + { // ascending order + frame1 = frameIterator.next(); + framePairs.add( new int[] { frame0, frame1 } ); + frame0 = frame1; + } + + // Prepare cost function + @SuppressWarnings( "unchecked" ) + final Map< String, Double > featurePenalties = ( Map< String, Double > ) settings.get( KEY_LINKING_FEATURE_PENALTIES ); + final CostFunction< Spot, Spot > costFunction = getCostFunction( featurePenalties ); + final Double minIOU = ( Double ) settings.get( KEY_LINKING_MIN_IOU ); + final double costThreshold = 1 - minIOU; + final double alternativeCostFactor = ( Double ) settings.get( KEY_ALTERNATIVE_LINKING_COST_FACTOR ); + + // Instantiate graph + graph = new SimpleWeightedGraph<>( DefaultWeightedEdge.class ); + + // Prepare workers. + final AtomicInteger progress = new AtomicInteger( 0 ); + final AtomicBoolean ok = new AtomicBoolean( true ); + final ExecutorService executors = Threads.newFixedThreadPool( numThreads ); + final List< Future< Void > > futures = new ArrayList<>( framePairs.size() ); + for ( final int[] framePair : framePairs ) + { + final Future< Void > future = executors.submit( new Callable< Void >() + { + + @Override + public Void call() throws Exception + { + if ( !ok.get() || isCanceled() ) + return null; + + // Get frame pairs + final int lFrame0 = framePair[ 0 ]; + final int lFrame1 = framePair[ 1 ]; + + // Get spots - we have to create a list from each + // content. + final List< Spot > sources = new ArrayList<>( spots.getNSpots( lFrame0, true ) ); + for ( final Iterator< Spot > iterator = spots.iterator( lFrame0, true ); iterator.hasNext(); ) + sources.add( iterator.next() ); + + final List< Spot > targets = new ArrayList<>( spots.getNSpots( lFrame1, true ) ); + for ( final Iterator< Spot > iterator = spots.iterator( lFrame1, true ); iterator.hasNext(); ) + targets.add( iterator.next() ); + + if ( sources.isEmpty() || targets.isEmpty() ) + return null; + + /* + * Run the linker. + */ + + final JaqamanLinkingCostMatrixCreator< Spot, Spot > creator = new JaqamanLinkingCostMatrixCreator<>( sources, targets, costFunction, costThreshold, alternativeCostFactor, 1d ); + final JaqamanLinker< Spot, Spot > linker = new JaqamanLinker<>( creator ); + if ( !linker.checkInput() || !linker.process() ) + { + errorMessage = "At frame " + lFrame0 + " to " + lFrame1 + ": " + linker.getErrorMessage(); + ok.set( false ); + return null; + } + + /* + * Update graph. + */ + + synchronized ( graph ) + { + final Map< Spot, Double > costs = linker.getAssignmentCosts(); + final Map< Spot, Spot > assignment = linker.getResult(); + for ( final Spot source : assignment.keySet() ) + { + final double cost = costs.get( source ); + final Spot target = assignment.get( source ); + graph.addVertex( source ); + graph.addVertex( target ); + final DefaultWeightedEdge edge = graph.addEdge( source, target ); + graph.setEdgeWeight( edge, cost ); + } + } + + logger.setProgress( progress.incrementAndGet() / framePairs.size() ); + return null; + } + } ); + futures.add( future ); + } + + logger.setStatus( "Frame to frame linking..." ); + try + { + for ( final Future< ? > future : futures ) + future.get(); + + executors.shutdown(); + } + catch ( InterruptedException | ExecutionException e ) + { + ok.set( false ); + errorMessage = BASE_ERROR_MESSAGE + e.getMessage(); + e.printStackTrace(); + } + logger.setProgress( 1. ); + logger.setStatus( "" ); + + final long end = System.currentTimeMillis(); + processingTime = end - start; + + return ok.get(); + } + + /** + * Creates a suitable cost function. + * + * @param featurePenalties + * feature penalties to base costs on. Can be null. + * @return a new {@link CostFunction} + */ + protected CostFunction< Spot, Spot > getCostFunction( final Map< String, Double > featurePenalties ) + { + if ( null == featurePenalties || featurePenalties.isEmpty() ) + return new OverlapCostFunction(); + + return new OverlapFeaturePenaltyCostFunction( featurePenalties ); + } + + protected boolean checkSettingsValidity( final Map< String, Object > settings, final StringBuilder str ) + { + if ( null == settings ) + { + str.append( "Settings map is null.\n" ); + return false; + } + + boolean ok = true; + // Linking + ok = ok & checkParameter( settings, KEY_LINKING_MIN_IOU, Double.class, str ); + ok = ok & checkFeatureMap( settings, KEY_LINKING_FEATURE_PENALTIES, str ); + // Others + ok = ok & checkParameter( settings, KEY_ALTERNATIVE_LINKING_COST_FACTOR, Double.class, str ); + + // Check keys + final List< String > mandatoryKeys = new ArrayList<>(); + mandatoryKeys.add( KEY_LINKING_MIN_IOU ); + mandatoryKeys.add( KEY_ALTERNATIVE_LINKING_COST_FACTOR ); + final List< String > optionalKeys = new ArrayList<>(); + optionalKeys.add( KEY_LINKING_FEATURE_PENALTIES ); + ok = ok & checkMapKeys( settings, mandatoryKeys, optionalKeys, str ); + + return ok; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPFrameToFrameTracker.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPFrameToFrameTracker.java index 5da1d6b53..779d79a3a 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPFrameToFrameTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPFrameToFrameTracker.java @@ -56,7 +56,7 @@ public class SparseLAPFrameToFrameTracker extends MultiThreadedBenchmarkAlgorithm implements SpotTracker, Cancelable { - private final static String BASE_ERROR_MESSAGE = "[SparseLAPFrameToFrameTracker] "; + protected final static String BASE_ERROR_MESSAGE = "[SparseLAPFrameToFrameTracker] "; protected SimpleWeightedGraph< Spot, DefaultWeightedEdge > graph; @@ -66,9 +66,9 @@ public class SparseLAPFrameToFrameTracker extends MultiThreadedBenchmarkAlgorith protected final Map< String, Object > settings; - private boolean isCanceled; + protected boolean isCanceled; - private String cancelReason; + protected String cancelReason; /* * CONSTRUCTOR diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPOverlapTracker.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPOverlapTracker.java new file mode 100644 index 000000000..09e11c551 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPOverlapTracker.java @@ -0,0 +1,201 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2023 TrackMate developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * . + * #L% + */ +package fiji.plugin.trackmate.tracking.jaqaman; + +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_ALTERNATIVE_LINKING_COST_FACTOR; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_MIN_IOU; + +import java.util.HashMap; +import java.util.Map; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.SimpleWeightedGraph; +import org.scijava.Cancelable; + +import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Logger.SlaveLogger; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.tracking.SpotTracker; +import net.imglib2.algorithm.MultiThreadedBenchmarkAlgorithm; + +public class SparseLAPOverlapTracker extends MultiThreadedBenchmarkAlgorithm implements SpotTracker, Cancelable +{ + private final static String BASE_ERROR_MESSAGE = "[SparseLAPTracker] "; + + private SimpleWeightedGraph< Spot, DefaultWeightedEdge > graph; + + private Logger logger = Logger.VOID_LOGGER; + + private final SpotCollection spots; + + private final Map< String, Object > settings; + + private boolean isCanceled; + + private String cancelReason; + + private Cancelable cancelable; + + /* + * CONSTRUCTOR + */ + + public SparseLAPOverlapTracker( final SpotCollection spots, final Map< String, Object > settings ) + { + this.spots = spots; + this.settings = settings; + } + + /* + * METHODS + */ + + @Override + public SimpleWeightedGraph< Spot, DefaultWeightedEdge > getResult() + { + return graph; + } + + @Override + public boolean checkInput() + { + return true; + } + + @Override + public boolean process() + { + isCanceled = false; + cancelReason = null; + cancelable = null; + + /* + * Check input now. + */ + + // Check that the objects list itself isn't null + if ( null == spots ) + { + errorMessage = BASE_ERROR_MESSAGE + "The spot collection is null."; + return false; + } + + // Check that the objects list contains inner collections. + if ( spots.keySet().isEmpty() ) + { + errorMessage = BASE_ERROR_MESSAGE + "The spot collection is empty."; + return false; + } + + // Check that at least one inner collection contains an object. + boolean empty = true; + for ( final int frame : spots.keySet() ) + { + if ( spots.getNSpots( frame, true ) > 0 ) + { + empty = false; + break; + } + } + if ( empty ) + { + errorMessage = BASE_ERROR_MESSAGE + "The spot collection is empty."; + return false; + } + + /* + * Process: 1. Frame to frame linking... + */ + + final long start = System.currentTimeMillis(); + + // Prepare settings object + final Map< String, Object > ftfSettings = new HashMap<>(); + ftfSettings.put( KEY_LINKING_MIN_IOU, settings.get( KEY_LINKING_MIN_IOU ) ); + ftfSettings.put( KEY_ALTERNATIVE_LINKING_COST_FACTOR, settings.get( KEY_ALTERNATIVE_LINKING_COST_FACTOR ) ); + ftfSettings.put( KEY_LINKING_FEATURE_PENALTIES, settings.get( KEY_LINKING_FEATURE_PENALTIES ) ); + + final SparseLAPFrameToFrameOverlapTracker frameToFrameLinker = new SparseLAPFrameToFrameOverlapTracker( spots, ftfSettings ); + cancelable = frameToFrameLinker; + frameToFrameLinker.setNumThreads( numThreads ); + final SlaveLogger ftfLogger = new SlaveLogger( logger, 0, 0.5 ); + frameToFrameLinker.setLogger( ftfLogger ); + + if ( !frameToFrameLinker.checkInput() || !frameToFrameLinker.process() ) + { + errorMessage = frameToFrameLinker.getErrorMessage(); + return false; + } + + graph = frameToFrameLinker.getResult(); + cancelable = null; + + /* + * 2. Gap-closing, merging and splitting. + */ + final SegmentTracker segmentLinker = new SegmentTracker( graph, settings, logger ); + if ( !segmentLinker.checkInput() || !segmentLinker.process() ) + { + errorMessage = segmentLinker.getErrorMessage(); + return false; + } + // graph = segmentLinker.getResult(); + + logger.setStatus( "" ); + logger.setProgress( 1d ); + final long end = System.currentTimeMillis(); + processingTime = end - start; + + return true; + } + + @Override + public void setLogger( final Logger logger ) + { + this.logger = logger; + } + + // --- org.scijava.Cancelable methods --- + + @Override + public boolean isCanceled() + { + return isCanceled; + } + + @Override + public void cancel( final String reason ) + { + isCanceled = true; + cancelReason = reason; + if ( cancelable != null ) + cancelable.cancel( reason ); + } + + @Override + public String getCancelReason() + { + return cancelReason; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPOverlapTrackerFactory.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPOverlapTrackerFactory.java new file mode 100644 index 000000000..bc13a6792 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/SparseLAPOverlapTrackerFactory.java @@ -0,0 +1,214 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2023 TrackMate developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * . + * #L% + */ +package fiji.plugin.trackmate.tracking.jaqaman; + +import static fiji.plugin.trackmate.io.IOUtils.marshallMap; +import static fiji.plugin.trackmate.io.IOUtils.readDoubleAttribute; +import static fiji.plugin.trackmate.io.IOUtils.unmarshallMap; +import static fiji.plugin.trackmate.io.IOUtils.writeAttribute; +import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_LINKING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.DEFAULT_LINKING_MIN_IOU; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_LINKING_MIN_IOU; +import static fiji.plugin.trackmate.tracking.jaqaman.LAPUtils.XML_ELEMENT_NAME_FEATURE_PENALTIES; +import static fiji.plugin.trackmate.tracking.jaqaman.LAPUtils.XML_ELEMENT_NAME_LINKING; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.ImageIcon; + +import org.jdom2.Element; +import org.scijava.plugin.Plugin; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.gui.components.ConfigurationPanel; +import fiji.plugin.trackmate.gui.components.tracker.LAPOverlapTrackerSettingsPanel; +import fiji.plugin.trackmate.tracking.SpotTracker; +import fiji.plugin.trackmate.tracking.SpotTrackerFactory; + +@Plugin( type = SpotTrackerFactory.class ) +public class SparseLAPOverlapTrackerFactory extends SegmentTrackerFactory +{ + + public static final String THIS_TRACKER_KEY = "SPARSE_LAP_OVERLAP_TRACKER"; + + public static final String THIS_NAME = "LAP Overlap Tracker"; + + public static final String THIS_INFO_TEXT = "" + + "This tracker only differs from LAP Tracker by the fact that it is based on
" + + "Intersection over Union (IoU) instead of distance. " + + ""; + + @Override + public String getInfoText() + { + return THIS_INFO_TEXT; + } + + @Override + public ImageIcon getIcon() + { + return null; + } + + @Override + public String getKey() + { + return THIS_TRACKER_KEY; + } + + @Override + public String getName() + { + return THIS_NAME; + } + + @Override + public SpotTracker create( final SpotCollection spots, final Map< String, Object > settings ) + { + return new SparseLAPOverlapTracker( spots, settings ); + } + + @Override + public SparseLAPOverlapTrackerFactory copy() + { + return new SparseLAPOverlapTrackerFactory(); + } + + @Override + public boolean marshall( final Map< String, Object > settings, final Element element ) + { + boolean ok = true; + final StringBuilder str = new StringBuilder(); + + // Linking + final Element linkingElement = new Element( XML_ELEMENT_NAME_LINKING ); + ok = ok & writeAttribute( settings, linkingElement, KEY_LINKING_MIN_IOU, Double.class, str ); + // feature penalties + @SuppressWarnings( "unchecked" ) + final Map< String, Double > lfpm = ( Map< String, Double > ) settings.get( KEY_LINKING_FEATURE_PENALTIES ); + final Element lfpElement = new Element( XML_ELEMENT_NAME_FEATURE_PENALTIES ); + marshallMap( lfpm, lfpElement ); + linkingElement.addContent( lfpElement ); + element.addContent( linkingElement ); + return ( ok & super.marshall( settings, element ) ); + } + + @Override + public boolean unmarshall( final Element element, final Map< String, Object > settings ) + { + final StringBuilder errorHolder = new StringBuilder(); + boolean ok = unmarshallSegment( element, settings, errorHolder ); // common parameters + + // Linking + final Element linkingElement = element.getChild( XML_ELEMENT_NAME_LINKING ); + if ( null == linkingElement ) + { + errorHolder.append( "Could not found the " + XML_ELEMENT_NAME_LINKING + " element in XML.\n" ); + ok = false; + + } + else + { + + ok = ok & readDoubleAttribute( linkingElement, settings, KEY_LINKING_MIN_IOU, errorHolder ); + // feature penalties + final Map< String, Double > lfpMap = new HashMap<>(); + final Element lfpElement = linkingElement.getChild( XML_ELEMENT_NAME_FEATURE_PENALTIES ); + if ( null != lfpElement ) + { + ok = ok & unmarshallMap( lfpElement, lfpMap, errorHolder ); + } + settings.put( KEY_LINKING_FEATURE_PENALTIES, lfpMap ); + } + + if ( !checkSettingsValidity( settings ) ) + { + ok = false; + errorHolder.append( errorMessage ); // append validity check message + } + + if ( !ok ) + { + errorMessage = errorHolder.toString(); + } + return ok; + + } + + @Override + public Map< String, Object > getDefaultSettings() + { + final Map< String, Object > settings = LAPUtils.getDefaultSegmentSettingsMap(); + // Linking + settings.put( KEY_LINKING_MIN_IOU, DEFAULT_LINKING_MIN_IOU ); + settings.put( KEY_LINKING_FEATURE_PENALTIES, new HashMap<>( DEFAULT_LINKING_FEATURE_PENALTIES ) ); + return settings; + } + + @Override + public boolean checkSettingsValidity( final Map< String, Object > settings ) + { + if ( null == settings ) + { + errorMessage = "Settings map is null.\n"; + return false; + } + + final StringBuilder str = new StringBuilder(); + final boolean ok = LAPUtils.checkSettingsValidity( settings, str, true ); + if ( !ok ) + { + errorMessage = str.toString(); + } + return ok; + } + + @Override + @SuppressWarnings( "unchecked" ) + public String toString( final Map< String, Object > sm ) + { + if ( !checkSettingsValidity( sm ) ) + { return errorMessage; } + + final StringBuilder str = new StringBuilder(); + + str.append( " Linking conditions:\n" ); + str.append( LAPUtils.echoFeaturePenalties( ( Map< String, Double > ) sm.get( KEY_LINKING_FEATURE_PENALTIES ) ) ); + + str.append( super.toString( sm ) ); + return str.toString(); + } + + @Override + public ConfigurationPanel getTrackerConfigurationPanel( final Model model ) + { + final String spaceUnits = model.getSpaceUnits(); + final Collection< String > features = model.getFeatureModel().getSpotFeatures(); + final Map< String, String > featureNames = model.getFeatureModel().getSpotFeatureNames(); + return new LAPOverlapTrackerSettingsPanel( getName(), spaceUnits, features, featureNames ); + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costfunction/OverlapCostFunction.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costfunction/OverlapCostFunction.java new file mode 100644 index 000000000..7c44146e8 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costfunction/OverlapCostFunction.java @@ -0,0 +1,69 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2023 TrackMate developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * . + * #L% + */ +package fiji.plugin.trackmate.tracking.jaqaman.costfunction; + +import math.geom2d.polygon.Polygons2D; +import math.geom2d.polygon.SimplePolygon2D; +import math.geom2d.conic.Circle2D; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotRoi; + +/** + * A cost function that returns cost equal to 1 - (Intersection over Union). + **/ +public class OverlapCostFunction implements CostFunction< Spot, Spot > +{ + private static SimplePolygon2D toPolygon( final Spot spot) + { + final double xc = spot.getDoublePosition( 0 ); + final double yc = spot.getDoublePosition( 1 ); + final SpotRoi roi = spot.getRoi(); + final SimplePolygon2D poly; + if ( roi == null ) + { + final double radius = spot.getFeature( Spot.RADIUS ).doubleValue(); + poly = new SimplePolygon2D( new Circle2D( xc, yc, radius ).asPolyline( 32 ) ); + } + else + { + final double[] xcoords = roi.toPolygonX( 1., 0., xc, 1. ); + final double[] ycoords = roi.toPolygonY( 1., 0., yc, 1. ); + poly = new SimplePolygon2D( xcoords, ycoords ); + } + return poly; + } + + @Override + public double linkingCost( final Spot source, final Spot target ) + { + final SimplePolygon2D targetPoly = toPolygon(target); + final SimplePolygon2D sourcePoly = toPolygon(source); + + final double intersection = Math.abs( Polygons2D.intersection( targetPoly, sourcePoly ).area() ); + final double union = Math.abs( sourcePoly.area() ) + Math.abs( targetPoly.area() ) - intersection; + + return 1 - intersection / union; + + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costfunction/OverlapFeaturePenaltyCostFunction.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costfunction/OverlapFeaturePenaltyCostFunction.java new file mode 100644 index 000000000..90a07e9c3 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costfunction/OverlapFeaturePenaltyCostFunction.java @@ -0,0 +1,110 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2023 TrackMate developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * . + * #L% + */ +package fiji.plugin.trackmate.tracking.jaqaman.costfunction; + +import java.util.Map; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotRoi; +import math.geom2d.conic.Circle2D; +import math.geom2d.polygon.Polygons2D; +import math.geom2d.polygon.SimplePolygon2D; + +/** + * A cost function that tempers an overlap cost by difference in feature + * values. + *

+ * This cost is calculated as follow: + *

    + *
  • The overlap cost between the two spots O is calculated + *
  • For each feature in the map, a penalty p is calculated as + * p = 3 × α × |f1-f2| / (f1+f2), where α is the + * factor associated to the feature in the map. This expression is such that: + *
      + *
    • there is no penalty if the 2 feature values f1 and + * f2 are the same; + *
    • that, with a factor of 1, the penalty if 1 is one value is the double of + * the other; + *
    • the penalty is 2 if one is 5 times the other one. + *
    + *
  • All penalties are summed, to form P = (1 + ∑ p ) + *
  • The cost is set to the square of the product: C = ( O × P )² + *
+ * + * + */ +public class OverlapFeaturePenaltyCostFunction implements CostFunction< Spot, Spot > +{ + + private final Map< String, Double > featurePenalties; + + public OverlapFeaturePenaltyCostFunction( final Map< String, Double > featurePenalties ) + { + this.featurePenalties = featurePenalties; + } + + private static SimplePolygon2D toPolygon( final Spot spot) + { + final double xc = spot.getDoublePosition( 0 ); + final double yc = spot.getDoublePosition( 1 ); + final SpotRoi roi = spot.getRoi(); + final SimplePolygon2D poly; + if ( roi == null ) + { + final double radius = spot.getFeature( Spot.RADIUS ).doubleValue(); + poly = new SimplePolygon2D( new Circle2D( xc, yc, radius ).asPolyline( 32 ) ); + } + else + { + final double[] xcoords = roi.toPolygonX( 1., 0., xc, 1. ); + final double[] ycoords = roi.toPolygonY( 1., 0., yc, 1. ); + poly = new SimplePolygon2D( xcoords, ycoords ); + } + return poly; + } + + @Override + public double linkingCost( final Spot source, final Spot target ) + { + final SimplePolygon2D targetPoly = toPolygon(target); + final SimplePolygon2D sourcePoly = toPolygon(source); + final double intersection = Math.abs( Polygons2D.intersection( targetPoly, sourcePoly ).area() ); + final double union = Math.abs( sourcePoly.area() ) + Math.abs( targetPoly.area() ) - intersection; + + final double d1 = 1 - intersection / union; + final double d2 = ( d1 == 0 ) ? Double.MIN_NORMAL : d1; + + double penalty = 1; + for ( final String feature : featurePenalties.keySet() ) + { + final double ndiff = source.normalizeDiffTo( target, feature ); + if ( Double.isNaN( ndiff ) ) + { + continue; + } + final double factor = featurePenalties.get( feature ); + penalty += factor * 1.5 * ndiff; + } + + return d2 * penalty * penalty; + } +}