From dbdc3d8dbee112862c580ad50cf485fc288aa3e1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 Apr 2024 18:00:42 +0200 Subject: [PATCH 01/10] Panel UI to select a metrics. --- .../ui/components/MetricsChooserPanel.java | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java new file mode 100644 index 0000000..f85473b --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java @@ -0,0 +1,223 @@ +package fiji.plugin.trackmate.helper.ui.components; + +import static fiji.plugin.trackmate.gui.Fonts.FONT; +import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ItemListener; +import java.awt.event.MouseAdapter; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.DecimalFormat; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JFormattedTextField; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +import org.scijava.prefs.PrefService; + +import fiji.plugin.trackmate.helper.ctc.CTCTrackingMetricsType; +import fiji.plugin.trackmate.helper.spt.SPTTrackingMetricsType; +import fiji.plugin.trackmate.util.TMUtils; +import net.imagej.ImageJ; + +public class MetricsChooserPanel extends JPanel +{ + + private static final long serialVersionUID = 1L; + + final JRadioButton rdbtnCTC; + + final JFormattedTextField ftfMaxDist; + + final JLabel lblUnits; + + public MetricsChooserPanel() + { + final GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.columnWeights = new double[] { 1.0, 0.0 }; + setLayout( gridBagLayout ); + + final JLabel lblChooseMetrics = new JLabel( "Metrics to use:" ); + lblChooseMetrics.setFont( FONT ); + final GridBagConstraints gbcLblChooseMetrics = new GridBagConstraints(); + gbcLblChooseMetrics.gridwidth = 2; + gbcLblChooseMetrics.anchor = GridBagConstraints.WEST; + gbcLblChooseMetrics.insets = new Insets( 0, 0, 5, 0 ); + gbcLblChooseMetrics.gridx = 0; + gbcLblChooseMetrics.gridy = 0; + add( lblChooseMetrics, gbcLblChooseMetrics ); + + rdbtnCTC = new JRadioButton( "Cell-Tracking challenge (CTC)" ); + rdbtnCTC.setFont( SMALL_FONT ); + final GridBagConstraints gbcRdbtnCTC = new GridBagConstraints(); + gbcRdbtnCTC.gridwidth = 2; + gbcRdbtnCTC.anchor = GridBagConstraints.WEST; + gbcRdbtnCTC.insets = new Insets( 0, 0, 5, 0 ); + gbcRdbtnCTC.gridx = 0; + gbcRdbtnCTC.gridy = 1; + add( rdbtnCTC, gbcRdbtnCTC ); + + final JRadioButton rdbtnSPT = new JRadioButton( "Single-Particle Tracking challenge (SPT)" ); + rdbtnSPT.setFont( SMALL_FONT ); + final GridBagConstraints gbcRdbtnSPT = new GridBagConstraints(); + gbcRdbtnSPT.gridwidth = 2; + gbcRdbtnSPT.insets = new Insets( 0, 0, 5, 0 ); + gbcRdbtnSPT.anchor = GridBagConstraints.WEST; + gbcRdbtnSPT.gridx = 0; + gbcRdbtnSPT.gridy = 2; + add( rdbtnSPT, gbcRdbtnSPT ); + + final JLabel lblMetricsDescription = new JLabel(); + lblMetricsDescription.setFont( SMALL_FONT.deriveFont( Font.ITALIC ) ); + final GridBagConstraints gbcLblMetricsDescription = new GridBagConstraints(); + gbcLblMetricsDescription.anchor = GridBagConstraints.SOUTH; + gbcLblMetricsDescription.fill = GridBagConstraints.HORIZONTAL; + gbcLblMetricsDescription.gridwidth = 2; + gbcLblMetricsDescription.insets = new Insets( 0, 0, 5, 0 ); + gbcLblMetricsDescription.gridx = 0; + gbcLblMetricsDescription.gridy = 3; + add( lblMetricsDescription, gbcLblMetricsDescription ); + + final JLabel lblUrl = new JLabel( " " ); + lblUrl.setFont( SMALL_FONT ); + lblUrl.setForeground( Color.BLUE ); + final GridBagConstraints gbcLblUrl = new GridBagConstraints(); + gbcLblUrl.anchor = GridBagConstraints.NORTH; + gbcLblUrl.gridwidth = 2; + gbcLblUrl.fill = GridBagConstraints.HORIZONTAL; + gbcLblUrl.insets = new Insets( 0, 0, 5, 0 ); + gbcLblUrl.gridx = 0; + gbcLblUrl.gridy = 4; + add( lblUrl, gbcLblUrl ); + + final JPanel panelConfigParams = new JPanel( new BorderLayout() ); + final GridBagConstraints gbcPanelConfigParams = new GridBagConstraints(); + gbcPanelConfigParams.anchor = GridBagConstraints.NORTH; + gbcPanelConfigParams.gridwidth = 2; + gbcPanelConfigParams.insets = new Insets( 0, 0, 5, 0 ); + gbcPanelConfigParams.fill = GridBagConstraints.BOTH; + gbcPanelConfigParams.gridx = 0; + gbcPanelConfigParams.gridy = 5; + add( panelConfigParams, gbcPanelConfigParams ); + + /* + * Config panel. + */ + + final JPanel ctcParamPanel = new JPanel(); // Empty + final JPanel sptParamPanel = new JPanel(); + sptParamPanel.setLayout( new BoxLayout( sptParamPanel, BoxLayout.LINE_AXIS ) ); + final JLabel lblMaxDist = new JLabel( "Max distance for pairing:" ); + lblMaxDist.setFont( SMALL_FONT ); + sptParamPanel.add( lblMaxDist ); + sptParamPanel.add( Box.createHorizontalStrut( 5 ) ); + ftfMaxDist = new JFormattedTextField( new DecimalFormat( "0.00" ) ); + ftfMaxDist.setColumns( 9 ); + ftfMaxDist.setFont( SMALL_FONT ); + ftfMaxDist.setHorizontalAlignment( JTextField.RIGHT ); + ftfMaxDist.setMaximumSize( new Dimension( 100, 40 ) ); + sptParamPanel.add( ftfMaxDist ); + sptParamPanel.add( Box.createHorizontalStrut( 5 ) ); + lblUnits = new JLabel( "image units" ); + lblUnits.setFont( SMALL_FONT ); + sptParamPanel.add( lblUnits ); + sptParamPanel.add( Box.createHorizontalGlue() ); + + /* + * Listeners & co. + */ + + final PrefService prefService = TMUtils.getContext().getService( PrefService.class ); + + final ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add( rdbtnSPT ); + buttonGroup.add( rdbtnCTC ); + + final ItemListener changeMetrics = e -> { + // Help and selection. + final String doc = rdbtnCTC.isSelected() ? DOC_CTC : DOC_SPT; + lblMetricsDescription.setText( doc ); + final String url = rdbtnCTC.isSelected() ? URL_CTC : URL_SPT; + lblUrl.setText( url ); + + // Config panel. + final JPanel paramPanel = rdbtnCTC.isSelected() ? ctcParamPanel : sptParamPanel; + panelConfigParams.removeAll(); + panelConfigParams.add( paramPanel, BorderLayout.CENTER ); + + prefService.put( getClass(), METRICS_TYPE_KEY, rdbtnCTC.isSelected() ); + }; + + rdbtnCTC.addItemListener( changeMetrics ); + lblUrl.addMouseListener( new MouseAdapter() + { + @Override + public void mouseClicked( final java.awt.event.MouseEvent e ) + { + try + { + final String link = rdbtnCTC.isSelected() ? LINK_CTC : LINK_SPT; + Desktop.getDesktop().browse( new URI( link ) ); + } + catch ( URISyntaxException | IOException ex ) + { + ex.printStackTrace(); + } + } + } ); + fiji.plugin.trackmate.gui.GuiUtils.selectAllOnFocus( ftfMaxDist ); + + final boolean ctcSelected = prefService.getBoolean( getClass(), METRICS_TYPE_KEY, true ); + rdbtnCTC.setSelected( ctcSelected ); + rdbtnSPT.setSelected( !ctcSelected ); + changeMetrics.itemStateChanged( null ); + + ftfMaxDist.setValue( Double.valueOf( 1.0 ) ); + } + + public static final void main( final String... args ) + { + final ImageJ ij = new ImageJ(); + ij.launch( args ); + final JFrame frame = new JFrame(); + frame.add( new MetricsChooserPanel() ); + frame.pack(); + frame.setLocationRelativeTo( null ); + frame.setVisible( true ); + } + + private static final String METRICS_TYPE_KEY = "METRICS_TYPE"; + + private static final String DOC_CTC = CTCTrackingMetricsType.INFO; + + private static final String DOC_SPT = SPTTrackingMetricsType.INFO; + + protected static final String LINK_CTC = CTCTrackingMetricsType.URL; + + protected static final String LINK_SPT = SPTTrackingMetricsType.URL; + + private static final String URL_CTC = "Ulman, Maška, et al. 2017"; + + private static final String URL_SPT = "Chenouard, Smal, de Chaumont, Maška, et al. 2014"; + + +} From f76aa361ddba562ef8bf3711eca296670e64479b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 Apr 2024 18:01:02 +0200 Subject: [PATCH 02/10] WIP: A panel UI for the plugin that computes metrics values. --- .../helper/ui/MetricsLauncherPanel.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java new file mode 100644 index 0000000..7181947 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java @@ -0,0 +1,91 @@ +package fiji.plugin.trackmate.helper.ui; + +import static fiji.plugin.trackmate.gui.Fonts.BIG_FONT; +import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; + +import org.scijava.util.VersionUtils; + +import fiji.plugin.trackmate.gui.Icons; +import fiji.plugin.trackmate.helper.ui.components.MetricsChooserPanel; +import net.imagej.ImageJ; + +public class MetricsLauncherPanel extends JPanel +{ + + private static final long serialVersionUID = 1L; + + public MetricsLauncherPanel() + { + setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); + + final GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.rowHeights = new int[] { 0, 0, 15, 0 }; + gridBagLayout.columnWeights = new double[] { 1.0, 0.0 }; + setLayout( gridBagLayout ); + + final Image im = Icons.TRACKMATE_ICON.getImage(); + final Image newimg = im.getScaledInstance( 32, 32, java.awt.Image.SCALE_SMOOTH ); + final ImageIcon icon = new ImageIcon( newimg ); + + final JLabel lblTitle = new JLabel( "TrackMate tracking metrics", icon, JLabel.LEADING ); + lblTitle.setFont( BIG_FONT ); + final GridBagConstraints gbcLblTitle = new GridBagConstraints(); + gbcLblTitle.gridwidth = 2; + gbcLblTitle.insets = new Insets( 0, 0, 5, 0 ); + gbcLblTitle.fill = GridBagConstraints.HORIZONTAL; + gbcLblTitle.gridx = 0; + gbcLblTitle.gridy = 0; + add( lblTitle, gbcLblTitle ); + + final JLabel lblVersion = new JLabel( "v" + VersionUtils.getVersion( getClass() ) ); + lblVersion.setFont( SMALL_FONT ); + final GridBagConstraints gbcLblVersion = new GridBagConstraints(); + gbcLblVersion.anchor = GridBagConstraints.WEST; + gbcLblVersion.gridwidth = 2; + gbcLblVersion.insets = new Insets( 0, 0, 5, 0 ); + gbcLblVersion.gridx = 0; + gbcLblVersion.gridy = 1; + add( lblVersion, gbcLblVersion ); + + final GridBagConstraints gbcSeparator = new GridBagConstraints(); + gbcSeparator.gridwidth = 2; + gbcSeparator.insets = new Insets( 0, 0, 5, 0 ); + gbcSeparator.fill = GridBagConstraints.BOTH; + gbcSeparator.gridx = 0; + gbcSeparator.gridy = 2; + add( new JSeparator(), gbcSeparator ); + + final MetricsChooserPanel metricsChooserPanel = new MetricsChooserPanel(); + final GridBagConstraints gbcMetricsPanel = new GridBagConstraints(); + gbcMetricsPanel.gridwidth = 2; + gbcMetricsPanel.insets = new Insets( 0, 0, 5, 0 ); + gbcMetricsPanel.fill = GridBagConstraints.BOTH; + gbcMetricsPanel.gridx = 0; + gbcMetricsPanel.gridy = 3; + add( metricsChooserPanel, gbcMetricsPanel ); + } + + public static final void main( final String... args ) + { + final ImageJ ij = new ImageJ(); + ij.launch( args ); + final JFrame frame = new JFrame(); + frame.getContentPane().add( new MetricsLauncherPanel() ); + frame.setSize( 400, 600 ); + frame.setLocationRelativeTo( null ); + frame.setVisible( true ); + } + +} From ef5a1fb4867a0700cf92eaec494069be7116f65d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 3 Apr 2024 17:05:13 +0200 Subject: [PATCH 03/10] Use the metric chooser panel also in the helper launcher GUI. --- .../helper/ui/HelperLauncherController.java | 4 +- .../helper/ui/HelperLauncherPanel.java | 237 +++--------------- .../ui/components/MetricsChooserPanel.java | 33 ++- 3 files changed, 71 insertions(+), 203 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherController.java b/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherController.java index e41236a..1ff316a 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherController.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherController.java @@ -53,7 +53,7 @@ public HelperLauncherController() gui.btnOK.addActionListener( e -> { final ImagePlus imp; final boolean impOpen = WindowManager.getImageCount() > 0; - final boolean ctcSelected = gui.rdbtnCTC.isSelected(); + final boolean ctcSelected = gui.isCTCSelected(); final String gtPath = gui.tfGTPath.getText(); if ( impOpen ) @@ -81,7 +81,7 @@ public HelperLauncherController() frame.dispose(); final TrackingMetricsType type = ctcSelected ? new CTCTrackingMetricsType() - : new SPTTrackingMetricsType( ( ( Number ) gui.ftfMaxDist.getValue() ).doubleValue() ); + : new SPTTrackingMetricsType( ( gui.getSPTMaxPairingDistance() ) ); final File modelFile = ParameterSweepModelIO.makeSettingsFileForGTPath( gtPath ); if ( !modelFile.exists() ) diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherPanel.java index ff52c26..f0ce9f8 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherPanel.java @@ -25,11 +25,6 @@ import static fiji.plugin.trackmate.gui.Fonts.FONT; import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Desktop; -import java.awt.Dimension; -import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Image; @@ -39,28 +34,17 @@ import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDropEvent; import java.awt.event.FocusAdapter; -import java.awt.event.ItemListener; -import java.awt.event.MouseAdapter; import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.text.DecimalFormat; import java.util.List; import java.util.stream.Collectors; import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; import javax.swing.DefaultComboBoxModel; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; -import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JRadioButton; import javax.swing.JSeparator; import javax.swing.JTextField; @@ -68,8 +52,7 @@ import org.scijava.util.VersionUtils; import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.helper.ctc.CTCTrackingMetricsType; -import fiji.plugin.trackmate.helper.spt.SPTTrackingMetricsType; +import fiji.plugin.trackmate.helper.ui.components.MetricsChooserPanel; import fiji.plugin.trackmate.util.FileChooser; import fiji.plugin.trackmate.util.FileChooser.DialogType; import fiji.plugin.trackmate.util.FileChooser.SelectionMode; @@ -90,11 +73,9 @@ public class HelperLauncherPanel extends JPanel final JButton btnCancel; - final JRadioButton rdbtnCTC; - final JComboBox< String > cmbboxImp; - final JFormattedTextField ftfMaxDist; + private MetricsChooserPanel metricsChooserPanel; public HelperLauncherPanel() { @@ -102,9 +83,9 @@ public HelperLauncherPanel() final GridBagLayout gridBagLayout = new GridBagLayout(); gridBagLayout.columnWidths = new int[] { 0, 0, 0 }; - gridBagLayout.rowHeights = new int[] { 0, 0, 24, 0, 0, 0, 100, 0, 13, 31, 24, 0, 0, 0, 0, 24, 0, 0, 0, 0 }; + gridBagLayout.rowHeights = new int[] { 0, 0, 24, 31, 24, 0, 0, 0, 0, 24, 0, 0, 0, 0 }; gridBagLayout.columnWeights = new double[] { 1.0, 0.0, Double.MIN_VALUE }; - gridBagLayout.rowWeights = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE }; + gridBagLayout.rowWeights = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE }; setLayout( gridBagLayout ); final Image im = Icons.TRACKMATE_ICON.getImage(); @@ -139,83 +120,21 @@ public HelperLauncherPanel() gbcSeparator.gridy = 2; add( new JSeparator(), gbcSeparator ); - final JLabel lblChooseMetrics = new JLabel( "Metrics to use:" ); - lblChooseMetrics.setFont( FONT ); - final GridBagConstraints gbcLblChooseMetrics = new GridBagConstraints(); - gbcLblChooseMetrics.gridwidth = 2; - gbcLblChooseMetrics.anchor = GridBagConstraints.WEST; - gbcLblChooseMetrics.insets = new Insets( 0, 0, 5, 0 ); - gbcLblChooseMetrics.gridx = 0; - gbcLblChooseMetrics.gridy = 3; - add( lblChooseMetrics, gbcLblChooseMetrics ); - - rdbtnCTC = new JRadioButton( "Cell-Tracking challenge (CTC)" ); - rdbtnCTC.setFont( SMALL_FONT ); - final GridBagConstraints gbcRdbtnCTC = new GridBagConstraints(); - gbcRdbtnCTC.gridwidth = 2; - gbcRdbtnCTC.anchor = GridBagConstraints.WEST; - gbcRdbtnCTC.insets = new Insets( 0, 0, 5, 0 ); - gbcRdbtnCTC.gridx = 0; - gbcRdbtnCTC.gridy = 4; - add( rdbtnCTC, gbcRdbtnCTC ); - - final JRadioButton rdbtnSPT = new JRadioButton( "Single-Particle Tracking challenge (SPT)" ); - rdbtnSPT.setFont( SMALL_FONT ); - final GridBagConstraints gbcRdbtnSPT = new GridBagConstraints(); - gbcRdbtnSPT.gridwidth = 2; - gbcRdbtnSPT.insets = new Insets( 0, 0, 5, 0 ); - gbcRdbtnSPT.anchor = GridBagConstraints.WEST; - gbcRdbtnSPT.gridx = 0; - gbcRdbtnSPT.gridy = 5; - add( rdbtnSPT, gbcRdbtnSPT ); - - final JLabel lblMetricsDescription = new JLabel(); - lblMetricsDescription.setFont( SMALL_FONT.deriveFont( Font.ITALIC ) ); - final GridBagConstraints gbcLblMetricsDescription = new GridBagConstraints(); - gbcLblMetricsDescription.anchor = GridBagConstraints.SOUTH; - gbcLblMetricsDescription.fill = GridBagConstraints.HORIZONTAL; - gbcLblMetricsDescription.gridwidth = 2; - gbcLblMetricsDescription.insets = new Insets( 0, 0, 5, 0 ); - gbcLblMetricsDescription.gridx = 0; - gbcLblMetricsDescription.gridy = 6; - add( lblMetricsDescription, gbcLblMetricsDescription ); - - final JLabel lblUrl = new JLabel( " " ); - lblUrl.setFont( SMALL_FONT ); - lblUrl.setForeground( Color.BLUE ); - final GridBagConstraints gbcLblUrl = new GridBagConstraints(); - gbcLblUrl.anchor = GridBagConstraints.NORTH; - gbcLblUrl.gridwidth = 2; - gbcLblUrl.fill = GridBagConstraints.HORIZONTAL; - gbcLblUrl.insets = new Insets( 0, 0, 5, 0 ); - gbcLblUrl.gridx = 0; - gbcLblUrl.gridy = 7; - add( lblUrl, gbcLblUrl ); - - final GridBagConstraints gbcSeparator2 = new GridBagConstraints(); - gbcSeparator2.fill = GridBagConstraints.BOTH; - gbcSeparator2.gridwidth = 2; - gbcSeparator2.insets = new Insets( 0, 0, 5, 5 ); - gbcSeparator2.gridx = 0; - gbcSeparator2.gridy = 8; - add( new JSeparator(), gbcSeparator2 ); - - final JPanel panelConfigParams = new JPanel( new BorderLayout() ); - final GridBagConstraints gbcPanelConfigParams = new GridBagConstraints(); - gbcPanelConfigParams.anchor = GridBagConstraints.NORTH; - gbcPanelConfigParams.gridwidth = 2; - gbcPanelConfigParams.insets = new Insets( 0, 0, 5, 0 ); - gbcPanelConfigParams.fill = GridBagConstraints.BOTH; - gbcPanelConfigParams.gridx = 0; - gbcPanelConfigParams.gridy = 9; - add( panelConfigParams, gbcPanelConfigParams ); + this.metricsChooserPanel = new MetricsChooserPanel(); + final GridBagConstraints gbcMetricsPanel = new GridBagConstraints(); + gbcMetricsPanel.gridwidth = 2; + gbcMetricsPanel.insets = new Insets( 0, 0, 5, 0 ); + gbcMetricsPanel.fill = GridBagConstraints.BOTH; + gbcMetricsPanel.gridx = 0; + gbcMetricsPanel.gridy = 3; + add( metricsChooserPanel, gbcMetricsPanel ); final GridBagConstraints gbcSeparator1 = new GridBagConstraints(); gbcSeparator1.gridwidth = 2; gbcSeparator1.insets = new Insets( 0, 0, 5, 0 ); gbcSeparator1.fill = GridBagConstraints.BOTH; gbcSeparator1.gridx = 0; - gbcSeparator1.gridy = 10; + gbcSeparator1.gridy = 4; add( new JSeparator(), gbcSeparator1 ); final JLabel lblPleaseSelectImage = new JLabel( "Please select an image to run the tracking on." ); @@ -225,7 +144,7 @@ public HelperLauncherPanel() gbcLblPleaseSelectImage.insets = new Insets( 0, 0, 5, 0 ); gbcLblPleaseSelectImage.anchor = GridBagConstraints.WEST; gbcLblPleaseSelectImage.gridx = 0; - gbcLblPleaseSelectImage.gridy = 11; + gbcLblPleaseSelectImage.gridy = 5; add( lblPleaseSelectImage, gbcLblPleaseSelectImage ); final JLabel lblImagePath = new JLabel( "Path to the source image:" ); @@ -235,7 +154,7 @@ public HelperLauncherPanel() gbcLblImagePath.insets = new Insets( 0, 0, 5, 0 ); gbcLblImagePath.anchor = GridBagConstraints.WEST; gbcLblImagePath.gridx = 0; - gbcLblImagePath.gridy = 12; + gbcLblImagePath.gridy = 6; add( lblImagePath, gbcLblImagePath ); tfImagePath = new JTextField(); @@ -244,7 +163,7 @@ public HelperLauncherPanel() gbcTfImagePath.insets = new Insets( 0, 0, 5, 5 ); gbcTfImagePath.fill = GridBagConstraints.HORIZONTAL; gbcTfImagePath.gridx = 0; - gbcTfImagePath.gridy = 13; + gbcTfImagePath.gridy = 7; add( tfImagePath, gbcTfImagePath ); tfImagePath.setColumns( 10 ); @@ -253,7 +172,7 @@ public HelperLauncherPanel() final GridBagConstraints gbcBtnBrowseImage = new GridBagConstraints(); gbcBtnBrowseImage.insets = new Insets( 0, 0, 5, 0 ); gbcBtnBrowseImage.gridx = 1; - gbcBtnBrowseImage.gridy = 13; + gbcBtnBrowseImage.gridy = 7; add( btnBrowseImage, gbcBtnBrowseImage ); cmbboxImp = new JComboBox<>(); @@ -263,7 +182,7 @@ public HelperLauncherPanel() gbcCmbboxImp.gridwidth = 2; gbcCmbboxImp.fill = GridBagConstraints.HORIZONTAL; gbcCmbboxImp.gridx = 0; - gbcCmbboxImp.gridy = 14; + gbcCmbboxImp.gridy = 8; add( cmbboxImp, gbcCmbboxImp ); final GridBagConstraints gbcSeparator3 = new GridBagConstraints(); @@ -271,7 +190,7 @@ public HelperLauncherPanel() gbcSeparator3.gridwidth = 2; gbcSeparator3.fill = GridBagConstraints.BOTH; gbcSeparator3.gridx = 0; - gbcSeparator3.gridy = 15; + gbcSeparator3.gridy = 9; add( new JSeparator(), gbcSeparator3 ); final JLabel lblBrowseGroundTruth = new JLabel( "Please browse to the ground truth file or folder:" ); @@ -281,7 +200,7 @@ public HelperLauncherPanel() gbcLblBrowseGroundTruth.anchor = GridBagConstraints.WEST; gbcLblBrowseGroundTruth.gridwidth = 2; gbcLblBrowseGroundTruth.gridx = 0; - gbcLblBrowseGroundTruth.gridy = 16; + gbcLblBrowseGroundTruth.gridy = 10; add( lblBrowseGroundTruth, gbcLblBrowseGroundTruth ); tfGTPath = new JTextField(); @@ -290,7 +209,7 @@ public HelperLauncherPanel() gbcTfGTPath.insets = new Insets( 0, 0, 5, 5 ); gbcTfGTPath.fill = GridBagConstraints.HORIZONTAL; gbcTfGTPath.gridx = 0; - gbcTfGTPath.gridy = 17; + gbcTfGTPath.gridy = 11; add( tfGTPath, gbcTfGTPath ); tfGTPath.setColumns( 10 ); @@ -299,7 +218,7 @@ public HelperLauncherPanel() final GridBagConstraints gbcBtnBrowseGT = new GridBagConstraints(); gbcBtnBrowseGT.insets = new Insets( 0, 0, 5, 0 ); gbcBtnBrowseGT.gridx = 1; - gbcBtnBrowseGT.gridy = 17; + gbcBtnBrowseGT.gridy = 11; add( btnBrowseGT, gbcBtnBrowseGT ); final JPanel panelButtons = new JPanel(); @@ -307,7 +226,7 @@ public HelperLauncherPanel() gbcPanelButtons.anchor = GridBagConstraints.SOUTHEAST; gbcPanelButtons.gridwidth = 2; gbcPanelButtons.gridx = 0; - gbcPanelButtons.gridy = 18; + gbcPanelButtons.gridy = 12; add( panelButtons, gbcPanelButtons ); btnCancel = new JButton( "Cancel" ); @@ -342,83 +261,12 @@ public HelperLauncherPanel() tfImagePath.setVisible( !impOpen ); btnBrowseImage.setVisible( !impOpen ); - /* - * Config panel. - */ - - final JPanel ctcParamPanel = new JPanel(); // Empty - final JPanel sptParamPanel = new JPanel(); - sptParamPanel.setLayout( new BoxLayout( sptParamPanel, BoxLayout.LINE_AXIS ) ); - final JLabel lblMaxDist = new JLabel( "Max distance for pairing:" ); - lblMaxDist.setFont( SMALL_FONT ); - sptParamPanel.add( lblMaxDist ); - sptParamPanel.add( Box.createHorizontalStrut( 5 ) ); - ftfMaxDist = new JFormattedTextField( new DecimalFormat( "0.00" ) ); - ftfMaxDist.setColumns( 9 ); - ftfMaxDist.setFont( SMALL_FONT ); - ftfMaxDist.setHorizontalAlignment( JTextField.RIGHT ); - ftfMaxDist.setMaximumSize( new Dimension( 100, 40 ) ); - sptParamPanel.add( ftfMaxDist ); - sptParamPanel.add( Box.createHorizontalStrut( 5 ) ); - final JLabel lblUnits = new JLabel( "image units" ); - lblUnits.setFont( SMALL_FONT ); - sptParamPanel.add( lblUnits ); - sptParamPanel.add( Box.createHorizontalGlue() ); - /* * Listeners & co. */ - final ButtonGroup buttonGroup = new ButtonGroup(); - buttonGroup.add( rdbtnSPT ); - buttonGroup.add( rdbtnCTC ); - - final ItemListener changeMetrics = e -> { - // Help and selection. - final String doc = rdbtnCTC.isSelected() ? DOC_CTC : DOC_SPT; - lblMetricsDescription.setText( doc ); - final String url = rdbtnCTC.isSelected() ? URL_CTC : URL_SPT; - lblUrl.setText( url ); - - // Config panel. - String units = "image units"; - if ( cmbboxImp.isVisible() ) - { - final String imName = ( String ) cmbboxImp.getSelectedItem(); - final ImagePlus imp = WindowManager.getImage( imName ); - if ( imp != null ) - units = imp.getCalibration().getUnits(); - } - lblUnits.setText( units ); - - final JPanel paramPanel = rdbtnCTC.isSelected() ? ctcParamPanel : sptParamPanel; - panelConfigParams.removeAll(); - panelConfigParams.add( paramPanel, BorderLayout.CENTER ); - - prefService.put( getClass(), METRICS_TYPE_KEY, rdbtnCTC.isSelected() ); - }; - - rdbtnCTC.addItemListener( changeMetrics ); - lblUrl.addMouseListener( new MouseAdapter() - { - @Override - public void mouseClicked( final java.awt.event.MouseEvent e ) - { - try - { - final String link = rdbtnCTC.isSelected() ? LINK_CTC : LINK_SPT; - Desktop.getDesktop().browse( new URI( link ) ); - } - catch ( URISyntaxException | IOException ex ) - { - ex.printStackTrace(); - } - } - } ); - fiji.plugin.trackmate.gui.GuiUtils.selectAllOnFocus( tfImagePath ); fiji.plugin.trackmate.gui.GuiUtils.selectAllOnFocus( tfGTPath ); - fiji.plugin.trackmate.gui.GuiUtils.selectAllOnFocus( ftfMaxDist ); final Runnable storeGtPath = () -> prefService.put( getClass(), GT_PATH_KEY, tfGTPath.getText() ); final Runnable storeImagePath = () -> prefService.put( getClass(), IMAGE_PATH_KEY, tfImagePath.getText() ); @@ -456,10 +304,10 @@ public void focusLost( final java.awt.event.FocusEvent e ) } ); btnBrowseGT.addActionListener( e -> { - final String dialogTitle = rdbtnCTC.isSelected() + final String dialogTitle = metricsChooserPanel.isCTCSelected() ? "Select a CTC ground-ruth folder." : "Select a SPT ground-truth XML file."; - final SelectionMode selectionMode = rdbtnCTC.isSelected() + final SelectionMode selectionMode = metricsChooserPanel.isCTCSelected() ? SelectionMode.DIRECTORIES_ONLY : SelectionMode.FILES_ONLY; final File file = FileChooser.chooseFile( this, tfGTPath.getText(), null, dialogTitle, DialogType.LOAD, selectionMode ); @@ -482,13 +330,17 @@ public void focusLost( final java.awt.event.FocusEvent e ) final String lastUsedGtPath = prefService.get( getClass(), GT_PATH_KEY, System.getProperty( "user.home" ) ); tfGTPath.setText( lastUsedGtPath ); + } - final boolean ctcSelected = prefService.getBoolean( getClass(), METRICS_TYPE_KEY, true ); - rdbtnCTC.setSelected( ctcSelected ); - rdbtnSPT.setSelected( !ctcSelected ); - changeMetrics.itemStateChanged( null ); - - ftfMaxDist.setValue( Double.valueOf( 1.0 ) ); + /** + * Returns true if the CTC metrics are selected. If + * false, the SPT metrics are selected. + * + * @return true if the CTC metrics are selected + */ + public boolean isCTCSelected() + { + return metricsChooserPanel.isCTCSelected(); } private static class SetFileDropTarget extends DropTarget @@ -532,21 +384,10 @@ public synchronized void drop( final DropTargetDropEvent evt ) private static final String IMAGE_PATH_KEY = "IMAGE_PATH"; - private static final String METRICS_TYPE_KEY = "METRICS_TYPE"; - - private static final String DOC_CTC = CTCTrackingMetricsType.INFO; - - private static final String DOC_SPT = SPTTrackingMetricsType.INFO; - - protected static final String LINK_CTC = CTCTrackingMetricsType.URL; - - protected static final String LINK_SPT = SPTTrackingMetricsType.URL; + public double getSPTMaxPairingDistance() + { + return metricsChooserPanel.getSPTMaxPairingDistance(); + } - private static final String URL_CTC = "Ulman, Maška, et al. 2017"; - private static final String URL_SPT = "Chenouard, Smal, de Chaumont, Maška, et al. 2014"; } diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java index f85473b..b12a0d1 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java @@ -40,11 +40,11 @@ public class MetricsChooserPanel extends JPanel private static final long serialVersionUID = 1L; - final JRadioButton rdbtnCTC; + private final JRadioButton rdbtnCTC; - final JFormattedTextField ftfMaxDist; + private final JFormattedTextField ftfMaxDist; - final JLabel lblUnits; + private final JLabel lblUnits; public MetricsChooserPanel() { @@ -190,6 +190,33 @@ public void mouseClicked( final java.awt.event.MouseEvent e ) ftfMaxDist.setValue( Double.valueOf( 1.0 ) ); } + void setUnits( final String units ) + { + lblUnits.setText( units ); + } + + /** + * Returns the value of the max pairing distance specified in this panel, + * used for the SPT metrics. + * + * @return the value of the max pairing distance + */ + public double getSPTMaxPairingDistance() + { + return ( ( Number ) ftfMaxDist.getValue() ).doubleValue(); + } + + /** + * Returns true if the CTC metrics are selected. If + * false, the SPT metrics are selected. + * + * @return true if the CTC metrics are selected + */ + public boolean isCTCSelected() + { + return rdbtnCTC.isSelected(); + } + public static final void main( final String... args ) { final ImageJ ij = new ImageJ(); From 944bdfe453ee8d1c818c1cd117e5849553e368b2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 3 Apr 2024 19:51:20 +0200 Subject: [PATCH 04/10] Don't crash if we do not provide an image when running metrics. Could be improved. A default string could be used instead of the empty string because this will be used to generated the CSV results file. --- .../java/fiji/plugin/trackmate/helper/MetricsRunner.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/helper/MetricsRunner.java b/src/main/java/fiji/plugin/trackmate/helper/MetricsRunner.java index 05eac8e..37598f8 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/MetricsRunner.java +++ b/src/main/java/fiji/plugin/trackmate/helper/MetricsRunner.java @@ -156,7 +156,12 @@ public double execTracking( final TrackMate trackmate ) protected File findSuitableCSVFile( final Settings settings ) { - final String imFileName = settings.imp.getShortTitle(); + final String imFileName; + if ( settings.imp == null ) + imFileName = ""; + else + imFileName = settings.imp.getShortTitle(); + // Prepare CSV headers. final String[] csvHeader1 = toCSVHeader( settings ); final String[] csvHeader = type.concatWithHeader( csvHeader1 ); From e325dd62dc4dce12f87105e9983189e111edc715 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 3 Apr 2024 19:52:13 +0200 Subject: [PATCH 05/10] Ground truth input for SPT metrics can be a TrackMate file. --- .../helper/spt/SPTMetricsRunner.java | 44 ++++++++++++++++++- .../spt/importer/SPTFormatImporter.java | 9 ++-- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/helper/spt/SPTMetricsRunner.java b/src/main/java/fiji/plugin/trackmate/helper/spt/SPTMetricsRunner.java index 9a60081..1222b15 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/spt/SPTMetricsRunner.java +++ b/src/main/java/fiji/plugin/trackmate/helper/spt/SPTMetricsRunner.java @@ -23,16 +23,22 @@ import java.io.File; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.helper.MetricsRunner; import fiji.plugin.trackmate.helper.TrackingMetrics; import fiji.plugin.trackmate.helper.spt.importer.SPTFormatImporter; +import fiji.plugin.trackmate.helper.spt.importer.XMLUtil; import fiji.plugin.trackmate.helper.spt.measure.DistanceTypes; import fiji.plugin.trackmate.helper.spt.measure.TrackSegment; +import fiji.plugin.trackmate.io.TmXmlReader; public class SPTMetricsRunner extends MetricsRunner { @@ -45,7 +51,37 @@ public SPTMetricsRunner( final String gtPath, final String saveFolder, final dou { super( Paths.get( saveFolder ), new SPTTrackingMetricsType( maxDist ) ); this.maxDist = maxDist; - this.referenceTracks = SPTFormatImporter.fromXML( new File( gtPath ) ); + + // Is the GT a TrackMate or a ISBI challenge file? + final File gtFile = new File( gtPath ); + final Document document = XMLUtil.loadDocument( gtFile ); + final Element root = XMLUtil.getRootElement( document ); + if ( root == null ) + throw new IllegalArgumentException( "can't find: tag." ); + + final ArrayList< Element > rootEls = XMLUtil.getElements( root, "TrackContestISBI2012" ); + if ( rootEls.size() == 0 ) + { + // Not an ISBI challenge file. + final TmXmlReader reader = new TmXmlReader( gtFile ); + if ( !reader.isReadingOk() ) + throw new IllegalArgumentException( "Ground-truth file is neither a TrackMate file nor a ISBI SPT challenge file." ); + + final Model model = reader.getModel(); + try + { + this.referenceTracks = SPTFormatImporter.fromTrackMate( model ); + } + catch ( final Exception iae ) + { + throw new IllegalArgumentException( "TrackMate ground-truth file cannot be used with SPT metrics:\n" + iae.getMessage() ); + } + } + else + { + // ISBI challenge file. + this.referenceTracks = SPTFormatImporter.fromXML( gtFile ); + } } @Override @@ -58,9 +94,13 @@ public void performMetricsMeasurements( final TrackMate trackmate, final double final List< TrackSegment > candidateTracks = SPTFormatImporter.fromTrackMate( model ); + String units = "image units"; + if ( settings.imp != null ) + units = settings.imp.getCalibration().getUnits(); + // Perform SPT measurements. batchLogger.log( String.format( "Performing SPT metrics measurements with max pairing dist = %.2f %s\n", - maxDist, settings.imp.getCalibration().getUnits() ) ); + maxDist, units ) ); final double[] score = ISBIScoring.score( referenceTracks, candidateTracks, maxDist, DistanceTypes.DISTANCE_EUCLIDIAN ); final TrackingMetrics metrics = new TrackingMetrics( type ); diff --git a/src/main/java/fiji/plugin/trackmate/helper/spt/importer/SPTFormatImporter.java b/src/main/java/fiji/plugin/trackmate/helper/spt/importer/SPTFormatImporter.java index 444f1a7..4af319e 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/spt/importer/SPTFormatImporter.java +++ b/src/main/java/fiji/plugin/trackmate/helper/spt/importer/SPTFormatImporter.java @@ -83,12 +83,13 @@ public static List< TrackSegment > fromXML( final File inputFile ) throws Illega final Document document = XMLUtil.loadDocument( inputFile ); final Element root = XMLUtil.getRootElement( document ); if ( root == null ) - { throw new IllegalArgumentException( "can't find: tag." ); } - final Element trackingSet = XMLUtil.getElements( root, "TrackContestISBI2012" ).get( 0 ); + throw new IllegalArgumentException( "can't find: tag." ); - if ( trackingSet == null ) + final ArrayList< Element > trackingSets = XMLUtil.getElements( root, "TrackContestISBI2012" ); + if ( trackingSets.size() == 0 ) throw new IllegalArgumentException( "can't find: tag." ); - + + final Element trackingSet = trackingSets.get( 0 ); final List< Element > particleElementArrayList = XMLUtil.getElements( trackingSet, "particle" ); for ( final Element particleElement : particleElementArrayList ) From 378d34472eec77a237c1b45525bae40371214189 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 3 Apr 2024 19:52:50 +0200 Subject: [PATCH 06/10] First version of the metric computation GUI. A bit clumsy and the retrofit really shows. --- .../helper/ui/HelperLauncherPanel.java | 6 +- .../helper/ui/MetricsLauncherController.java | 192 +++++++++++++ .../helper/ui/MetricsLauncherPanel.java | 261 +++++++++++++++++- .../ui/components/MetricsChooserPanel.java | 2 +- 4 files changed, 444 insertions(+), 17 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherPanel.java index f0ce9f8..b4ad987 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherPanel.java @@ -305,7 +305,7 @@ public void focusLost( final java.awt.event.FocusEvent e ) btnBrowseGT.addActionListener( e -> { final String dialogTitle = metricsChooserPanel.isCTCSelected() - ? "Select a CTC ground-ruth folder." + ? "Select a CTC ground-truth folder." : "Select a SPT ground-truth XML file."; final SelectionMode selectionMode = metricsChooserPanel.isCTCSelected() ? SelectionMode.DIRECTORIES_ONLY @@ -343,7 +343,7 @@ public boolean isCTCSelected() return metricsChooserPanel.isCTCSelected(); } - private static class SetFileDropTarget extends DropTarget + static class SetFileDropTarget extends DropTarget { private final JTextField tf; @@ -380,7 +380,7 @@ public synchronized void drop( final DropTargetDropEvent evt ) } } - private static final String GT_PATH_KEY = "GT_FOLDER"; + static final String GT_PATH_KEY = "GT_FOLDER"; private static final String IMAGE_PATH_KEY = "IMAGE_PATH"; diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java new file mode 100644 index 0000000..8e74e39 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java @@ -0,0 +1,192 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2021 - 2024 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.helper.ui; + +import java.io.File; + +import javax.swing.JFrame; +import javax.swing.JLabel; + +import org.scijava.Cancelable; + +import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.gui.Icons; +import fiji.plugin.trackmate.helper.MetricsRunner; +import fiji.plugin.trackmate.helper.TrackingMetricsType; +import fiji.plugin.trackmate.helper.ctc.CTCTrackingMetricsType; +import fiji.plugin.trackmate.helper.spt.SPTTrackingMetricsType; +import fiji.plugin.trackmate.io.TmXmlReader; +import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; +import net.imagej.ImageJ; + +public class MetricsLauncherController implements Cancelable +{ + + private final Logger logger = Logger.IJ_LOGGER; + + private boolean isCanceled; + + private String cancelReason; + + public MetricsLauncherController() + { + final MetricsLauncherPanel gui = new MetricsLauncherPanel(); + final JFrame frame = new JFrame( "TrackMate tracking metrics" ); + frame.setIconImage( Icons.TRACKMATE_ICON.getImage() ); + frame.getContentPane().add( gui ); + frame.setSize( 350, 550 ); + frame.setLocationRelativeTo( null ); + + gui.btnCancel.addActionListener( e -> cancel( "User pressed the cancel button" ) ); + gui.btnOK.addActionListener( e -> { + + final EverythingDisablerAndReenabler disabler = new EverythingDisablerAndReenabler( gui, new Class[] { JLabel.class } ); + disabler.disable(); + new Thread( "TrackMate tracking metrics computation thread" ) + { + @Override + public void run() + { + try + { + final boolean ctcSelected = gui.isCTCSelected(); + final String gtPath = gui.tfGTPath.getText(); + final String inputPath = gui.tfInputPath.getText(); + final double maxDist = gui.getSPTMaxPairingDistance(); + computeMetrics( ctcSelected, gtPath, inputPath, maxDist ); + } + finally + { + disabler.reenable(); + } + } + }.start(); + } ); + frame.setVisible( true ); + } + + private void computeMetrics( final boolean ctcSelected, final String gtPath, final String inputPath, final double maxDist ) + { + isCanceled = false; + + // Save folder. + final String saveFolder; + final File input = new File( inputPath ); + if ( input.isDirectory() ) + saveFolder = input.getAbsolutePath(); + else + saveFolder = input.getParent(); + + logger.log( "Performing tracking metrics measurements.\n" + + " - Tracking metrics type: " + ( ctcSelected ? "Cell Tracking Challenge" : "Single-Particle Tracking Challenge" ) + "\n" + + " - Ground-truth file: " + gtPath +"\n" ); + + // Prepare the runner. + final TrackingMetricsType type = ctcSelected + ? new CTCTrackingMetricsType() + : new SPTTrackingMetricsType( maxDist ); + + try + { + final MetricsRunner runner = type.runner( gtPath, saveFolder ); + runner.setBatchLogger( logger ); + // Process candidate files. + if ( input.isDirectory() ) + { + // Loop over the TrackMate files it contains. + final File[] xmlFiles = input.listFiles( ( dir, name ) -> name.toLowerCase().endsWith( ".xml" ) ); + for ( final File xmlFile : xmlFiles ) + { + if ( isCanceled ) + { + logger.log( "Canceled" ); + return; + } + process( xmlFile, runner ); + } + } + else + { + process( input, runner ); + } + logger.log( "____________________________________\nDone.\n" ); + } + catch ( final Exception e ) + { + logger.error( "The ground-path file " + gtPath + " cannot be used:\n" + e.getMessage() ); + } + } + + private void process( final File xmlFile, final MetricsRunner runner ) + { + logger.log( "____________________________________\nProcessing file " + xmlFile + "\n" ); + try + { + final TmXmlReader reader = new TmXmlReader( xmlFile ); + final Model model = reader.getModel(); + /* + * Warning! We don't load the image for the sake of time. We will + * then get a default CSV name for the output. + */ + final Settings settings = reader.readSettings( null ); + final TrackMate trackmate = new TrackMate( model, settings ); + runner.performMetricsMeasurements( trackmate, Double.NaN, Double.NaN ); + } + catch ( final Exception ex ) + { + logger.error( "File " + xmlFile + " is not a TrackMate file. Skipping.\n" ); + ex.printStackTrace(); + } + } + + // --- org.scijava.Cancelable methods --- + + @Override + public boolean isCanceled() + { + return isCanceled; + } + + @Override + public void cancel( final String reason ) + { + isCanceled = true; + cancelReason = reason; + } + + @Override + public String getCancelReason() + { + return cancelReason; + } + + public static final void main( final String... args ) + { + final ImageJ ij = new ImageJ(); + ij.launch( args ); + new MetricsLauncherController(); + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java index 7181947..1e3d0c0 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java @@ -1,38 +1,65 @@ package fiji.plugin.trackmate.helper.ui; 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.helper.ui.HelperLauncherPanel.GT_PATH_KEY; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Insets; +import java.awt.event.FocusAdapter; +import java.io.File; import javax.swing.BorderFactory; import javax.swing.ImageIcon; -import javax.swing.JFrame; +import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSeparator; +import javax.swing.JTextField; +import org.scijava.prefs.PrefService; import org.scijava.util.VersionUtils; +import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.gui.Icons; +import fiji.plugin.trackmate.helper.ui.HelperLauncherPanel.SetFileDropTarget; import fiji.plugin.trackmate.helper.ui.components.MetricsChooserPanel; -import net.imagej.ImageJ; +import fiji.plugin.trackmate.io.TmXmlReader; +import fiji.plugin.trackmate.util.FileChooser; +import fiji.plugin.trackmate.util.FileChooser.DialogType; +import fiji.plugin.trackmate.util.FileChooser.SelectionMode; +import fiji.plugin.trackmate.util.JLabelLogger; +import fiji.plugin.trackmate.util.TMUtils; public class MetricsLauncherPanel extends JPanel { private static final long serialVersionUID = 1L; + final JTextField tfInputPath; + + final JTextField tfGTPath; + + final JButton btnCancel; + + final JButton btnOK; + + private final MetricsChooserPanel metricsChooserPanel; + + private Logger logger; + public MetricsLauncherPanel() { setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); final GridBagLayout gridBagLayout = new GridBagLayout(); - gridBagLayout.rowHeights = new int[] { 0, 0, 15, 0 }; - gridBagLayout.columnWeights = new double[] { 1.0, 0.0 }; + gridBagLayout.rowWeights = new double[] { 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1. }; + gridBagLayout.rowHeights = new int[] { 0, 0, 15, 200, 15 }; + gridBagLayout.columnWeights = new double[] { 1., 0. }; setLayout( gridBagLayout ); final Image im = Icons.TRACKMATE_ICON.getImage(); @@ -67,7 +94,7 @@ public MetricsLauncherPanel() gbcSeparator.gridy = 2; add( new JSeparator(), gbcSeparator ); - final MetricsChooserPanel metricsChooserPanel = new MetricsChooserPanel(); + metricsChooserPanel = new MetricsChooserPanel(); final GridBagConstraints gbcMetricsPanel = new GridBagConstraints(); gbcMetricsPanel.gridwidth = 2; gbcMetricsPanel.insets = new Insets( 0, 0, 5, 0 ); @@ -75,17 +102,225 @@ public MetricsLauncherPanel() gbcMetricsPanel.gridx = 0; gbcMetricsPanel.gridy = 3; add( metricsChooserPanel, gbcMetricsPanel ); + + final GridBagConstraints gbcSeparator1 = new GridBagConstraints(); + gbcSeparator1.gridwidth = 2; + gbcSeparator1.insets = new Insets( 0, 0, 5, 0 ); + gbcSeparator1.fill = GridBagConstraints.BOTH; + gbcSeparator1.gridx = 0; + gbcSeparator1.gridy = 4; + add( new JSeparator(), gbcSeparator1 ); + + final JLabel lblPleaseSelectTestFile = new JLabel( "" + + "Please select the TrackMate file or folder containing " + + "the files on which the metrics will be measured." + + "" ); + lblPleaseSelectTestFile.setFont( FONT ); + final GridBagConstraints gbcLblPleaseSelectTestFile = new GridBagConstraints(); + gbcLblPleaseSelectTestFile.fill = GridBagConstraints.BOTH; + gbcLblPleaseSelectTestFile.gridwidth = 2; + gbcLblPleaseSelectTestFile.insets = new Insets( 0, 0, 5, 0 ); + gbcLblPleaseSelectTestFile.anchor = GridBagConstraints.WEST; + gbcLblPleaseSelectTestFile.gridx = 0; + gbcLblPleaseSelectTestFile.gridy = 5; + add( lblPleaseSelectTestFile, gbcLblPleaseSelectTestFile ); + + final JLabel lblInputPath = new JLabel( "Input path:" ); + lblInputPath.setFont( SMALL_FONT ); + final GridBagConstraints gbcLblInputPath = new GridBagConstraints(); + gbcLblInputPath.gridwidth = 2; + gbcLblInputPath.insets = new Insets( 0, 0, 5, 0 ); + gbcLblInputPath.anchor = GridBagConstraints.WEST; + gbcLblInputPath.gridx = 0; + gbcLblInputPath.gridy = 6; + add( lblInputPath, gbcLblInputPath ); + + tfInputPath = new JTextField(); + tfInputPath.setFont( SMALL_FONT ); + final GridBagConstraints gbcTfImagePath = new GridBagConstraints(); + gbcTfImagePath.insets = new Insets( 0, 0, 5, 5 ); + gbcTfImagePath.fill = GridBagConstraints.HORIZONTAL; + gbcTfImagePath.gridx = 0; + gbcTfImagePath.gridy = 7; + add( tfInputPath, gbcTfImagePath ); + tfInputPath.setColumns( 10 ); + + final JButton btnBrowseInput = new JButton( "Browse" ); + btnBrowseInput.setFont( SMALL_FONT ); + final GridBagConstraints gbcBtnBrowseInput = new GridBagConstraints(); + gbcBtnBrowseInput.insets = new Insets( 0, 0, 5, 0 ); + gbcBtnBrowseInput.gridx = 1; + gbcBtnBrowseInput.gridy = 7; + add( btnBrowseInput, gbcBtnBrowseInput ); + + final GridBagConstraints gbcSeparator3 = new GridBagConstraints(); + gbcSeparator3.insets = new Insets( 0, 0, 5, 0 ); + gbcSeparator3.gridwidth = 2; + gbcSeparator3.fill = GridBagConstraints.BOTH; + gbcSeparator3.gridx = 0; + gbcSeparator3.gridy = 9; + add( new JSeparator(), gbcSeparator3 ); + + final JLabel lblBrowseGroundTruth = new JLabel( "Please browse to the ground truth file or folder:" ); + lblBrowseGroundTruth.setFont( FONT ); + final GridBagConstraints gbcLblBrowseGroundTruth = new GridBagConstraints(); + gbcLblBrowseGroundTruth.insets = new Insets( 0, 0, 5, 0 ); + gbcLblBrowseGroundTruth.anchor = GridBagConstraints.WEST; + gbcLblBrowseGroundTruth.gridwidth = 2; + gbcLblBrowseGroundTruth.gridx = 0; + gbcLblBrowseGroundTruth.gridy = 10; + add( lblBrowseGroundTruth, gbcLblBrowseGroundTruth ); + + tfGTPath = new JTextField(); + tfGTPath.setFont( SMALL_FONT ); + final GridBagConstraints gbcTfGTPath = new GridBagConstraints(); + gbcTfGTPath.insets = new Insets( 0, 0, 5, 5 ); + gbcTfGTPath.fill = GridBagConstraints.HORIZONTAL; + gbcTfGTPath.gridx = 0; + gbcTfGTPath.gridy = 11; + add( tfGTPath, gbcTfGTPath ); + tfGTPath.setColumns( 10 ); + + final JButton btnBrowseGT = new JButton( "Browse" ); + btnBrowseGT.setFont( SMALL_FONT ); + final GridBagConstraints gbcBtnBrowseGT = new GridBagConstraints(); + gbcBtnBrowseGT.insets = new Insets( 0, 0, 5, 0 ); + gbcBtnBrowseGT.gridx = 1; + gbcBtnBrowseGT.gridy = 11; + add( btnBrowseGT, gbcBtnBrowseGT ); + + final JLabelLogger labelLogger = new JLabelLogger(); + labelLogger.setFont( SMALL_FONT ); + final GridBagConstraints gbcLogger = new GridBagConstraints(); + gbcLogger.anchor = GridBagConstraints.SOUTHEAST; + gbcLogger.fill = GridBagConstraints.BOTH; + gbcLogger.gridwidth = 2; + gbcLogger.gridx = 0; + gbcLogger.gridy = 12; + add( labelLogger, gbcLogger ); + + final JPanel panelButtons = new JPanel(); + final GridBagConstraints gbcPanelButtons = new GridBagConstraints(); + gbcPanelButtons.anchor = GridBagConstraints.SOUTHEAST; + gbcPanelButtons.gridwidth = 2; + gbcPanelButtons.gridx = 0; + gbcPanelButtons.gridy = 13; + add( panelButtons, gbcPanelButtons ); + + btnCancel = new JButton( "Cancel" ); + btnCancel.setFont( SMALL_FONT ); + panelButtons.add( btnCancel ); + + btnOK = new JButton( "OK" ); + btnOK.setFont( SMALL_FONT ); + panelButtons.add( btnOK ); + + /* + * Listeners & co. + */ + + this.logger = labelLogger.getLogger(); + final PrefService prefService = TMUtils.getContext().getService( PrefService.class ); + + fiji.plugin.trackmate.gui.GuiUtils.selectAllOnFocus( tfInputPath ); + fiji.plugin.trackmate.gui.GuiUtils.selectAllOnFocus( tfGTPath ); + + final Runnable storeGtPath = () -> prefService.put( getClass(), GT_PATH_KEY, tfGTPath.getText() ); + final Runnable storeInputPath = () -> prefService.put( getClass(), INPUT_PATH_KEY, tfInputPath.getText() ); + + tfInputPath.addActionListener( e -> storeInputPath.run() ); + final FocusAdapter faIm = new FocusAdapter() + { + @Override + public void focusLost( final java.awt.event.FocusEvent e ) + { + storeInputPath.run(); + } + }; + tfInputPath.addFocusListener( faIm ); + + tfGTPath.addActionListener( e -> storeGtPath.run() ); + final FocusAdapter faGt = new FocusAdapter() + { + @Override + public void focusLost( final java.awt.event.FocusEvent e ) + { + storeGtPath.run(); + readPixelUnits(); + } + }; + tfGTPath.addFocusListener( faGt ); + + btnBrowseInput.addActionListener( e -> { + final File file = FileChooser.chooseFile( this, tfInputPath.getText(), null, + "Select an input file or folder", DialogType.LOAD, SelectionMode.FILES_AND_DIRECTORIES ); + if ( file == null ) + return; + + tfInputPath.setText( file.getAbsolutePath() ); + storeInputPath.run(); + } ); + + btnBrowseGT.addActionListener( e -> { + final String dialogTitle = metricsChooserPanel.isCTCSelected() + ? "Select a CTC ground-truth folder." + : "Select a SPT ground-truth XML file."; + final SelectionMode selectionMode = metricsChooserPanel.isCTCSelected() + ? SelectionMode.DIRECTORIES_ONLY + : SelectionMode.FILES_ONLY; + final File file = FileChooser.chooseFile( this, tfGTPath.getText(), null, dialogTitle, DialogType.LOAD, selectionMode ); + if ( file == null ) + return; + + tfGTPath.setText( file.getAbsolutePath() ); + storeGtPath.run(); + readPixelUnits(); + } ); + + tfInputPath.setDropTarget( new SetFileDropTarget( tfInputPath, storeInputPath ) ); + tfGTPath.setDropTarget( new SetFileDropTarget( tfGTPath, storeGtPath ) ); + + /* + * Default values. + */ + + final String lastUsedImagePathFolder = prefService.get( getClass(), INPUT_PATH_KEY, System.getProperty( "user.home" ) ); + tfInputPath.setText( lastUsedImagePathFolder ); + + final String lastUsedGtPath = prefService.get( getClass(), GT_PATH_KEY, System.getProperty( "user.home" ) ); + tfGTPath.setText( lastUsedGtPath ); + readPixelUnits(); + } + + private void readPixelUnits() + { + logger.log( "" ); + if ( !isCTCSelected() ) + { + metricsChooserPanel.setUnits( "image units" ); + // Try to read the pixel units. + final TmXmlReader reader = new TmXmlReader( new File( tfGTPath.getText() ) ); + try + { + final Model model = reader.getModel(); + if ( model != null ) + metricsChooserPanel.setUnits( model.getSpaceUnits() ); + } + catch ( final Exception ex ) + {} + } } - public static final void main( final String... args ) + public boolean isCTCSelected() { - final ImageJ ij = new ImageJ(); - ij.launch( args ); - final JFrame frame = new JFrame(); - frame.getContentPane().add( new MetricsLauncherPanel() ); - frame.setSize( 400, 600 ); - frame.setLocationRelativeTo( null ); - frame.setVisible( true ); + return metricsChooserPanel.isCTCSelected(); } + public double getSPTMaxPairingDistance() + { + return metricsChooserPanel.getSPTMaxPairingDistance(); + } + + private static final String INPUT_PATH_KEY = "INPUT_PATH"; + } diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java index b12a0d1..01a28b4 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java @@ -190,7 +190,7 @@ public void mouseClicked( final java.awt.event.MouseEvent e ) ftfMaxDist.setValue( Double.valueOf( 1.0 ) ); } - void setUnits( final String units ) + public void setUnits( final String units ) { lblUnits.setText( units ); } From a218c36ade861d07bdfb860b0986e1774f10f564 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 Apr 2024 15:09:37 +0200 Subject: [PATCH 07/10] Rework metric computation in the runners. When used in the Helper: - An abstract method does the computation and return the metrics. - A method in the mother class calls this method and takes care of saving the results to a CSV file. This diminishes slightly duplicated code, allows lowering visibility of some methods in MetricsRunner, but will also allow using the runners just to compute metrics oustide of the helper. Also for SPT runner: the units is specified at construction. Nevermind if we cannot get it right, it is just for display. --- .../plugin/trackmate/helper/HelperRunner.java | 4 +- .../trackmate/helper/MetricsRunner.java | 61 ++++++++++++++++--- .../TrackMateParameterSweepResultsPlugin.java | 2 +- .../helper/ctc/CTCMetricsRunner.java | 12 +--- .../helper/spt/SPTMetricsRunner.java | 21 +++---- .../helper/spt/SPTTrackingMetricsType.java | 9 ++- .../helper/ui/HelperLauncherController.java | 5 +- .../helper/ui/MetricsLauncherPanel.java | 5 ++ .../ui/components/MetricsChooserPanel.java | 7 ++- .../plugin/trackmate/ctc/CLITestDrive.java | 3 +- 10 files changed, 89 insertions(+), 40 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/helper/HelperRunner.java b/src/main/java/fiji/plugin/trackmate/helper/HelperRunner.java index 01759c8..d034b49 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/HelperRunner.java +++ b/src/main/java/fiji/plugin/trackmate/helper/HelperRunner.java @@ -303,7 +303,7 @@ public void run() final double trackingTiming = runner.execTracking( trackmate ); // Perform and save metrics measurements. - runner.performMetricsMeasurements( trackmate, detectionTiming, trackingTiming ); + runner.performAndSaveMetricsMeasurements( trackmate, detectionTiming, trackingTiming ); // Save TrackMate file if required. if ( saveTrackMateFiles ) @@ -625,7 +625,7 @@ else if ( typeStr.equals( "SPT" ) ) str.append( "Max pairing distance for SPT metrics has not been set.\n" ); ok = false; } - this.type = new SPTTrackingMetricsType( maxDist ); + this.type = new SPTTrackingMetricsType( maxDist, "image units" ); } else { diff --git a/src/main/java/fiji/plugin/trackmate/helper/MetricsRunner.java b/src/main/java/fiji/plugin/trackmate/helper/MetricsRunner.java index 37598f8..d1f5799 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/MetricsRunner.java +++ b/src/main/java/fiji/plugin/trackmate/helper/MetricsRunner.java @@ -75,7 +75,47 @@ public MetricsRunner( final Path resultsRootPath, final TrackingMetricsType type this.nameGenWithID = ( imName, i ) -> String.format( "%s_" + type.csvSuffix() + "_%02d.csv", imName, i ); } - public abstract void performMetricsMeasurements( TrackMate trackmate, double detectionTiming, double trackingTiming ); + /** + * Performs the tracking metrics measurements for the tracks in the model in + * the specified TrackMate instance, against the ground truth given at + * construction. Metric values are returned as a {@link TrackingMetrics}. + * + * @param trackmate + * the tracks on which to measure tracking metrics. + * @return the metric values. + */ + public abstract TrackingMetrics performMetricsMeasurements( final TrackMate trackmate ) throws MetricsComputationErrorException; + + /** + * Performs the tracking metrics measurements for the tracks in the model in + * the specified TrackMate instance, against the ground truth given at + * construction, and save the results in an adequate CSV file. + * + * @param trackmate + * the tracks on which to measure tracking metrics. + * @param detectionTiming + * the metric measuring the detection time. + * @param trackingTiming + * the metric measuring the tracking time. + */ + public void performAndSaveMetricsMeasurements( final TrackMate trackmate, final double detectionTiming, final double trackingTiming ) + { + final Settings settings = trackmate.getSettings(); + final File csvFile = findSuitableCSVFile( settings ); + final String[] csvHeader1 = toCSVHeader( settings ); + + try + { + final TrackingMetrics metrics = performMetricsMeasurements( trackmate ); + batchLogger.log( "SPT metrics:\n" ); + batchLogger.log( metrics.toString() + '\n' ); + writeResults( csvFile, metrics, detectionTiming, trackingTiming, settings, csvHeader1 ); + } + catch ( final MetricsComputationErrorException e ) + { + writeFailedResults( csvFile, settings, csvHeader1 ); + } + } public ValuePair< TrackMate, Double > execDetection( final Settings settings ) { @@ -154,7 +194,7 @@ public double execTracking( final TrackMate trackmate ) return trackingTiming; } - protected File findSuitableCSVFile( final Settings settings ) + private File findSuitableCSVFile( final Settings settings ) { final String imFileName; if ( settings.imp == null ) @@ -225,7 +265,7 @@ protected File findSuitableCSVFile( final Settings settings ) * @param csvHeader * the header name for each setting value. */ - protected void writeResults( + private void writeResults( final File csvFile, final TrackingMetrics metrics, final double detectionTiming, @@ -237,8 +277,6 @@ protected void writeResults( metrics.set( TrackingMetricsType.TIM, detectionTiming + trackingTiming ); metrics.set( TrackingMetricsType.DETECTION_TIME, detectionTiming ); metrics.set( TrackingMetricsType.TRACKING_TIME, trackingTiming ); - batchLogger.log( "SPT metrics:\n" ); - batchLogger.log( metrics.toString() + '\n' ); // Write to CSV. final String[] line1 = toCSVLine( settings, csvHeader ); @@ -272,7 +310,7 @@ protected void writeResults( * @param csvHeader * the header name for each setting value. */ - protected void writeFailedResults( final File csvFile, final Settings settings, final String[] csvHeader ) + private void writeFailedResults( final File csvFile, final Settings settings, final String[] csvHeader ) { // Write default values to CSV. final String[] settingsValueColumns = toCSVLine( settings, csvHeader ); @@ -329,7 +367,7 @@ private final File getCSVFile( final String resultsRootPath, final String imageN return csvFilePath.toFile(); } - protected static final String[] toCSVHeader( final Settings settings ) + private static final String[] toCSVHeader( final Settings settings ) { final int nDetectorParams = settings.detectorSettings.size(); final int nTrackerParams = settings.trackerSettings.size(); @@ -346,7 +384,7 @@ protected static final String[] toCSVHeader( final Settings settings ) return out; } - protected static final String[] toCSVLine( final Settings settings, final String[] csvHeader ) + private static final String[] toCSVLine( final Settings settings, final String[] csvHeader ) { final int nDetectorParams = settings.detectorSettings.size(); final int nTrackerParams = settings.trackerSettings.size(); @@ -368,4 +406,11 @@ protected static final String[] toCSVLine( final Settings settings, final String } return out; } + + public static class MetricsComputationErrorException extends Exception + { + + private static final long serialVersionUID = 1L; + + } } diff --git a/src/main/java/fiji/plugin/trackmate/helper/TrackMateParameterSweepResultsPlugin.java b/src/main/java/fiji/plugin/trackmate/helper/TrackMateParameterSweepResultsPlugin.java index 54ce077..2ccf322 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/TrackMateParameterSweepResultsPlugin.java +++ b/src/main/java/fiji/plugin/trackmate/helper/TrackMateParameterSweepResultsPlugin.java @@ -89,7 +89,7 @@ public void run( final String arg ) else if ( typeStr.equals( "SPT" ) ) { // SPT max distance does not matter for inspection. - type = new SPTTrackingMetricsType( 1. ); + type = new SPTTrackingMetricsType( 1., "image units" ); } else { diff --git a/src/main/java/fiji/plugin/trackmate/helper/ctc/CTCMetricsRunner.java b/src/main/java/fiji/plugin/trackmate/helper/ctc/CTCMetricsRunner.java index 9ab1083..9405ac4 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ctc/CTCMetricsRunner.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ctc/CTCMetricsRunner.java @@ -21,7 +21,6 @@ */ package fiji.plugin.trackmate.helper.ctc; -import java.io.File; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -32,7 +31,6 @@ import org.scijava.Context; -import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.action.CTCExporter; import fiji.plugin.trackmate.action.CTCExporter.ExportType; @@ -67,12 +65,9 @@ public CTCMetricsRunner( final String gtPath, final String saveFolder, final Con } @Override - public void performMetricsMeasurements( final TrackMate trackmate, final double detectionTiming, final double trackingTiming ) + public TrackingMetrics performMetricsMeasurements( final TrackMate trackmate ) throws MetricsComputationErrorException { batchLogger.log( "Exporting as CTC results.\n" ); - final Settings settings = trackmate.getSettings(); - final File csvFile = findSuitableCSVFile( settings ); - final String[] csvHeader1 = toCSVHeader( settings ); final int id = CTCExporter.getAvailableDatasetID( resultsRootPath.toString() ); final String resultsFolder = CTCExporter.getExportTrackingDataPath( resultsRootPath.toString(), id, ExportType.RESULTS, trackmate ); @@ -84,13 +79,12 @@ public void performMetricsMeasurements( final TrackMate trackmate, final double // Perform CTC measurements. batchLogger.log( "Performing CTC metrics measurements.\n" ); final TrackingMetrics metrics = ctc.process( gtPath, resultsFolder ); - - writeResults( csvFile, metrics, detectionTiming, trackingTiming, settings, csvHeader1 ); + return metrics; } catch ( final IOException | IllegalArgumentException e ) { batchLogger.error( "Could not export tracking data to CTC files:\n" + e.getMessage() + '\n' ); - writeFailedResults( csvFile, settings, csvHeader1 ); + throw new MetricsComputationErrorException(); } finally { diff --git a/src/main/java/fiji/plugin/trackmate/helper/spt/SPTMetricsRunner.java b/src/main/java/fiji/plugin/trackmate/helper/spt/SPTMetricsRunner.java index 1222b15..d80c8f6 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/spt/SPTMetricsRunner.java +++ b/src/main/java/fiji/plugin/trackmate/helper/spt/SPTMetricsRunner.java @@ -30,7 +30,6 @@ import org.w3c.dom.Element; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.helper.MetricsRunner; import fiji.plugin.trackmate.helper.TrackingMetrics; @@ -47,10 +46,13 @@ public class SPTMetricsRunner extends MetricsRunner private final double maxDist; - public SPTMetricsRunner( final String gtPath, final String saveFolder, final double maxDist ) + private final String units; + + public SPTMetricsRunner( final String gtPath, final String saveFolder, final double maxDist, final String units ) { - super( Paths.get( saveFolder ), new SPTTrackingMetricsType( maxDist ) ); + super( Paths.get( saveFolder ), new SPTTrackingMetricsType( maxDist, units ) ); this.maxDist = maxDist; + this.units = units; // Is the GT a TrackMate or a ISBI challenge file? final File gtFile = new File( gtPath ); @@ -85,19 +87,10 @@ public SPTMetricsRunner( final String gtPath, final String saveFolder, final dou } @Override - public void performMetricsMeasurements( final TrackMate trackmate, final double detectionTiming, final double trackingTiming ) + public TrackingMetrics performMetricsMeasurements( final TrackMate trackmate ) { - final Settings settings = trackmate.getSettings(); final Model model = trackmate.getModel(); - final File csvFile = findSuitableCSVFile( settings ); - final String[] csvHeader1 = toCSVHeader( settings ); - final List< TrackSegment > candidateTracks = SPTFormatImporter.fromTrackMate( model ); - - String units = "image units"; - if ( settings.imp != null ) - units = settings.imp.getCalibration().getUnits(); - // Perform SPT measurements. batchLogger.log( String.format( "Performing SPT metrics measurements with max pairing dist = %.2f %s\n", maxDist, units ) ); @@ -107,6 +100,6 @@ public void performMetricsMeasurements( final TrackMate trackmate, final double for ( int i = 0; i < score.length; i++ ) metrics.set( i, score[ i ] ); - writeResults( csvFile, metrics, detectionTiming, trackingTiming, settings, csvHeader1 ); + return metrics; } } diff --git a/src/main/java/fiji/plugin/trackmate/helper/spt/SPTTrackingMetricsType.java b/src/main/java/fiji/plugin/trackmate/helper/spt/SPTTrackingMetricsType.java index fca75da..3596bc8 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/spt/SPTTrackingMetricsType.java +++ b/src/main/java/fiji/plugin/trackmate/helper/spt/SPTTrackingMetricsType.java @@ -60,6 +60,8 @@ public class SPTTrackingMetricsType extends TrackingMetricsType private final double maxDist; + private final String units; + /** * Builds a new metrics type based on the SPT challenge, with the specified * max pairing distance given in physical units. @@ -72,17 +74,20 @@ public class SPTTrackingMetricsType extends TrackingMetricsType * * @param maxDist * the max pairing distance. + * @param units + * the physical units in which maxDist is specified. */ - public SPTTrackingMetricsType( final double maxDist ) + public SPTTrackingMetricsType( final double maxDist, final String units ) { super( KEYS ); this.maxDist = maxDist; + this.units = units; } @Override public MetricsRunner runner( final String gtPath, final String saveFolder ) { - return new SPTMetricsRunner( gtPath, saveFolder, maxDist ); + return new SPTMetricsRunner( gtPath, saveFolder, maxDist, units ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherController.java b/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherController.java index 1ff316a..2b31467 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherController.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/HelperLauncherController.java @@ -56,6 +56,7 @@ public HelperLauncherController() final boolean ctcSelected = gui.isCTCSelected(); final String gtPath = gui.tfGTPath.getText(); + String units; if ( impOpen ) { final String imName = ( String ) gui.cmbboxImp.getSelectedItem(); @@ -65,6 +66,7 @@ public HelperLauncherController() IJ.error( "TrackMate-Helper", "Could not find opened image with name " + imName ); return; } + units = imp.getCalibration().getUnit(); } else { @@ -76,12 +78,13 @@ public HelperLauncherController() return; } imp.show(); + units = imp.getCalibration().getUnit(); } frame.dispose(); final TrackingMetricsType type = ctcSelected ? new CTCTrackingMetricsType() - : new SPTTrackingMetricsType( ( gui.getSPTMaxPairingDistance() ) ); + : new SPTTrackingMetricsType( gui.getSPTMaxPairingDistance(), units ); final File modelFile = ParameterSweepModelIO.makeSettingsFileForGTPath( gtPath ); if ( !modelFile.exists() ) diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java index 1e3d0c0..8dda0dd 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherPanel.java @@ -321,6 +321,11 @@ public double getSPTMaxPairingDistance() return metricsChooserPanel.getSPTMaxPairingDistance(); } + public String getUnits() + { + return metricsChooserPanel.getUnits(); + } + private static final String INPUT_PATH_KEY = "INPUT_PATH"; } diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java b/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java index 01a28b4..87a6c29 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/components/MetricsChooserPanel.java @@ -206,6 +206,11 @@ public double getSPTMaxPairingDistance() return ( ( Number ) ftfMaxDist.getValue() ).doubleValue(); } + public String getUnits() + { + return lblUnits.getText(); + } + /** * Returns true if the CTC metrics are selected. If * false, the SPT metrics are selected. @@ -245,6 +250,4 @@ public static final void main( final String... args ) private static final String URL_SPT = "Chenouard, Smal, de Chaumont, Maška, et al. 2014"; - - } diff --git a/src/test/java/fiji/plugin/trackmate/ctc/CLITestDrive.java b/src/test/java/fiji/plugin/trackmate/ctc/CLITestDrive.java index 1ad0898..d3b22d1 100644 --- a/src/test/java/fiji/plugin/trackmate/ctc/CLITestDrive.java +++ b/src/test/java/fiji/plugin/trackmate/ctc/CLITestDrive.java @@ -57,7 +57,8 @@ public static void main( final String[] args ) throws ClassNotFoundException, In final ImagePlus imp = IJ.openImage( sourceImagePath ); final double maxDist = 1.; - final TrackingMetricsType type = new SPTTrackingMetricsType( maxDist ); + final String units = "image units"; + final TrackingMetricsType type = new SPTTrackingMetricsType( maxDist, units ); final Builder builder = HelperRunner.create(); final HelperRunner runner = builder From a3dce7c981bbf728213577635fca8721ec79f0df Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 Apr 2024 15:10:07 +0200 Subject: [PATCH 08/10] Working metric computation tool. --- .../helper/ui/MetricsLauncherController.java | 97 ++++++++++++++++--- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java index 8e74e39..de6a1a0 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java @@ -22,23 +22,30 @@ package fiji.plugin.trackmate.helper.ui; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import javax.swing.JFrame; import javax.swing.JLabel; +import org.apache.commons.io.FilenameUtils; import org.scijava.Cancelable; +import com.opencsv.CSVWriter; + import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.helper.MetricsRunner; +import fiji.plugin.trackmate.helper.TrackingMetrics; import fiji.plugin.trackmate.helper.TrackingMetricsType; import fiji.plugin.trackmate.helper.ctc.CTCTrackingMetricsType; import fiji.plugin.trackmate.helper.spt.SPTTrackingMetricsType; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; +import ij.ImagePlus; import net.imagej.ImageJ; public class MetricsLauncherController implements Cancelable @@ -75,7 +82,8 @@ public void run() final String gtPath = gui.tfGTPath.getText(); final String inputPath = gui.tfInputPath.getText(); final double maxDist = gui.getSPTMaxPairingDistance(); - computeMetrics( ctcSelected, gtPath, inputPath, maxDist ); + final String units = gui.getUnits(); + computeMetrics( ctcSelected, gtPath, inputPath, maxDist, units ); } finally { @@ -87,7 +95,7 @@ public void run() frame.setVisible( true ); } - private void computeMetrics( final boolean ctcSelected, final String gtPath, final String inputPath, final double maxDist ) + private void computeMetrics( final boolean ctcSelected, final String gtPath, final String inputPath, final double maxDist, final String units ) { isCanceled = false; @@ -99,15 +107,47 @@ private void computeMetrics( final boolean ctcSelected, final String gtPath, fin else saveFolder = input.getParent(); - logger.log( "Performing tracking metrics measurements.\n" - + " - Tracking metrics type: " + ( ctcSelected ? "Cell Tracking Challenge" : "Single-Particle Tracking Challenge" ) + "\n" - + " - Ground-truth file: " + gtPath +"\n" ); - // Prepare the runner. final TrackingMetricsType type = ctcSelected ? new CTCTrackingMetricsType() - : new SPTTrackingMetricsType( maxDist ); + : new SPTTrackingMetricsType( maxDist, units ); + + // Prepare the CSV file. + final String gtName = FilenameUtils.removeExtension( new File( gtPath ).getName() ); + final String csvFileName = type.csvSuffix() + "_" + gtName + ".csv"; + final File csvFile = new File( saveFolder, csvFileName ); + + // Echo info + logger.log( "Performing tracking metrics measurements.\n" + + " - Tracking metrics type: " + ( ctcSelected ? "Cell Tracking Challenge" : "Single-Particle Tracking Challenge" ) + "\n" + + " - Ground-truth file: " + gtPath +"\n" + + " - Saving metrics to CSV file: " + csvFile + "\n" ); + final StringBuilder str = new StringBuilder(); + for ( int i = 0; i < type.metrics().size(); i++ ) + str.append( " - " + type.metrics().get( i ).key + ": " + type.metrics().get( i ).description + '\n' ); + logger.log( str.toString() ); + + // Save CSV header. + final String[] csvHeader = new String[ 1 + type.metrics().size() ]; + csvHeader[ 0 ] = "File"; + for ( int i = 0; i < type.metrics().size(); i++ ) + csvHeader[ i + 1 ] = type.metrics().get( i ).key; + + try (CSVWriter csvWriter = new CSVWriter( new FileWriter( csvFile, false ), + CSVWriter.DEFAULT_SEPARATOR, + CSVWriter.NO_QUOTE_CHARACTER, + CSVWriter.DEFAULT_ESCAPE_CHARACTER, + CSVWriter.DEFAULT_LINE_END )) + { + csvWriter.writeNext( csvHeader ); + } + catch ( final IOException e ) + { + logger.error( "Error saving results to CSV file " + csvFile + ":\n" + e.getMessage() ); + return; + } + // Process input. try { final MetricsRunner runner = type.runner( gtPath, saveFolder ); @@ -124,12 +164,12 @@ private void computeMetrics( final boolean ctcSelected, final String gtPath, fin logger.log( "Canceled" ); return; } - process( xmlFile, runner ); + process( xmlFile, runner, csvFile ); } } else { - process( input, runner ); + process( input, runner, csvFile ); } logger.log( "____________________________________\nDone.\n" ); } @@ -139,20 +179,45 @@ private void computeMetrics( final boolean ctcSelected, final String gtPath, fin } } - private void process( final File xmlFile, final MetricsRunner runner ) + private void process( final File xmlFile, final MetricsRunner runner, final File csvFile ) { logger.log( "____________________________________\nProcessing file " + xmlFile + "\n" ); try { final TmXmlReader reader = new TmXmlReader( xmlFile ); final Model model = reader.getModel(); - /* - * Warning! We don't load the image for the sake of time. We will - * then get a default CSV name for the output. - */ - final Settings settings = reader.readSettings( null ); + final ImagePlus imp = reader.readImage(); + final Settings settings = reader.readSettings( imp ); final TrackMate trackmate = new TrackMate( model, settings ); - runner.performMetricsMeasurements( trackmate, Double.NaN, Double.NaN ); + final TrackingMetrics metrics = runner.performMetricsMeasurements( trackmate ); + + logger.log( metrics.toString() ); + + final double[] values = metrics.toArray(); + final String[] line = new String[ 1 + values.length ]; + line[ 0 ] = xmlFile.getName(); + for ( int i = 0; i < values.length; i++ ) + line[ i + 1 ] = "" + values[ i ]; + + try (CSVWriter csvWriter = new CSVWriter( new FileWriter( csvFile, true ), + CSVWriter.DEFAULT_SEPARATOR, + CSVWriter.NO_QUOTE_CHARACTER, + CSVWriter.DEFAULT_ESCAPE_CHARACTER, + CSVWriter.DEFAULT_LINE_END )) + { + csvWriter.writeNext( line ); + } + catch ( final IOException e ) + { + logger.error( "Error saving results to CSV file " + csvFile + ":\n" + e.getMessage() ); + } + + // Loop + if ( imp != null ) + { + imp.changes = false; + imp.close(); + } } catch ( final Exception ex ) { From 85d578f436f1697efc18438401c75c1786811025 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 Apr 2024 15:25:56 +0200 Subject: [PATCH 09/10] Make a IJ1 plugin for the metrics computation UI. --- .../helper/TrackMateComputeMetricsPlugin.java | 45 +++++++++++++++++++ .../helper/ui/MetricsLauncherController.java | 9 ---- src/main/resources/plugins.config | 1 + 3 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/helper/TrackMateComputeMetricsPlugin.java diff --git a/src/main/java/fiji/plugin/trackmate/helper/TrackMateComputeMetricsPlugin.java b/src/main/java/fiji/plugin/trackmate/helper/TrackMateComputeMetricsPlugin.java new file mode 100644 index 0000000..12ea9fa --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/helper/TrackMateComputeMetricsPlugin.java @@ -0,0 +1,45 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2021 - 2024 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.helper; + +import javax.swing.SwingUtilities; + +import fiji.plugin.trackmate.helper.ui.MetricsLauncherController; +import ij.plugin.PlugIn; +import net.imagej.ImageJ; + +public class TrackMateComputeMetricsPlugin implements PlugIn +{ + + @Override + public void run( final String arg ) + { + SwingUtilities.invokeLater( () -> new MetricsLauncherController() ); + } + + public static void main( final String[] args ) + { + final ImageJ ij = new ImageJ(); + ij.launch( args ); + new TrackMateComputeMetricsPlugin().run( null ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java index de6a1a0..b1a2655 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java +++ b/src/main/java/fiji/plugin/trackmate/helper/ui/MetricsLauncherController.java @@ -46,7 +46,6 @@ import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; import ij.ImagePlus; -import net.imagej.ImageJ; public class MetricsLauncherController implements Cancelable { @@ -246,12 +245,4 @@ public String getCancelReason() { return cancelReason; } - - public static final void main( final String... args ) - { - final ImageJ ij = new ImageJ(); - ij.launch( args ); - new MetricsLauncherController(); - } - } diff --git a/src/main/resources/plugins.config b/src/main/resources/plugins.config index eff4e03..2dfe0bd 100644 --- a/src/main/resources/plugins.config +++ b/src/main/resources/plugins.config @@ -28,3 +28,4 @@ Plugins>Tracking, "TrackMate Batcher", fiji.plugin.trackmate.batcher.TrackMateBatcherPlugin Plugins>Tracking, "TrackMate Helper", fiji.plugin.trackmate.helper.TrackMateParameterSweepPlugin Plugins>Tracking, "TrackMate Helper results inspector", fiji.plugin.trackmate.helper.TrackMateParameterSweepResultsPlugin +Plugins>Tracking, "TrackMate Metrics computation", fiji.plugin.trackmate.helper.TrackMateComputeMetricsPlugin From 4c999ab8ad765e6b2f576aabf55525499490b3be Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 Apr 2024 15:26:17 +0200 Subject: [PATCH 10/10] Launch Helper GUI from the EDT. --- .../trackmate/helper/TrackMateParameterSweepPlugin.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/helper/TrackMateParameterSweepPlugin.java b/src/main/java/fiji/plugin/trackmate/helper/TrackMateParameterSweepPlugin.java index c78980b..a18d620 100644 --- a/src/main/java/fiji/plugin/trackmate/helper/TrackMateParameterSweepPlugin.java +++ b/src/main/java/fiji/plugin/trackmate/helper/TrackMateParameterSweepPlugin.java @@ -21,6 +21,8 @@ */ package fiji.plugin.trackmate.helper; +import javax.swing.SwingUtilities; + import fiji.plugin.trackmate.helper.ui.HelperLauncherController; import ij.plugin.PlugIn; @@ -30,6 +32,6 @@ public class TrackMateParameterSweepPlugin implements PlugIn @Override public void run( final String arg ) { - new HelperLauncherController(); + SwingUtilities.invokeLater( () -> new HelperLauncherController() ); } }