From dc27c902c3a607ef91f19ff4d7c528870d5c8966 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 1 Sep 2023 18:01:15 +0200 Subject: [PATCH 001/263] Add LbKit as a dependency. And remove Gson from now. Apparently there are incompatibilities from the (same) declaration in LabKit pom, which triggers the errors I complained about today on the forum. --- pom.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b282df815..97689a66c 100644 --- a/pom.xml +++ b/pom.xml @@ -156,6 +156,11 @@ sc.fiji fiji-lib + + sc.fiji + labkit-ui + 0.3.12-SNAPSHOT + @@ -208,10 +213,10 @@ com.github.vlsi.mxgraph jgraphx - + com.itextpdf itextpdf From 4bbf6652724bdbaded10f73862321044ec8656ce Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 1 Sep 2023 18:07:14 +0200 Subject: [PATCH 002/263] A LabKit launcher made to edit spots in the current frame. This launcher catches what time-point is currently diplayed, and wrap all information needed for LabKit to edit the spot it contains, as labels. While the user edits the labels in LabKit, the TrackMate UI is disabled. Once the user closes the LabKit window, the modifications they made are inspected. This class compares the new labels with the previous ones, and can determine whether a spot has been added, removed or modified. In the last case it updates the model with the modified spots, and reintroduces it in the tracks as it should. All features of modified spots and their edges are recomputed. If a label has several connected components, they are added as separate spots. The one closest to the original spot is reintroduced in the tracks. The label names are important: they are used to retrieve the original spot id and the original spot shape for comparison. If the user modifies a label, it will be perceived as a new spot instead of a modified one. Tested in 2D so far. --- .../trackmate/gui/editor/LabkitLauncher.java | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java new file mode 100644 index 000000000..f5491e1ec --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -0,0 +1,276 @@ +package fiji.plugin.trackmate.gui.editor; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.util.List; +import java.util.Set; + +import javax.swing.JLabel; +import javax.swing.JRootPane; +import javax.swing.SwingUtilities; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.scijava.Context; +import org.scijava.ui.behaviour.util.AbstractNamedAction; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.detection.MaskUtils; +import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; +import fiji.plugin.trackmate.util.TMUtils; +import ij.ImagePlus; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.axis.AxisType; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.test.ImgLib2Assert; +import net.imglib2.type.logic.BitType; +import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import sc.fiji.labkit.ui.LabkitFrame; +import sc.fiji.labkit.ui.inputimage.DatasetInputImage; +import sc.fiji.labkit.ui.labeling.Label; +import sc.fiji.labkit.ui.labeling.Labeling; +import sc.fiji.labkit.ui.models.DefaultSegmentationModel; +import sc.fiji.labkit.ui.models.ImageLabelingModel; + +public class LabkitLauncher +{ + + private final double[] calibration; + + private final TrackMate trackmate; + + private final EverythingDisablerAndReenabler disabler; + + private Labeling previousLabels; + + private int currentTimePoint; + + private final boolean is2D; + + private final double dt; + + public LabkitLauncher( final TrackMate trackmate, final EverythingDisablerAndReenabler disabler ) + { + this.trackmate = trackmate; + this.disabler = disabler; + final ImagePlus imp = trackmate.getSettings().imp; + this.calibration = TMUtils.getSpatialCalibration( imp ); + this.is2D = DetectionUtils.is2D( imp ); + this.dt = imp.getCalibration().frameInterval; + + } + + @SuppressWarnings( { "rawtypes", "unchecked" } ) + protected void launch() + { + final Context context = TMUtils.getContext(); + final ImagePlus imp = trackmate.getSettings().imp; + final ImgPlus src = TMUtils.rawWraps( imp ); + final int timeAxis = src.dimensionIndex( Axes.TIME ); + + // Reslice for current time-point. + this.currentTimePoint = imp.getFrame() - 1; + final ImgPlus frame = ImgPlusViews.hyperSlice( src, timeAxis, currentTimePoint ); + final DatasetInputImage input = new DatasetInputImage( frame ); + + // Prepare label image. + final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y, Axes.Z, }; + final long[] dims = new long[ is2D ? 2 : 3 ]; + for ( int d = 0; d < dims.length; d++ ) + dims[ d ] = src.dimension( src.dimensionIndex( axes[ d ] ) ); + final Img< UnsignedShortType > lblImg = ArrayImgs.unsignedShorts( dims ); + final double[] calibration = TMUtils.getSpatialCalibration( imp ); + final ImgPlus< UnsignedShortType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); + + // Write spots in it. + final Iterable< Spot > spotsThisFrame = trackmate.getModel().getSpots().iterable( currentTimePoint, true ); + for ( final Spot spot : spotsThisFrame ) + spot.iterable( lblImgPlus ).forEach( p -> p.set( spot.ID() + 1 ) ); + + // Labeling model. + final DefaultSegmentationModel model = new DefaultSegmentationModel( context, input ); + model.imageLabelingModel().labeling().set( Labeling.fromImg( lblImgPlus ) ); + + // Store a copy. + final ImgPlus< UnsignedShortType > copy = lblImgPlus.copy(); + this.previousLabels = Labeling.fromImg( copy ); + + // Show LabKit. + final LabkitFrame labkit = LabkitFrame.show( model, "Edit TrackMate data frame " + ( currentTimePoint + 1 ) ); + labkit.onCloseListeners().addWeakListener( () -> reimportData( model.imageLabelingModel(), currentTimePoint ) ); + } + + private void reimportData( final ImageLabelingModel lm, final int currentTimePoint ) + { + final Model model = trackmate.getModel(); + final SpotCollection spots = model.getSpots(); + final Labeling labeling = lm.labeling().get(); + final List< Label > labels = labeling.getLabels(); + model.beginUpdate(); + try + { + for ( final Label label : labels ) + { + + try + { + final int id = Integer.parseInt( label.name() ) - 1; + final Spot spot = spots.search( id ); + if ( spot == null ) + addNewSpot( label, labeling ); + else + modifySpot( label, labeling, spot ); + + } + catch ( final NumberFormatException nfe ) + { + addNewSpot( label, labeling ); + } + } + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + finally + { + model.endUpdate(); + disabler.reenable(); + } + } + + private void modifySpot( final Label label, final Labeling labeling, final Spot spot ) + { + final Label previousLabel = previousLabels.getLabel( label.name() ); + final RandomAccessibleInterval< BitType > previousRegion = previousLabels.getRegion( previousLabel ); + final RandomAccessibleInterval< BitType > region = labeling.getRegion( label ); + if ( !hasChanged( region, previousRegion ) ) + return; + + final boolean simplify = true; + final int numThreads = 1; + final RandomAccessibleInterval< UnsignedByteType > qualityImage = null; + final List< Spot > spots = MaskUtils.fromMaskWithROI( region, region, calibration, simplify, numThreads, qualityImage ); + + // Time position. + for ( final Spot s : spots ) + s.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + + // If there is no spot, it's because we removed it. + final Model model = trackmate.getModel(); + if ( spots.isEmpty() ) + { + model.removeSpot( spot ); + return; + } + // Hopefully there is only one, if not we pick the closest one. + Spot closest = null; + double minD2 = Double.POSITIVE_INFINITY; + for ( final Spot s : spots ) + { + final double d2 = s.squareDistanceTo( spot ); + if ( d2 < minD2 ) + { + minD2 = d2; + closest = s; + } + } + closest.setName( spot.getName() ); + model.addSpotTo( closest, Integer.valueOf( currentTimePoint ) ); + final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( spot ); + for ( final DefaultWeightedEdge e : edges ) + { + final double weight = model.getTrackModel().getEdgeWeight( e ); + final Spot source = model.getTrackModel().getEdgeSource( e ); + final Spot target = model.getTrackModel().getEdgeTarget( e ); + if ( source == spot ) + model.addEdge( closest, target, weight ); + else if ( target == spot ) + model.addEdge( source, closest, weight ); + else + throw new IllegalArgumentException( "The edge of a spot does not have the spot as source or target?!?" ); + } + model.removeSpot( spot ); + + // If not, add the other ones as new ones. + for ( int i = 1; i < spots.size(); i++ ) + { + final Spot s = spots.get( i ); + s.setName( spot.getName() + "_" + i ); + model.addSpotTo( s, Integer.valueOf( currentTimePoint ) ); + } + } + + private static final boolean hasChanged( final RandomAccessibleInterval< BitType > region, final RandomAccessibleInterval< BitType > previousRegion ) + { + try + { + ImgLib2Assert.assertImageEquals( region, previousRegion ); + } + catch ( final AssertionError e ) + { + return true; + } + return false; + } + + + private void addNewSpot( final Label label, final Labeling labeling ) + { + final boolean simplify = true; + final int numThreads = 1; + final RandomAccessibleInterval< UnsignedByteType > qualityImage = null; + + // Slice by time. + final RandomAccessibleInterval< BitType > region = labeling.getRegion( label ); + final List< Spot > spots = MaskUtils.fromMaskWithROI( region, region, calibration, simplify, numThreads, qualityImage ); + for ( final Spot spot : spots ) + { + spot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + spot.setName( label.name() ); + trackmate.getModel().addSpotTo( spot, Integer.valueOf( currentTimePoint ) ); + } + + } + + public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate ) + { + return new AbstractNamedAction( "launch labkit editor" ) + { + + private static final long serialVersionUID = 1L; + + @Override + public void actionPerformed( final ActionEvent ae ) + { + new Thread( "TrackMate editor thread" ) + { + @Override + public void run() + { + final JRootPane parent = SwingUtilities.getRootPane( ( Component ) ae.getSource() ); + final EverythingDisablerAndReenabler disabler = new EverythingDisablerAndReenabler( parent, new Class[] { JLabel.class } ); + disabler.disable(); + try + { + final LabkitLauncher launcher = new LabkitLauncher( trackmate, disabler ); + launcher.launch(); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + }; + }.start(); + } + }; + } +} From 43f8687dfae9ab03f71c1067d3e7f07283acaab7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 1 Sep 2023 18:08:14 +0200 Subject: [PATCH 003/263] Add a button to launch LabKit in the configure views panel of the wizard. But because the other table, trackscheme and bvv buttons take too much place, we don't see it without resizing the window. --- .../trackmate/gui/components/ConfigureViewsPanel.java | 8 +++++++- .../trackmate/gui/wizard/TrackMateWizardSequence.java | 5 ++--- .../gui/wizard/descriptors/ConfigureViewsDescriptor.java | 4 +++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java index 57a48197d..f1358762a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -79,7 +79,8 @@ public ConfigureViewsPanel( final String spaceUnits, final Action launchTrackSchemeAction, final Action showTrackTablesAction, - final Action showSpotTableAction ) + final Action showSpotTableAction, + final Action launchLabKitAction ) { this.setPreferredSize( new Dimension( 300, 521 ) ); this.setSize( 300, 500 ); @@ -363,6 +364,11 @@ public ConfigureViewsPanel( final JButton btnShowSpotTable = new JButton( showSpotTableAction ); panelButtons.add( btnShowSpotTable ); btnShowSpotTable.setFont( FONT ); + + // Labkit button. + final JButton btnLabKit = new JButton( launchLabKitAction ); + panelButtons.add( btnLabKit ); + btnLabKit.setFont( FONT ); final GridBagConstraints gbcPanelButtons = new GridBagConstraints(); gbcPanelButtons.gridwidth = 2; diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index 63103ea39..04856c18f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -50,6 +50,7 @@ import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.LogPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.editor.LabkitLauncher; import fiji.plugin.trackmate.gui.wizard.descriptors.ActionChooserDescriptor; import fiji.plugin.trackmate.gui.wizard.descriptors.ChooseDetectorDescriptor; import fiji.plugin.trackmate.gui.wizard.descriptors.ChooseTrackerDescriptor; @@ -146,7 +147,7 @@ public TrackMateWizardSequence( final TrackMate trackmate, final SelectionModel chooseTrackerDescriptor = new ChooseTrackerDescriptor( new TrackerProvider(), trackmate ); executeTrackingDescriptor = new ExecuteTrackingDescriptor( trackmate, logPanel ); trackFilterDescriptor = new TrackFilterDescriptor( trackmate, trackFilters, featureSelector ); - configureViewsDescriptor = new ConfigureViewsDescriptor( displaySettings, featureSelector, new LaunchTrackSchemeAction(), new ShowTrackTablesAction(), new ShowSpotTableAction(), model.getSpaceUnits() ); + configureViewsDescriptor = new ConfigureViewsDescriptor( displaySettings, featureSelector, new LaunchTrackSchemeAction(), new ShowTrackTablesAction(), new ShowSpotTableAction(), LabkitLauncher.getLaunchAction( trackmate ), model.getSpaceUnits() ); grapherDescriptor = new GrapherDescriptor( trackmate, selectionModel, displaySettings ); actionChooserDescriptor = new ActionChooserDescriptor( new ActionProvider(), trackmate, selectionModel, displaySettings ); saveDescriptor = new SaveDescriptor( trackmate, displaySettings, this ); @@ -175,7 +176,6 @@ public WizardPanelDescriptor next() return current; } - @Override public WizardPanelDescriptor previous() { @@ -201,7 +201,6 @@ public WizardPanelDescriptor current() return current; } - @Override public WizardPanelDescriptor logDescriptor() { diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java index 1d849386c..7856dd874 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java @@ -39,6 +39,7 @@ public ConfigureViewsDescriptor( final Action launchTrackSchemeAction, final Action showTrackTablesAction, final Action showSpotTableAction, + final Action launchLabkitAction, final String spaceUnits ) { super( KEY ); @@ -48,6 +49,7 @@ public ConfigureViewsDescriptor( spaceUnits, launchTrackSchemeAction, showTrackTablesAction, - showSpotTableAction ); + showSpotTableAction, + launchLabkitAction ); } } From c1e6c09b3be33125d0521724246a8140daff174e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 7 Jul 2024 12:47:44 +0200 Subject: [PATCH 004/263] Do not use a yet non-existing method in MaskUtils. --- .../trackmate/gui/editor/LabkitLauncher.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index f5491e1ec..d36f7a2e3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -20,6 +20,7 @@ import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.detection.MaskUtils; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; +import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; import ij.ImagePlus; import net.imagej.ImgPlus; @@ -93,7 +94,7 @@ protected void launch() // Write spots in it. final Iterable< Spot > spotsThisFrame = trackmate.getModel().getSpots().iterable( currentTimePoint, true ); for ( final Spot spot : spotsThisFrame ) - spot.iterable( lblImgPlus ).forEach( p -> p.set( spot.ID() + 1 ) ); + SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( spot.ID() + 1 ) ); // Labeling model. final DefaultSegmentationModel model = new DefaultSegmentationModel( context, input ); @@ -158,7 +159,15 @@ private void modifySpot( final Label label, final Labeling labeling, final Spot final boolean simplify = true; final int numThreads = 1; final RandomAccessibleInterval< UnsignedByteType > qualityImage = null; - final List< Spot > spots = MaskUtils.fromMaskWithROI( region, region, calibration, simplify, numThreads, qualityImage ); + final double threshold = 0.5; + final List< Spot > spots = MaskUtils.fromThresholdWithROI( + region, + region, + calibration, + threshold, + simplify, + numThreads, + qualityImage ); // Time position. for ( final Spot s : spots ) @@ -231,7 +240,15 @@ private void addNewSpot( final Label label, final Labeling labeling ) // Slice by time. final RandomAccessibleInterval< BitType > region = labeling.getRegion( label ); - final List< Spot > spots = MaskUtils.fromMaskWithROI( region, region, calibration, simplify, numThreads, qualityImage ); + final double threshold = 0.5; + final List< Spot > spots = MaskUtils.fromThresholdWithROI( + region, + region, + calibration, + threshold, + simplify, + numThreads, + qualityImage ); for ( final Spot spot : spots ) { spot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); From bb139c2ceb477ea946c6a4e4aec2db3dbed92bab Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 2 Sep 2023 18:34:50 +0200 Subject: [PATCH 005/263] Wrap the button line of the configure views panel. Otherwise the button for the editor is not visible without resizing the window. I also had to programmatically resize the main TrackMate frame after display so that components are properly aligned. Such a hack for something simple... Also give the editor button a proper name and a temporary icon. --- .../plugin/trackmate/LoadTrackMatePlugIn.java | 12 +- .../gui/components/ConfigureViewsPanel.java | 11 +- .../plugin/trackmate/util/WrapLayout.java | 117 ++++++++++++++++++ 3 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/util/WrapLayout.java diff --git a/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java b/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java index 840076e19..78aee0a46 100644 --- a/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java @@ -24,6 +24,7 @@ import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; import java.awt.Color; +import java.awt.Dimension; import java.io.File; import javax.swing.JFrame; @@ -65,8 +66,6 @@ public class LoadTrackMatePlugIn extends TrackMatePlugIn @Override public void run( final String filePath ) { -// GuiUtils.setSystemLookAndFeel(); - final Logger logger = Logger.IJ_LOGGER; File file; if ( null == filePath || filePath.length() == 0 ) @@ -215,6 +214,8 @@ public void run( final String filePath ) frame.setIconImage( TRACKMATE_ICON.getImage() ); GuiUtils.positionWindow( frame, settings.imp.getWindow() ); frame.setVisible( true ); + final Dimension size = frame.getSize(); + frame.setSize( size.width, size.height + 1 ); // Text final LogPanelDescriptor2 logDescriptor = ( LogPanelDescriptor2 ) sequence.logDescriptor(); @@ -230,7 +231,7 @@ public void run( final String filePath ) final String warning = reader.getErrorMessage(); if ( !warning.isEmpty() ) { - logger2.log( "Warnings occured during reading the file:\n" + logger2.log( "Warnings occurred during reading the file:\n" + "--------------------\n" + warning + "--------------------\n", @@ -292,11 +293,12 @@ protected TmXmlReader createReader( final File lFile ) public static void main( final String[] args ) { + GuiUtils.setSystemLookAndFeel(); ImageJ.main( args ); final LoadTrackMatePlugIn plugIn = new LoadTrackMatePlugIn(); - plugIn.run( null ); +// plugIn.run( null ); // plugIn.run( "samples/FakeTracks.xml" ); -// plugIn.run( "samples/MAX_Merged.xml" ); + plugIn.run( "samples/MAX_Merged.xml" ); // plugIn.run( "c:/Users/tinevez/Development/TrackMateWS/TrackMate-Cellpose/samples/R2_multiC.xml" ); // plugIn.run( "/Users/tinevez/Desktop/230901_DeltaRcsB-ZipA-mCh_timestep5min_Stage9_reg/230901_DeltaRcsB-ZipA-mCh_timestep5min_Stage9_reg_merge65.xml" ); } diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java index f1358762a..fd82589d0 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -50,10 +50,12 @@ import javax.swing.border.LineBorder; import fiji.plugin.trackmate.gui.GuiUtils; +import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.ConfigTrackMateDisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackDisplayMode; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; +import fiji.plugin.trackmate.util.WrapLayout; /** * A configuration panel used to tune the aspect of spots and tracks in multiple @@ -327,7 +329,7 @@ public ConfigureViewsPanel( final GridBagConstraints gbcPanelDrawingZDepth = new GridBagConstraints(); gbcPanelDrawingZDepth.gridwidth = 2; gbcPanelDrawingZDepth.insets = new Insets( 0, 5, 5, 5 ); - gbcPanelDrawingZDepth.fill = GridBagConstraints.BOTH; + gbcPanelDrawingZDepth.fill = GridBagConstraints.HORIZONTAL; gbcPanelDrawingZDepth.gridx = 0; gbcPanelDrawingZDepth.gridy = 5; add( panelDrawingZDepth, gbcPanelDrawingZDepth ); @@ -350,6 +352,7 @@ public ConfigureViewsPanel( */ final JPanel panelButtons = new JPanel(); + panelButtons.setLayout( new WrapLayout() ); // TrackScheme button. final JButton btnShowTrackScheme = new JButton( launchTrackSchemeAction ); @@ -367,9 +370,12 @@ public ConfigureViewsPanel( // Labkit button. final JButton btnLabKit = new JButton( launchLabKitAction ); - panelButtons.add( btnLabKit ); btnLabKit.setFont( FONT ); + btnLabKit.setText( "Launch spot editor" ); + btnLabKit.setIcon( Icons.PENCIL_ICON ); + panelButtons.add( btnLabKit ); + panelButtons.setSize( new Dimension( 300, 1 ) ); final GridBagConstraints gbcPanelButtons = new GridBagConstraints(); gbcPanelButtons.gridwidth = 2; gbcPanelButtons.anchor = GridBagConstraints.SOUTH; @@ -377,6 +383,7 @@ public ConfigureViewsPanel( gbcPanelButtons.gridx = 0; gbcPanelButtons.gridy = 7; add( panelButtons, gbcPanelButtons ); + setSize( new Dimension( 300, 1 ) ); /* * Listeners & co. diff --git a/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java b/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java new file mode 100644 index 000000000..ccd1bac73 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java @@ -0,0 +1,117 @@ +package fiji.plugin.trackmate.util; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Insets; + +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; + +public class WrapLayout extends FlowLayout +{ + private static final long serialVersionUID = 1L; + + public WrapLayout() + { + super(); + } + + public WrapLayout( final int align ) + { + super( align ); + } + + public WrapLayout( final int align, final int hgap, final int vgap ) + { + super( align, hgap, vgap ); + } + + @Override + public Dimension preferredLayoutSize( final Container target ) + { + return layoutSize( target, true ); + } + + @Override + public Dimension minimumLayoutSize( final Container target ) + { + final Dimension minimum = layoutSize( target, false ); + minimum.width -= ( getHgap() + 1 ); + return minimum; + } + + private Dimension layoutSize( final Container target, final boolean preferred ) + { + synchronized ( target.getTreeLock() ) + { + int targetWidth = target.getSize().width; + + if ( targetWidth == 0 ) + targetWidth = Integer.MAX_VALUE; + + final int hgap = getHgap(); + final int vgap = getVgap(); + final Insets insets = target.getInsets(); + final int horizontalInsetsAndGap = insets.left + insets.right + ( hgap * 2 ); + final int maxWidth = targetWidth - horizontalInsetsAndGap; + + final Dimension dim = new Dimension( 0, 0 ); + int rowWidth = 0; + int rowHeight = 0; + + final int nmembers = target.getComponentCount(); + + for ( int i = 0; i < nmembers; i++ ) + { + final Component m = target.getComponent( i ); + + if ( m.isVisible() ) + { + final Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); + + if ( rowWidth + d.width > maxWidth ) + { + addRow( dim, rowWidth, rowHeight ); + rowWidth = 0; + rowHeight = 0; + } + + if ( rowWidth != 0 ) + { + rowWidth += hgap; + } + + rowWidth += d.width; + rowHeight = Math.max( rowHeight, d.height ); + } + } + + addRow( dim, rowWidth, rowHeight ); + + dim.width += horizontalInsetsAndGap; + dim.height += insets.top + insets.bottom + vgap * 2; + + final Container scrollPane = SwingUtilities.getAncestorOfClass( JScrollPane.class, target ); + if ( scrollPane != null ) + { + dim.width -= ( hgap + 1 ); + } + + return dim; + } + } + + private void addRow( final Dimension dim, final int rowWidth, final int rowHeight ) + { + dim.width = Math.max( dim.width, rowWidth ); + + if ( dim.height > 0 ) + { + dim.height += getVgap(); + } + + dim.height += rowHeight; + } +} From 2815d6324d62b69fb6da80041b062a9605580c5d Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 2 Sep 2023 18:44:00 +0200 Subject: [PATCH 006/263] Rework and improve the import method of the spot editor. - We don't depend on labels anymore, but directly operate and compare the index images (before modification and after). Because the index is directly related to the spot ID, we can get a match from previous spot to novel spot in an easy manner. - The spots from the edited version are created directly from the novel index image, using something adapted from the label image detector code, so again, just one pass. We use the fact that we can provide it with a 'quality' image, and read the index of the label image 'under' the spot and write it into its quality value. This way we can retrieve the id of the matching previous spot in an easy manner. - The price to pay for not working with labels anymore is that we don't have access to the label name, but that's life. - We make only one pass over the image to collect the ids of the spots that have been modified, instead of one pass per spot. Also, this pass is multithreaded (thanks LoopBuilder). - I have also learned that I should not use weakListeners() if I am doing something with threads inside the listener. Using listeners() instead works, but I do not know why the other one does not. Probably something arcane with Java WeakReferences being collected. - As a result of all this the performance is much better than before and the 'return to TrackMate' should happen without the user noticing the process. --- .../trackmate/gui/editor/LabkitLauncher.java | 259 +++++++++++------- 1 file changed, 159 insertions(+), 100 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index d36f7a2e3..7a46f7974 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -2,8 +2,14 @@ import java.awt.Component; import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicInteger; import javax.swing.JLabel; import javax.swing.JRootPane; @@ -30,13 +36,12 @@ import net.imglib2.img.Img; import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.display.imagej.ImgPlusViews; -import net.imglib2.test.ImgLib2Assert; -import net.imglib2.type.logic.BitType; -import net.imglib2.type.numeric.integer.UnsignedByteType; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.view.Views; import sc.fiji.labkit.ui.LabkitFrame; import sc.fiji.labkit.ui.inputimage.DatasetInputImage; -import sc.fiji.labkit.ui.labeling.Label; import sc.fiji.labkit.ui.labeling.Labeling; import sc.fiji.labkit.ui.models.DefaultSegmentationModel; import sc.fiji.labkit.ui.models.ImageLabelingModel; @@ -66,13 +71,11 @@ public LabkitLauncher( final TrackMate trackmate, final EverythingDisablerAndRee this.calibration = TMUtils.getSpatialCalibration( imp ); this.is2D = DetectionUtils.is2D( imp ); this.dt = imp.getCalibration().frameInterval; - } @SuppressWarnings( { "rawtypes", "unchecked" } ) protected void launch() { - final Context context = TMUtils.getContext(); final ImagePlus imp = trackmate.getSettings().imp; final ImgPlus src = TMUtils.rawWraps( imp ); final int timeAxis = src.dimensionIndex( Axes.TIME ); @@ -91,12 +94,13 @@ protected void launch() final double[] calibration = TMUtils.getSpatialCalibration( imp ); final ImgPlus< UnsignedShortType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); - // Write spots in it. + // Write spots in it with index = id + 1 final Iterable< Spot > spotsThisFrame = trackmate.getModel().getSpots().iterable( currentTimePoint, true ); for ( final Spot spot : spotsThisFrame ) SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( spot.ID() + 1 ) ); - // Labeling model. + // Make a labeling model from it. + final Context context = TMUtils.getContext(); final DefaultSegmentationModel model = new DefaultSegmentationModel( context, input ); model.imageLabelingModel().labeling().set( Labeling.fromImg( lblImgPlus ) ); @@ -106,34 +110,86 @@ protected void launch() // Show LabKit. final LabkitFrame labkit = LabkitFrame.show( model, "Edit TrackMate data frame " + ( currentTimePoint + 1 ) ); - labkit.onCloseListeners().addWeakListener( () -> reimportData( model.imageLabelingModel(), currentTimePoint ) ); + labkit.onCloseListeners().addListener( () -> reimportData( model.imageLabelingModel(), currentTimePoint ) ); } + @SuppressWarnings( "unchecked" ) private void reimportData( final ImageLabelingModel lm, final int currentTimePoint ) { final Model model = trackmate.getModel(); - final SpotCollection spots = model.getSpots(); - final Labeling labeling = lm.labeling().get(); - final List< Label > labels = labeling.getLabels(); model.beginUpdate(); try { - for ( final Label label : labels ) + /* + * We will update the spots using a comparison based on only the + * index images. + */ + final Labeling labeling = lm.labeling().get(); + final RandomAccessibleInterval< UnsignedShortType > novelIndexImg = ( RandomAccessibleInterval< UnsignedShortType > ) labeling.getIndexImg(); + final RandomAccessibleInterval< UnsignedShortType > previousIndexImg = ( RandomAccessibleInterval< UnsignedShortType > ) previousLabels.getIndexImg(); + + // Map of previous spots against their ID: + final SpotCollection spots = model.getSpots(); + final Map< Integer, Spot > previousSpotIDs = new HashMap<>(); + spots.iterable( currentTimePoint, true ).forEach( s -> previousSpotIDs.put( Integer.valueOf( s.ID() ), s ) ); + + /* + * Get all the spots present in the new image. Because we specified + * the novel label image as 'quality' image, they have a quality + * value equal to the index in the label image (id+1). + */ + final List< Spot > novelSpots = getSpots( novelIndexImg ); + + /* + * Map of novel spots against the ID taken from the index of the + * novel label image. Normally, this index, and hence the novel id, + * corresponds to the id of previous spots. If one of the novel spot + * has an id we cannot find in the previous spot list, it means that + * it is a new one. + * + * Careful! The user might have created several connected components + * with the same label in LabKit, which will result in having + * several spots with the same quality value. We don't want to loose + * them, so the map is that of a id to a list of spots. + */ + final Map< Integer, List< Spot > > novelSpotIDs = new HashMap<>(); + novelSpots.forEach( s -> { + final int id = Integer.valueOf( s.getFeature( Spot.QUALITY ).intValue() - 1 ); + final List< Spot > list = novelSpotIDs.computeIfAbsent( Integer.valueOf( id ), ( i ) -> new ArrayList<>() ); + list.add( s ); + } ); + + // Collect ids of spots that have been modified. id = index - 1 + final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg, previousIndexImg ); + + // Update model for those spots. + for ( final int id : modifiedIDs ) { - - try + final Spot previousSpot = previousSpotIDs.get( Integer.valueOf( id ) ); + final List< Spot > novelSpotList = novelSpotIDs.get( Integer.valueOf( id ) ); + if ( previousSpot == null ) { - final int id = Integer.parseInt( label.name() ) - 1; - final Spot spot = spots.search( id ); - if ( spot == null ) - addNewSpot( label, labeling ); - else - modifySpot( label, labeling, spot ); - + /* + * A new one (possible several) I cannot find in the + * previous list -> add as a new spot. + */ + addNewSpot( novelSpotList ); + } + else if ( novelSpotList == null || novelSpotList.isEmpty() ) + { + /* + * One I add in the previous spot list, but that has + * disappeared. Remove it. + */ + model.removeSpot( previousSpot ); } - catch ( final NumberFormatException nfe ) + else { - addNewSpot( label, labeling ); + /* + * I know of them both. Treat the case as if the previous + * spot was modified. + */ + modifySpot( novelSpotList, previousSpot ); } } } @@ -148,114 +204,117 @@ private void reimportData( final ImageLabelingModel lm, final int currentTimePoi } } - private void modifySpot( final Label label, final Labeling labeling, final Spot spot ) + private void modifySpot( final List< Spot > novelSpotList, final Spot previousSpot ) { - final Label previousLabel = previousLabels.getLabel( label.name() ); - final RandomAccessibleInterval< BitType > previousRegion = previousLabels.getRegion( previousLabel ); - final RandomAccessibleInterval< BitType > region = labeling.getRegion( label ); - if ( !hasChanged( region, previousRegion ) ) - return; - - final boolean simplify = true; - final int numThreads = 1; - final RandomAccessibleInterval< UnsignedByteType > qualityImage = null; - final double threshold = 0.5; - final List< Spot > spots = MaskUtils.fromThresholdWithROI( - region, - region, - calibration, - threshold, - simplify, - numThreads, - qualityImage ); - - // Time position. - for ( final Spot s : spots ) - s.putFeature( Spot.POSITION_T, currentTimePoint * dt ); - - // If there is no spot, it's because we removed it. final Model model = trackmate.getModel(); - if ( spots.isEmpty() ) + + /* + * Hopefully there is only one spot in the novel spot list. If not we + * privilege the one closest to the previous spots. + */ + final Spot mainNovelSpot; + if ( novelSpotList.size() == 1 ) { - model.removeSpot( spot ); - return; + mainNovelSpot = novelSpotList.get( 0 ); } - // Hopefully there is only one, if not we pick the closest one. - Spot closest = null; - double minD2 = Double.POSITIVE_INFINITY; - for ( final Spot s : spots ) + else { - final double d2 = s.squareDistanceTo( spot ); - if ( d2 < minD2 ) + Spot closest = null; + double minD2 = Double.POSITIVE_INFINITY; + for ( final Spot s : novelSpotList ) { - minD2 = d2; - closest = s; + final double d2 = s.squareDistanceTo( previousSpot ); + if ( d2 < minD2 ) + { + minD2 = d2; + closest = s; + } } + mainNovelSpot = closest; } - closest.setName( spot.getName() ); - model.addSpotTo( closest, Integer.valueOf( currentTimePoint ) ); - final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( spot ); + + // Add it properly. + mainNovelSpot.setName( previousSpot.getName() ); + mainNovelSpot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + model.addSpotTo( mainNovelSpot, Integer.valueOf( currentTimePoint ) ); + // Recreate links. + final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( previousSpot ); for ( final DefaultWeightedEdge e : edges ) { final double weight = model.getTrackModel().getEdgeWeight( e ); final Spot source = model.getTrackModel().getEdgeSource( e ); final Spot target = model.getTrackModel().getEdgeTarget( e ); - if ( source == spot ) - model.addEdge( closest, target, weight ); - else if ( target == spot ) - model.addEdge( source, closest, weight ); + if ( source == previousSpot ) + model.addEdge( mainNovelSpot, target, weight ); + else if ( target == previousSpot ) + model.addEdge( source, mainNovelSpot, weight ); else throw new IllegalArgumentException( "The edge of a spot does not have the spot as source or target?!?" ); } - model.removeSpot( spot ); + model.removeSpot( previousSpot ); - // If not, add the other ones as new ones. - for ( int i = 1; i < spots.size(); i++ ) + // Deal with the other ones. + final HashSet< Spot > extraSpots = new HashSet<>( novelSpotList ); + extraSpots.remove( mainNovelSpot ); + int i = 1; + for ( final Spot s: extraSpots ) { - final Spot s = spots.get( i ); - s.setName( spot.getName() + "_" + i ); + s.setName( previousSpot.getName() + "_" + i++ ); + s.putFeature( Spot.POSITION_T, currentTimePoint * dt ); model.addSpotTo( s, Integer.valueOf( currentTimePoint ) ); } } - private static final boolean hasChanged( final RandomAccessibleInterval< BitType > region, final RandomAccessibleInterval< BitType > previousRegion ) + private void addNewSpot( final List< Spot > novelSpotList ) { - try + for ( final Spot spot : novelSpotList ) { - ImgLib2Assert.assertImageEquals( region, previousRegion ); - } - catch ( final AssertionError e ) - { - return true; + spot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + trackmate.getModel().addSpotTo( spot, Integer.valueOf( currentTimePoint ) ); } - return false; } + private static final Set< Integer > getModifiedIDs( final RandomAccessibleInterval< UnsignedShortType > novelIndexImg, final RandomAccessibleInterval< UnsignedShortType > previousIndexImg ) + { + final ConcurrentSkipListSet< Integer > modifiedIDs = new ConcurrentSkipListSet<>(); + LoopBuilder.setImages( novelIndexImg, previousIndexImg ) + .multiThreaded( false ) + .forEachPixel( ( c, p ) -> { + final int ci = c.get(); + final int pi = p.get(); + if ( ci == 0 && pi == 0 ) + return; + if ( ci != pi ) + { + modifiedIDs.add( Integer.valueOf( pi - 1 ) ); + modifiedIDs.add( Integer.valueOf( ci - 1 ) ); + } + } ); + modifiedIDs.remove( Integer.valueOf( -1 ) ); + return modifiedIDs; + } - private void addNewSpot( final Label label, final Labeling labeling ) + private List< Spot > getSpots( final RandomAccessibleInterval< UnsignedShortType > rai ) { + // Get all labels. + final AtomicInteger max = new AtomicInteger( 0 ); + Views.iterable( rai ).forEach( p -> { + final int val = p.getInteger(); + if ( val != 0 && val > max.get() ) + max.set( val ); + } ); + final List< Integer > indices = new ArrayList<>( max.get() ); + for ( int i = 0; i < max.get(); i++ ) + indices.add( Integer.valueOf( i + 1 ) ); + + final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); final boolean simplify = true; - final int numThreads = 1; - final RandomAccessibleInterval< UnsignedByteType > qualityImage = null; - - // Slice by time. - final RandomAccessibleInterval< BitType > region = labeling.getRegion( label ); - final double threshold = 0.5; - final List< Spot > spots = MaskUtils.fromThresholdWithROI( - region, - region, + return MaskUtils.fromLabelingWithROI( + labeling, + labeling, calibration, - threshold, simplify, - numThreads, - qualityImage ); - for ( final Spot spot : spots ) - { - spot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); - spot.setName( label.name() ); - trackmate.getModel().addSpotTo( spot, Integer.valueOf( currentTimePoint ) ); - } - + rai ); } public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate ) From 0fd41934fb68d46d54f20b0fe26c36585f9d7c51 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 2 Sep 2023 19:12:57 +0200 Subject: [PATCH 007/263] Utility to know whether a class is present at runtime. I wanted to use it to check whether the user has activated the 'LabKit' update site, but apparently the labkit jars are included with vanilla Fiji. --- .../fiji/plugin/trackmate/util/TMUtils.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index 0550f0adb..2896511f5 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -865,6 +865,30 @@ public static String getImagePathWithoutExtension( final Settings settings ) } } + /** + * Returns true if the class with the fully qualified name is + * present at runtime. This is useful to detect whether a certain update + * site has been activated in Fiji and to enable or disable component based + * on this. + * + * @param className + * the fully qualified class name, e.g. + * "sc.fiji.labkit.ui.LabkitFrame" + * @return true if the the class with the specified name is + * present, false otherwise. + */ + public static boolean isClassPresent( final String className ) + { + try + { + Class.forName( className, false, TMUtils.class.getClassLoader() ); + return true; + } + catch ( final ClassNotFoundException e1 ) + {} + return false; + } + private TMUtils() {} } From a73f8327d5e18d3f4350d2d7c956ee950855199e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 2 Sep 2023 19:13:19 +0200 Subject: [PATCH 008/263] Make the editor button visible only if LabKit is available. --- .../gui/components/ConfigureViewsPanel.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java index fd82589d0..e537d96e3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -55,6 +55,7 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackDisplayMode; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; +import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.util.WrapLayout; /** @@ -369,11 +370,20 @@ public ConfigureViewsPanel( btnShowSpotTable.setFont( FONT ); // Labkit button. - final JButton btnLabKit = new JButton( launchLabKitAction ); - btnLabKit.setFont( FONT ); - btnLabKit.setText( "Launch spot editor" ); - btnLabKit.setIcon( Icons.PENCIL_ICON ); - panelButtons.add( btnLabKit ); + // Is labkit available? + if ( TMUtils.isClassPresent( "sc.fiji.labkit.ui.LabkitFrame" ) ) + { + System.out.println( "LabKit found." ); // DEBUG + final JButton btnLabKit = new JButton( launchLabKitAction ); + btnLabKit.setFont( FONT ); + btnLabKit.setText( "Launch spot editor" ); + btnLabKit.setIcon( Icons.PENCIL_ICON ); + panelButtons.add( btnLabKit ); + } + else + { + System.out.println( "LabKit not found." ); // DEBUG + } panelButtons.setSize( new Dimension( 300, 1 ) ); final GridBagConstraints gbcPanelButtons = new GridBagConstraints(); From d7b25d6b7c0e14fde06c66a5d7ae57d3cdbbebaf Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 3 Sep 2023 17:38:08 +0200 Subject: [PATCH 009/263] After editing in LbKit, message the user Let them choose to discard or commit the changes. --- .../trackmate/gui/editor/LabkitLauncher.java | 152 +++++++++++------- 1 file changed, 90 insertions(+), 62 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 7a46f7974..06ac1bdfe 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -12,6 +12,7 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JRootPane; import javax.swing.SwingUtilities; @@ -25,6 +26,7 @@ import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.detection.MaskUtils; +import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; @@ -116,8 +118,6 @@ protected void launch() @SuppressWarnings( "unchecked" ) private void reimportData( final ImageLabelingModel lm, final int currentTimePoint ) { - final Model model = trackmate.getModel(); - model.beginUpdate(); try { /* @@ -128,70 +128,99 @@ private void reimportData( final ImageLabelingModel lm, final int currentTimePoi final RandomAccessibleInterval< UnsignedShortType > novelIndexImg = ( RandomAccessibleInterval< UnsignedShortType > ) labeling.getIndexImg(); final RandomAccessibleInterval< UnsignedShortType > previousIndexImg = ( RandomAccessibleInterval< UnsignedShortType > ) previousLabels.getIndexImg(); - // Map of previous spots against their ID: - final SpotCollection spots = model.getSpots(); - final Map< Integer, Spot > previousSpotIDs = new HashMap<>(); - spots.iterable( currentTimePoint, true ).forEach( s -> previousSpotIDs.put( Integer.valueOf( s.ID() ), s ) ); - - /* - * Get all the spots present in the new image. Because we specified - * the novel label image as 'quality' image, they have a quality - * value equal to the index in the label image (id+1). - */ - final List< Spot > novelSpots = getSpots( novelIndexImg ); - - /* - * Map of novel spots against the ID taken from the index of the - * novel label image. Normally, this index, and hence the novel id, - * corresponds to the id of previous spots. If one of the novel spot - * has an id we cannot find in the previous spot list, it means that - * it is a new one. - * - * Careful! The user might have created several connected components - * with the same label in LabKit, which will result in having - * several spots with the same quality value. We don't want to loose - * them, so the map is that of a id to a list of spots. - */ - final Map< Integer, List< Spot > > novelSpotIDs = new HashMap<>(); - novelSpots.forEach( s -> { - final int id = Integer.valueOf( s.getFeature( Spot.QUALITY ).intValue() - 1 ); - final List< Spot > list = novelSpotIDs.computeIfAbsent( Integer.valueOf( id ), ( i ) -> new ArrayList<>() ); - list.add( s ); - } ); - // Collect ids of spots that have been modified. id = index - 1 final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg, previousIndexImg ); - - // Update model for those spots. - for ( final int id : modifiedIDs ) + final int nModified = modifiedIDs.size(); + + if ( nModified == 0 ) + return; + + // Message the user. + final String msg = + "Commit the changes made to the\n" + + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; + final String title = "Commit edits to TrackMate"; + final int returnedValue = JOptionPane.showConfirmDialog( + null, + msg, + title, + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + Icons.TRACKMATE_ICON ); + if ( returnedValue != JOptionPane.YES_OPTION ) + return; + + final Model model = trackmate.getModel(); + model.beginUpdate(); + try { - final Spot previousSpot = previousSpotIDs.get( Integer.valueOf( id ) ); - final List< Spot > novelSpotList = novelSpotIDs.get( Integer.valueOf( id ) ); - if ( previousSpot == null ) - { - /* - * A new one (possible several) I cannot find in the - * previous list -> add as a new spot. - */ - addNewSpot( novelSpotList ); - } - else if ( novelSpotList == null || novelSpotList.isEmpty() ) - { - /* - * One I add in the previous spot list, but that has - * disappeared. Remove it. - */ - model.removeSpot( previousSpot ); - } - else + // Map of previous spots against their ID: + final SpotCollection spots = model.getSpots(); + final Map< Integer, Spot > previousSpotIDs = new HashMap<>(); + spots.iterable( currentTimePoint, true ).forEach( s -> previousSpotIDs.put( Integer.valueOf( s.ID() ), s ) ); + + /* + * Get all the spots present in the new image. Because we + * specified the novel label image as 'quality' image, they have + * a quality value equal to the index in the label image (id+1). + */ + final List< Spot > novelSpots = getSpots( novelIndexImg ); + + /* + * Map of novel spots against the ID taken from the index of the + * novel label image. Normally, this index, and hence the novel + * id, corresponds to the id of previous spots. If one of the + * novel spot has an id we cannot find in the previous spot + * list, it means that it is a new one. + * + * Careful! The user might have created several connected + * components with the same label in LabKit, which will result + * in having several spots with the same quality value. We don't + * want to loose them, so the map is that of a id to a list of + * spots. + */ + final Map< Integer, List< Spot > > novelSpotIDs = new HashMap<>(); + novelSpots.forEach( s -> { + final int id = Integer.valueOf( s.getFeature( Spot.QUALITY ).intValue() - 1 ); + final List< Spot > list = novelSpotIDs.computeIfAbsent( Integer.valueOf( id ), ( i ) -> new ArrayList<>() ); + list.add( s ); + } ); + + // Update model for those spots. + for ( final int id : modifiedIDs ) { - /* - * I know of them both. Treat the case as if the previous - * spot was modified. - */ - modifySpot( novelSpotList, previousSpot ); + final Spot previousSpot = previousSpotIDs.get( Integer.valueOf( id ) ); + final List< Spot > novelSpotList = novelSpotIDs.get( Integer.valueOf( id ) ); + if ( previousSpot == null ) + { + /* + * A new one (possible several) I cannot find in the + * previous list -> add as a new spot. + */ + addNewSpot( novelSpotList ); + } + else if ( novelSpotList == null || novelSpotList.isEmpty() ) + { + /* + * One I add in the previous spot list, but that has + * disappeared. Remove it. + */ + model.removeSpot( previousSpot ); + } + else + { + /* + * I know of them both. Treat the case as if the + * previous spot was modified. + */ + modifySpot( novelSpotList, previousSpot ); + } } } + finally + { + model.endUpdate(); + } } catch ( final Exception e ) { @@ -199,7 +228,6 @@ else if ( novelSpotList == null || novelSpotList.isEmpty() ) } finally { - model.endUpdate(); disabler.reenable(); } } @@ -257,7 +285,7 @@ else if ( target == previousSpot ) final HashSet< Spot > extraSpots = new HashSet<>( novelSpotList ); extraSpots.remove( mainNovelSpot ); int i = 1; - for ( final Spot s: extraSpots ) + for ( final Spot s : extraSpots ) { s.setName( previousSpot.getName() + "_" + i++ ); s.putFeature( Spot.POSITION_T, currentTimePoint * dt ); From 6c23066a04fe558a273dda457b080072aad310f9 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 3 Sep 2023 18:35:50 +0200 Subject: [PATCH 010/263] Copy the channel color, min & max, etc to the editor BDV window. So that the image in the LabKit window opens with the same display settings than in the ImagePlus main view. --- .../trackmate/gui/editor/ImpBdvShowable.java | 177 ++++++++++++++++++ .../trackmate/gui/editor/LabkitLauncher.java | 3 +- 2 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java new file mode 100644 index 000000000..b37bf9d66 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -0,0 +1,177 @@ +package fiji.plugin.trackmate.gui.editor; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import bdv.tools.brightness.ConverterSetup; +import bdv.util.AxisOrder; +import bdv.util.BdvFunctions; +import bdv.util.BdvOptions; +import bdv.util.BdvStackSource; +import bdv.viewer.DisplayMode; +import bdv.viewer.SourceAndConverter; +import bdv.viewer.SynchronizedViewerState; +import bdv.viewer.ViewerState; +import ij.CompositeImage; +import ij.IJ; +import ij.ImagePlus; +import ij.process.LUT; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.axis.AxisType; +import net.imglib2.Interval; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.NumericType; +import sc.fiji.labkit.ui.bdv.BdvShowable; + +/** + * A {@link BdvShowable} from a {@link ImgPlus}, but with channel colors, min & + * max, channel visibility and display mode taken from a specified + * {@link ImagePlus}. + *

+ * Adapted from Matthias Arzt' ImgPlusBdvShowable, reusing code I made for + * Mastodon support of IJ ImagePlus. + * + * @author Jean-Yves Tinevez + */ +public class ImpBdvShowable implements BdvShowable +{ + + public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImgPlus< T > frame, final ImagePlus imp ) + { + return new ImpBdvShowable( frame, imp ); + } + + private final ImgPlus< ? extends NumericType< ? > > image; + + private final ImagePlus imp; + + ImpBdvShowable( final ImgPlus< ? extends NumericType< ? > > image, final ImagePlus imp ) + { + this.image = image; + this.imp = imp; + } + + @Override + public Interval interval() + { + return image; + } + + @Override + public AffineTransform3D transformation() + { + final AffineTransform3D transform = new AffineTransform3D(); + transform.set( + getCalibration( Axes.X ), 0, 0, 0, + 0, getCalibration( Axes.Y ), 0, 0, + 0, 0, getCalibration( Axes.Z ), 0 ); + return transform; + } + + @Override + public BdvStackSource< ? > show( final String title, final BdvOptions options ) + { + final String name = image.getName(); + final BdvOptions options1 = options.axisOrder( getAxisOrder() ).sourceTransform( transformation() ); + final BdvStackSource< ? extends NumericType< ? > > stackSource = BdvFunctions.show( image, name == null + ? title : name, options1 ); + + final List< ConverterSetup > converterSetups = stackSource.getConverterSetups(); + final SynchronizedViewerState state = stackSource.getBdvHandle().getViewerPanel().state(); + + final int numActiveChannels = transferChannelVisibility( state ); + transferChannelSettings( converterSetups ); + state.setDisplayMode( numActiveChannels > 1 ? DisplayMode.FUSED : DisplayMode.SINGLE ); + return stackSource; + } + + private AxisOrder getAxisOrder() + { + final String code = IntStream + .range( 0, image.numDimensions() ) + .mapToObj( i -> image + .axis( i ) + .type() + .getLabel().substring( 0, 1 ) ) + .collect( Collectors.joining() ); + try + { + return AxisOrder.valueOf( code ); + } + catch ( final IllegalArgumentException e ) + { + return AxisOrder.DEFAULT; + } + } + + private double getCalibration( final AxisType axisType ) + { + final int d = image.dimensionIndex( axisType ); + if ( d == -1 ) + return 1; + return image.axis( d ).averageScale( image.min( d ), image.max( d ) ); + } + + private int transferChannelVisibility( final ViewerState state ) + { + final int nChannels = imp.getNChannels(); + final CompositeImage ci = imp.isComposite() ? ( CompositeImage ) imp : null; + final List< SourceAndConverter< ? > > sources = state.getSources(); + if ( ci != null && ci.getCompositeMode() == IJ.COMPOSITE ) + { + final boolean[] activeChannels = ci.getActiveChannels(); + int numActiveChannels = 0; + for ( int i = 0; i < Math.min( activeChannels.length, nChannels ); ++i ) + { + final SourceAndConverter< ? > source = sources.get( i ); + state.setSourceActive( source, activeChannels[ i ] ); + state.setCurrentSource( source ); + numActiveChannels += activeChannels[ i ] ? 1 : 0; + } + return numActiveChannels; + } + else + { + final int activeChannel = imp.getChannel() - 1; + for ( int i = 0; i < nChannels; ++i ) + state.setSourceActive( sources.get( i ), i == activeChannel ); + state.setCurrentSource( sources.get( activeChannel ) ); + return 1; + } + } + + private void transferChannelSettings( final List< ConverterSetup > converterSetups ) + { + final int nChannels = imp.getNChannels(); + final CompositeImage ci = imp.isComposite() ? ( CompositeImage ) imp : null; + if ( ci != null ) + { + final int mode = ci.getCompositeMode(); + final boolean transferColor = mode == IJ.COMPOSITE || mode == IJ.COLOR; + for ( int c = 0; c < nChannels; ++c ) + { + final LUT lut = ci.getChannelLut( c + 1 ); + final ConverterSetup setup = converterSetups.get( c ); + if ( transferColor ) + setup.setColor( new ARGBType( lut.getRGB( 255 ) ) ); + setup.setDisplayRange( lut.min, lut.max ); + } + } + else + { + final double displayRangeMin = imp.getDisplayRangeMin(); + final double displayRangeMax = imp.getDisplayRangeMax(); + for ( int c = 0; c < nChannels; ++c ) + { + final ConverterSetup setup = converterSetups.get( c ); + final LUT[] luts = imp.getLuts(); + if ( luts.length != 0 ) + setup.setColor( new ARGBType( luts[ 0 ].getRGB( 255 ) ) ); + setup.setDisplayRange( displayRangeMin, displayRangeMax ); + } + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 06ac1bdfe..b739f374a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -85,7 +85,8 @@ protected void launch() // Reslice for current time-point. this.currentTimePoint = imp.getFrame() - 1; final ImgPlus frame = ImgPlusViews.hyperSlice( src, timeAxis, currentTimePoint ); - final DatasetInputImage input = new DatasetInputImage( frame ); + final ImpBdvShowable showable = ImpBdvShowable.fromImp( frame, imp ); + final DatasetInputImage input = new DatasetInputImage( frame, showable ); // Prepare label image. final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y, Axes.Z, }; From cf486c431b253f8eaf7560b5fac3bffb10773b4e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 3 Sep 2023 18:41:14 +0200 Subject: [PATCH 011/263] Directly store the index image as previous labels. We don't need the Labeling wrapper. --- .../plugin/trackmate/gui/editor/LabkitLauncher.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index b739f374a..23b685ca5 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -57,7 +57,7 @@ public class LabkitLauncher private final EverythingDisablerAndReenabler disabler; - private Labeling previousLabels; + private ImgPlus< UnsignedShortType > previousIndexImg; private int currentTimePoint; @@ -108,8 +108,7 @@ protected void launch() model.imageLabelingModel().labeling().set( Labeling.fromImg( lblImgPlus ) ); // Store a copy. - final ImgPlus< UnsignedShortType > copy = lblImgPlus.copy(); - this.previousLabels = Labeling.fromImg( copy ); + this.previousIndexImg = lblImgPlus.copy(); // Show LabKit. final LabkitFrame labkit = LabkitFrame.show( model, "Edit TrackMate data frame " + ( currentTimePoint + 1 ) ); @@ -127,10 +126,9 @@ private void reimportData( final ImageLabelingModel lm, final int currentTimePoi */ final Labeling labeling = lm.labeling().get(); final RandomAccessibleInterval< UnsignedShortType > novelIndexImg = ( RandomAccessibleInterval< UnsignedShortType > ) labeling.getIndexImg(); - final RandomAccessibleInterval< UnsignedShortType > previousIndexImg = ( RandomAccessibleInterval< UnsignedShortType > ) previousLabels.getIndexImg(); // Collect ids of spots that have been modified. id = index - 1 - final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg, previousIndexImg ); + final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg ); final int nModified = modifiedIDs.size(); if ( nModified == 0 ) @@ -303,7 +301,7 @@ private void addNewSpot( final List< Spot > novelSpotList ) } } - private static final Set< Integer > getModifiedIDs( final RandomAccessibleInterval< UnsignedShortType > novelIndexImg, final RandomAccessibleInterval< UnsignedShortType > previousIndexImg ) + private final Set< Integer > getModifiedIDs( final RandomAccessibleInterval< UnsignedShortType > novelIndexImg ) { final ConcurrentSkipListSet< Integer > modifiedIDs = new ConcurrentSkipListSet<>(); LoopBuilder.setImages( novelIndexImg, previousIndexImg ) From 73d096a01b3694014bba54713e597949f0a6551d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Sep 2023 13:16:31 +0200 Subject: [PATCH 012/263] Modifed and new spots get a QUALITY value of -1. --- .../java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 23b685ca5..59f3aa58a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -263,6 +263,7 @@ private void modifySpot( final List< Spot > novelSpotList, final Spot previousSp // Add it properly. mainNovelSpot.setName( previousSpot.getName() ); mainNovelSpot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + mainNovelSpot.putFeature( Spot.QUALITY, -1. ); model.addSpotTo( mainNovelSpot, Integer.valueOf( currentTimePoint ) ); // Recreate links. final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( previousSpot ); @@ -288,6 +289,7 @@ else if ( target == previousSpot ) { s.setName( previousSpot.getName() + "_" + i++ ); s.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + s.putFeature( Spot.QUALITY, -1. ); model.addSpotTo( s, Integer.valueOf( currentTimePoint ) ); } } @@ -297,6 +299,7 @@ private void addNewSpot( final List< Spot > novelSpotList ) for ( final Spot spot : novelSpotList ) { spot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + spot.putFeature( Spot.QUALITY, -1. ); trackmate.getModel().addSpotTo( spot, Integer.valueOf( currentTimePoint ) ); } } From 9de374b40839c54f6adfbf770757474d3d3ad87b Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 12 Sep 2023 14:11:36 +0200 Subject: [PATCH 013/263] Fix editing spots in 3D. The LabKit editor worked fine foe us but only in 2D. In 3D the labels imported from the spots into LabKit were off along the Z axis, as if the Z caibration was not handled properly. This was caused by the custom BDV showable I made missing some important preparation steps. I simply copied these steps from the working version of BDVShowabble in the core LabKit code. --- .../trackmate/gui/editor/ImpBdvShowable.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index b37bf9d66..287417135 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -1,5 +1,6 @@ package fiji.plugin.trackmate.gui.editor; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -25,6 +26,7 @@ import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.NumericType; import sc.fiji.labkit.ui.bdv.BdvShowable; +import sc.fiji.labkit.ui.inputimage.ImgPlusViewsOld; /** * A {@link BdvShowable} from a {@link ImgPlus}, but with channel colors, min & @@ -41,7 +43,7 @@ public class ImpBdvShowable implements BdvShowable public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImgPlus< T > frame, final ImagePlus imp ) { - return new ImpBdvShowable( frame, imp ); + return new ImpBdvShowable( prepareImage( frame ), imp ); } private final ImgPlus< ? extends NumericType< ? > > image; @@ -174,4 +176,25 @@ private void transferChannelSettings( final List< ConverterSetup > converterSetu } } } + + private static ImgPlus< ? extends NumericType< ? > > prepareImage( + final ImgPlus< ? extends NumericType< ? > > image ) + { + final List< AxisType > order = Arrays.asList( Axes.X, Axes.Y, Axes.Z, Axes.CHANNEL, + Axes.TIME ); + return ImgPlusViewsOld.sortAxes( labelAxes( image ), order ); + } + + private static ImgPlus< ? extends NumericType< ? > > labelAxes( + final ImgPlus< ? extends NumericType< ? > > image ) + { + if ( image.firstElement() instanceof ARGBType ) + return ImgPlusViewsOld + .fixAxes( image, Arrays.asList( Axes.X, Axes.Y, Axes.Z, Axes.TIME ) ); + if ( image.numDimensions() == 4 ) + return ImgPlusViewsOld.fixAxes( image, Arrays + .asList( Axes.X, Axes.Y, Axes.Z, Axes.TIME, Axes.CHANNEL ) ); + return ImgPlusViewsOld.fixAxes( image, Arrays.asList( Axes.X, Axes.Y, Axes.Z, + Axes.CHANNEL, Axes.TIME ) ); + } } From 3f7dc783926bd80025dbd3f328f19e62103bdaec Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 17 Sep 2023 11:53:08 +0200 Subject: [PATCH 014/263] ImpBdvShowable wraps around the ImgPlusBdvShowable. As suggested by Matthias. --- .../trackmate/gui/editor/ImpBdvShowable.java | 63 +++---------------- 1 file changed, 10 insertions(+), 53 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index 287417135..8a5ffe1b1 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -2,12 +2,8 @@ import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import bdv.tools.brightness.ConverterSetup; -import bdv.util.AxisOrder; -import bdv.util.BdvFunctions; import bdv.util.BdvOptions; import bdv.util.BdvStackSource; import bdv.viewer.DisplayMode; @@ -43,44 +39,32 @@ public class ImpBdvShowable implements BdvShowable public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImgPlus< T > frame, final ImagePlus imp ) { - return new ImpBdvShowable( prepareImage( frame ), imp ); + return new ImpBdvShowable( BdvShowable.wrap( prepareImage( frame ) ), imp ); } - private final ImgPlus< ? extends NumericType< ? > > image; + private final BdvShowable showable; private final ImagePlus imp; - ImpBdvShowable( final ImgPlus< ? extends NumericType< ? > > image, final ImagePlus imp ) + ImpBdvShowable( final BdvShowable showable, final ImagePlus imp ) { - this.image = image; + this.showable = showable; this.imp = imp; } @Override - public Interval interval() - { - return image; + public Interval interval() { + return showable.interval(); } @Override - public AffineTransform3D transformation() - { - final AffineTransform3D transform = new AffineTransform3D(); - transform.set( - getCalibration( Axes.X ), 0, 0, 0, - 0, getCalibration( Axes.Y ), 0, 0, - 0, 0, getCalibration( Axes.Z ), 0 ); - return transform; + public AffineTransform3D transformation() { + return showable.transformation(); } @Override - public BdvStackSource< ? > show( final String title, final BdvOptions options ) - { - final String name = image.getName(); - final BdvOptions options1 = options.axisOrder( getAxisOrder() ).sourceTransform( transformation() ); - final BdvStackSource< ? extends NumericType< ? > > stackSource = BdvFunctions.show( image, name == null - ? title : name, options1 ); - + public BdvStackSource< ? > show( final String title, final BdvOptions options ) { + final BdvStackSource stackSource = showable.show(title, options); final List< ConverterSetup > converterSetups = stackSource.getConverterSetups(); final SynchronizedViewerState state = stackSource.getBdvHandle().getViewerPanel().state(); @@ -90,33 +74,6 @@ public AffineTransform3D transformation() return stackSource; } - private AxisOrder getAxisOrder() - { - final String code = IntStream - .range( 0, image.numDimensions() ) - .mapToObj( i -> image - .axis( i ) - .type() - .getLabel().substring( 0, 1 ) ) - .collect( Collectors.joining() ); - try - { - return AxisOrder.valueOf( code ); - } - catch ( final IllegalArgumentException e ) - { - return AxisOrder.DEFAULT; - } - } - - private double getCalibration( final AxisType axisType ) - { - final int d = image.dimensionIndex( axisType ); - if ( d == -1 ) - return 1; - return image.axis( d ).averageScale( image.min( d ), image.max( d ) ); - } - private int transferChannelVisibility( final ViewerState state ) { final int nChannels = imp.getNChannels(); From e263f83863e3a50172acba12b50b43137b2af46b Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 17 Sep 2023 13:22:30 +0200 Subject: [PATCH 015/263] The Labkit editor can be launched on all time-points at once. Shift-click on the button. --- .../trackmate/gui/editor/ImpBdvShowable.java | 31 ++ .../trackmate/gui/editor/LabkitImporter.java | 281 ++++++++++++ .../trackmate/gui/editor/LabkitLauncher.java | 428 +++++++----------- 3 files changed, 487 insertions(+), 253 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index 8a5ffe1b1..223a37c9e 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -10,6 +10,7 @@ import bdv.viewer.SourceAndConverter; import bdv.viewer.SynchronizedViewerState; import bdv.viewer.ViewerState; +import fiji.plugin.trackmate.util.TMUtils; import ij.CompositeImage; import ij.IJ; import ij.ImagePlus; @@ -37,6 +38,36 @@ public class ImpBdvShowable implements BdvShowable { + /** + * Returns a new {@link BdvShowable} that wraps the specified + * {@link ImagePlus}. The LUT and display settings are read from the + * {@link ImagePlus}. + * + * @param + * the pixel type. + * @param imp + * the {@link ImagePlus} to wrap and read LUT and display + * settings from. + * @return a new {@link BdvShowable} + */ + public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImagePlus imp ) + { + final ImgPlus< T > src = TMUtils.rawWraps( imp ); + return fromImp( src, imp ); + } + + /** + * Returns a new {@link BdvShowable} for the specified image, but using the + * LUT and display settings of the specified {@link ImagePlus}. + * + * @param + * the pixel type. + * @param frame + * the image to wrap in a {@link BdvShowable}. + * @param imp + * the {@link ImagePlus} to read LUT and display settings from. + * @return a new {@link BdvShowable} + */ public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImgPlus< T > frame, final ImagePlus imp ) { return new ImpBdvShowable( BdvShowable.wrap( prepareImage( frame ) ), imp ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java new file mode 100644 index 000000000..420828128 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -0,0 +1,281 @@ +package fiji.plugin.trackmate.gui.editor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jgrapht.graph.DefaultWeightedEdge; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.detection.MaskUtils; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.roi.labeling.ImgLabeling; +import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.view.Views; + +/** + * Re-import the edited segmentation made in Labkit into the TrackMate model it + * started from. + */ +public class LabkitImporter +{ + + private final Model model; + + private final double[] calibration; + + private final double dt; + + /** + * Creates a new re-importer. + * + * @param model + * the model to add, remove or edit spots in. + * @param currentTimePoint + * the time-point used for editing. If negative, then all the + * time-points in the input movie will be processed. + * @param calibration + * the spatial calibration array: [dx, dy dz]. + * @param dt + * the frame interval. + */ + public LabkitImporter( + final Model model, + final double[] calibration, + final double dt ) + { + this.model = model; + this.calibration = calibration; + this.dt = dt; + } + + /** + * Re-import the specified label image (specified by its index image) into + * the TrackMate model. The label images must corresponds to one time-point. + *

+ * The index image after modification is compared with the original one, and + * modifications are detected. Spots corresponding to modifications are + * added, removed or edited in the TrackMate model. + *

+ * To properly detect modifications, the indices in the label images must + * correspond to the spot ID + 1 (index = id + 1). + * + * @param novelIndexImg + * the new index image of the labeling model, that represents the + * TrackMate model in the specified time-point after + * modification. + * @param previousIndexImg + * the previous index image, that represents the TrackMate model + * before modification. + * @param currentTimePoint + * the time-point in the TrackMate model that corresponds to the + * index image. + */ + public void reimport( + final RandomAccessibleInterval< UnsignedShortType > novelIndexImg, + final RandomAccessibleInterval< UnsignedShortType > previousIndexImg, + final int currentTimePoint ) + { + + // Collect ids of spots that have been modified. id = index - 1 + final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg, previousIndexImg ); + final int nModified = modifiedIDs.size(); + + if ( nModified == 0 ) + return; + + model.beginUpdate(); + try + { + // Map of previous spots against their ID: + final SpotCollection spots = model.getSpots(); + final Map< Integer, Spot > previousSpotIDs = new HashMap<>(); + spots.iterable( currentTimePoint, true ).forEach( s -> previousSpotIDs.put( Integer.valueOf( s.ID() ), s ) ); + + /* + * Get all the spots present in the new image. Because we specified + * the novel label image as 'quality' image, they have a quality + * value equal to the index in the label image (id+1). + */ + final List< Spot > novelSpots = getSpots( novelIndexImg ); + + /* + * Map of novel spots against the ID taken from the index of the + * novel label image. Normally, this index, and hence the novel id, + * corresponds to the id of previous spots. If one of the novel spot + * has an id we cannot find in the previous spot list, it means that + * it is a new one. + * + * Careful! The user might have created several connected components + * with the same label in LabKit, which will result in having + * several spots with the same quality value. We don't want to loose + * them, so the map is that of a id to a list of spots. + */ + final Map< Integer, List< Spot > > novelSpotIDs = new HashMap<>(); + novelSpots.forEach( s -> { + final int id = Integer.valueOf( s.getFeature( Spot.QUALITY ).intValue() - 1 ); + final List< Spot > list = novelSpotIDs.computeIfAbsent( Integer.valueOf( id ), ( i ) -> new ArrayList<>() ); + list.add( s ); + } ); + + // Update model for those spots. + for ( final int id : modifiedIDs ) + { + final Spot previousSpot = previousSpotIDs.get( Integer.valueOf( id ) ); + final List< Spot > novelSpotList = novelSpotIDs.get( Integer.valueOf( id ) ); + if ( previousSpot == null ) + { + /* + * A new one (possible several) I cannot find in the + * previous list -> add as a new spot. + */ + addNewSpot( novelSpotList, currentTimePoint ); + } + else if ( novelSpotList == null || novelSpotList.isEmpty() ) + { + /* + * One I add in the previous spot list, but that has + * disappeared. Remove it. + */ + model.removeSpot( previousSpot ); + } + else + { + /* + * I know of them both. Treat the case as if the previous + * spot was modified. + */ + modifySpot( novelSpotList, previousSpot, currentTimePoint ); + } + } + } + finally + { + model.endUpdate(); + } + } + + private void modifySpot( final List< Spot > novelSpotList, final Spot previousSpot, final int currentTimePoint ) + { + /* + * Hopefully there is only one spot in the novel spot list. If not we + * privilege the one closest to the previous spots. + */ + final Spot mainNovelSpot; + if ( novelSpotList.size() == 1 ) + { + mainNovelSpot = novelSpotList.get( 0 ); + } + else + { + Spot closest = null; + double minD2 = Double.POSITIVE_INFINITY; + for ( final Spot s : novelSpotList ) + { + final double d2 = s.squareDistanceTo( previousSpot ); + if ( d2 < minD2 ) + { + minD2 = d2; + closest = s; + } + } + mainNovelSpot = closest; + } + + // Add it properly. + mainNovelSpot.setName( previousSpot.getName() ); + mainNovelSpot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + mainNovelSpot.putFeature( Spot.QUALITY, -1. ); + model.addSpotTo( mainNovelSpot, Integer.valueOf( currentTimePoint ) ); + // Recreate links. + final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( previousSpot ); + for ( final DefaultWeightedEdge e : edges ) + { + final double weight = model.getTrackModel().getEdgeWeight( e ); + final Spot source = model.getTrackModel().getEdgeSource( e ); + final Spot target = model.getTrackModel().getEdgeTarget( e ); + if ( source == previousSpot ) + model.addEdge( mainNovelSpot, target, weight ); + else if ( target == previousSpot ) + model.addEdge( source, mainNovelSpot, weight ); + else + throw new IllegalArgumentException( "The edge of a spot does not have the spot as source or target?!?" ); + } + model.removeSpot( previousSpot ); + + // Deal with the other ones. + final HashSet< Spot > extraSpots = new HashSet<>( novelSpotList ); + extraSpots.remove( mainNovelSpot ); + int i = 1; + for ( final Spot s : extraSpots ) + { + s.setName( previousSpot.getName() + "_" + i++ ); + s.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + s.putFeature( Spot.QUALITY, -1. ); + model.addSpotTo( s, Integer.valueOf( currentTimePoint ) ); + } + } + + private void addNewSpot( final List< Spot > novelSpotList, final int currentTimePoint ) + { + for ( final Spot spot : novelSpotList ) + { + spot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); + spot.putFeature( Spot.QUALITY, -1. ); + model.addSpotTo( spot, Integer.valueOf( currentTimePoint ) ); + } + } + + private final Set< Integer > getModifiedIDs( + final RandomAccessibleInterval< UnsignedShortType > novelIndexImg, + final RandomAccessibleInterval< UnsignedShortType > previousIndexImg ) + { + final ConcurrentSkipListSet< Integer > modifiedIDs = new ConcurrentSkipListSet<>(); + LoopBuilder.setImages( novelIndexImg, previousIndexImg ) + .multiThreaded( false ) + .forEachPixel( ( c, p ) -> { + final int ci = c.get(); + final int pi = p.get(); + if ( ci == 0 && pi == 0 ) + return; + if ( ci != pi ) + { + modifiedIDs.add( Integer.valueOf( pi - 1 ) ); + modifiedIDs.add( Integer.valueOf( ci - 1 ) ); + } + } ); + modifiedIDs.remove( Integer.valueOf( -1 ) ); + return modifiedIDs; + } + + private List< Spot > getSpots( final RandomAccessibleInterval< UnsignedShortType > rai ) + { + // Get all labels. + final AtomicInteger max = new AtomicInteger( 0 ); + Views.iterable( rai ).forEach( p -> { + final int val = p.getInteger(); + if ( val != 0 && val > max.get() ) + max.set( val ); + } ); + final List< Integer > indices = new ArrayList<>( max.get() ); + for ( int i = 0; i < max.get(); i++ ) + indices.add( Integer.valueOf( i + 1 ) ); + + final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); + final boolean simplify = true; + return MaskUtils.fromLabelingWithROI( + labeling, + labeling, + calibration, + simplify, + rai ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 59f3aa58a..552a71b93 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -2,30 +2,20 @@ import java.awt.Component; import java.awt.event.ActionEvent; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentSkipListSet; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JRootPane; import javax.swing.SwingUtilities; -import org.jgrapht.graph.DefaultWeightedEdge; import org.scijava.Context; import org.scijava.ui.behaviour.util.AbstractNamedAction; -import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.detection.DetectionUtils; -import fiji.plugin.trackmate.detection.MaskUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; import fiji.plugin.trackmate.util.SpotUtil; @@ -39,14 +29,12 @@ import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.loops.LoopBuilder; -import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.type.numeric.integer.UnsignedShortType; import net.imglib2.view.Views; import sc.fiji.labkit.ui.LabkitFrame; import sc.fiji.labkit.ui.inputimage.DatasetInputImage; import sc.fiji.labkit.ui.labeling.Labeling; import sc.fiji.labkit.ui.models.DefaultSegmentationModel; -import sc.fiji.labkit.ui.models.ImageLabelingModel; public class LabkitLauncher { @@ -57,50 +45,29 @@ public class LabkitLauncher private final EverythingDisablerAndReenabler disabler; - private ImgPlus< UnsignedShortType > previousIndexImg; - private int currentTimePoint; - private final boolean is2D; - - private final double dt; - public LabkitLauncher( final TrackMate trackmate, final EverythingDisablerAndReenabler disabler ) { this.trackmate = trackmate; this.disabler = disabler; final ImagePlus imp = trackmate.getSettings().imp; this.calibration = TMUtils.getSpatialCalibration( imp ); - this.is2D = DetectionUtils.is2D( imp ); - this.dt = imp.getCalibration().frameInterval; } - @SuppressWarnings( { "rawtypes", "unchecked" } ) - protected void launch() + /** + * Launches the Labkit editor. + * + * @param singleTimePoint + * if true, will launch the editor using only the + * time-point currently displayed in the main view. Otherwise, + * will edit all time-points. + */ + protected void launch( final boolean singleTimePoint ) { final ImagePlus imp = trackmate.getSettings().imp; - final ImgPlus src = TMUtils.rawWraps( imp ); - final int timeAxis = src.dimensionIndex( Axes.TIME ); - - // Reslice for current time-point. - this.currentTimePoint = imp.getFrame() - 1; - final ImgPlus frame = ImgPlusViews.hyperSlice( src, timeAxis, currentTimePoint ); - final ImpBdvShowable showable = ImpBdvShowable.fromImp( frame, imp ); - final DatasetInputImage input = new DatasetInputImage( frame, showable ); - - // Prepare label image. - final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y, Axes.Z, }; - final long[] dims = new long[ is2D ? 2 : 3 ]; - for ( int d = 0; d < dims.length; d++ ) - dims[ d ] = src.dimension( src.dimensionIndex( axes[ d ] ) ); - final Img< UnsignedShortType > lblImg = ArrayImgs.unsignedShorts( dims ); - final double[] calibration = TMUtils.getSpatialCalibration( imp ); - final ImgPlus< UnsignedShortType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); - - // Write spots in it with index = id + 1 - final Iterable< Spot > spotsThisFrame = trackmate.getModel().getSpots().iterable( currentTimePoint, true ); - for ( final Spot spot : spotsThisFrame ) - SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( spot.ID() + 1 ) ); + final DatasetInputImage input = makeInput( imp, singleTimePoint ); + final ImgPlus< UnsignedShortType > lblImgPlus = makeLblImage( imp, trackmate.getModel().getSpots(), singleTimePoint ); // Make a labeling model from it. final Context context = TMUtils.getContext(); @@ -108,243 +75,193 @@ protected void launch() model.imageLabelingModel().labeling().set( Labeling.fromImg( lblImgPlus ) ); // Store a copy. - this.previousIndexImg = lblImgPlus.copy(); + final ImgPlus< UnsignedShortType > previousIndexImg = lblImgPlus.copy(); // Show LabKit. final LabkitFrame labkit = LabkitFrame.show( model, "Edit TrackMate data frame " + ( currentTimePoint + 1 ) ); - labkit.onCloseListeners().addListener( () -> reimportData( model.imageLabelingModel(), currentTimePoint ) ); - } - @SuppressWarnings( "unchecked" ) - private void reimportData( final ImageLabelingModel lm, final int currentTimePoint ) - { - try - { - /* - * We will update the spots using a comparison based on only the - * index images. - */ - final Labeling labeling = lm.labeling().get(); - final RandomAccessibleInterval< UnsignedShortType > novelIndexImg = ( RandomAccessibleInterval< UnsignedShortType > ) labeling.getIndexImg(); - - // Collect ids of spots that have been modified. id = index - 1 - final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg ); - final int nModified = modifiedIDs.size(); - - if ( nModified == 0 ) - return; - - // Message the user. - final String msg = - "Commit the changes made to the\n" - + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; - final String title = "Commit edits to TrackMate"; - final int returnedValue = JOptionPane.showConfirmDialog( - null, - msg, - title, - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE, - Icons.TRACKMATE_ICON ); - if ( returnedValue != JOptionPane.YES_OPTION ) - return; - - final Model model = trackmate.getModel(); - model.beginUpdate(); + // Prepare re-importer. + final double dt = imp.getCalibration().frameInterval; + final LabkitImporter reimporter = new LabkitImporter( trackmate.getModel(), calibration, dt ); + labkit.onCloseListeners().addListener( () -> { try { - // Map of previous spots against their ID: - final SpotCollection spots = model.getSpots(); - final Map< Integer, Spot > previousSpotIDs = new HashMap<>(); - spots.iterable( currentTimePoint, true ).forEach( s -> previousSpotIDs.put( Integer.valueOf( s.ID() ), s ) ); - - /* - * Get all the spots present in the new image. Because we - * specified the novel label image as 'quality' image, they have - * a quality value equal to the index in the label image (id+1). - */ - final List< Spot > novelSpots = getSpots( novelIndexImg ); - - /* - * Map of novel spots against the ID taken from the index of the - * novel label image. Normally, this index, and hence the novel - * id, corresponds to the id of previous spots. If one of the - * novel spot has an id we cannot find in the previous spot - * list, it means that it is a new one. - * - * Careful! The user might have created several connected - * components with the same label in LabKit, which will result - * in having several spots with the same quality value. We don't - * want to loose them, so the map is that of a id to a list of - * spots. - */ - final Map< Integer, List< Spot > > novelSpotIDs = new HashMap<>(); - novelSpots.forEach( s -> { - final int id = Integer.valueOf( s.getFeature( Spot.QUALITY ).intValue() - 1 ); - final List< Spot > list = novelSpotIDs.computeIfAbsent( Integer.valueOf( id ), ( i ) -> new ArrayList<>() ); - list.add( s ); - } ); - - // Update model for those spots. - for ( final int id : modifiedIDs ) + @SuppressWarnings( "unchecked" ) + final RandomAccessibleInterval< UnsignedShortType > indexImg = ( RandomAccessibleInterval< UnsignedShortType > ) model.imageLabelingModel().labeling().get().getIndexImg(); + + // Do we have something to reimport? + final AtomicBoolean modified = new AtomicBoolean( false ); + LoopBuilder.setImages( previousIndexImg, indexImg ) + .multiThreaded() + .forEachChunk( chunk -> { + if ( modified.get() ) + return null; + chunk.forEachPixel( ( p1, p2 ) -> { + if ( p1.get() != p2.get() ) + { + modified.set( true ); + return; + } + } ); + return null; + } ); + if ( !modified.get() ) + return; + + // Message the user. + final String msg = ( currentTimePoint < 0 ) + ? "Commit the changes made to the\n" + + "segmentation in whole movie?" + : "Commit the changes made to the\n" + + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; + final String title = "Commit edits to TrackMate"; + final int returnedValue = JOptionPane.showConfirmDialog( + null, + msg, + title, + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + Icons.TRACKMATE_ICON ); + if ( returnedValue != JOptionPane.YES_OPTION ) + return; + + final int timeDim = 2; + if ( currentTimePoint < 0 ) { - final Spot previousSpot = previousSpotIDs.get( Integer.valueOf( id ) ); - final List< Spot > novelSpotList = novelSpotIDs.get( Integer.valueOf( id ) ); - if ( previousSpot == null ) - { - /* - * A new one (possible several) I cannot find in the - * previous list -> add as a new spot. - */ - addNewSpot( novelSpotList ); - } - else if ( novelSpotList == null || novelSpotList.isEmpty() ) + // All time-points. + final Logger log = Logger.IJ_LOGGER; + log.setStatus( "Re-importing from Labkit..." ); + for ( int t = 0; t < imp.getNFrames(); t++ ) { - /* - * One I add in the previous spot list, but that has - * disappeared. Remove it. - */ - model.removeSpot( previousSpot ); - } - else - { - /* - * I know of them both. Treat the case as if the - * previous spot was modified. - */ - modifySpot( novelSpotList, previousSpot ); + final RandomAccessibleInterval< UnsignedShortType > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t ); + final RandomAccessibleInterval< UnsignedShortType > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t ); + reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); + log.setProgress( ++t / ( double ) imp.getNFrames() ); } + log.setStatus( "" ); + log.setProgress( 0. ); + } + else + { + // Only one. + reimporter.reimport( indexImg, previousIndexImg, currentTimePoint ); } } + catch ( final Exception e ) + { + e.printStackTrace(); + } finally { - model.endUpdate(); + disabler.reenable(); } - } - catch ( final Exception e ) - { - e.printStackTrace(); - } - finally - { - disabler.reenable(); - } + } ); } - private void modifySpot( final List< Spot > novelSpotList, final Spot previousSpot ) + /** + * Creates a new {@link DatasetInputImage} from the specified + * {@link ImagePlus}. The embedded label image is empty. + * + * @param imp + * the input {@link ImagePlus}. + * @param singleTimePoint + * if true, then the dataset will be created only + * for the time-point currently displayed in the + * {@link ImagePlus}. This time-point is then stored in the + * {@link #currentTimePoint} field. If false, the + * dataset is created for the whole movie and the + * {@link #currentTimePoint} takes the value -1. + * @return a new {@link DatasetInputImage}. + */ + @SuppressWarnings( { "rawtypes", "unchecked" } ) + private final DatasetInputImage makeInput( final ImagePlus imp, final boolean singleTimePoint ) { - final Model model = trackmate.getModel(); - - /* - * Hopefully there is only one spot in the novel spot list. If not we - * privilege the one closest to the previous spots. - */ - final Spot mainNovelSpot; - if ( novelSpotList.size() == 1 ) + final ImgPlus src = TMUtils.rawWraps( imp ); + // Possibly reslice for current time-point. + final ImpBdvShowable showable; + final ImgPlus inputImg; + if ( singleTimePoint ) { - mainNovelSpot = novelSpotList.get( 0 ); + this.currentTimePoint = imp.getFrame() - 1; + final int timeAxis = src.dimensionIndex( Axes.TIME ); + inputImg = ImgPlusViews.hyperSlice( src, timeAxis, currentTimePoint ); + showable = ImpBdvShowable.fromImp( inputImg, imp ); } else { - Spot closest = null; - double minD2 = Double.POSITIVE_INFINITY; - for ( final Spot s : novelSpotList ) - { - final double d2 = s.squareDistanceTo( previousSpot ); - if ( d2 < minD2 ) - { - minD2 = d2; - closest = s; - } - } - mainNovelSpot = closest; + this.currentTimePoint = -1; + showable = ImpBdvShowable.fromImp( imp ); + inputImg = src; } + return new DatasetInputImage( inputImg, showable ); + } - // Add it properly. - mainNovelSpot.setName( previousSpot.getName() ); - mainNovelSpot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); - mainNovelSpot.putFeature( Spot.QUALITY, -1. ); - model.addSpotTo( mainNovelSpot, Integer.valueOf( currentTimePoint ) ); - // Recreate links. - final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( previousSpot ); - for ( final DefaultWeightedEdge e : edges ) - { - final double weight = model.getTrackModel().getEdgeWeight( e ); - final Spot source = model.getTrackModel().getEdgeSource( e ); - final Spot target = model.getTrackModel().getEdgeTarget( e ); - if ( source == previousSpot ) - model.addEdge( mainNovelSpot, target, weight ); - else if ( target == previousSpot ) - model.addEdge( source, mainNovelSpot, weight ); - else - throw new IllegalArgumentException( "The edge of a spot does not have the spot as source or target?!?" ); - } - model.removeSpot( previousSpot ); + /** + * Prepare the label image for annotation. + * + * @param imp + * the source image plus. + * @param spots + * the spot collection. + * @param singleTimePoint + * if true we only annotate one time-point. + * @return a new {@link ImgPlus}. + */ + private ImgPlus< UnsignedShortType > makeLblImage( final ImagePlus imp, final SpotCollection spots, final boolean singleTimePoint ) + { + // Careful: Only works for 2D images! FIXME + + // Axes. + final AxisType[] axes = ( singleTimePoint ) + ? new AxisType[] { Axes.X, Axes.Y } + : new AxisType[] { Axes.X, Axes.Y, Axes.TIME }; - // Deal with the other ones. - final HashSet< Spot > extraSpots = new HashSet<>( novelSpotList ); - extraSpots.remove( mainNovelSpot ); - int i = 1; - for ( final Spot s : extraSpots ) + // N dimensions. + final int timeDim = 2; + final int nDims = singleTimePoint ? 2 : 3; + + // Dimensions. + final long[] dims = new long[ nDims ]; + dims[ 0 ] = imp.getWidth(); + dims[ 1 ] = imp.getHeight(); + if ( !singleTimePoint ) + dims[ timeDim ] = imp.getNFrames(); + + // Raw image. + final Img< UnsignedShortType > lblImg = ArrayImgs.unsignedShorts( dims ); + + // Calibration. + final double[] c = TMUtils.getSpatialCalibration( imp ); + final double[] calibration = new double[ nDims ]; + for ( int i = 0; i < 2; i++ ) + calibration[ i ] = c[ i ]; + if ( !singleTimePoint ) + calibration[ timeDim ] = 1.; + + // Label image holder. + final ImgPlus< UnsignedShortType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); + + // Write spots in it with index = id + 1 + if ( singleTimePoint ) { - s.setName( previousSpot.getName() + "_" + i++ ); - s.putFeature( Spot.POSITION_T, currentTimePoint * dt ); - s.putFeature( Spot.QUALITY, -1. ); - model.addSpotTo( s, Integer.valueOf( currentTimePoint ) ); + processFrame( lblImgPlus, spots, currentTimePoint ); } - } - - private void addNewSpot( final List< Spot > novelSpotList ) - { - for ( final Spot spot : novelSpotList ) + else { - spot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); - spot.putFeature( Spot.QUALITY, -1. ); - trackmate.getModel().addSpotTo( spot, Integer.valueOf( currentTimePoint ) ); + for ( int t = 0; t < imp.getNFrames(); t++ ) + { + final ImgPlus< UnsignedShortType > lblImgPlusThisFrame = ImgPlusViews.hyperSlice( lblImgPlus, timeDim, t ); + processFrame( lblImgPlusThisFrame, spots, t ); + } } + return lblImgPlus; } - private final Set< Integer > getModifiedIDs( final RandomAccessibleInterval< UnsignedShortType > novelIndexImg ) + private static final void processFrame( final ImgPlus< UnsignedShortType > lblImgPlus, final SpotCollection spots, final int t ) { - final ConcurrentSkipListSet< Integer > modifiedIDs = new ConcurrentSkipListSet<>(); - LoopBuilder.setImages( novelIndexImg, previousIndexImg ) - .multiThreaded( false ) - .forEachPixel( ( c, p ) -> { - final int ci = c.get(); - final int pi = p.get(); - if ( ci == 0 && pi == 0 ) - return; - if ( ci != pi ) - { - modifiedIDs.add( Integer.valueOf( pi - 1 ) ); - modifiedIDs.add( Integer.valueOf( ci - 1 ) ); - } - } ); - modifiedIDs.remove( Integer.valueOf( -1 ) ); - return modifiedIDs; - } - - private List< Spot > getSpots( final RandomAccessibleInterval< UnsignedShortType > rai ) - { - // Get all labels. - final AtomicInteger max = new AtomicInteger( 0 ); - Views.iterable( rai ).forEach( p -> { - final int val = p.getInteger(); - if ( val != 0 && val > max.get() ) - max.set( val ); - } ); - final List< Integer > indices = new ArrayList<>( max.get() ); - for ( int i = 0; i < max.get(); i++ ) - indices.add( Integer.valueOf( i + 1 ) ); - - final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); - final boolean simplify = true; - return MaskUtils.fromLabelingWithROI( - labeling, - labeling, - calibration, - simplify, - rai ); + final Iterable< Spot > spotsThisFrame = spots.iterable( t, true ); + for ( final Spot spot : spotsThisFrame ) + SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( spot.ID() + 1 ) ); } public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate ) @@ -362,13 +279,18 @@ public void actionPerformed( final ActionEvent ae ) @Override public void run() { + // Is shift pressed? + final int mod = ae.getModifiers(); + final boolean shiftPressed = ( mod & ActionEvent.SHIFT_MASK ) > 0; + final boolean singleTimepoint = !shiftPressed; + final JRootPane parent = SwingUtilities.getRootPane( ( Component ) ae.getSource() ); final EverythingDisablerAndReenabler disabler = new EverythingDisablerAndReenabler( parent, new Class[] { JLabel.class } ); disabler.disable(); try { final LabkitLauncher launcher = new LabkitLauncher( trackmate, disabler ); - launcher.launch(); + launcher.launch( singleTimepoint ); } catch ( final Exception e ) { From 3757a83158bc0a4880580c6e7bf2faf6a43fc044 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 17 Sep 2023 13:22:47 +0200 Subject: [PATCH 016/263] Add tooltip for the editor button. And remove debug code. --- .../gui/components/ConfigureViewsPanel.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java index e537d96e3..85ddbf7c2 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -373,17 +373,19 @@ public ConfigureViewsPanel( // Is labkit available? if ( TMUtils.isClassPresent( "sc.fiji.labkit.ui.LabkitFrame" ) ) { - System.out.println( "LabKit found." ); // DEBUG final JButton btnLabKit = new JButton( launchLabKitAction ); btnLabKit.setFont( FONT ); btnLabKit.setText( "Launch spot editor" ); btnLabKit.setIcon( Icons.PENCIL_ICON ); + btnLabKit.setToolTipText( "" + + "Launch the Labkit editor to edit spot segmentation
" + + "on the time-point currently displayed in the main
" + + "view." + + "

" + + "Shift + click will launch the editor on all the
" + + "time-points in the movie." ); panelButtons.add( btnLabKit ); } - else - { - System.out.println( "LabKit not found." ); // DEBUG - } panelButtons.setSize( new Dimension( 300, 1 ) ); final GridBagConstraints gbcPanelButtons = new GridBagConstraints(); From f9c0b73c57585acb53a6b63939c63fc1dd5f4c04 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 17 Sep 2023 17:40:20 +0200 Subject: [PATCH 017/263] In Labkit editor, labels receive name and color from spots. I am not so sure it is a good idea for the color... --- .../trackmate/gui/editor/LabkitLauncher.java | 208 +++++++++++------- .../gui/wizard/TrackMateWizardSequence.java | 9 +- 2 files changed, 136 insertions(+), 81 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 552a71b93..34a53fd0c 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -2,6 +2,8 @@ import java.awt.Component; import java.awt.event.ActionEvent; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.JLabel; @@ -16,23 +18,31 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.gui.Icons; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import ij.ImagePlus; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.Img; +import net.imglib2.img.ImgFactory; import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.loops.LoopBuilder; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.util.Util; import net.imglib2.view.Views; import sc.fiji.labkit.ui.LabkitFrame; import sc.fiji.labkit.ui.inputimage.DatasetInputImage; +import sc.fiji.labkit.ui.labeling.Label; import sc.fiji.labkit.ui.labeling.Labeling; import sc.fiji.labkit.ui.models.DefaultSegmentationModel; @@ -47,9 +57,12 @@ public class LabkitLauncher private int currentTimePoint; - public LabkitLauncher( final TrackMate trackmate, final EverythingDisablerAndReenabler disabler ) + private final DisplaySettings ds; + + public LabkitLauncher( final TrackMate trackmate, final DisplaySettings ds, final EverythingDisablerAndReenabler disabler ) { this.trackmate = trackmate; + this.ds = ds; this.disabler = disabler; final ImagePlus imp = trackmate.getSettings().imp; this.calibration = TMUtils.getSpatialCalibration( imp ); @@ -67,95 +80,110 @@ protected void launch( final boolean singleTimePoint ) { final ImagePlus imp = trackmate.getSettings().imp; final DatasetInputImage input = makeInput( imp, singleTimePoint ); - final ImgPlus< UnsignedShortType > lblImgPlus = makeLblImage( imp, trackmate.getModel().getSpots(), singleTimePoint ); + final Labeling labeling = makeLabeling( imp, trackmate.getModel().getSpots(), singleTimePoint ); // Make a labeling model from it. final Context context = TMUtils.getContext(); final DefaultSegmentationModel model = new DefaultSegmentationModel( context, input ); - model.imageLabelingModel().labeling().set( Labeling.fromImg( lblImgPlus ) ); + model.imageLabelingModel().labeling().set( labeling ); // Store a copy. - final ImgPlus< UnsignedShortType > previousIndexImg = lblImgPlus.copy(); + final RandomAccessibleInterval< UnsignedShortType > previousIndexImg = copy( labeling.getIndexImg() ); // Show LabKit. final LabkitFrame labkit = LabkitFrame.show( model, "Edit TrackMate data frame " + ( currentTimePoint + 1 ) ); // Prepare re-importer. final double dt = imp.getCalibration().frameInterval; - final LabkitImporter reimporter = new LabkitImporter( trackmate.getModel(), calibration, dt ); labkit.onCloseListeners().addListener( () -> { - try - { - @SuppressWarnings( "unchecked" ) - final RandomAccessibleInterval< UnsignedShortType > indexImg = ( RandomAccessibleInterval< UnsignedShortType > ) model.imageLabelingModel().labeling().get().getIndexImg(); - - // Do we have something to reimport? - final AtomicBoolean modified = new AtomicBoolean( false ); - LoopBuilder.setImages( previousIndexImg, indexImg ) - .multiThreaded() - .forEachChunk( chunk -> { - if ( modified.get() ) - return null; - chunk.forEachPixel( ( p1, p2 ) -> { - if ( p1.get() != p2.get() ) - { - modified.set( true ); - return; - } - } ); + @SuppressWarnings( "unchecked" ) + final RandomAccessibleInterval< UnsignedShortType > indexImg = ( RandomAccessibleInterval< UnsignedShortType > ) model.imageLabelingModel().labeling().get().getIndexImg(); + reimport( indexImg, previousIndexImg, dt ); + } ); + } + + private void reimport( final RandomAccessibleInterval< UnsignedShortType > indexImg, final RandomAccessibleInterval< UnsignedShortType > previousIndexImg, final double dt ) + { + try + { + // Do we have something to reimport? + final AtomicBoolean modified = new AtomicBoolean( false ); + LoopBuilder.setImages( previousIndexImg, indexImg ) + .multiThreaded() + .forEachChunk( chunk -> { + if ( modified.get() ) return null; + chunk.forEachPixel( ( p1, p2 ) -> { + if ( p1.get() != p2.get() ) + { + modified.set( true ); + return; + } } ); - if ( !modified.get() ) - return; - - // Message the user. - final String msg = ( currentTimePoint < 0 ) - ? "Commit the changes made to the\n" - + "segmentation in whole movie?" - : "Commit the changes made to the\n" - + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; - final String title = "Commit edits to TrackMate"; - final int returnedValue = JOptionPane.showConfirmDialog( - null, - msg, - title, - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE, - Icons.TRACKMATE_ICON ); - if ( returnedValue != JOptionPane.YES_OPTION ) - return; - + return null; + } ); + if ( !modified.get() ) + return; + + // Message the user. + final String msg = ( currentTimePoint < 0 ) + ? "Commit the changes made to the\n" + + "segmentation in whole movie?" + : "Commit the changes made to the\n" + + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; + final String title = "Commit edits to TrackMate"; + final int returnedValue = JOptionPane.showConfirmDialog( + null, + msg, + title, + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + Icons.TRACKMATE_ICON ); + if ( returnedValue != JOptionPane.YES_OPTION ) + return; + + final LabkitImporter reimporter = new LabkitImporter( trackmate.getModel(), calibration, dt ); + if ( currentTimePoint < 0 ) + { + // All time-points. final int timeDim = 2; - if ( currentTimePoint < 0 ) - { - // All time-points. - final Logger log = Logger.IJ_LOGGER; - log.setStatus( "Re-importing from Labkit..." ); - for ( int t = 0; t < imp.getNFrames(); t++ ) - { - final RandomAccessibleInterval< UnsignedShortType > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t ); - final RandomAccessibleInterval< UnsignedShortType > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t ); - reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); - log.setProgress( ++t / ( double ) imp.getNFrames() ); - } - log.setStatus( "" ); - log.setProgress( 0. ); - } - else + final long nTimepoints = indexImg.dimension( timeDim ); + final Logger log = Logger.IJ_LOGGER; + log.setStatus( "Re-importing from Labkit..." ); + for ( int t = 0; t < nTimepoints; t++ ) { - // Only one. - reimporter.reimport( indexImg, previousIndexImg, currentTimePoint ); + final RandomAccessibleInterval< UnsignedShortType > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t ); + final RandomAccessibleInterval< UnsignedShortType > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t ); + reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); + log.setProgress( ++t / ( double ) nTimepoints ); } + log.setStatus( "" ); + log.setProgress( 0. ); } - catch ( final Exception e ) + else { - e.printStackTrace(); + // Only one. + reimporter.reimport( indexImg, previousIndexImg, currentTimePoint ); } - finally - { - disabler.reenable(); - } - } ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + finally + { + disabler.reenable(); + } + } + + private static Img< UnsignedShortType > copy( final RandomAccessibleInterval< ? extends IntegerType< ? > > in ) + { + final ImgFactory< UnsignedShortType > factory = Util.getArrayOrCellImgFactory( in, new UnsignedShortType() ); + final Img< UnsignedShortType > out = factory.create( in ); + LoopBuilder.setImages( in, out ) + .multiThreaded() + .forEachPixel( ( i, o ) -> o.set( i.getInteger() ) ); + return out; } /** @@ -197,7 +225,9 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si } /** - * Prepare the label image for annotation. + * Prepare the label image for annotation. The labeling is created and each + * of its labels receive the name and the color from the spot it is created + * from. * * @param imp * the source image plus. @@ -205,9 +235,9 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si * the spot collection. * @param singleTimePoint * if true we only annotate one time-point. - * @return a new {@link ImgPlus}. + * @return a new {@link Labeling}. */ - private ImgPlus< UnsignedShortType > makeLblImage( final ImagePlus imp, final SpotCollection spots, final boolean singleTimePoint ) + private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, final boolean singleTimePoint ) { // Careful: Only works for 2D images! FIXME @@ -241,30 +271,48 @@ private ImgPlus< UnsignedShortType > makeLblImage( final ImagePlus imp, final Sp // Label image holder. final ImgPlus< UnsignedShortType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); - // Write spots in it with index = id + 1 + // Write spots in it with index = id + 1 and build a map index -> spot. + final Map< Integer, Spot > spotIDs = new HashMap<>(); if ( singleTimePoint ) { - processFrame( lblImgPlus, spots, currentTimePoint ); + processFrame( lblImgPlus, spots, currentTimePoint, spotIDs ); } else { for ( int t = 0; t < imp.getNFrames(); t++ ) { final ImgPlus< UnsignedShortType > lblImgPlusThisFrame = ImgPlusViews.hyperSlice( lblImgPlus, timeDim, t ); - processFrame( lblImgPlusThisFrame, spots, t ); + processFrame( lblImgPlusThisFrame, spots, t, spotIDs ); } } - return lblImgPlus; + final Labeling labeling = Labeling.fromImg( lblImgPlus ); + + // Fine tune labels name and color. + final FeatureColorGenerator< Spot > colorGen = FeatureUtils.createSpotColorGenerator( trackmate.getModel(), ds ); + for ( final Label label : labeling.getLabels() ) + { + final String name = label.name(); + final int index = Integer.parseInt( name ); + final Spot spot = spotIDs.get( index ); + label.setName( spot.getName() ); + label.setColor( new ARGBType( colorGen.color( spot ).getRGB() ) ); + } + + return labeling; } - private static final void processFrame( final ImgPlus< UnsignedShortType > lblImgPlus, final SpotCollection spots, final int t ) + private static final void processFrame( final ImgPlus< UnsignedShortType > lblImgPlus, final SpotCollection spots, final int t, final Map< Integer, Spot > spotIDs ) { final Iterable< Spot > spotsThisFrame = spots.iterable( t, true ); for ( final Spot spot : spotsThisFrame ) - SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( spot.ID() + 1 ) ); + { + final int index = spot.ID() + 1; + SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) ); + spotIDs.put( index, spot ); + } } - public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate ) + public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate, final DisplaySettings ds ) { return new AbstractNamedAction( "launch labkit editor" ) { @@ -289,7 +337,7 @@ public void run() disabler.disable(); try { - final LabkitLauncher launcher = new LabkitLauncher( trackmate, disabler ); + final LabkitLauncher launcher = new LabkitLauncher( trackmate, ds, disabler ); launcher.launch( singleTimepoint ); } catch ( final Exception e ) diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index 04856c18f..9ab663389 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -147,7 +147,14 @@ public TrackMateWizardSequence( final TrackMate trackmate, final SelectionModel chooseTrackerDescriptor = new ChooseTrackerDescriptor( new TrackerProvider(), trackmate ); executeTrackingDescriptor = new ExecuteTrackingDescriptor( trackmate, logPanel ); trackFilterDescriptor = new TrackFilterDescriptor( trackmate, trackFilters, featureSelector ); - configureViewsDescriptor = new ConfigureViewsDescriptor( displaySettings, featureSelector, new LaunchTrackSchemeAction(), new ShowTrackTablesAction(), new ShowSpotTableAction(), LabkitLauncher.getLaunchAction( trackmate ), model.getSpaceUnits() ); + configureViewsDescriptor = new ConfigureViewsDescriptor( + displaySettings, + featureSelector, + new LaunchTrackSchemeAction(), + new ShowTrackTablesAction(), + new ShowSpotTableAction(), + LabkitLauncher.getLaunchAction( trackmate, displaySettings ), + model.getSpaceUnits() ); grapherDescriptor = new GrapherDescriptor( trackmate, selectionModel, displaySettings ); actionChooserDescriptor = new ActionChooserDescriptor( new ActionProvider(), trackmate, selectionModel, displaySettings ); saveDescriptor = new SaveDescriptor( trackmate, displaySettings, this ); From ee3b86add17c879d3f2649d8dd698ae91fc7d000 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 17 Sep 2023 18:06:18 +0200 Subject: [PATCH 018/263] Better editor frame title. --- .../fiji/plugin/trackmate/gui/editor/LabkitLauncher.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 34a53fd0c..c76b27ff5 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -91,7 +91,10 @@ protected void launch( final boolean singleTimePoint ) final RandomAccessibleInterval< UnsignedShortType > previousIndexImg = copy( labeling.getIndexImg() ); // Show LabKit. - final LabkitFrame labkit = LabkitFrame.show( model, "Edit TrackMate data frame " + ( currentTimePoint + 1 ) ); + String title = "Editing TrackMate data for " + imp.getShortTitle(); + if ( singleTimePoint ) + title += "at frame " + ( currentTimePoint + 1 ); + final LabkitFrame labkit = LabkitFrame.show( model, title ); // Prepare re-importer. final double dt = imp.getCalibration().frameInterval; From 8aaf763e8cb14389ae5f822a1475237c957bba1d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 19 Sep 2023 15:40:07 +0200 Subject: [PATCH 019/263] Properly handle 2D and 3D with or without time in the editor. --- .../trackmate/gui/editor/LabkitLauncher.java | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index c76b27ff5..0b4cd461f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -18,6 +18,7 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -59,6 +60,8 @@ public class LabkitLauncher private final DisplaySettings ds; + private final boolean is3D; + public LabkitLauncher( final TrackMate trackmate, final DisplaySettings ds, final EverythingDisablerAndReenabler disabler ) { this.trackmate = trackmate; @@ -66,6 +69,7 @@ public LabkitLauncher( final TrackMate trackmate, final DisplaySettings ds, fina this.disabler = disabler; final ImagePlus imp = trackmate.getSettings().imp; this.calibration = TMUtils.getSpatialCalibration( imp ); + this.is3D = !DetectionUtils.is2D( imp ); } /** @@ -149,14 +153,13 @@ private void reimport( final RandomAccessibleInterval< UnsignedShortType > index if ( currentTimePoint < 0 ) { // All time-points. - final int timeDim = 2; - final long nTimepoints = indexImg.dimension( timeDim ); + final long nTimepoints = indexImg.dimension( 3 ); final Logger log = Logger.IJ_LOGGER; log.setStatus( "Re-importing from Labkit..." ); for ( int t = 0; t < nTimepoints; t++ ) { - final RandomAccessibleInterval< UnsignedShortType > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t ); - final RandomAccessibleInterval< UnsignedShortType > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t ); + final RandomAccessibleInterval< UnsignedShortType > novelIndexImgThisFrame = Views.hyperSlice( indexImg, 3, t ); + final RandomAccessibleInterval< UnsignedShortType > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, 3, t ); reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); log.setProgress( ++t / ( double ) nTimepoints ); } @@ -242,23 +245,29 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si */ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, final boolean singleTimePoint ) { - // Careful: Only works for 2D images! FIXME - // Axes. - final AxisType[] axes = ( singleTimePoint ) - ? new AxisType[] { Axes.X, Axes.Y } - : new AxisType[] { Axes.X, Axes.Y, Axes.TIME }; + final AxisType[] axes = ( is3D ) + ? ( singleTimePoint ) + ? new AxisType[] { Axes.X, Axes.Y, Axes.Z } + : new AxisType[] { Axes.X, Axes.Y, Axes.Z, Axes.TIME } + : ( singleTimePoint ) + ? new AxisType[] { Axes.X, Axes.Y } + : new AxisType[] { Axes.X, Axes.Y, Axes.TIME }; // N dimensions. - final int timeDim = 2; - final int nDims = singleTimePoint ? 2 : 3; + final int nDims = is3D + ? singleTimePoint ? 3 : 4 + : singleTimePoint ? 2 : 3; // Dimensions. final long[] dims = new long[ nDims ]; - dims[ 0 ] = imp.getWidth(); - dims[ 1 ] = imp.getHeight(); + int dim = 0; + dims[ dim++ ] = imp.getWidth(); + dims[ dim++ ] = imp.getHeight(); + if ( is3D ) + dims[ dim++ ] = imp.getNSlices(); if ( !singleTimePoint ) - dims[ timeDim ] = imp.getNFrames(); + dims[ dim++ ] = imp.getNFrames(); // Raw image. final Img< UnsignedShortType > lblImg = ArrayImgs.unsignedShorts( dims ); @@ -266,10 +275,13 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, // Calibration. final double[] c = TMUtils.getSpatialCalibration( imp ); final double[] calibration = new double[ nDims ]; - for ( int i = 0; i < 2; i++ ) - calibration[ i ] = c[ i ]; + dim = 0; + calibration[ dim++ ] = c[ 0 ]; + calibration[ dim++ ] = c[ 1 ]; + if ( is3D ) + calibration[ dim++ ] = c[ 2 ]; if ( !singleTimePoint ) - calibration[ timeDim ] = 1.; + calibration[ dim++ ] = 1.; // Label image holder. final ImgPlus< UnsignedShortType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); @@ -282,6 +294,7 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, } else { + final int timeDim = lblImgPlus.dimensionIndex( Axes.TIME ); for ( int t = 0; t < imp.getNFrames(); t++ ) { final ImgPlus< UnsignedShortType > lblImgPlusThisFrame = ImgPlusViews.hyperSlice( lblImgPlus, timeDim, t ); From 544b3dfb5a8d819df36ae2457b76f9ce14c94a17 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 19 Sep 2023 15:46:14 +0200 Subject: [PATCH 020/263] Always start the editor in FUSED mode, and use the white LUT when the imp is not displayed as a Composite. --- .../fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index 223a37c9e..1293e4616 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -1,5 +1,6 @@ package fiji.plugin.trackmate.gui.editor; +import java.awt.Color; import java.util.Arrays; import java.util.List; @@ -99,9 +100,9 @@ public AffineTransform3D transformation() { final List< ConverterSetup > converterSetups = stackSource.getConverterSetups(); final SynchronizedViewerState state = stackSource.getBdvHandle().getViewerPanel().state(); - final int numActiveChannels = transferChannelVisibility( state ); + transferChannelVisibility( state ); transferChannelSettings( converterSetups ); - state.setDisplayMode( numActiveChannels > 1 ? DisplayMode.FUSED : DisplayMode.SINGLE ); + state.setDisplayMode( DisplayMode.FUSED ); return stackSource; } @@ -147,6 +148,8 @@ private void transferChannelSettings( final List< ConverterSetup > converterSetu final ConverterSetup setup = converterSetups.get( c ); if ( transferColor ) setup.setColor( new ARGBType( lut.getRGB( 255 ) ) ); + else + setup.setColor( new ARGBType( Color.WHITE.getRGB() ) ); setup.setDisplayRange( lut.min, lut.max ); } } From 48594c2c2668407481baf8286327e761f1203f9d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 22 Sep 2023 15:11:01 +0200 Subject: [PATCH 021/263] Don't crash when editing on images with 1 channel. Noticed by @MiniMiette --- .../trackmate/gui/editor/ImpBdvShowable.java | 68 ++++++++++++++++--- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index 1293e4616..426db661a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -3,8 +3,12 @@ import java.awt.Color; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import bdv.tools.brightness.ConverterSetup; +import bdv.util.AxisOrder; +import bdv.util.BdvFunctions; import bdv.util.BdvOptions; import bdv.util.BdvStackSource; import bdv.viewer.DisplayMode; @@ -23,6 +27,7 @@ import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.NumericType; +import net.imglib2.view.Views; import sc.fiji.labkit.ui.bdv.BdvShowable; import sc.fiji.labkit.ui.inputimage.ImgPlusViewsOld; @@ -54,6 +59,8 @@ public class ImpBdvShowable implements BdvShowable public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImagePlus imp ) { final ImgPlus< T > src = TMUtils.rawWraps( imp ); + if ( src.dimensionIndex( Axes.CHANNEL ) < 0 ) + Views.addDimension( src ); return fromImp( src, imp ); } @@ -71,32 +78,44 @@ public static < T extends NumericType< T > > ImpBdvShowable fromImp( final Image */ public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImgPlus< T > frame, final ImagePlus imp ) { - return new ImpBdvShowable( BdvShowable.wrap( prepareImage( frame ) ), imp ); + return new ImpBdvShowable( prepareImage( frame ), imp ); } - private final BdvShowable showable; - private final ImagePlus imp; - ImpBdvShowable( final BdvShowable showable, final ImagePlus imp ) + private final ImgPlus< ? extends NumericType< ? > > image; + + ImpBdvShowable( final ImgPlus< ? extends NumericType< ? > > image, final ImagePlus imp ) { - this.showable = showable; + this.image = image; this.imp = imp; } @Override - public Interval interval() { - return showable.interval(); + public Interval interval() + { + return image; } @Override - public AffineTransform3D transformation() { - return showable.transformation(); + public AffineTransform3D transformation() + { + final AffineTransform3D transform = new AffineTransform3D(); + transform.set( + getCalibration( Axes.X ), 0, 0, 0, + 0, getCalibration( Axes.Y ), 0, 0, + 0, 0, getCalibration( Axes.Z ), 0 ); + return transform; } @Override - public BdvStackSource< ? > show( final String title, final BdvOptions options ) { - final BdvStackSource stackSource = showable.show(title, options); + public BdvStackSource< ? > show( final String title, final BdvOptions options ) + { + final String name = image.getName(); + final BdvOptions options1 = options.axisOrder( getAxisOrder() ).sourceTransform( transformation() ); + final BdvStackSource< ? extends NumericType< ? > > stackSource = BdvFunctions.show( image, name == null + ? title : name, options1 ); + final List< ConverterSetup > converterSetups = stackSource.getConverterSetups(); final SynchronizedViewerState state = stackSource.getBdvHandle().getViewerPanel().state(); @@ -188,4 +207,31 @@ private void transferChannelSettings( final List< ConverterSetup > converterSetu return ImgPlusViewsOld.fixAxes( image, Arrays.asList( Axes.X, Axes.Y, Axes.Z, Axes.CHANNEL, Axes.TIME ) ); } + + private double getCalibration( final AxisType axisType ) + { + final int d = image.dimensionIndex( axisType ); + if ( d == -1 ) + return 1; + return image.axis( d ).averageScale( image.min( d ), image.max( d ) ); + } + + private AxisOrder getAxisOrder() + { + final String code = IntStream + .range( 0, image.numDimensions() ) + .mapToObj( i -> image + .axis( i ) + .type() + .getLabel().substring( 0, 1 ) ) + .collect( Collectors.joining() ); + try + { + return AxisOrder.valueOf( code ); + } + catch ( final IllegalArgumentException e ) + { + return AxisOrder.DEFAULT; + } + } } From a788cf128bf768d91f97968d0bad993445574908 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 22 Sep 2023 15:13:36 +0200 Subject: [PATCH 022/263] If we crash when launching the editor, unfreeze TrackMate. --- .../java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 0b4cd461f..06a35a1d3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -358,6 +358,7 @@ public void run() } catch ( final Exception e ) { + disabler.reenable(); e.printStackTrace(); } }; From b503a63a3c7c76ae4e1b792a4f357aa3d26fe8ad Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 22 Sep 2023 15:55:11 +0200 Subject: [PATCH 023/263] Store labels on ints, not on shorts. Otherwise we get crashes when we have more than 4k labels. Which is not what Labkit is optimized for but we will see that in a second time. In case we change our minds on the backing integer type, right now the labkit launcher and importer classes are generic. --- .../trackmate/gui/editor/LabkitImporter.java | 19 +++++---- .../trackmate/gui/editor/LabkitLauncher.java | 42 +++++++++++-------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 420828128..bb01cd41b 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -18,14 +18,15 @@ import net.imglib2.RandomAccessibleInterval; import net.imglib2.loops.LoopBuilder; import net.imglib2.roi.labeling.ImgLabeling; -import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.IntegerType; import net.imglib2.view.Views; /** * Re-import the edited segmentation made in Labkit into the TrackMate model it * started from. */ -public class LabkitImporter +public class LabkitImporter< T extends IntegerType< T > & NativeType< T > > { private final Model model; @@ -80,8 +81,8 @@ public LabkitImporter( * index image. */ public void reimport( - final RandomAccessibleInterval< UnsignedShortType > novelIndexImg, - final RandomAccessibleInterval< UnsignedShortType > previousIndexImg, + final RandomAccessibleInterval< T > novelIndexImg, + final RandomAccessibleInterval< T > previousIndexImg, final int currentTimePoint ) { @@ -235,15 +236,15 @@ private void addNewSpot( final List< Spot > novelSpotList, final int currentTime } private final Set< Integer > getModifiedIDs( - final RandomAccessibleInterval< UnsignedShortType > novelIndexImg, - final RandomAccessibleInterval< UnsignedShortType > previousIndexImg ) + final RandomAccessibleInterval< T > novelIndexImg, + final RandomAccessibleInterval< T > previousIndexImg ) { final ConcurrentSkipListSet< Integer > modifiedIDs = new ConcurrentSkipListSet<>(); LoopBuilder.setImages( novelIndexImg, previousIndexImg ) .multiThreaded( false ) .forEachPixel( ( c, p ) -> { - final int ci = c.get(); - final int pi = p.get(); + final int ci = c.getInteger(); + final int pi = p.getInteger(); if ( ci == 0 && pi == 0 ) return; if ( ci != pi ) @@ -256,7 +257,7 @@ private final Set< Integer > getModifiedIDs( return modifiedIDs; } - private List< Spot > getSpots( final RandomAccessibleInterval< UnsignedShortType > rai ) + private List< Spot > getSpots( final RandomAccessibleInterval< T > rai ) { // Get all labels. final AtomicInteger max = new AtomicInteger( 0 ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 06a35a1d3..2238bc328 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -36,9 +36,10 @@ import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.loops.LoopBuilder; +import net.imglib2.type.NativeType; import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.IntegerType; -import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.type.numeric.integer.UnsignedIntType; import net.imglib2.util.Util; import net.imglib2.view.Views; import sc.fiji.labkit.ui.LabkitFrame; @@ -47,7 +48,7 @@ import sc.fiji.labkit.ui.labeling.Labeling; import sc.fiji.labkit.ui.models.DefaultSegmentationModel; -public class LabkitLauncher +public class LabkitLauncher< T extends IntegerType< T > & NativeType< T > > { private final double[] calibration; @@ -92,7 +93,8 @@ protected void launch( final boolean singleTimePoint ) model.imageLabelingModel().labeling().set( labeling ); // Store a copy. - final RandomAccessibleInterval< UnsignedShortType > previousIndexImg = copy( labeling.getIndexImg() ); + @SuppressWarnings( "unchecked" ) + final RandomAccessibleInterval< T > previousIndexImg = copy( ( RandomAccessibleInterval< T > ) labeling.getIndexImg() ); // Show LabKit. String title = "Editing TrackMate data for " + imp.getShortTitle(); @@ -104,12 +106,12 @@ protected void launch( final boolean singleTimePoint ) final double dt = imp.getCalibration().frameInterval; labkit.onCloseListeners().addListener( () -> { @SuppressWarnings( "unchecked" ) - final RandomAccessibleInterval< UnsignedShortType > indexImg = ( RandomAccessibleInterval< UnsignedShortType > ) model.imageLabelingModel().labeling().get().getIndexImg(); + final RandomAccessibleInterval< T > indexImg = ( RandomAccessibleInterval< T > ) model.imageLabelingModel().labeling().get().getIndexImg(); reimport( indexImg, previousIndexImg, dt ); } ); } - private void reimport( final RandomAccessibleInterval< UnsignedShortType > indexImg, final RandomAccessibleInterval< UnsignedShortType > previousIndexImg, final double dt ) + private void reimport( final RandomAccessibleInterval< T > indexImg, final RandomAccessibleInterval< T > previousIndexImg, final double dt ) { try { @@ -121,7 +123,7 @@ private void reimport( final RandomAccessibleInterval< UnsignedShortType > index if ( modified.get() ) return null; chunk.forEachPixel( ( p1, p2 ) -> { - if ( p1.get() != p2.get() ) + if ( p1.getInteger() != p2.getInteger() ) { modified.set( true ); return; @@ -149,7 +151,7 @@ private void reimport( final RandomAccessibleInterval< UnsignedShortType > index if ( returnedValue != JOptionPane.YES_OPTION ) return; - final LabkitImporter reimporter = new LabkitImporter( trackmate.getModel(), calibration, dt ); + final LabkitImporter< T > reimporter = new LabkitImporter<>( trackmate.getModel(), calibration, dt ); if ( currentTimePoint < 0 ) { // All time-points. @@ -158,8 +160,8 @@ private void reimport( final RandomAccessibleInterval< UnsignedShortType > index log.setStatus( "Re-importing from Labkit..." ); for ( int t = 0; t < nTimepoints; t++ ) { - final RandomAccessibleInterval< UnsignedShortType > novelIndexImgThisFrame = Views.hyperSlice( indexImg, 3, t ); - final RandomAccessibleInterval< UnsignedShortType > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, 3, t ); + final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, 3, t ); + final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, 3, t ); reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); log.setProgress( ++t / ( double ) nTimepoints ); } @@ -182,13 +184,13 @@ private void reimport( final RandomAccessibleInterval< UnsignedShortType > index } } - private static Img< UnsignedShortType > copy( final RandomAccessibleInterval< ? extends IntegerType< ? > > in ) + private Img< T > copy( final RandomAccessibleInterval< T > in ) { - final ImgFactory< UnsignedShortType > factory = Util.getArrayOrCellImgFactory( in, new UnsignedShortType() ); - final Img< UnsignedShortType > out = factory.create( in ); + final ImgFactory< T > factory = Util.getArrayOrCellImgFactory( in, Util.getTypeFromInterval( in ) ); + final Img< T > out = factory.create( in ); LoopBuilder.setImages( in, out ) .multiThreaded() - .forEachPixel( ( i, o ) -> o.set( i.getInteger() ) ); + .forEachPixel( ( i, o ) -> o.setInteger( i.getInteger() ) ); return out; } @@ -270,7 +272,7 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, dims[ dim++ ] = imp.getNFrames(); // Raw image. - final Img< UnsignedShortType > lblImg = ArrayImgs.unsignedShorts( dims ); + final Img< UnsignedIntType > lblImg = ArrayImgs.unsignedInts( dims ); // Calibration. final double[] c = TMUtils.getSpatialCalibration( imp ); @@ -284,7 +286,7 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, calibration[ dim++ ] = 1.; // Label image holder. - final ImgPlus< UnsignedShortType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); + final ImgPlus< UnsignedIntType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); // Write spots in it with index = id + 1 and build a map index -> spot. final Map< Integer, Spot > spotIDs = new HashMap<>(); @@ -297,7 +299,7 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, final int timeDim = lblImgPlus.dimensionIndex( Axes.TIME ); for ( int t = 0; t < imp.getNFrames(); t++ ) { - final ImgPlus< UnsignedShortType > lblImgPlusThisFrame = ImgPlusViews.hyperSlice( lblImgPlus, timeDim, t ); + final ImgPlus< UnsignedIntType > lblImgPlusThisFrame = ImgPlusViews.hyperSlice( lblImgPlus, timeDim, t ); processFrame( lblImgPlusThisFrame, spots, t, spotIDs ); } } @@ -310,6 +312,11 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, final String name = label.name(); final int index = Integer.parseInt( name ); final Spot spot = spotIDs.get( index ); + if ( spot == null ) + { + System.out.println( "Spot is null for index " + index + "!!" ); // DEBUG + continue; + } label.setName( spot.getName() ); label.setColor( new ARGBType( colorGen.color( spot ).getRGB() ) ); } @@ -317,7 +324,7 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, return labeling; } - private static final void processFrame( final ImgPlus< UnsignedShortType > lblImgPlus, final SpotCollection spots, final int t, final Map< Integer, Spot > spotIDs ) + private static final void processFrame( final ImgPlus< UnsignedIntType > lblImgPlus, final SpotCollection spots, final int t, final Map< Integer, Spot > spotIDs ) { final Iterable< Spot > spotsThisFrame = spots.iterable( t, true ); for ( final Spot spot : spotsThisFrame ) @@ -353,6 +360,7 @@ public void run() disabler.disable(); try { + @SuppressWarnings( "rawtypes" ) final LabkitLauncher launcher = new LabkitLauncher( trackmate, ds, disabler ); launcher.launch( singleTimepoint ); } From 4d3a3267d70ac74e0506d36a5d1d5153a33f26a6 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 7 Jul 2024 20:18:04 +0200 Subject: [PATCH 024/263] Fix reimporting labels with large IDs in 2D. The re-importing of labels from Tabkit to TrackMate could fail for 2D images and labels with a large index. For instance, it failed consistently when trying to re-import labels with an index larger than 65643. This problem roots in the getSpots() method of LabkitImporter. It relies on a trick: We get the new label image, and create spots from this label image. But we want the new spots to keep track of the index in the label image they were generated from. For this, in 2D, we use the MaskUtils.fromLabelingWithRoi() method. These methods accept an image as last argument used to read a value in the label image within the spot, that is normally used for the quality value of the new spot. But the SpotRoiUtils.from2DLabelingWithRoi() method converted the extra image to ImagePlus (because I was lazy). So the label image was effectively cast on ushort for an IntegerType image, hence the problem with the max label being 65453. The solution is to rewrite the from2DLabelingWithRoi() so that it does not rely on converting to ImagePlus, but on good old iteration with imglib2. --- .../plugin/trackmate/detection/MaskUtils.java | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 53cf70fd8..12c01a0c5 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -26,14 +26,18 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutorService; + import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotRoi; +import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.Threads; -import ij.ImagePlus; import ij.gui.PolygonRoi; -import ij.measure.Measurements; import ij.process.FloatPolygon; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.axis.AxisType; import net.imglib2.Interval; +import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; @@ -45,7 +49,6 @@ import net.imglib2.histogram.Real1dBinMapper; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; -import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegionCursor; @@ -53,7 +56,6 @@ import net.imglib2.type.BooleanType; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.IntegerType; -import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; import net.imglib2.util.Util; @@ -428,7 +430,7 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > * the image in which to read the quality value. * @return a list of spots, with ROI. */ - public static final < T extends RealType< T >, S extends NumericType< S > > List< Spot > fromThresholdWithROI( + public static final < T extends RealType< T >, S extends RealType< S > > List< Spot > fromThresholdWithROI( final RandomAccessible< T > input, final Interval interval, final double[] calibration, @@ -447,7 +449,7 @@ public static final < T extends RealType< T >, S extends NumericType< S > > List /** * Creates spots with ROIs from a 2D label image. The quality - * value is read from a secondary image, byt taking the max value in each + * value is read from a secondary image, by taking the max value in each * ROI. * * @param @@ -468,7 +470,7 @@ public static final < T extends RealType< T >, S extends NumericType< S > > List * the image in which to read the quality value. * @return a list of spots, with ROI. */ - public static < R extends IntegerType< R >, S extends NumericType< S > > List< Spot > fromLabelingWithROI( + public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > fromLabelingWithROI( final ImgLabeling< Integer, R > labeling, final Interval interval, final double[] calibration, @@ -497,9 +499,6 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S // Quality image. final List< Spot > spots = new ArrayList<>( polygons.size() ); - final ImagePlus qualityImp = ( null == qualityImage ) - ? null - : ImageJFunctions.wrap( qualityImage, "QualityImage" ); // Simplify them and compute a quality. for ( final Polygon polygon : polygons ) @@ -517,18 +516,6 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) continue; - // Measure quality. - final double quality; - if ( null == qualityImp ) - { - quality = fRoi.getStatistics().area; - } - else - { - qualityImp.setRoi( fRoi ); - quality = qualityImp.getStatistics( Measurements.MIN_MAX ).max; - } - final Polygon fPolygon = fRoi.getPolygon(); final double[] xpoly = new double[ fPolygon.npoints ]; final double[] ypoly = new double[ fPolygon.npoints ]; @@ -538,7 +525,33 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 ); } - spots.add( SpotRoi.createSpot( xpoly, ypoly, quality ) ); + final Spot spot = SpotRoi.createSpot( xpoly, ypoly, -1. ); + + // Measure quality. + final double quality; + if ( null == qualityImage ) + { + quality = fRoi.getStatistics().area; + } + else + { + final String name = "QualityImage"; + final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y }; + final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] }; + final String[] units = new String[] { "unitX", "unitY" }; + final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units ); + final IterableInterval< S > iterable = SpotUtil.iterable( spot, qualityImgPlus ); + double max = Double.NEGATIVE_INFINITY; + for ( final S s : iterable ) + { + final double val = s.getRealDouble(); + if ( val > max ) + max = val; + } + quality = max; + } + spot.putFeature( Spot.QUALITY, quality ); + spots.add( spot ); } return spots; } From 39e16fc6b0354509672cdbf2bdc438c89be349c5 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 18:25:47 +0200 Subject: [PATCH 025/263] Fix javadoc errors. --- .../java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java | 4 ++-- .../java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index 426db661a..27ee12401 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -32,8 +32,8 @@ import sc.fiji.labkit.ui.inputimage.ImgPlusViewsOld; /** - * A {@link BdvShowable} from a {@link ImgPlus}, but with channel colors, min & - * max, channel visibility and display mode taken from a specified + * A {@link BdvShowable} from a {@link ImgPlus}, but with channel colors, min + * and max, channel visibility and display mode taken from a specified * {@link ImagePlus}. *

* Adapted from Matthias Arzt' ImgPlusBdvShowable, reusing code I made for diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index bb01cd41b..608dc5788 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -40,9 +40,6 @@ public class LabkitImporter< T extends IntegerType< T > & NativeType< T > > * * @param model * the model to add, remove or edit spots in. - * @param currentTimePoint - * the time-point used for editing. If negative, then all the - * time-points in the input movie will be processed. * @param calibration * the spatial calibration array: [dx, dy dz]. * @param dt From 2d9edceae02bee3727a77cc798abce9103e37f87 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 8 Feb 2024 15:14:32 +0100 Subject: [PATCH 026/263] Make the spot editor work with 1-timepoint images. Noticed by @m-albert --- .../trackmate/gui/editor/LabkitLauncher.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 2238bc328..e6099a9ef 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -75,7 +75,7 @@ public LabkitLauncher( final TrackMate trackmate, final DisplaySettings ds, fina /** * Launches the Labkit editor. - * + * * @param singleTimePoint * if true, will launch the editor using only the * time-point currently displayed in the main view. Otherwise, @@ -135,11 +135,15 @@ private void reimport( final RandomAccessibleInterval< T > indexImg, final Rando return; // Message the user. - final String msg = ( currentTimePoint < 0 ) + final long nTimepoints = indexImg.dimension( 3 ); + final String msg = ( nTimepoints <= 1 ) ? "Commit the changes made to the\n" - + "segmentation in whole movie?" - : "Commit the changes made to the\n" - + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; + + "segmentation in the image?" + : ( currentTimePoint < 0 ) + ? "Commit the changes made to the\n" + + "segmentation in whole movie?" + : "Commit the changes made to the\n" + + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; final String title = "Commit edits to TrackMate"; final int returnedValue = JOptionPane.showConfirmDialog( null, @@ -152,10 +156,9 @@ private void reimport( final RandomAccessibleInterval< T > indexImg, final Rando return; final LabkitImporter< T > reimporter = new LabkitImporter<>( trackmate.getModel(), calibration, dt ); - if ( currentTimePoint < 0 ) + if ( currentTimePoint < 0 && nTimepoints > 1 ) { // All time-points. - final long nTimepoints = indexImg.dimension( 3 ); final Logger log = Logger.IJ_LOGGER; log.setStatus( "Re-importing from Labkit..." ); for ( int t = 0; t < nTimepoints; t++ ) @@ -171,7 +174,8 @@ private void reimport( final RandomAccessibleInterval< T > indexImg, final Rando else { // Only one. - reimporter.reimport( indexImg, previousIndexImg, currentTimePoint ); + final int localT = Math.max( 0, currentTimePoint ); + reimporter.reimport( indexImg, previousIndexImg, localT ); } } catch ( final Exception e ) @@ -197,7 +201,7 @@ private Img< T > copy( final RandomAccessibleInterval< T > in ) /** * Creates a new {@link DatasetInputImage} from the specified * {@link ImagePlus}. The embedded label image is empty. - * + * * @param imp * the input {@link ImagePlus}. * @param singleTimePoint @@ -216,10 +220,10 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si // Possibly reslice for current time-point. final ImpBdvShowable showable; final ImgPlus inputImg; - if ( singleTimePoint ) + final int timeAxis = src.dimensionIndex( Axes.TIME ); + if ( singleTimePoint && timeAxis >= 0 ) { this.currentTimePoint = imp.getFrame() - 1; - final int timeAxis = src.dimensionIndex( Axes.TIME ); inputImg = ImgPlusViews.hyperSlice( src, timeAxis, currentTimePoint ); showable = ImpBdvShowable.fromImp( inputImg, imp ); } @@ -236,7 +240,7 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si * Prepare the label image for annotation. The labeling is created and each * of its labels receive the name and the color from the spot it is created * from. - * + * * @param imp * the source image plus. * @param spots From e4eb1bc8fcc23b6327a6114a601911418475ddbd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Mar 2024 14:54:13 +0100 Subject: [PATCH 027/263] Fix import of spots in LabKit for 2D single time-point images. --- .../java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index e6099a9ef..fa89eb424 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -330,7 +330,9 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, private static final void processFrame( final ImgPlus< UnsignedIntType > lblImgPlus, final SpotCollection spots, final int t, final Map< Integer, Spot > spotIDs ) { - final Iterable< Spot > spotsThisFrame = spots.iterable( t, true ); + // If we have a single timepoint, don't use -1 to retrieve spots. + final int lt = t < 0 ? 0 : t; + final Iterable< Spot > spotsThisFrame = spots.iterable( lt, true ); for ( final Spot spot : spotsThisFrame ) { final int index = spot.ID() + 1; From 8c9961b3b8093548d04a84c67b876769bd76e8b5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Mar 2024 14:54:32 +0100 Subject: [PATCH 028/263] Protect against weird error caused by empty labels. --- .../fiji/plugin/trackmate/gui/editor/LabkitImporter.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 608dc5788..cba71804c 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -37,7 +37,7 @@ public class LabkitImporter< T extends IntegerType< T > & NativeType< T > > /** * Creates a new re-importer. - * + * * @param model * the model to add, remove or edit spots in. * @param calibration @@ -65,7 +65,7 @@ public LabkitImporter( *

* To properly detect modifications, the indices in the label images must * correspond to the spot ID + 1 (index = id + 1). - * + * * @param novelIndexImg * the new index image of the labeling model, that represents the * TrackMate model in the specified time-point after @@ -135,12 +135,13 @@ public void reimport( * A new one (possible several) I cannot find in the * previous list -> add as a new spot. */ - addNewSpot( novelSpotList, currentTimePoint ); + if ( novelSpotList != null ) + addNewSpot( novelSpotList, currentTimePoint ); } else if ( novelSpotList == null || novelSpotList.isEmpty() ) { /* - * One I add in the previous spot list, but that has + * One I had in the previous spot list, but that has * disappeared. Remove it. */ model.removeSpot( previousSpot ); From e65687cee02e1fb230906eec55431221237a9a2b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 28 Nov 2023 16:08:48 +0100 Subject: [PATCH 029/263] setFont() utility. --- src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java b/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java index a2d82584d..3b59b62d8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java @@ -87,6 +87,12 @@ public static final void selectAllOnFocus( final JTextField tf ) tf.addFocusListener( selectAllFocusListener ); } + public static final void setFont( final JComponent panel, final Font font ) + { + for ( final Component c : panel.getComponents() ) + c.setFont( font ); + } + /** * Returns the black color or white color depending on the specified * background color, to ensure proper readability of the text on said From 1d95bea5bceba4ccd9f06be534fe9bbdbbf8c8cf Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 28 Mar 2024 20:18:00 +0100 Subject: [PATCH 030/263] The detection preview is cancelable. Provided that the detector that is called is cancelable. --- .../trackmate/util/DetectionPreview.java | 77 ++++++++++++------- .../trackmate/util/DetectionPreviewPanel.java | 21 ++++- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java b/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java index d0edab222..3bf2102d9 100644 --- a/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java +++ b/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java @@ -48,6 +48,8 @@ public class DetectionPreview private final DetectionPreviewPanel panel; + private TrackMate trackmate; + protected DetectionPreview( final Model model, final Settings settings, @@ -66,6 +68,7 @@ protected DetectionPreview( detectionSettingsSupplier.get(), currentFrameSupplier.get(), thresholdKey ) ); + panel.btnCancel.addActionListener( l -> cancel() ); } public DetectionPreviewPanel getPanel() @@ -86,7 +89,9 @@ protected void preview( final int frame, final String thresholdKey ) { - panel.btnPreview.setEnabled( false ); + panel.btnPreview.setVisible( false ); + panel.btnCancel.setVisible( true ); + panel.btnCancel.setEnabled( true ); Threads.run( "TrackMate preview detection thread", () -> { try @@ -115,7 +120,8 @@ protected void preview( } finally { - panel.btnPreview.setEnabled( true ); + panel.btnPreview.setVisible( true ); + panel.btnCancel.setVisible( false ); } } ); } @@ -161,38 +167,53 @@ protected Pair< Model, Double > runPreviewDetection( lSettings.detectorFactory = detectorFactory; lSettings.detectorSettings = new HashMap<>( detectorSettings ); - // Does this detector have a THRESHOLD parameter? - final boolean hasThreshold = ( thresholdKey != null ) && ( detectorSettings.containsKey( thresholdKey ) ); - final double threshold; - if ( hasThreshold ) - { - threshold = ( ( Double ) detectorSettings.get( thresholdKey ) ).doubleValue(); - lSettings.detectorSettings.put( thresholdKey, Double.valueOf( Double.NEGATIVE_INFINITY ) ); - } - else + this.trackmate = new TrackMate( lSettings ); + + try { - threshold = Double.NaN; - } + // Does this detector have a THRESHOLD parameter? + final boolean hasThreshold = ( thresholdKey != null ) && ( detectorSettings.containsKey( thresholdKey ) ); + final double threshold; + if ( hasThreshold ) + { + threshold = ( ( Double ) detectorSettings.get( thresholdKey ) ).doubleValue(); + lSettings.detectorSettings.put( thresholdKey, Double.valueOf( Double.NEGATIVE_INFINITY ) ); + } + else + { + threshold = Double.NaN; + } - // Execute preview. - final TrackMate trackmate = new TrackMate( lSettings ); - trackmate.getModel().setLogger( panel.logger ); + // Execute preview. + trackmate.getModel().setLogger( panel.logger ); - final boolean detectionOk = trackmate.execDetection(); - if ( !detectionOk ) + final boolean detectionOk = trackmate.execDetection(); + if ( !detectionOk ) + { + panel.logger.error( trackmate.getErrorMessage() ); + return null; + } + + if ( hasThreshold ) + // Filter by the initial threshold value. + trackmate.getModel().getSpots().filter( new FeatureFilter( Spot.QUALITY, threshold, true ) ); + else + // Make them all visible. + trackmate.getModel().getSpots().setVisible( true ); + + return new ValuePair< Model, Double >( trackmate.getModel(), Double.valueOf( threshold ) ); + } + finally { - panel.logger.error( trackmate.getErrorMessage() ); - return null; + this.trackmate = null; } + } - if ( hasThreshold ) - // Filter by the initial threshold value. - trackmate.getModel().getSpots().filter( new FeatureFilter( Spot.QUALITY, threshold, true ) ); - else - // Make them all visible. - trackmate.getModel().getSpots().setVisible( true ); - - return new ValuePair< Model, Double >( trackmate.getModel(), Double.valueOf( threshold ) ); + private void cancel() + { + panel.btnCancel.setEnabled( false ); + if ( null != trackmate ) + trackmate.cancel( "User canceled preview" ); } protected void updateModelAndHistogram( final Model targetModel, final Model sourceModel, final int frame, final double threshold ) diff --git a/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java b/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java index f3adad062..0c6f887be 100644 --- a/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java +++ b/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java @@ -22,6 +22,7 @@ package fiji.plugin.trackmate.util; import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; +import static fiji.plugin.trackmate.gui.Icons.CANCEL_ICON; import static fiji.plugin.trackmate.gui.Icons.PREVIEW_ICON; import java.awt.GridBagConstraints; @@ -48,12 +49,19 @@ public class DetectionPreviewPanel extends JPanel + "get rid of them later." + ""; + private static final String TOOLTIP_CANCEL = "" + + "Cancel the current preview." + + ""; + final Logger logger; final JButton btnPreview; + final JButton btnCancel; + final QualityHistogramChart chart; + public DetectionPreviewPanel( final DoubleConsumer thresholdUpdater, final String axisLabel ) { final GridBagLayout gridBagLayout = new GridBagLayout(); @@ -84,12 +92,21 @@ public DetectionPreviewPanel( final DoubleConsumer thresholdUpdater, final Strin this.btnPreview = new JButton( "Preview", PREVIEW_ICON ); btnPreview.setToolTipText( TOOLTIP_PREVIEW ); + btnPreview.setFont( SMALL_FONT ); + this.btnCancel = new JButton( "Cancel", CANCEL_ICON ); + btnCancel.setToolTipText( TOOLTIP_CANCEL ); + btnCancel.setVisible( false ); + btnCancel.setFont( SMALL_FONT ); + + final JPanel btnPanel = new JPanel(); + btnPanel.add( btnPreview ); + btnPanel.add( btnCancel ); + final GridBagConstraints gbcBtnPreview = new GridBagConstraints(); gbcBtnPreview.anchor = GridBagConstraints.NORTHEAST; gbcBtnPreview.insets = new Insets( 5, 5, 0, 0 ); gbcBtnPreview.gridx = 1; gbcBtnPreview.gridy = 1; - this.add( btnPreview, gbcBtnPreview ); - btnPreview.setFont( SMALL_FONT ); + this.add( btnPanel, gbcBtnPreview ); } } From 0bd86ec35b902489d08495e3e96c6d8c1230652a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 21 May 2024 14:23:00 +0200 Subject: [PATCH 031/263] Fix preview panel size in free-size layouts. --- .../java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java b/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java index 0c6f887be..604162741 100644 --- a/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java +++ b/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java @@ -25,6 +25,7 @@ import static fiji.plugin.trackmate.gui.Icons.CANCEL_ICON; import static fiji.plugin.trackmate.gui.Icons.PREVIEW_ICON; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -108,5 +109,7 @@ public DetectionPreviewPanel( final DoubleConsumer thresholdUpdater, final Strin gbcBtnPreview.gridx = 1; gbcBtnPreview.gridy = 1; this.add( btnPanel, gbcBtnPreview ); + + setPreferredSize( new Dimension( 240, 100 ) ); } } From d6fdfb5a2a84773eb4ecbee04d854480f452bab1 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 9 Jul 2024 18:36:30 +0200 Subject: [PATCH 032/263] Detection preview panel tweak. Make sure the detection preview panel is slanted at the bottom of its display. --- .../trackmate/util/DetectionPreviewPanel.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java b/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java index 604162741..e2b36983b 100644 --- a/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java +++ b/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java @@ -32,6 +32,7 @@ import java.util.function.DoubleConsumer; import javax.swing.JButton; +import javax.swing.JLabel; import javax.swing.JPanel; import fiji.plugin.trackmate.Logger; @@ -67,18 +68,20 @@ public DetectionPreviewPanel( final DoubleConsumer thresholdUpdater, final Strin { final GridBagLayout gridBagLayout = new GridBagLayout(); gridBagLayout.columnWeights = new double[] { 1.0, 0.0 }; - gridBagLayout.rowWeights = new double[] { 0., 0. }; - gridBagLayout.rowHeights = new int[] { 120, 20 }; + gridBagLayout.rowWeights = new double[] { 1., 0., 0. }; + gridBagLayout.rowHeights = new int[] { 0, 120, 20 }; setLayout( gridBagLayout ); + add( new JLabel() ); + this.chart = new QualityHistogramChart( thresholdUpdater, axisLabel ); final GridBagConstraints gbcHistogram = new GridBagConstraints(); gbcHistogram.gridwidth = 2; gbcHistogram.insets = new Insets( 0, 0, 5, 0 ); gbcHistogram.fill = GridBagConstraints.BOTH; gbcHistogram.gridx = 0; - gbcHistogram.gridy = 0; + gbcHistogram.gridy = 1; add( chart, gbcHistogram ); final JLabelLogger labelLogger = new JLabelLogger(); @@ -87,7 +90,7 @@ public DetectionPreviewPanel( final DoubleConsumer thresholdUpdater, final Strin gbcLabelLogger.insets = new Insets( 5, 5, 0, 5 ); gbcLabelLogger.fill = GridBagConstraints.BOTH; gbcLabelLogger.gridx = 0; - gbcLabelLogger.gridy = 1; + gbcLabelLogger.gridy = 2; add( labelLogger, gbcLabelLogger ); this.logger = labelLogger.getLogger(); @@ -107,7 +110,7 @@ public DetectionPreviewPanel( final DoubleConsumer thresholdUpdater, final Strin gbcBtnPreview.anchor = GridBagConstraints.NORTHEAST; gbcBtnPreview.insets = new Insets( 5, 5, 0, 0 ); gbcBtnPreview.gridx = 1; - gbcBtnPreview.gridy = 1; + gbcBtnPreview.gridy = 2; this.add( btnPanel, gbcBtnPreview ); setPreferredSize( new Dimension( 240, 100 ) ); From de5de593262af69c71107a81e77a8692b9a0dc68 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 10 Jul 2024 20:53:25 +0200 Subject: [PATCH 033/263] Fixed a difficult bug with the LabKit editor reimporter. The bug was causing weird issues with unedited spots being deleted, unedited spots being duplicated etc. It took me really long to understand the cause. It was hidden in the step where we go from a label image to a collection of spots. Because spots are polygons, with simplified contours, there might be some pixels on the edges of the object that are not strictly inside the label. In this importer, we read the label value in one go, by storing it in the QUALITY value of the spot, in the MaskUtils class. But since the spots have simplified contours, and since the QUALITY value is the maximal value iterated over, our approach might fail on border cases: - when the contout is approximated and include pixels from another object - and when this object has a label value higher than the lael of the spot. This commit include a temporary fix: we reiterate over the spot but takes the median value iterated over, to make sure we read the correct value for the label. Other attempts will follow, for reference. But a true fix involves making a method that returns a map from label value to spot. --- .../trackmate/gui/editor/LabkitImporter.java | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index cba71804c..98875ff1a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -1,6 +1,7 @@ package fiji.plugin.trackmate.gui.editor; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -10,11 +11,16 @@ import java.util.concurrent.atomic.AtomicInteger; import org.jgrapht.graph.DefaultWeightedEdge; +import org.scijava.util.IntArray; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.detection.MaskUtils; +import fiji.plugin.trackmate.util.SpotUtil; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.axis.AxisType; import net.imglib2.RandomAccessibleInterval; import net.imglib2.loops.LoopBuilder; import net.imglib2.roi.labeling.ImgLabeling; @@ -82,11 +88,9 @@ public void reimport( final RandomAccessibleInterval< T > previousIndexImg, final int currentTimePoint ) { - // Collect ids of spots that have been modified. id = index - 1 final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg, previousIndexImg ); final int nModified = modifiedIDs.size(); - if ( nModified == 0 ) return; @@ -213,6 +217,7 @@ else if ( target == previousSpot ) // Deal with the other ones. final HashSet< Spot > extraSpots = new HashSet<>( novelSpotList ); extraSpots.remove( mainNovelSpot ); + int i = 1; for ( final Spot s : extraSpots ) { @@ -223,7 +228,7 @@ else if ( target == previousSpot ) } } - private void addNewSpot( final List< Spot > novelSpotList, final int currentTimePoint ) + private void addNewSpot( final Iterable< Spot > novelSpotList, final int currentTimePoint ) { for ( final Spot spot : novelSpotList ) { @@ -270,11 +275,36 @@ private List< Spot > getSpots( final RandomAccessibleInterval< T > rai ) final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); final boolean simplify = true; - return MaskUtils.fromLabelingWithROI( + final List< Spot > spots = MaskUtils.fromLabelingWithROI( labeling, labeling, calibration, simplify, - rai ); + null ); + + /* + * Read the label from the index image and store it in the quality + * feature. To ensure we don't have weirdness with the non + * convex-objects we iterate through all pixels and take the median. + */ + + final String name = "LabelImgPlus"; + final boolean is3D = rai.numDimensions() == 3; + final AxisType[] axes = ( is3D ) ? new AxisType[] { Axes.X, Axes.Y, Axes.Z } : new AxisType[] { Axes.X, Axes.Y }; + final double[] cal = ( is3D ) ? new double[] { calibration[ 0 ], calibration[ 1 ], calibration[ 2 ] } : new double[] { calibration[ 0 ], calibration[ 1 ] }; + final String[] units = ( is3D ) ? new String[] { "unitX", "unitY", "unitZ" } : new String[] { "unitX", "unitY" }; + final ImgPlus< T > labelImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( rai ), name, axes, cal, units ); + + final IntArray arr = new IntArray(); + for ( final Spot spot : spots ) + { + arr.clear(); + SpotUtil.iterable( spot, labelImgPlus ).forEach( p -> arr.addValue( p.getInteger() ) ); + Arrays.sort( arr.getArray(), 0, arr.size() ); + final int label = arr.getValue( arr.size() / 2 ); + spot.putFeature( Spot.QUALITY, Double.valueOf( label ) ); + } + + return spots; } } From 2c56943fe8acc1e0c0cddf8603c847934fbef268 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 10 Jul 2024 20:55:14 +0200 Subject: [PATCH 034/263] Another fix for the LabKit reimporter bug. This time we fix it by creating spots that do not have a simplified contours. In that case we strictly iterate over the pixels inside label and get the correct value. However the created spots have a pixelated aspect (contours are not simplified), which might not be what the user wants. We should let them choose. Still not the perfect solution, as mentionned in the previous commmit. --- .../trackmate/gui/editor/LabkitImporter.java | 34 ++----------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 98875ff1a..fbfebe142 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -1,7 +1,6 @@ package fiji.plugin.trackmate.gui.editor; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -11,16 +10,11 @@ import java.util.concurrent.atomic.AtomicInteger; import org.jgrapht.graph.DefaultWeightedEdge; -import org.scijava.util.IntArray; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.detection.MaskUtils; -import fiji.plugin.trackmate.util.SpotUtil; -import net.imagej.ImgPlus; -import net.imagej.axis.Axes; -import net.imagej.axis.AxisType; import net.imglib2.RandomAccessibleInterval; import net.imglib2.loops.LoopBuilder; import net.imglib2.roi.labeling.ImgLabeling; @@ -274,37 +268,13 @@ private List< Spot > getSpots( final RandomAccessibleInterval< T > rai ) indices.add( Integer.valueOf( i + 1 ) ); final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); - final boolean simplify = true; + final boolean simplify = false; // needed to properly read label values. final List< Spot > spots = MaskUtils.fromLabelingWithROI( labeling, labeling, calibration, simplify, - null ); - - /* - * Read the label from the index image and store it in the quality - * feature. To ensure we don't have weirdness with the non - * convex-objects we iterate through all pixels and take the median. - */ - - final String name = "LabelImgPlus"; - final boolean is3D = rai.numDimensions() == 3; - final AxisType[] axes = ( is3D ) ? new AxisType[] { Axes.X, Axes.Y, Axes.Z } : new AxisType[] { Axes.X, Axes.Y }; - final double[] cal = ( is3D ) ? new double[] { calibration[ 0 ], calibration[ 1 ], calibration[ 2 ] } : new double[] { calibration[ 0 ], calibration[ 1 ] }; - final String[] units = ( is3D ) ? new String[] { "unitX", "unitY", "unitZ" } : new String[] { "unitX", "unitY" }; - final ImgPlus< T > labelImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( rai ), name, axes, cal, units ); - - final IntArray arr = new IntArray(); - for ( final Spot spot : spots ) - { - arr.clear(); - SpotUtil.iterable( spot, labelImgPlus ).forEach( p -> arr.addValue( p.getInteger() ) ); - Arrays.sort( arr.getArray(), 0, arr.size() ); - final int label = arr.getValue( arr.size() / 2 ); - spot.putFeature( Spot.QUALITY, Double.valueOf( label ) ); - } - + rai ); return spots; } } From fc19713b9d23580860d4dd50084966676192c91c Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 10 Jul 2024 21:08:26 +0200 Subject: [PATCH 035/263] Label image to spots: also returns the label corresponding to created spots. --- .../plugin/trackmate/detection/MaskUtils.java | 160 ++++++++++++------ 1 file changed, 110 insertions(+), 50 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 12c01a0c5..5ead9f1e1 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -23,8 +23,10 @@ import java.awt.Polygon; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import fiji.plugin.trackmate.Spot; @@ -441,7 +443,7 @@ public static final < T extends RealType< T >, S extends RealType< S > > List< S { if ( input.numDimensions() != 2 ) throw new IllegalArgumentException( "Can only process 2D images with this method, but got " + input.numDimensions() + "D." ); - + // Get labeling. final ImgLabeling< Integer, IntType > labeling = toLabeling( input, interval, threshold, numThreads ); return fromLabelingWithROI( labeling, interval, calibration, simplify, qualityImage ); @@ -476,15 +478,64 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot final double[] calibration, final boolean simplify, final RandomAccessibleInterval< S > qualityImage ) + { + final Map< Integer, List< Spot > > map = fromLabelingWithROIMap( labeling, interval, calibration, simplify, qualityImage ); + final List spots = new ArrayList<>(); + for ( final List< Spot > s : map.values() ) + spots.addAll( s ); + + return spots; + } + + /** + * Creates spots with ROIs from a 2D label image. The quality + * value is read from a secondary image, by taking the max value in each + * ROI. + *

+ * The spots are returned in a map, where the key is the integer value of + * the label they correspond to in the label image. Because one spot + * corresponds to one connected component in the label image, there might be + * several spots for a label, hence the values of the map are list of spots. + * + * @param + * the type that backs-up the labeling. + * @param + * the type of the quality image. Must be real, scalar. + * @param labeling + * the labeling, must be zero-min and 2D.. + * @param interval + * the interval, used to reposition the spots from the zero-min + * labeling to the proper coordinates. + * @param calibration + * the physical calibration. + * @param simplify + * if true the polygon will be post-processed to be + * smoother and contain less points. + * @param qualityImage + * the image in which to read the quality value. + * @return a map linking the label integer value to the list of spots, with + * ROI, it corresponds to. + */ + public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > fromLabelingWithROIMap( + final ImgLabeling< Integer, R > labeling, + final Interval interval, + final double[] calibration, + final boolean simplify, + final RandomAccessibleInterval< S > qualityImage ) { if ( labeling.numDimensions() != 2 ) throw new IllegalArgumentException( "Can only process 2D images with this method, but got " + labeling.numDimensions() + "D." ); final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); - // Parse regions to create polygons on boundaries. - final List< Polygon > polygons = new ArrayList<>( regions.getExistingLabels().size() ); + /* + * Map of label in the label image to a collection of polygons around + * this label. Because 1 polygon correspond to 1 connected component, + * there might be several polygons for a label. + */ + final Map< Integer, List< Polygon > > polygonsMap = new HashMap<>( regions.getExistingLabels().size() ); final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); + // Parse regions to create polygons on boundaries. while ( iterator.hasNext() ) { final LabelRegion< Integer > region = iterator.next(); @@ -494,66 +545,75 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot for ( final Polygon polygon : pp ) polygon.translate( ( int ) region.min( 0 ), ( int ) region.min( 1 ) ); - polygons.addAll( pp ); + final Integer label = region.getLabel(); + polygonsMap.put( label, pp ); } - // Quality image. - final List< Spot > spots = new ArrayList<>( polygons.size() ); + + // Storage for results. + final Map< Integer, List< Spot > > output = new HashMap<>( polygonsMap.size() ); // Simplify them and compute a quality. - for ( final Polygon polygon : polygons ) + for ( final Integer label : polygonsMap.keySet() ) { - final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON ); + final List< Spot > spots = new ArrayList<>( polygonsMap.size() ); + output.put( label, spots ); + + final List< Polygon > polygons = polygonsMap.get( label ); + for ( final Polygon polygon : polygons ) + { + final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON ); - // Create Spot ROI. - final PolygonRoi fRoi; - if ( simplify ) - fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE ); - else - fRoi = roi; + // Create Spot ROI. + final PolygonRoi fRoi; + if ( simplify ) + fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE ); + else + fRoi = roi; - // Don't include ROIs that have been shrunk to < 1 pixel. - if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) - continue; + // Don't include ROIs that have been shrunk to < 1 pixel. + if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) + continue; - final Polygon fPolygon = fRoi.getPolygon(); - final double[] xpoly = new double[ fPolygon.npoints ]; - final double[] ypoly = new double[ fPolygon.npoints ]; - for ( int i = 0; i < fPolygon.npoints; i++ ) - { - xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 ); - ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 ); - } + final Polygon fPolygon = fRoi.getPolygon(); + final double[] xpoly = new double[ fPolygon.npoints ]; + final double[] ypoly = new double[ fPolygon.npoints ]; + for ( int i = 0; i < fPolygon.npoints; i++ ) + { + xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 ); + ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 ); + } - final Spot spot = SpotRoi.createSpot( xpoly, ypoly, -1. ); + final Spot spot = SpotRoi.createSpot( xpoly, ypoly, -1. ); - // Measure quality. - final double quality; - if ( null == qualityImage ) - { - quality = fRoi.getStatistics().area; - } - else - { - final String name = "QualityImage"; - final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y }; - final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] }; - final String[] units = new String[] { "unitX", "unitY" }; - final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units ); - final IterableInterval< S > iterable = SpotUtil.iterable( spot, qualityImgPlus ); - double max = Double.NEGATIVE_INFINITY; - for ( final S s : iterable ) + // Measure quality. + final double quality; + if ( null == qualityImage ) { - final double val = s.getRealDouble(); - if ( val > max ) - max = val; + quality = fRoi.getStatistics().area; } - quality = max; + else + { + final String name = "QualityImage"; + final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y }; + final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] }; + final String[] units = new String[] { "unitX", "unitY" }; + final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units ); + final IterableInterval< S > iterable = SpotUtil.iterable( spot, qualityImgPlus ); + double max = Double.NEGATIVE_INFINITY; + for ( final S s : iterable ) + { + final double val = s.getRealDouble(); + if ( val > max ) + max = val; + } + quality = max; + } + spot.putFeature( Spot.QUALITY, quality ); + spots.add( spot ); } - spot.putFeature( Spot.QUALITY, quality ); - spots.add( spot ); } - return spots; + return output; } private static final double distanceSquaredBetweenPoints( final double vx, final double vy, final double wx, final double wy ) @@ -649,7 +709,7 @@ private static final void douglasPeucker( final List< double[] > list, final int */ public static final List< double[] > douglasPeucker( final List< double[] > list, final double epsilon ) { - final List< double[] > resultList = new ArrayList< >(); + final List< double[] > resultList = new ArrayList<>(); douglasPeucker( list, 0, list.size(), epsilon, resultList ); return resultList; } From f886b653d836b6a557a72e7c00e5abcef842fbbe Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 10 Jul 2024 21:08:50 +0200 Subject: [PATCH 036/263] Do not execute the LabKit reimporter in the AWT thread. --- .../trackmate/gui/editor/LabkitLauncher.java | 143 +++++++++--------- 1 file changed, 75 insertions(+), 68 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index fa89eb424..05271c4c5 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -113,79 +113,86 @@ protected void launch( final boolean singleTimePoint ) private void reimport( final RandomAccessibleInterval< T > indexImg, final RandomAccessibleInterval< T > previousIndexImg, final double dt ) { - try + new Thread( "TrackMate-LabKit-Importer-thread" ) { - // Do we have something to reimport? - final AtomicBoolean modified = new AtomicBoolean( false ); - LoopBuilder.setImages( previousIndexImg, indexImg ) - .multiThreaded() - .forEachChunk( chunk -> { - if ( modified.get() ) - return null; - chunk.forEachPixel( ( p1, p2 ) -> { - if ( p1.getInteger() != p2.getInteger() ) - { - modified.set( true ); - return; - } - } ); - return null; - } ); - if ( !modified.get() ) - return; - - // Message the user. - final long nTimepoints = indexImg.dimension( 3 ); - final String msg = ( nTimepoints <= 1 ) - ? "Commit the changes made to the\n" - + "segmentation in the image?" - : ( currentTimePoint < 0 ) - ? "Commit the changes made to the\n" - + "segmentation in whole movie?" - : "Commit the changes made to the\n" - + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; - final String title = "Commit edits to TrackMate"; - final int returnedValue = JOptionPane.showConfirmDialog( - null, - msg, - title, - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE, - Icons.TRACKMATE_ICON ); - if ( returnedValue != JOptionPane.YES_OPTION ) - return; - - final LabkitImporter< T > reimporter = new LabkitImporter<>( trackmate.getModel(), calibration, dt ); - if ( currentTimePoint < 0 && nTimepoints > 1 ) + @Override + public void run() { - // All time-points. - final Logger log = Logger.IJ_LOGGER; - log.setStatus( "Re-importing from Labkit..." ); - for ( int t = 0; t < nTimepoints; t++ ) + try { - final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, 3, t ); - final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, 3, t ); - reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); - log.setProgress( ++t / ( double ) nTimepoints ); + // Do we have something to reimport? + final AtomicBoolean modified = new AtomicBoolean( false ); + LoopBuilder.setImages( previousIndexImg, indexImg ) + .multiThreaded() + .forEachChunk( chunk -> { + if ( modified.get() ) + return null; + chunk.forEachPixel( ( p1, p2 ) -> { + if ( p1.getInteger() != p2.getInteger() ) + { + modified.set( true ); + return; + } + } ); + return null; + } ); + if ( !modified.get() ) + return; + + // Message the user. + final long nTimepoints = indexImg.dimension( 3 ); + final String msg = ( nTimepoints <= 1 ) + ? "Commit the changes made to the\n" + + "segmentation in the image?" + : ( currentTimePoint < 0 ) + ? "Commit the changes made to the\n" + + "segmentation in whole movie?" + : "Commit the changes made to the\n" + + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; + final String title = "Commit edits to TrackMate"; + final int returnedValue = JOptionPane.showConfirmDialog( + null, + msg, + title, + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + Icons.TRACKMATE_ICON ); + if ( returnedValue != JOptionPane.YES_OPTION ) + return; + + final LabkitImporter< T > reimporter = new LabkitImporter<>( trackmate.getModel(), calibration, dt ); + if ( currentTimePoint < 0 && nTimepoints > 1 ) + { + // All time-points. + final Logger log = Logger.IJ_LOGGER; + log.setStatus( "Re-importing from Labkit..." ); + for ( int t = 0; t < nTimepoints; t++ ) + { + final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, 3, t ); + final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, 3, t ); + reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); + log.setProgress( ++t / ( double ) nTimepoints ); + } + log.setStatus( "" ); + log.setProgress( 0. ); + } + else + { + // Only one. + final int localT = Math.max( 0, currentTimePoint ); + reimporter.reimport( indexImg, previousIndexImg, localT ); + } + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + finally + { + disabler.reenable(); } - log.setStatus( "" ); - log.setProgress( 0. ); - } - else - { - // Only one. - final int localT = Math.max( 0, currentTimePoint ); - reimporter.reimport( indexImg, previousIndexImg, localT ); } - } - catch ( final Exception e ) - { - e.printStackTrace(); - } - finally - { - disabler.reenable(); - } + }.start(); } private Img< T > copy( final RandomAccessibleInterval< T > in ) From d1ca968743b4b1e25bd5077e964334b9fbd2298e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 11 Jul 2024 14:17:57 +0200 Subject: [PATCH 037/263] Simplify the LabKit importer. Use the new label immg to spot method to get spots AND the label they come from. --- .../trackmate/gui/editor/LabkitImporter.java | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index fbfebe142..7e5c19bf9 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -97,36 +97,17 @@ public void reimport( spots.iterable( currentTimePoint, true ).forEach( s -> previousSpotIDs.put( Integer.valueOf( s.ID() ), s ) ); /* - * Get all the spots present in the new image. Because we specified - * the novel label image as 'quality' image, they have a quality - * value equal to the index in the label image (id+1). + * Get all the spots present in the new image, as a map against the + * label in the novel index image. This label value corresponds to + * the ids of the spots in the previous index image (label = id+1). */ - final List< Spot > novelSpots = getSpots( novelIndexImg ); - - /* - * Map of novel spots against the ID taken from the index of the - * novel label image. Normally, this index, and hence the novel id, - * corresponds to the id of previous spots. If one of the novel spot - * has an id we cannot find in the previous spot list, it means that - * it is a new one. - * - * Careful! The user might have created several connected components - * with the same label in LabKit, which will result in having - * several spots with the same quality value. We don't want to loose - * them, so the map is that of a id to a list of spots. - */ - final Map< Integer, List< Spot > > novelSpotIDs = new HashMap<>(); - novelSpots.forEach( s -> { - final int id = Integer.valueOf( s.getFeature( Spot.QUALITY ).intValue() - 1 ); - final List< Spot > list = novelSpotIDs.computeIfAbsent( Integer.valueOf( id ), ( i ) -> new ArrayList<>() ); - list.add( s ); - } ); + final Map< Integer, List< Spot > > novelSpots = getSpots( novelIndexImg ); // Update model for those spots. for ( final int id : modifiedIDs ) { final Spot previousSpot = previousSpotIDs.get( Integer.valueOf( id ) ); - final List< Spot > novelSpotList = novelSpotIDs.get( Integer.valueOf( id ) ); + final List< Spot > novelSpotList = novelSpots.get( Integer.valueOf( id ) + 1 ); if ( previousSpot == null ) { /* @@ -254,7 +235,7 @@ private final Set< Integer > getModifiedIDs( return modifiedIDs; } - private List< Spot > getSpots( final RandomAccessibleInterval< T > rai ) + private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T > rai ) { // Get all labels. final AtomicInteger max = new AtomicInteger( 0 ); @@ -269,7 +250,7 @@ private List< Spot > getSpots( final RandomAccessibleInterval< T > rai ) final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); final boolean simplify = false; // needed to properly read label values. - final List< Spot > spots = MaskUtils.fromLabelingWithROI( + final Map< Integer, List< Spot > > spots = MaskUtils.fromLabelingWithROIMap( labeling, labeling, calibration, From aa9dc52df190650e1bfebf8132a9accf8971ef72 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 11 Jul 2024 15:11:21 +0200 Subject: [PATCH 038/263] In LabKit editor, offer to simplify the contour of modified spots. Also better message when closing the editor. --- .../trackmate/gui/editor/LabkitImporter.java | 11 +++++++-- .../trackmate/gui/editor/LabkitLauncher.java | 23 +++++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 7e5c19bf9..7c2eb390e 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -35,6 +35,8 @@ public class LabkitImporter< T extends IntegerType< T > & NativeType< T > > private final double dt; + private final boolean simplify; + /** * Creates a new re-importer. * @@ -44,15 +46,21 @@ public class LabkitImporter< T extends IntegerType< T > & NativeType< T > > * the spatial calibration array: [dx, dy dz]. * @param dt * the frame interval. + * @param simplifyContours + * if true the contours of the spots imported and + * modified will be simplified. If false their + * contour will follow pixel edges. */ public LabkitImporter( final Model model, final double[] calibration, - final double dt ) + final double dt, + final boolean simplifyContours ) { this.model = model; this.calibration = calibration; this.dt = dt; + this.simplify = simplifyContours; } /** @@ -249,7 +257,6 @@ private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T indices.add( Integer.valueOf( i + 1 ) ); final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); - final boolean simplify = false; // needed to properly read label values. final Map< Integer, List< Spot > > spots = MaskUtils.fromLabelingWithROIMap( labeling, labeling, diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 05271c4c5..ba133c455 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -6,9 +6,11 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JRootPane; +import javax.swing.JSeparator; import javax.swing.SwingUtilities; import org.scijava.Context; @@ -63,6 +65,10 @@ public class LabkitLauncher< T extends IntegerType< T > & NativeType< T > > private final boolean is3D; + private final boolean isSingleTimePoint; + + private static boolean simplify = true; + public LabkitLauncher( final TrackMate trackmate, final DisplaySettings ds, final EverythingDisablerAndReenabler disabler ) { this.trackmate = trackmate; @@ -71,6 +77,7 @@ public LabkitLauncher( final TrackMate trackmate, final DisplaySettings ds, fina final ImagePlus imp = trackmate.getSettings().imp; this.calibration = TMUtils.getSpatialCalibration( imp ); this.is3D = !DetectionUtils.is2D( imp ); + this.isSingleTimePoint = imp.getNFrames() <= 1; } /** @@ -140,8 +147,12 @@ public void run() return; // Message the user. - final long nTimepoints = indexImg.dimension( 3 ); - final String msg = ( nTimepoints <= 1 ) + // Axes. + final int timeDim = ( isSingleTimePoint ) + ? -1 + : ( is3D ) ? 3 : 2; + final long nTimepoints = ( timeDim < 0 ) ? 0 : indexImg.dimension( timeDim ); + final String msg = ( isSingleTimePoint ) ? "Commit the changes made to the\n" + "segmentation in the image?" : ( currentTimePoint < 0 ) @@ -150,9 +161,12 @@ public void run() : "Commit the changes made to the\n" + "segmentation in frame " + ( currentTimePoint + 1 ) + "?"; final String title = "Commit edits to TrackMate"; + final JCheckBox chkbox = new JCheckBox( "Simplify the contours of modified spots" ); + chkbox.setSelected( simplify ); + final Object[] objs = new Object[] { msg, new JSeparator(), chkbox }; final int returnedValue = JOptionPane.showConfirmDialog( null, - msg, + objs, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, @@ -160,7 +174,8 @@ public void run() if ( returnedValue != JOptionPane.YES_OPTION ) return; - final LabkitImporter< T > reimporter = new LabkitImporter<>( trackmate.getModel(), calibration, dt ); + simplify = chkbox.isSelected(); + final LabkitImporter< T > reimporter = new LabkitImporter<>( trackmate.getModel(), calibration, dt, simplify ); if ( currentTimePoint < 0 && nTimepoints > 1 ) { // All time-points. From 73c85db2fd3f6bbc4109674b4841f29ee070e2e2 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 12 Jul 2024 16:38:27 +0200 Subject: [PATCH 039/263] Let the user edit a ROI in the image with the LabKit editor. If a ROI exists in the input image when launching the LabKit editor, only the image and the spots present in the ROI are sent to the editor. Spots touching the borders are not imported. --- .../trackmate/gui/editor/ImpBdvShowable.java | 22 ++- .../trackmate/gui/editor/LabkitImporter.java | 2 +- .../trackmate/gui/editor/LabkitLauncher.java | 138 +++++++++++++++--- 3 files changed, 141 insertions(+), 21 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index 27ee12401..6d2e6fde6 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -19,10 +19,12 @@ import ij.CompositeImage; import ij.IJ; import ij.ImagePlus; +import ij.gui.Roi; import ij.process.LUT; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; +import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.numeric.ARGBType; @@ -58,6 +60,7 @@ public class ImpBdvShowable implements BdvShowable */ public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImagePlus imp ) { + @SuppressWarnings( "unchecked" ) final ImgPlus< T > src = TMUtils.rawWraps( imp ); if ( src.dimensionIndex( Axes.CHANNEL ) < 0 ) Views.addDimension( src ); @@ -85,16 +88,33 @@ public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImgPl private final ImgPlus< ? extends NumericType< ? > > image; + private Interval interval; + ImpBdvShowable( final ImgPlus< ? extends NumericType< ? > > image, final ImagePlus imp ) { this.image = image; this.imp = imp; + final Roi roi = imp.getRoi(); + if ( roi == null ) + { + this.interval = image; + } + else + { + final long[] min = image.minAsLongArray(); + final long[] max = image.maxAsLongArray(); + min[ 0 ] = roi.getBounds().x; + min[ 1 ] = roi.getBounds().y; + max[ 0 ] = roi.getBounds().x + roi.getBounds().width; + max[ 1 ] = roi.getBounds().y + roi.getBounds().height; + this.interval = new FinalInterval( min, max ); + } } @Override public Interval interval() { - return image; + return interval; } @Override diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 7c2eb390e..2a2929fbe 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -259,7 +259,7 @@ private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); final Map< Integer, List< Spot > > spots = MaskUtils.fromLabelingWithROIMap( labeling, - labeling, + Views.zeroMin( labeling ), calibration, simplify, rai ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index ba133c455..08936f544 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -19,6 +19,7 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.features.FeatureUtils; @@ -29,12 +30,15 @@ import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import ij.ImagePlus; +import ij.gui.Roi; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; +import net.imglib2.FinalInterval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; +import net.imglib2.img.ImgView; import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.loops.LoopBuilder; @@ -42,6 +46,7 @@ import net.imglib2.type.numeric.ARGBType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.integer.UnsignedIntType; +import net.imglib2.util.Intervals; import net.imglib2.util.Util; import net.imglib2.view.Views; import sc.fiji.labkit.ui.LabkitFrame; @@ -147,11 +152,6 @@ public void run() return; // Message the user. - // Axes. - final int timeDim = ( isSingleTimePoint ) - ? -1 - : ( is3D ) ? 3 : 2; - final long nTimepoints = ( timeDim < 0 ) ? 0 : indexImg.dimension( timeDim ); final String msg = ( isSingleTimePoint ) ? "Commit the changes made to the\n" + "segmentation in the image?" @@ -176,6 +176,15 @@ public void run() simplify = chkbox.isSelected(); final LabkitImporter< T > reimporter = new LabkitImporter<>( trackmate.getModel(), calibration, dt, simplify ); + + // Possibly determine the number of time-points to parse. + final int timeDim = ( isSingleTimePoint ) + ? -1 + : ( is3D ) ? 3 : 2; + final long nTimepoints = ( timeDim < 0 ) + ? 0 + : indexImg.numDimensions() > timeDim ? indexImg.dimension( timeDim ) : 0; + if ( currentTimePoint < 0 && nTimepoints > 1 ) { // All time-points. @@ -183,10 +192,10 @@ public void run() log.setStatus( "Re-importing from Labkit..." ); for ( int t = 0; t < nTimepoints; t++ ) { - final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, 3, t ); - final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, 3, t ); + final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t ); + final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t ); reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); - log.setProgress( ++t / ( double ) nTimepoints ); + log.setProgress( t / ( double ) nTimepoints ); } log.setStatus( "" ); log.setProgress( 0. ); @@ -239,6 +248,25 @@ private Img< T > copy( final RandomAccessibleInterval< T > in ) private final DatasetInputImage makeInput( final ImagePlus imp, final boolean singleTimePoint ) { final ImgPlus src = TMUtils.rawWraps( imp ); + // Crop if we have a ROI. + final Roi roi = imp.getRoi(); + final RandomAccessibleInterval crop; + if ( roi != null ) + { + final long[] min = src.minAsLongArray(); + final long[] max = src.maxAsLongArray(); + min[ 0 ] = roi.getBounds().x; + min[ 1 ] = roi.getBounds().y; + max[ 0 ] = roi.getBounds().x + roi.getBounds().width - 1; + max[ 1 ] = roi.getBounds().y + roi.getBounds().height - 1; + crop = Views.interval( src, min, max ); + } + else + { + crop = src; + } + final ImgPlus srcCropped = new ImgPlus<>( ImgView.wrap( crop ), src ); + // Possibly reslice for current time-point. final ImpBdvShowable showable; final ImgPlus inputImg; @@ -246,14 +274,14 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si if ( singleTimePoint && timeAxis >= 0 ) { this.currentTimePoint = imp.getFrame() - 1; - inputImg = ImgPlusViews.hyperSlice( src, timeAxis, currentTimePoint ); + inputImg = ImgPlusViews.hyperSlice( srcCropped, timeAxis, currentTimePoint ); showable = ImpBdvShowable.fromImp( inputImg, imp ); } else { this.currentTimePoint = -1; - showable = ImpBdvShowable.fromImp( imp ); - inputImg = src; + inputImg = srcCropped; + showable = ImpBdvShowable.fromImp( inputImg, imp ); } return new DatasetInputImage( inputImg, showable ); } @@ -297,8 +325,29 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, if ( !singleTimePoint ) dims[ dim++ ] = imp.getNFrames(); + // Possibly crop. + final Roi roi = imp.getRoi(); + final long[] origin; + if ( roi != null ) + { + dims[ 0 ] = roi.getBounds().width + 1; + dims[ 1 ] = roi.getBounds().height + 1; + origin = new long[ dims.length ]; + origin[ 0 ] = roi.getBounds().x; + origin[ 1 ] = roi.getBounds().y; + } + else + { + origin = null; + } + // Raw image. - final Img< UnsignedIntType > lblImg = ArrayImgs.unsignedInts( dims ); + Img< UnsignedIntType > lblImg = ArrayImgs.unsignedInts( dims ); + if ( origin != null ) + { + final RandomAccessibleInterval< UnsignedIntType > translated = Views.translate( lblImg, origin ); + lblImg = ImgView.wrap( translated ); + } // Calibration. final double[] c = TMUtils.getSpatialCalibration( imp ); @@ -318,7 +367,7 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, final Map< Integer, Spot > spotIDs = new HashMap<>(); if ( singleTimePoint ) { - processFrame( lblImgPlus, spots, currentTimePoint, spotIDs ); + processFrame( lblImgPlus, spots, currentTimePoint, spotIDs, origin ); } else { @@ -326,7 +375,7 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, for ( int t = 0; t < imp.getNFrames(); t++ ) { final ImgPlus< UnsignedIntType > lblImgPlusThisFrame = ImgPlusViews.hyperSlice( lblImgPlus, timeDim, t ); - processFrame( lblImgPlusThisFrame, spots, t, spotIDs ); + processFrame( lblImgPlusThisFrame, spots, t, spotIDs, origin ); } } final Labeling labeling = Labeling.fromImg( lblImgPlus ); @@ -350,16 +399,67 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, return labeling; } - private static final void processFrame( final ImgPlus< UnsignedIntType > lblImgPlus, final SpotCollection spots, final int t, final Map< Integer, Spot > spotIDs ) + private final void processFrame( + final ImgPlus< UnsignedIntType > lblImgPlus, + final SpotCollection spots, + final int t, + final Map< Integer, Spot > spotIDs, + final long[] origin ) { // If we have a single timepoint, don't use -1 to retrieve spots. final int lt = t < 0 ? 0 : t; final Iterable< Spot > spotsThisFrame = spots.iterable( lt, true ); - for ( final Spot spot : spotsThisFrame ) + if ( null == origin ) + { + for ( final Spot spot : spotsThisFrame ) + { + final int index = spot.ID() + 1; + SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) ); + spotIDs.put( index, spot ); + } + } + else + { + final long[] min = new long[ 2 ]; + final long[] max = new long[ 2 ]; + final FinalInterval spotBB = FinalInterval.wrap( min, max ); + final FinalInterval imgBB = Intervals.createMinSize( origin[ 0 ], origin[ 1 ], lblImgPlus.dimension( 0 ), lblImgPlus.dimension( 1 ) ); + for ( final Spot spot : spotsThisFrame ) + { + boundingBox( spot, min, max ); + // Inside? We skip if we touch the border. + final boolean isInside = Intervals.contains( imgBB, spotBB ); + if ( !isInside ) + continue; + + final int index = spot.ID() + 1; + SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) ); + spotIDs.put( index, spot ); + } + } + } + + private void boundingBox( final Spot spot, final long[] min, final long[] max ) + { + final SpotRoi roi = spot.getRoi(); + if ( roi == null ) + { + final double cx = spot.getDoublePosition( 0 ); + final double cy = spot.getDoublePosition( 1 ); + final double r = spot.getFeature( Spot.RADIUS ).doubleValue(); + min[ 0 ] = ( long ) Math.floor( ( cx - r ) / calibration[ 0 ] ); + min[ 1 ] = ( long ) Math.floor( ( cy - r ) / calibration[ 1 ] ); + max[ 0 ] = ( long ) Math.ceil( ( cx + r ) / calibration[ 0 ] ); + max[ 1 ] = ( long ) Math.ceil( ( cy + r ) / calibration[ 1 ] ); + } + else { - final int index = spot.ID() + 1; - SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) ); - spotIDs.put( index, spot ); + final double[] x = roi.toPolygonX( calibration[ 0 ], 0, spot.getDoublePosition( 0 ), 1. ); + final double[] y = roi.toPolygonY( calibration[ 1 ], 0, spot.getDoublePosition( 1 ), 1. ); + min[ 0 ] = ( long ) Math.floor( Util.min( x ) ); + min[ 1 ] = ( long ) Math.floor( Util.min( y ) ); + max[ 0 ] = ( long ) Math.ceil( Util.max( x ) ); + max[ 1 ] = ( long ) Math.ceil( Util.max( y ) ); } } From 186520e92611b0b783702568c1851ad686292f1b Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 17 Jul 2024 17:49:29 +0200 Subject: [PATCH 040/263] Fix panning not working when the TrackMate tool is selected. Normally pressing space and dragging should move the view, as for normal ImageJ tools. The removed line was preventing it. --- .../plugin/trackmate/visualization/hyperstack/SpotEditTool.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java index 1fc20c149..d091862d2 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java @@ -355,7 +355,6 @@ public void keyPressed( final KeyEvent e ) case KeyEvent.VK_SPACE: { actions.startMoveSpot(); - e.consume(); break; } From 28666c3379309bfae9d50c4b1420208e0b284401 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 18 Jul 2024 16:01:56 +0200 Subject: [PATCH 041/263] In LabKit editor, import spots at the border of the image. --- .../plugin/trackmate/gui/editor/LabkitLauncher.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 08936f544..0cbb26d78 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -257,8 +257,10 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si final long[] max = src.maxAsLongArray(); min[ 0 ] = roi.getBounds().x; min[ 1 ] = roi.getBounds().y; - max[ 0 ] = roi.getBounds().x + roi.getBounds().width - 1; - max[ 1 ] = roi.getBounds().y + roi.getBounds().height - 1; + max[ 0 ] = roi.getBounds().x + roi.getBounds().width; + max[ 1 ] = roi.getBounds().y + roi.getBounds().height; +// max[ 0 ] = roi.getBounds().x + roi.getBounds().width - 1; +// max[ 1 ] = roi.getBounds().y + roi.getBounds().height - 1; crop = Views.interval( src, min, max ); } else @@ -461,6 +463,12 @@ private void boundingBox( final Spot spot, final long[] min, final long[] max ) max[ 0 ] = ( long ) Math.ceil( Util.max( x ) ); max[ 1 ] = ( long ) Math.ceil( Util.max( y ) ); } + + min[ 0 ] = Math.max( 0, min[ 0 ] ); + min[ 1 ] = Math.max( 0, min[ 1 ] ); + final ImagePlus imp = trackmate.getSettings().imp; + max[ 0 ] = Math.min( imp.getWidth(), max[ 0 ] ); + max[ 1 ] = Math.min( imp.getHeight(), max[ 1 ] ); } public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate, final DisplaySettings ds ) From faa87b90ccb57b5883a63c40aafcaf74820a6a14 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 18 Jul 2024 16:20:11 +0200 Subject: [PATCH 042/263] Fix bug in LabKit editor: avoid spot ID and label conflicts. When editing a sub-portion of the image, the spots outside the ROI are not imported in the editor. It is possible that the user creates a new label in LabKit that will have the same id that an existing spot outside the ROI. Before this fix, the spots in that case were replaced by the new ones, creating a mess. The fix consists in reminding what spots are imported in the LabKit editor, then compariing to this list the new ids when reimporting from the editor. --- .../trackmate/gui/editor/LabkitImporter.java | 13 ++++----- .../trackmate/gui/editor/LabkitLauncher.java | 28 +++++++++++++------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 2a2929fbe..0cfe53c5a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -1,7 +1,6 @@ package fiji.plugin.trackmate.gui.editor; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -13,7 +12,6 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.detection.MaskUtils; import net.imglib2.RandomAccessibleInterval; import net.imglib2.loops.LoopBuilder; @@ -84,11 +82,15 @@ public LabkitImporter( * @param currentTimePoint * the time-point in the TrackMate model that corresponds to the * index image. + * @param the + * map of spots (vs their ID) that were written in the previous + * index image. */ public void reimport( final RandomAccessibleInterval< T > novelIndexImg, final RandomAccessibleInterval< T > previousIndexImg, - final int currentTimePoint ) + final int currentTimePoint, + final Map< Integer, Spot > previousSpotIDs ) { // Collect ids of spots that have been modified. id = index - 1 final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg, previousIndexImg ); @@ -99,11 +101,6 @@ public void reimport( model.beginUpdate(); try { - // Map of previous spots against their ID: - final SpotCollection spots = model.getSpots(); - final Map< Integer, Spot > previousSpotIDs = new HashMap<>(); - spots.iterable( currentTimePoint, true ).forEach( s -> previousSpotIDs.put( Integer.valueOf( s.ID() ), s ) ); - /* * Get all the spots present in the new image, as a map against the * label in the novel index image. This label value corresponds to diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 0cbb26d78..b01b4912f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -47,7 +47,9 @@ import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.integer.UnsignedIntType; import net.imglib2.util.Intervals; +import net.imglib2.util.Pair; import net.imglib2.util.Util; +import net.imglib2.util.ValuePair; import net.imglib2.view.Views; import sc.fiji.labkit.ui.LabkitFrame; import sc.fiji.labkit.ui.inputimage.DatasetInputImage; @@ -97,7 +99,9 @@ protected void launch( final boolean singleTimePoint ) { final ImagePlus imp = trackmate.getSettings().imp; final DatasetInputImage input = makeInput( imp, singleTimePoint ); - final Labeling labeling = makeLabeling( imp, trackmate.getModel().getSpots(), singleTimePoint ); + final Pair< Labeling, Map< Integer, Spot > > pair = makeLabeling( imp, trackmate.getModel().getSpots(), singleTimePoint ); + final Labeling labeling = pair.getA(); + final Map< Integer, Spot > spotIDs = pair.getB(); // Make a labeling model from it. final Context context = TMUtils.getContext(); @@ -119,11 +123,15 @@ protected void launch( final boolean singleTimePoint ) labkit.onCloseListeners().addListener( () -> { @SuppressWarnings( "unchecked" ) final RandomAccessibleInterval< T > indexImg = ( RandomAccessibleInterval< T > ) model.imageLabelingModel().labeling().get().getIndexImg(); - reimport( indexImg, previousIndexImg, dt ); + reimport( indexImg, previousIndexImg, spotIDs, dt ); } ); } - private void reimport( final RandomAccessibleInterval< T > indexImg, final RandomAccessibleInterval< T > previousIndexImg, final double dt ) + private void reimport( + final RandomAccessibleInterval< T > indexImg, + final RandomAccessibleInterval< T > previousIndexImg, + final Map< Integer, Spot > spotIDs, + final double dt ) { new Thread( "TrackMate-LabKit-Importer-thread" ) { @@ -194,7 +202,7 @@ public void run() { final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t ); final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t ); - reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t ); + reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t, spotIDs ); log.setProgress( t / ( double ) nTimepoints ); } log.setStatus( "" ); @@ -204,7 +212,7 @@ public void run() { // Only one. final int localT = Math.max( 0, currentTimePoint ); - reimporter.reimport( indexImg, previousIndexImg, localT ); + reimporter.reimport( indexImg, previousIndexImg, localT, spotIDs ); } } catch ( final Exception e ) @@ -291,7 +299,8 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si /** * Prepare the label image for annotation. The labeling is created and each * of its labels receive the name and the color from the spot it is created - * from. + * from. Only the spots fully included in the bounding box of the ROI of the + * source image are written in the labeling. * * @param imp * the source image plus. @@ -299,9 +308,10 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si * the spot collection. * @param singleTimePoint * if true we only annotate one time-point. - * @return a new {@link Labeling}. + * @return the pair of: A. a new {@link Labeling}, B. the map of spots that + * were written in the labeling. */ - private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, final boolean singleTimePoint ) + private Pair< Labeling, Map< Integer, Spot > > makeLabeling( final ImagePlus imp, final SpotCollection spots, final boolean singleTimePoint ) { // Axes. final AxisType[] axes = ( is3D ) @@ -398,7 +408,7 @@ private Labeling makeLabeling( final ImagePlus imp, final SpotCollection spots, label.setColor( new ARGBType( colorGen.color( spot ).getRGB() ) ); } - return labeling; + return new ValuePair<>( labeling, spotIDs ); } private final void processFrame( From 8ccd3ee8b3d340025880d2e4faf7218a595e1f57 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 19 Jul 2024 09:41:53 +0200 Subject: [PATCH 043/263] Custom LabKit frame and segmentation component. Simplified, removing the menu, the segmentation features and the segmenter features. Somply done by copy-pasting Matthias classes and removing the lines with the features to prune. --- .../trackmate/gui/editor/LabkitLauncher.java | 3 +- .../TrackMateLabKitSegmentationComponent.java | 169 ++++++++++++++++++ .../gui/editor/TrackMateLabkitFrame.java | 148 +++++++++++++++ 3 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabKitSegmentationComponent.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabkitFrame.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index b01b4912f..73e5d3570 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -51,7 +51,6 @@ import net.imglib2.util.Util; import net.imglib2.util.ValuePair; import net.imglib2.view.Views; -import sc.fiji.labkit.ui.LabkitFrame; import sc.fiji.labkit.ui.inputimage.DatasetInputImage; import sc.fiji.labkit.ui.labeling.Label; import sc.fiji.labkit.ui.labeling.Labeling; @@ -116,7 +115,7 @@ protected void launch( final boolean singleTimePoint ) String title = "Editing TrackMate data for " + imp.getShortTitle(); if ( singleTimePoint ) title += "at frame " + ( currentTimePoint + 1 ); - final LabkitFrame labkit = LabkitFrame.show( model, title ); + final TrackMateLabkitFrame labkit = TrackMateLabkitFrame.show( model, title ); // Prepare re-importer. final double dt = imp.getCalibration().frameInterval; diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabKitSegmentationComponent.java b/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabKitSegmentationComponent.java new file mode 100644 index 000000000..10c7bc50e --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabKitSegmentationComponent.java @@ -0,0 +1,169 @@ +/*- + * #%L + * The Labkit image segmentation tool for Fiji. + * %% + * Copyright (C) 2017 - 2024 Matthias Arzt + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package fiji.plugin.trackmate.gui.editor; + +import java.awt.BorderLayout; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JPanel; +import javax.swing.JSplitPane; + +import net.miginfocom.swing.MigLayout; +import sc.fiji.labkit.ui.BasicLabelingComponent; +import sc.fiji.labkit.ui.DefaultExtensible; +import sc.fiji.labkit.ui.MenuBar; +import sc.fiji.labkit.ui.actions.AddLabelingIoAction; +import sc.fiji.labkit.ui.actions.LabelEditAction; +import sc.fiji.labkit.ui.actions.LabelingIoAction; +import sc.fiji.labkit.ui.actions.ResetViewAction; +import sc.fiji.labkit.ui.actions.ShowHelpAction; +import sc.fiji.labkit.ui.menu.MenuKey; +import sc.fiji.labkit.ui.models.ColoredLabelsModel; +import sc.fiji.labkit.ui.models.ImageLabelingModel; +import sc.fiji.labkit.ui.models.SegmentationItem; +import sc.fiji.labkit.ui.models.SegmentationModel; +import sc.fiji.labkit.ui.panel.ImageInfoPanel; +import sc.fiji.labkit.ui.panel.LabelPanel; +import sc.fiji.labkit.ui.segmentation.PredictionLayer; + +/** + * {@link SegmentationComponent} is the central Labkit UI component. Provides UI + * to display and modify a {@link SegmentationModel}. + *

+ * The UI consist of a Big Data Viewer panel, with brush tools and a side bar. + * The side bar lists panels and segmentation algorithms. + *

+ * A main menu that contains many actions for open and saving data, can be + * accessed by using {@link #getMenuBar()}. + */ +public class TrackMateLabKitSegmentationComponent extends JPanel implements AutoCloseable +{ + + private static final long serialVersionUID = 1L; + + private final boolean unmodifiableLabels; + + private final DefaultExtensible extensible; + + private final BasicLabelingComponent labelingComponent; + + private final SegmentationModel segmentationModel; + + public TrackMateLabKitSegmentationComponent( final JFrame dialogBoxOwner, + final SegmentationModel segmentationModel, final boolean unmodifiableLabels ) + { + this.extensible = new DefaultExtensible( segmentationModel.context(), dialogBoxOwner ); + this.unmodifiableLabels = unmodifiableLabels; + this.segmentationModel = segmentationModel; + final ImageLabelingModel imageLabelingModel = segmentationModel.imageLabelingModel(); + labelingComponent = new BasicLabelingComponent( dialogBoxOwner, imageLabelingModel ); + labelingComponent.addBdvLayer( PredictionLayer.createPredictionLayer( segmentationModel ) ); + initActions(); + setLayout( new BorderLayout() ); + add( initGui() ); + } + + private void initActions() + { +// final Holder< SegmentationItem > selectedSegmenter = segmentationModel.segmenterList().selectedSegmenter(); + final ImageLabelingModel labelingModel = segmentationModel.imageLabelingModel(); +// new TrainClassifier( extensible, segmentationModel.segmenterList() ); +// new ClassifierSettingsAction( extensible, segmentationModel.segmenterList() ); +// new ClassifierIoAction( extensible, segmentationModel.segmenterList() ); + new LabelingIoAction( extensible, labelingModel ); + new AddLabelingIoAction( extensible, labelingModel.labeling() ); +// new SegmentationExportAction( extensible, labelingModel ); + new ResetViewAction( extensible, labelingModel ); +// new BatchSegmentAction( extensible, selectedSegmenter ); +// new SegmentationAsLabelAction( extensible, segmentationModel ); +// new BitmapImportExportAction( extensible, labelingModel ); + new LabelEditAction( extensible, unmodifiableLabels, new ColoredLabelsModel( labelingModel ) ); +// MeasureConnectedComponents.addAction( extensible, labelingModel ); + new ShowHelpAction( extensible ); + labelingComponent.addShortcuts( extensible.getShortCuts() ); + } + + private JPanel initLeftPanel() + { + final JPanel panel = new JPanel(); + panel.setLayout( new MigLayout( "", "[grow]", "[][grow]" ) ); + panel.add( ImageInfoPanel.newFramedImageInfoPanel( segmentationModel.imageLabelingModel(), labelingComponent ), "grow, wrap" ); + panel.add( LabelPanel.newFramedLabelPanel( segmentationModel.imageLabelingModel(), extensible, unmodifiableLabels ), "grow, wrap, height 0:5000" ); +// panel.add( SegmenterPanel.newFramedSegmeterPanel( segmentationModel.segmenterList(), extensible ), "grow, height 0:50" ); + panel.invalidate(); + panel.repaint(); + return panel; + } + + private JSplitPane initGui() + { + final JSplitPane panel = new JSplitPane(); + panel.setOneTouchExpandable( true ); + panel.setLeftComponent( initLeftPanel() ); + panel.setRightComponent( labelingComponent ); + panel.setBorder( BorderFactory.createEmptyBorder() ); + return panel; + } + + @Deprecated + public JComponent getComponent() + { + return this; + } + + public JMenu createMenu( final MenuKey< Void > key ) + { + if ( key == MenuBar.SEGMENTER_MENU ) + return extensible.createMenu( + SegmentationItem.SEGMENTER_MENU, segmentationModel + .segmenterList().selectedSegmenter()::get ); + return extensible.createMenu( key, () -> null ); + } + + @Override + public void close() + { + labelingComponent.close(); + } + + public JMenuBar getMenuBar() + { + return new MenuBar( this::createMenu ); + } + + public void autoContrast() + { + labelingComponent.autoContrast(); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabkitFrame.java b/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabkitFrame.java new file mode 100644 index 000000000..5d3014ba1 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabkitFrame.java @@ -0,0 +1,148 @@ +/*- + * #%L + * The Labkit image segmentation tool for Fiji. + * %% + * Copyright (C) 2017 - 2024 Matthias Arzt + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package fiji.plugin.trackmate.gui.editor; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; + +import javax.swing.JFrame; + +import org.scijava.Context; + +import fiji.plugin.trackmate.gui.Icons; +import io.scif.services.DatasetIOService; +import net.imagej.Dataset; +import sc.fiji.labkit.pixel_classification.utils.SingletonContext; +import sc.fiji.labkit.ui.InitialLabeling; +import sc.fiji.labkit.ui.inputimage.DatasetInputImage; +import sc.fiji.labkit.ui.inputimage.InputImage; +import sc.fiji.labkit.ui.models.DefaultSegmentationModel; +import sc.fiji.labkit.ui.models.SegmentationModel; +import sc.fiji.labkit.ui.utils.Notifier; + +/** + * The main Labkit window. (This window allows to segment a single image. It has + * to be distinguished from the LabkitProjectFrame, which allows to operation on + * multiple images.) The window only contains a {@link SegmentationComponent} + * and shows the associated main menu. + * + * @author Matthias Arzt + */ +public class TrackMateLabkitFrame +{ + + private final JFrame frame = initFrame(); + + private final Notifier onCloseListeners = new Notifier(); + + public static TrackMateLabkitFrame showForFile( Context context, + final String filename ) + { + if ( context == null ) + context = SingletonContext.getInstance(); + final Dataset dataset = openDataset( context, filename ); + return showForImage( context, new DatasetInputImage( dataset ) ); + } + + private static Dataset openDataset( final Context context, final String filename ) + { + try + { + return context.service( DatasetIOService.class ).open( filename ); + } + catch ( final IOException e ) + { + throw new RuntimeException( e ); + } + } + + public static TrackMateLabkitFrame showForImage( Context context, final InputImage inputImage ) + { + if ( context == null ) + context = SingletonContext.getInstance(); + final SegmentationModel model = new DefaultSegmentationModel( context, inputImage ); + model.imageLabelingModel().labeling().set( InitialLabeling.initialLabeling( context, inputImage ) ); + return show( model, inputImage.imageForSegmentation().getName() ); + } + + public static TrackMateLabkitFrame show( final SegmentationModel model, final String title ) + { + return new TrackMateLabkitFrame( model, title ); + } + + private TrackMateLabkitFrame( final SegmentationModel model, final String title ) + { + @SuppressWarnings( "unused" ) + final TrackMateLabKitSegmentationComponent segmentationComponent = initSegmentationComponent( model ); + setTitle( title ); + frame.setIconImage( Icons.TRACKMATE_ICON.getImage() ); +// frame.setJMenuBar( new MenuBar( segmentationComponent::createMenu ) ); + frame.setVisible( true ); + } + + private TrackMateLabKitSegmentationComponent initSegmentationComponent( final SegmentationModel segmentationModel ) + { + final TrackMateLabKitSegmentationComponent segmentationComponent = new TrackMateLabKitSegmentationComponent( frame, segmentationModel, false ); + frame.add( segmentationComponent ); + frame.addWindowListener( new WindowAdapter() + { + + @Override + public void windowClosed( final WindowEvent e ) + { + segmentationComponent.close(); + onCloseListeners.notifyListeners(); + } + } ); + return segmentationComponent; + } + + private JFrame initFrame() + { + final JFrame frame = new JFrame(); + frame.setBounds( 50, 50, 1200, 900 ); + frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); + return frame; + } + + private void setTitle( final String name ) + { + if ( name == null || name.isEmpty() ) + frame.setTitle( "Labkit" ); + else + frame.setTitle( "Labkit - " + name ); + } + + public Notifier onCloseListeners() + { + return onCloseListeners; + } +} From 2f9583e67e339b2fc4f4eb74a68b11bfa95ffbb9 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 19 Jul 2024 10:00:21 +0200 Subject: [PATCH 044/263] Fix another bug with the LabKit importer. This time it was due to confusion between a map from spot ID to spots, and a map from label value to spot. I fixed it by rewriting everything in terms of label values in the labeling. So we do not need to keep track of the spot ID anymore. The labels could be anything. They are still equal to spot ID + 1, because it is convenient to debug. --- .../trackmate/gui/editor/LabkitImporter.java | 27 ++++++++------- .../trackmate/gui/editor/LabkitLauncher.java | 33 ++++++++++--------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 0cfe53c5a..45153f6e6 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -83,18 +83,18 @@ public LabkitImporter( * the time-point in the TrackMate model that corresponds to the * index image. * @param the - * map of spots (vs their ID) that were written in the previous - * index image. + * map of spots (vs the label value in the previous labeling) + * that were written in the previous index image. */ public void reimport( final RandomAccessibleInterval< T > novelIndexImg, final RandomAccessibleInterval< T > previousIndexImg, final int currentTimePoint, - final Map< Integer, Spot > previousSpotIDs ) + final Map< Integer, Spot > previousSpotLabels ) { - // Collect ids of spots that have been modified. id = index - 1 - final Set< Integer > modifiedIDs = getModifiedIDs( novelIndexImg, previousIndexImg ); - final int nModified = modifiedIDs.size(); + // Collect labels corresponding to spots that have been modified. + final Set< Integer > modifiedLabels = getModifiedLabels( novelIndexImg, previousIndexImg ); + final int nModified = modifiedLabels.size(); if ( nModified == 0 ) return; @@ -103,16 +103,15 @@ public void reimport( { /* * Get all the spots present in the new image, as a map against the - * label in the novel index image. This label value corresponds to - * the ids of the spots in the previous index image (label = id+1). + * label in the novel index image. */ final Map< Integer, List< Spot > > novelSpots = getSpots( novelIndexImg ); // Update model for those spots. - for ( final int id : modifiedIDs ) + for ( final int labelValue : modifiedLabels ) { - final Spot previousSpot = previousSpotIDs.get( Integer.valueOf( id ) ); - final List< Spot > novelSpotList = novelSpots.get( Integer.valueOf( id ) + 1 ); + final Spot previousSpot = previousSpotLabels.get( labelValue ); + final List< Spot > novelSpotList = novelSpots.get( labelValue ); if ( previousSpot == null ) { /* @@ -218,7 +217,7 @@ private void addNewSpot( final Iterable< Spot > novelSpotList, final int current } } - private final Set< Integer > getModifiedIDs( + private final Set< Integer > getModifiedLabels( final RandomAccessibleInterval< T > novelIndexImg, final RandomAccessibleInterval< T > previousIndexImg ) { @@ -232,8 +231,8 @@ private final Set< Integer > getModifiedIDs( return; if ( ci != pi ) { - modifiedIDs.add( Integer.valueOf( pi - 1 ) ); - modifiedIDs.add( Integer.valueOf( ci - 1 ) ); + modifiedIDs.add( Integer.valueOf( pi ) ); + modifiedIDs.add( Integer.valueOf( ci ) ); } } ); modifiedIDs.remove( Integer.valueOf( -1 ) ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 73e5d3570..a4025239c 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -100,7 +100,7 @@ protected void launch( final boolean singleTimePoint ) final DatasetInputImage input = makeInput( imp, singleTimePoint ); final Pair< Labeling, Map< Integer, Spot > > pair = makeLabeling( imp, trackmate.getModel().getSpots(), singleTimePoint ); final Labeling labeling = pair.getA(); - final Map< Integer, Spot > spotIDs = pair.getB(); + final Map< Integer, Spot > spotLabels = pair.getB(); // Make a labeling model from it. final Context context = TMUtils.getContext(); @@ -122,14 +122,14 @@ protected void launch( final boolean singleTimePoint ) labkit.onCloseListeners().addListener( () -> { @SuppressWarnings( "unchecked" ) final RandomAccessibleInterval< T > indexImg = ( RandomAccessibleInterval< T > ) model.imageLabelingModel().labeling().get().getIndexImg(); - reimport( indexImg, previousIndexImg, spotIDs, dt ); + reimport( indexImg, previousIndexImg, spotLabels, dt ); } ); } private void reimport( final RandomAccessibleInterval< T > indexImg, final RandomAccessibleInterval< T > previousIndexImg, - final Map< Integer, Spot > spotIDs, + final Map< Integer, Spot > spotLabels, final double dt ) { new Thread( "TrackMate-LabKit-Importer-thread" ) @@ -201,7 +201,7 @@ public void run() { final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t ); final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t ); - reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t, spotIDs ); + reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t, spotLabels ); log.setProgress( t / ( double ) nTimepoints ); } log.setStatus( "" ); @@ -211,7 +211,7 @@ public void run() { // Only one. final int localT = Math.max( 0, currentTimePoint ); - reimporter.reimport( indexImg, previousIndexImg, localT, spotIDs ); + reimporter.reimport( indexImg, previousIndexImg, localT, spotLabels ); } } catch ( final Exception e ) @@ -308,7 +308,8 @@ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean si * @param singleTimePoint * if true we only annotate one time-point. * @return the pair of: A. a new {@link Labeling}, B. the map of spots that - * were written in the labeling. + * were written in the labeling. The keys are the label value in the + * labeling. */ private Pair< Labeling, Map< Integer, Spot > > makeLabeling( final ImagePlus imp, final SpotCollection spots, final boolean singleTimePoint ) { @@ -375,10 +376,10 @@ private Pair< Labeling, Map< Integer, Spot > > makeLabeling( final ImagePlus imp final ImgPlus< UnsignedIntType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); // Write spots in it with index = id + 1 and build a map index -> spot. - final Map< Integer, Spot > spotIDs = new HashMap<>(); + final Map< Integer, Spot > spotLabels = new HashMap<>(); if ( singleTimePoint ) { - processFrame( lblImgPlus, spots, currentTimePoint, spotIDs, origin ); + processFrame( lblImgPlus, spots, currentTimePoint, spotLabels, origin ); } else { @@ -386,7 +387,7 @@ private Pair< Labeling, Map< Integer, Spot > > makeLabeling( final ImagePlus imp for ( int t = 0; t < imp.getNFrames(); t++ ) { final ImgPlus< UnsignedIntType > lblImgPlusThisFrame = ImgPlusViews.hyperSlice( lblImgPlus, timeDim, t ); - processFrame( lblImgPlusThisFrame, spots, t, spotIDs, origin ); + processFrame( lblImgPlusThisFrame, spots, t, spotLabels, origin ); } } final Labeling labeling = Labeling.fromImg( lblImgPlus ); @@ -396,25 +397,25 @@ private Pair< Labeling, Map< Integer, Spot > > makeLabeling( final ImagePlus imp for ( final Label label : labeling.getLabels() ) { final String name = label.name(); - final int index = Integer.parseInt( name ); - final Spot spot = spotIDs.get( index ); + final int labelVal = Integer.parseInt( name ); + final Spot spot = spotLabels.get( labelVal ); if ( spot == null ) { - System.out.println( "Spot is null for index " + index + "!!" ); // DEBUG + System.out.println( "Spot is null for label " + labelVal + "!!" ); // DEBUG continue; } label.setName( spot.getName() ); label.setColor( new ARGBType( colorGen.color( spot ).getRGB() ) ); } - return new ValuePair<>( labeling, spotIDs ); + return new ValuePair<>( labeling, spotLabels ); } private final void processFrame( final ImgPlus< UnsignedIntType > lblImgPlus, final SpotCollection spots, final int t, - final Map< Integer, Spot > spotIDs, + final Map< Integer, Spot > spotLabels, final long[] origin ) { // If we have a single timepoint, don't use -1 to retrieve spots. @@ -426,7 +427,7 @@ private final void processFrame( { final int index = spot.ID() + 1; SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) ); - spotIDs.put( index, spot ); + spotLabels.put( index, spot ); } } else @@ -445,7 +446,7 @@ private final void processFrame( final int index = spot.ID() + 1; SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) ); - spotIDs.put( index, spot ); + spotLabels.put( index, spot ); } } } From 72d60f3c485259a114c1901bf20db44bcf3eb27c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 13 Apr 2023 17:07:06 +0200 Subject: [PATCH 045/263] Update parent pom version and SNAPSHOT version. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 97689a66c..9556a6443 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ sc.fiji TrackMate - 7.13.3-SNAPSHOT + 8.0.0-SNAPSHOT TrackMate TrackMate plugin for Fiji. From 64bc4b40a111505e9e1b45ad4e4b746fd01c7d0b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 13 Apr 2023 19:30:01 +0200 Subject: [PATCH 046/263] Add ImageJ-Mesh and ImageJ-Mesh-IO as dependencies. --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index 9556a6443..c9c570530 100644 --- a/pom.xml +++ b/pom.xml @@ -171,6 +171,14 @@ net.imagej imagej-common + + net.imagej + imagej-mesh + + + net.imagej + imagej-mesh-io + From d88b61efe3635e509d4629d4e284ebbf29350a13 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 13 Apr 2023 19:30:22 +0200 Subject: [PATCH 047/263] WIP: make objects that TrackMate can handle out of 3D masks. --- .../plugin/trackmate/mesh/Demo3DMesh.java | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java new file mode 100644 index 000000000..344c030fb --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -0,0 +1,230 @@ +package fiji.plugin.trackmate.mesh; + +import java.awt.geom.Point2D; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.itextpdf.text.pdf.codec.Base64; + +import fiji.plugin.trackmate.detection.MaskUtils; +import fiji.plugin.trackmate.util.TMUtils; +import ij.IJ; +import ij.ImageJ; +import ij.ImagePlus; +import ij.gui.Overlay; +import ij.gui.PolygonRoi; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.Triangles; +import net.imagej.mesh.Vertices; +import net.imagej.mesh.io.stl.STLMeshIO; +import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.converter.RealTypeConverters; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.roi.labeling.ImgLabeling; +import net.imglib2.roi.labeling.LabelRegion; +import net.imglib2.roi.labeling.LabelRegions; +import net.imglib2.type.logic.BitType; +import net.imglib2.type.logic.BoolType; +import net.imglib2.type.numeric.NumericType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.view.IntervalView; +import net.imglib2.view.Views; + +public class Demo3DMesh +{ + public static < T extends RealType< T > & NumericType< T > > void main( final String[] args ) throws IOException + { + final String filePath = "samples/mesh/CElegansMask3D.tif"; + + ImageJ.main( args ); + final ImagePlus imp = IJ.openImage( filePath ); + imp.show(); + + // To ImgLib2 boolean. + + // First channel is the mask. + @SuppressWarnings( "unchecked" ) + final ImgPlus< T > img = TMUtils.rawWraps( imp ); + final ImgPlus< T > c1 = ImgPlusViews.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 0 ); + + // Take the first time-point + final ImgPlus< T > t1 = ImgPlusViews.hyperSlice( c1, c1.dimensionIndex( Axes.TIME ), 0 ); + + // Make it to boolean. + final RandomAccessibleInterval< BitType > mask = RealTypeConverters.convert( t1, new BitType() ); + + // Convert it to labeling. + final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( mask, mask, 0.5, 1 ); + ImageJFunctions.show( labeling.getSource(), "labeling" ); + + // Iterate through all components. + final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); + final double[] cal = TMUtils.getSpatialCalibration( img ); + + // Parse regions to create polygons on boundaries. + final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); + int j = 0; + while ( iterator.hasNext() ) + { + final LabelRegion< Integer > region = iterator.next(); + final IntervalView< BoolType > box = Views.zeroMin( region ); + + // To mesh. + final Mesh mesh = Meshes.marchingCubes( box ); + + // Scale and offset with physical coordinates. + final double[] origin = region.minAsDoubleArray(); + scale( mesh.vertices(), cal, origin ); + + // Simplify. + final Mesh simplified = Meshes.simplify( mesh, 0.25f, 10f ); + + /* + * IO. + */ + testIO( simplified, ++j ); + + /* + * Display. + */ + + // Intersection with a XY plane at a fixed Z position. + final int zslice = 20; // plan + final double z = ( zslice ) * cal[ 2 ]; // um + final Triangles triangles = simplified.triangles(); + final Vertices vertices = simplified.vertices(); + final List< double[] > polygon = new ArrayList<>(); + for ( long i = 0; i < triangles.size(); i++ ) + { + final long v0 = triangles.vertex0( i ); + final double z0 = vertices.z( v0 ); + final long v1 = triangles.vertex1( i ); + final double z1 = vertices.z( v1 ); + final long v2 = triangles.vertex2( i ); + final double z2 = vertices.z( v2 ); + + if ( ( z0 <= z && z1 > z && z2 > z ) || + ( z1 <= z && z2 > z && z0 > z ) || + ( z2 <= z && z0 > z && z1 > z ) || + ( z0 >= z && z1 < z && z2 < z ) || + ( z1 >= z && z2 < z && z0 < z ) || + ( z2 >= z && z0 < z && z1 < z ) ) + { + final double[] i1 = intersect( vertices, v0, v1, z ); + final double[] i2 = intersect( vertices, v1, v2, z ); + final double[] i3 = intersect( vertices, v2, v0, z ); + polygon.add( i1 ); + polygon.add( i2 ); + polygon.add( i3 ); + } + } + + // Create a ROI to display. + final Set< Point2D > set = new HashSet<>(); + for ( int i = 0; i < polygon.size(); i++ ) + { + final double[] point = polygon.get( i ); + set.add( new Point2D.Double( point[ 0 ] / cal[ 1 ], point[ 1 ] / cal[ 1 ] ) ); + } + final List< Point2D > list = new ArrayList<>( set ); + final double mx = list.stream().mapToDouble( p -> p.getX() ).average().getAsDouble(); + final double my = list.stream().mapToDouble( p -> p.getY() ).average().getAsDouble(); + list.sort( new Comparator< Point2D >() + { + + @Override + public int compare( final Point2D o1, final Point2D o2 ) + { + final double angle1 = Math.atan2( o1.getY() - my, o1.getX() - mx ); + final double angle2 = Math.atan2( o2.getY() - my, o2.getX() - mx ); + return Double.compare( angle1, angle2 ); + } + } ); + final float[] xRoi = new float[ list.size() ]; + final float[] yRoi = new float[ list.size() ]; + for ( int i = 0; i < list.size(); i++ ) + { + xRoi[ i ] = ( float ) list.get( i ).getX(); + yRoi[ i ] = ( float ) list.get( i ).getY(); + } + final PolygonRoi roi = new PolygonRoi( xRoi, yRoi, PolygonRoi.POLYGON ); + Overlay overlay = imp.getOverlay(); + if ( overlay == null ) + { + overlay = new Overlay(); + imp.setOverlay( overlay ); + } + overlay.add( roi ); + } + System.out.println( "Done." ); + + } + + private static double[] intersect( final Vertices vertices, final long v1, final long v2, final double z ) + { + final double x1 = vertices.x( v1 ); + final double y1 = vertices.y( v1 ); + final double z1 = vertices.z( v1 ); + final double x2 = vertices.x( v2 ); + final double y2 = vertices.y( v2 ); + final double z2 = vertices.z( v2 ); + + final double t; + if ( z1 == z2 ) + t = 0.5; + else + t = ( z - z1 ) / ( z2 - z1 ); + final double x = x1 + t * ( x2 - x1 ); + final double y = y1 + t * ( y2 - y1 ); + return new double[] { x, y, z }; + } + + private static void testIO( final Mesh simplified, final int j ) + { + final STLMeshIO meshIO = new STLMeshIO(); + + // Encode to string. + final String str = Base64.encodeBytes( meshIO.write( simplified ) ); + + // Decode to mesh. + final int nVertices = ( int ) simplified.vertices().size(); + final int nTriangles = ( int ) simplified.triangles().size(); + // We need to know N in advance. Save it in the XML? + final Mesh decoded = new BufferMesh( nVertices, nTriangles ); + meshIO.read( decoded, Base64.decode( str ) ); + + // Serialize to disk. + try + { + meshIO.save( decoded, String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ) ); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + } + + private static void scale( final Vertices vertices, final double[] scale, final double[] origin ) + { + final long nv = vertices.size(); + for ( long i = 0; i < nv; i++ ) + { + final double x = ( origin[ 0 ] + vertices.x( i ) ) * scale[ 0 ]; + final double y = ( origin[ 1 ] + vertices.y( i ) ) * scale[ 1 ]; + final double z = ( origin[ 2 ] + vertices.z( i ) ) * scale[ 2 ]; + vertices.set( i, x, y, z ); + } + } + +} From d768bd18701dacd6bcb709822b4516615dfd1bcb Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 14 Apr 2023 15:49:08 +0200 Subject: [PATCH 048/263] WIP: Iterative intersection with a plane. Still not good enough, the vertices are not iterated in a monotonic manner. --- .../plugin/trackmate/mesh/Demo3DMesh.java | 371 ++++++++++++------ 1 file changed, 258 insertions(+), 113 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 344c030fb..bfc1e38d5 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -1,18 +1,14 @@ package fiji.plugin.trackmate.mesh; -import java.awt.geom.Point2D; import java.io.IOException; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import com.itextpdf.text.pdf.codec.Base64; import fiji.plugin.trackmate.detection.MaskUtils; import fiji.plugin.trackmate.util.TMUtils; +import gnu.trove.list.array.TDoubleArrayList; +import gnu.trove.list.linked.TLongLinkedList; +import gnu.trove.procedure.TLongProcedure; +import gnu.trove.set.hash.TLongHashSet; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; @@ -25,9 +21,9 @@ import net.imagej.mesh.Triangles; import net.imagej.mesh.Vertices; import net.imagej.mesh.io.stl.STLMeshIO; -import net.imagej.mesh.nio.BufferMesh; import net.imglib2.RandomAccessibleInterval; import net.imglib2.converter.RealTypeConverters; +import net.imglib2.img.ImgView; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.roi.labeling.ImgLabeling; @@ -38,39 +34,26 @@ import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.util.Util; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; public class Demo3DMesh { - public static < T extends RealType< T > & NumericType< T > > void main( final String[] args ) throws IOException - { - final String filePath = "samples/mesh/CElegansMask3D.tif"; + public static void main( final String[] args ) + { ImageJ.main( args ); - final ImagePlus imp = IJ.openImage( filePath ); - imp.show(); - - // To ImgLib2 boolean. - - // First channel is the mask. - @SuppressWarnings( "unchecked" ) - final ImgPlus< T > img = TMUtils.rawWraps( imp ); - final ImgPlus< T > c1 = ImgPlusViews.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 0 ); - - // Take the first time-point - final ImgPlus< T > t1 = ImgPlusViews.hyperSlice( c1, c1.dimensionIndex( Axes.TIME ), 0 ); - - // Make it to boolean. - final RandomAccessibleInterval< BitType > mask = RealTypeConverters.convert( t1, new BitType() ); + final ImgPlus< BitType > mask = loadTestMask2(); +// final ImgPlus< BitType > mask = loadTestMask(); // Convert it to labeling. final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( mask, mask, 0.5, 1 ); - ImageJFunctions.show( labeling.getSource(), "labeling" ); + final ImagePlus out = ImageJFunctions.show( labeling.getIndexImg(), "labeling" ); // Iterate through all components. final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); - final double[] cal = TMUtils.getSpatialCalibration( img ); + final double[] cal = TMUtils.getSpatialCalibration( mask ); // Parse regions to create polygons on boundaries. final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); @@ -82,13 +65,18 @@ public static < T extends RealType< T > & NumericType< T > > void main( final St // To mesh. final Mesh mesh = Meshes.marchingCubes( box ); + final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 0 ); + final Mesh simplified = Meshes.simplify( cleaned, 0.1f, 10 ); // Scale and offset with physical coordinates. final double[] origin = region.minAsDoubleArray(); - scale( mesh.vertices(), cal, origin ); + scale( simplified.vertices(), cal, origin ); // Simplify. - final Mesh simplified = Meshes.simplify( mesh, 0.25f, 10f ); + System.out.println( "Before cleaning: " + mesh.vertices().size() + " vertices and " + mesh.triangles().size() + " faces." ); + System.out.println( "Before simplification: " + cleaned.vertices().size() + " vertices and " + cleaned.triangles().size() + " faces." ); + System.out.println( "After simplification: " + simplified.vertices().size() + " vertices and " + simplified.triangles().size() + " faces." ); + System.out.println(); /* * IO. @@ -102,112 +90,174 @@ public static < T extends RealType< T > & NumericType< T > > void main( final St // Intersection with a XY plane at a fixed Z position. final int zslice = 20; // plan final double z = ( zslice ) * cal[ 2 ]; // um - final Triangles triangles = simplified.triangles(); - final Vertices vertices = simplified.vertices(); - final List< double[] > polygon = new ArrayList<>(); - for ( long i = 0; i < triangles.size(); i++ ) - { - final long v0 = triangles.vertex0( i ); - final double z0 = vertices.z( v0 ); - final long v1 = triangles.vertex1( i ); - final double z1 = vertices.z( v1 ); - final long v2 = triangles.vertex2( i ); - final double z2 = vertices.z( v2 ); - - if ( ( z0 <= z && z1 > z && z2 > z ) || - ( z1 <= z && z2 > z && z0 > z ) || - ( z2 <= z && z0 > z && z1 > z ) || - ( z0 >= z && z1 < z && z2 < z ) || - ( z1 >= z && z2 < z && z0 < z ) || - ( z2 >= z && z0 < z && z1 < z ) ) - { - final double[] i1 = intersect( vertices, v0, v1, z ); - final double[] i2 = intersect( vertices, v1, v2, z ); - final double[] i3 = intersect( vertices, v2, v0, z ); - polygon.add( i1 ); - polygon.add( i2 ); - polygon.add( i3 ); - } - } - - // Create a ROI to display. - final Set< Point2D > set = new HashSet<>(); - for ( int i = 0; i < polygon.size(); i++ ) - { - final double[] point = polygon.get( i ); - set.add( new Point2D.Double( point[ 0 ] / cal[ 1 ], point[ 1 ] / cal[ 1 ] ) ); - } - final List< Point2D > list = new ArrayList<>( set ); - final double mx = list.stream().mapToDouble( p -> p.getX() ).average().getAsDouble(); - final double my = list.stream().mapToDouble( p -> p.getY() ).average().getAsDouble(); - list.sort( new Comparator< Point2D >() - { - @Override - public int compare( final Point2D o1, final Point2D o2 ) - { - final double angle1 = Math.atan2( o1.getY() - my, o1.getX() - mx ); - final double angle2 = Math.atan2( o2.getY() - my, o2.getX() - mx ); - return Double.compare( angle1, angle2 ); - } - } ); - final float[] xRoi = new float[ list.size() ]; - final float[] yRoi = new float[ list.size() ]; - for ( int i = 0; i < list.size(); i++ ) + final double[][] xy = intersect2( simplified, z ); + final float[] xRoi = new float[ xy[ 0 ].length ]; + final float[] yRoi = new float[ xy[ 0 ].length ]; + for ( int i = 0; i < xy[ 0 ].length; i++ ) { - xRoi[ i ] = ( float ) list.get( i ).getX(); - yRoi[ i ] = ( float ) list.get( i ).getY(); + xRoi[ i ] = ( float ) ( xy[ 0 ][ i ] / cal[ 0 ] ); + yRoi[ i ] = ( float ) ( xy[ 1 ][ i ] / cal[ 1 ] ); } final PolygonRoi roi = new PolygonRoi( xRoi, yRoi, PolygonRoi.POLYGON ); - Overlay overlay = imp.getOverlay(); + Overlay overlay = out.getOverlay(); if ( overlay == null ) { overlay = new Overlay(); - imp.setOverlay( overlay ); + out.setOverlay( overlay ); } overlay.add( roi ); } System.out.println( "Done." ); + } + @SuppressWarnings( "unused" ) + private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask2() + { + final String filePath = "samples/mesh/Cube.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + @SuppressWarnings( "unchecked" ) + final ImgPlus< T > img = TMUtils.rawWraps( imp ); + final RandomAccessibleInterval< BitType > mask = RealTypeConverters.convert( img, new BitType() ); + return new ImgPlus<>( ImgView.wrap( mask ), img ); } - private static double[] intersect( final Vertices vertices, final long v1, final long v2, final double z ) + private static double[][] intersect2( final Mesh mesh, final double z ) { - final double x1 = vertices.x( v1 ); - final double y1 = vertices.y( v1 ); - final double z1 = vertices.z( v1 ); - final double x2 = vertices.x( v2 ); - final double y2 = vertices.y( v2 ); - final double z2 = vertices.z( v2 ); - - final double t; - if ( z1 == z2 ) - t = 0.5; - else - t = ( z - z1 ) / ( z2 - z1 ); - final double x = x1 + t * ( x2 - x1 ); - final double y = y1 + t * ( y2 - y1 ); - return new double[] { x, y, z }; + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + + // Find a line that intersects with z plane. + long[] start = null; + for ( long i = 0; i < triangles.size(); i++ ) + { + final long v0 = triangles.vertex0( i ); + final long v1 = triangles.vertex1( i ); + final long v2 = triangles.vertex2( i ); + if ( testLineIntersectPlane( vertices, v0, v1, z ) ) + { + start = new long[] { v0, v1 }; + break; + } + if ( testLineIntersectPlane( vertices, v0, v2, z ) ) + { + start = new long[] { v0, v2 }; + break; + } + if ( testLineIntersectPlane( vertices, v2, v1, z ) ) + { + start = new long[] { v2, v1 }; + break; + } + } + if ( start == null ) + { + System.out.println( "No intersection with Z = " + z + " found." ); + return null; + } + System.out.println( "Intersection with Z = " + z + ": " + Util.printCoordinates( start ) ); + + final TDoubleArrayList intersectionX = new TDoubleArrayList(); + final TDoubleArrayList intersectionY = new TDoubleArrayList(); + final TLongLinkedList queue = new TLongLinkedList(); + final TLongHashSet visited = new TLongHashSet(); + final TLongHashSet neighborVertices = new TLongHashSet( 12 ); + final LineIntersectProcedure lineIntersectProcedure = new LineIntersectProcedure( vertices, z, queue, visited, intersectionX, intersectionY ); + queue.add( start[ 0 ] ); + while ( !queue.isEmpty() ) + { + final long source = queue.removeAt( queue.size() - 1 ); + if (visited.contains( source )) + continue; + visited.add( source ); + + // Search neighbors of the current one that intersect with the Z + // plane. + searchNeighbors( mesh, source, z, neighborVertices ); + + // Check if line connecting neighbors intersect plane. + lineIntersectProcedure.setSourceV( source ); + neighborVertices.forEach( lineIntersectProcedure ); + } + + return new double[][] { intersectionX.toArray(), intersectionY.toArray() }; } - private static void testIO( final Mesh simplified, final int j ) + /** + * Finds the indices of the vertices that are connected to the vertex with + * the specified index in the specified mesh, if they make a line that + * crosses the Z plane at the specified position. + *

+ * TODO This search is inefficient, and would benefit from having a data + * structure that stores this info. + * + * @param mesh + * the mesh. + * @param v + * the index of the vertex to find the neighbors of. + * @param neighbors + * an array in which to write the indices of the neighbors. Is + * reset by this method. + */ + private static void searchNeighbors( final Mesh mesh, final long v, final double z, final TLongHashSet neighbors ) { - final STLMeshIO meshIO = new STLMeshIO(); + neighbors.clear(); + for ( long face = 0; face < mesh.triangles().size(); face++ ) + testFace( mesh, face, v, z, neighbors ); + } + + /** + * + * @param mesh + * the mesh to test. + * @param face + * the index of the face to test. + * @param v + * the index of the vertex we are searching. + * @param z + * the z position an edge needs to cross. + * @param neighbors + * the list of neighbors to add candidate to. + */ + private static final void testFace( final Mesh mesh, final long face, final long v, final double z, final TLongHashSet neighbors ) + { + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + final long v0 = triangles.vertex0( face ); + final long v1 = triangles.vertex1( face ); + final long v2 = triangles.vertex2( face ); + testFaceVertexIs( vertices, v, v0, v1, v2, z, neighbors ); + testFaceVertexIs( vertices, v, v1, v0, v2, z, neighbors ); + testFaceVertexIs( vertices, v, v2, v0, v1, z, neighbors ); + } - // Encode to string. - final String str = Base64.encodeBytes( meshIO.write( simplified ) ); + private static void testFaceVertexIs( final Vertices vertices, final long searched, final long source, final long v1, final long v2, final double z, final TLongHashSet neighbors ) + { + if ( source != searched ) + return; - // Decode to mesh. - final int nVertices = ( int ) simplified.vertices().size(); - final int nTriangles = ( int ) simplified.triangles().size(); - // We need to know N in advance. Save it in the XML? - final Mesh decoded = new BufferMesh( nVertices, nTriangles ); - meshIO.read( decoded, Base64.decode( str ) ); + if ( testLineIntersectPlane( vertices, source, v1, z ) ) + neighbors.add( v1 ); + if ( testLineIntersectPlane( vertices, source, v2, z ) ) + neighbors.add( v2 ); + } + + private static boolean testLineIntersectPlane( final Vertices vertices, final long source, final long target, final double z ) + { + final double z0 = vertices.z( source ); + final double z1 = vertices.z( target ); + if ( ( z0 > z && z1 > z ) || ( z0 < z && z1 < z ) ) + return false; + return true; + } + private static void testIO( final Mesh simplified, final int j ) + { + final STLMeshIO meshIO = new STLMeshIO(); // Serialize to disk. try { - meshIO.save( decoded, String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ) ); + meshIO.save( simplified, String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ) ); } catch ( final IOException e ) { @@ -227,4 +277,99 @@ private static void scale( final Vertices vertices, final double[] scale, final } } + /** + * Procedure that adds the intersection of the line made by the source + * vertex and target vertices iterated. + *

+ * The intersection is added only if the target vertex has not been visited. + * New targets are also added to the queue. + */ + private static final class LineIntersectProcedure implements TLongProcedure + { + + private final Vertices vertices; + + private long sv = -1; + + private final double z; + + private final TLongLinkedList queue; + + private final TLongHashSet visited; + + private final TDoubleArrayList intersectionX; + + private final TDoubleArrayList intersectionY; + + private double zs; + + private double xs; + + private double ys; + + public LineIntersectProcedure( + final Vertices vertices, + final double z, + final TLongLinkedList queue, + final TLongHashSet visited, + final TDoubleArrayList intersectionX, + final TDoubleArrayList intersectionY ) + { + this.vertices = vertices; + this.z = z; + this.queue = queue; + this.visited = visited; + this.intersectionX = intersectionX; + this.intersectionY = intersectionY; + } + + public void setSourceV( final long sourceV ) + { + this.sv = sourceV; + this.xs = vertices.x( sv ); + this.ys = vertices.y( sv ); + this.zs = vertices.z( sv ); + } + + @Override + public boolean execute( final long tv ) + { + if ( !visited.contains( tv ) ) + { + final double xt = vertices.x( tv ); + final double yt = vertices.y( tv ); + final double zt = vertices.z( tv ); + if ( zs == zt ) + { + intersectionX.add( 0.5 * ( xs + xt ) ); + intersectionY.add( 0.5 * ( ys + yt ) ); + } + else + { + final double t = ( z - zs ) / ( zt - zs ); + intersectionX.add( xs + t * ( xt - xs ) ); + intersectionY.add( ys + t * ( yt - ys ) ); + } + queue.add( tv ); + } + return true; + } + } + + private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask() + { + final String filePath = "samples/mesh/CElegansMask3D.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + + // First channel is the mask. + @SuppressWarnings( "unchecked" ) + final ImgPlus< T > img = TMUtils.rawWraps( imp ); + final ImgPlus< T > c1 = ImgPlusViews.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 0 ); + + // Take the first time-point + final ImgPlus< T > t1 = ImgPlusViews.hyperSlice( c1, c1.dimensionIndex( Axes.TIME ), 0 ); + // Make it to boolean. + final RandomAccessibleInterval< BitType > mask = RealTypeConverters.convert( t1, new BitType() ); + return new ImgPlus< BitType >( ImgView.wrap( mask ), t1 ); + } } From c82ef9e3f7a0992050e2dfe71c641f8747709a75 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 14 Apr 2023 18:29:11 +0200 Subject: [PATCH 049/263] Unother attempt, using an edge and a face map. Better but still not good enough: some points on the contours are repeated. --- .../plugin/trackmate/mesh/Demo3DMesh.java | 8 +- .../trackmate/mesh/MeshPlaneIntersection.java | 279 ++++++++++++++++++ 2 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index bfc1e38d5..268d67c17 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -44,8 +44,8 @@ public class Demo3DMesh public static void main( final String[] args ) { ImageJ.main( args ); - final ImgPlus< BitType > mask = loadTestMask2(); -// final ImgPlus< BitType > mask = loadTestMask(); +// final ImgPlus< BitType > mask = loadTestMask2(); + final ImgPlus< BitType > mask = loadTestMask(); // Convert it to labeling. final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( mask, mask, 0.5, 1 ); @@ -88,10 +88,10 @@ public static void main( final String[] args ) */ // Intersection with a XY plane at a fixed Z position. - final int zslice = 20; // plan + final int zslice = 22; // plan final double z = ( zslice ) * cal[ 2 ]; // um - final double[][] xy = intersect2( simplified, z ); + final double[][] xy = MeshPlaneIntersection.intersect( simplified, z ); final float[] xRoi = new float[ xy[ 0 ].length ]; final float[] yRoi = new float[ xy[ 0 ].length ]; for ( int i = 0; i < xy[ 0 ].length; i++ ) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java new file mode 100644 index 000000000..5fd7ef44d --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java @@ -0,0 +1,279 @@ +package fiji.plugin.trackmate.mesh; + +import java.util.Arrays; + +import gnu.trove.iterator.TLongIterator; +import gnu.trove.list.array.TDoubleArrayList; +import gnu.trove.list.array.TLongArrayList; +import gnu.trove.map.hash.TLongObjectHashMap; +import gnu.trove.set.hash.TLongHashSet; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Triangles; +import net.imagej.mesh.Vertices; + +public class MeshPlaneIntersection +{ + + public static double[][] intersect( final Mesh mesh, final double z ) + { + /* + * Build the edge and face maps. This could be more efficiently + * implemented in a read-only winged-edge mesh class. + */ + + final Vertices vertices = mesh.vertices(); + final Triangles triangles = mesh.triangles(); + + // Map of vertex id to list of faces they are in. + final TLongObjectHashMap< TLongArrayList > vertexList = new TLongObjectHashMap<>(); + + // Map of edge (va -> vb with always va < vb) to face pair (fa, fb). + // They are stored as a paired integers using Szudzik pairing. + // So it won't work if the index exceeds a few 100s of millions. + final TLongObjectHashMap< long[] > edgeList = new TLongObjectHashMap< long[] >(); + + // Iterate through all the faces. + final long startTime = System.currentTimeMillis(); + final long[] vs = new long[ 3 ]; + final int[] pairHolder = new int[ 2 ]; + for ( long face = 0; face < triangles.size(); face++ ) + { + vs[ 0 ] = triangles.vertex0( face ); + vs[ 1 ] = triangles.vertex1( face ); + vs[ 2 ] = triangles.vertex2( face ); + Arrays.sort( vs ); + + // Insert face into vertex list. + for ( final long v : vs ) + insertFaceIntoVertexList( v, face, vertexList ); + + // Deal with the 3 edges. + insertEdge( vs[ 0 ], vs[ 1 ], face, edgeList, pairHolder ); + insertEdge( vs[ 0 ], vs[ 2 ], face, edgeList, pairHolder ); + insertEdge( vs[ 1 ], vs[ 2 ], face, edgeList, pairHolder ); + } + final long endTime = System.currentTimeMillis(); + System.out.println( "Built edge and face lists for " + triangles.size() + " faces in " + ( endTime - startTime ) + " ms." ); + +// edgeList.forEachEntry( new TLongObjectProcedure< long[] >() +// { +// private final int[] phK = new int[ 2 ]; +// +// @Override +// public boolean execute( final long k, final long[] v ) +// { +// unpair( k, phK ); +// System.out.println( String.format( "%d, %d -> %d, %d", phK[ 0 ], phK[ 1 ], v[ 0 ], v[ 1 ] ) ); +// return true; +// } +// } ); + + /* + * Find one edge that crosses the Z plane. + */ + + final TLongIterator edgeIt = edgeList.keySet().iterator(); + long start = -1; + while ( edgeIt.hasNext() ) + { + final long edge = edgeIt.next(); + unpair( edge, pairHolder ); + final long va = pairHolder[ 0 ]; + final long vb = pairHolder[ 1 ]; + if ( testLineIntersectPlane( vertices, va, vb, z ) ) + { + start = edge; + break; + } + } + if ( start < 0 ) + { + System.out.println( "Could not find an edge that intersects with Z = " + z ); + return null; + } + + final TDoubleArrayList intersectionX = new TDoubleArrayList(); + final TDoubleArrayList intersectionY = new TDoubleArrayList(); + long current = start; + long previousFace = -1; + final long[][] edges = new long[ 3 ][ 2 ]; + final TLongHashSet visited = new TLongHashSet(); + while ( true ) + { + addEdgeToContour( vertices, current, z, intersectionX, intersectionY, pairHolder ); + final long face = getNextFace( edgeList, current, previousFace ); + if ( visited.contains( face ) ) + break; + + final long next = getNextEdge( mesh, edgeList, face, current, z, previousFace, edges, vs ); + if ( next < 0 ) + break; + + visited.add( face ); + previousFace = face; + current = next; + } + return new double[][] { intersectionX.toArray(), intersectionY.toArray() }; + } + + private static long getNextFace( final TLongObjectHashMap< long[] > edgeList, final long current, final long previousFace ) + { + // Get the faces of this edge. + final long[] faces = edgeList.get( current ); + // Retain the one we have not been visiting. + long face; + if ( faces[ 0 ] == previousFace ) + face = faces[ 1 ]; + else + face = faces[ 0 ]; + return face; + } + + private static long getNextEdge( final Mesh mesh, final TLongObjectHashMap< long[] > edgeList, final long face, final long current, final double z, final long previousFace, final long[][] edges, final long[] vs ) + { + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + + // Get the edges of this face. + vs[ 0 ] = triangles.vertex0( face ); + vs[ 1 ] = triangles.vertex1( face ); + vs[ 2 ] = triangles.vertex2( face ); + Arrays.sort( vs ); + edges[ 0 ][ 0 ] = vs[ 0 ]; + edges[ 0 ][ 1 ] = vs[ 1 ]; + edges[ 1 ][ 0 ] = vs[ 0 ]; + edges[ 1 ][ 1 ] = vs[ 2 ]; + edges[ 2 ][ 0 ] = vs[ 1 ]; + edges[ 2 ][ 1 ] = vs[ 2 ]; + for ( final long[] edge : edges ) + { + final long e = pair( edge[ 0 ], edge[ 1 ]); + if ( e == current ) + continue; + + if ( testLineIntersectPlane( vertices, edge[ 0 ], edge[ 1 ], z ) ) + return e; + } + return -1; + + } + + private static void addEdgeToContour( final Vertices vertices, final long edge, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy, final int[] pairHolder ) + { + unpair( edge, pairHolder ); + final int sv = pairHolder[ 0 ]; + final int tv = pairHolder[ 1 ]; + final double xs = vertices.x( sv ); + final double ys = vertices.y( sv ); + final double zs = vertices.z( sv ); + final double xt = vertices.x( tv ); + final double yt = vertices.y( tv ); + final double zt = vertices.z( tv ); + if ( zs == zt ) + { + cx.add( 0.5 * ( xs + xt ) ); + cy.add( 0.5 * ( ys + yt ) ); + } + else + { + final double t = ( z - zs ) / ( zt - zs ); + cx.add( xs + t * ( xt - xs ) ); + cy.add( ys + t * ( yt - ys ) ); + } + + } + + private static void insertEdge( final long va, final long vb, final long face, final TLongObjectHashMap< long[] > edgeList, final int[] pairHolder ) + { + assert va < vb; + final long edge = pair( va, vb ); + final long[] faces = edgeList.get( edge ); + if ( faces == null ) + { + edgeList.put( edge, new long[] { face, -1 } ); + return; + } + faces[ 1 ] = face; + } + + private static void insertFaceIntoVertexList( final long vertex, final long face, final TLongObjectHashMap< TLongArrayList > vertexList ) + { + TLongArrayList faceList = vertexList.get( vertex ); + if ( faceList == null ) + { + faceList = new TLongArrayList(); + vertexList.put( vertex, faceList ); + } + faceList.add( face ); + + } + + private static boolean testLineIntersectPlane( final Vertices vertices, final long source, final long target, final double z ) + { + final double z0 = vertices.z( source ); + final double z1 = vertices.z( target ); + if ( ( z0 > z && z1 > z ) || ( z0 < z && z1 < z ) ) + return false; + return true; + } + + /** + * Szudzik pairing. + * + * @param x + * the 1st int to pair. + * @param y + * the 2nd int to pair. + * @return Szudzik pairing. + */ + public static long pair( final double x, final double y ) + { + return ( long ) ( x >= y ? x * x + x + y : y * y + x ); + } + + /** + * Szudzik unpairing. + * + * @param z + * the factor to unpair. + * @param out + * where to write the results in. + */ + public static void unpair( final long z, final int[] out ) + { + final long b = ( long ) Math.sqrt( z ); + final int a = ( int ) ( z - b * b ); + if ( a < b ) + { + out[ 0 ] = a; + out[ 1 ] = ( int ) b; + } + else + { + out[ 0 ] = ( int ) b; + out[ 1 ] = ( int ) ( a - b ); + } + } + + public static void main( final String[] args ) + { + final int[][] tests = new int[][] { + { 1, 2 }, + { 100, 5 }, + { 5, 100 }, + { 0, 500 }, + { 9, 0 }, + { 120, 12345678 }, + { -1, 50 }, + { 20, -1 } + }; + final int[] out = new int[ 2 ]; + for ( final int[] test : tests ) + { + final long z = pair( test[ 0 ], test[ 1 ] ); + unpair( z, out ); + System.out.println( String.format( "%d, %d -> %d -> %d, %d", test[ 0 ], test[ 1 ], z, out[ 0 ], out[ 1 ] ) ); + } + } + +} From d290e9381bf154100f382d3c75d8234cbae7ae00 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 14 Apr 2023 20:43:36 +0200 Subject: [PATCH 050/263] We actually don't need the vertex list. The edge list is sufficient. Also do not add duplicate points on the contour (happens often when there is no smoothing). Still not good: there are some cases where the loop breaks too early. --- .../plugin/trackmate/mesh/Demo3DMesh.java | 43 +++++++++++-------- .../trackmate/mesh/MeshPlaneIntersection.java | 38 ++++++++-------- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 268d67c17..0cd9e9515 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -1,5 +1,6 @@ package fiji.plugin.trackmate.mesh; +import java.awt.Color; import java.io.IOException; import java.util.Iterator; @@ -66,7 +67,8 @@ public static void main( final String[] args ) // To mesh. final Mesh mesh = Meshes.marchingCubes( box ); final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 0 ); - final Mesh simplified = Meshes.simplify( cleaned, 0.1f, 10 ); +// final Mesh simplified = Meshes.simplify( cleaned, 0.25f, 10 ); + final Mesh simplified = cleaned; // Scale and offset with physical coordinates. final double[] origin = region.minAsDoubleArray(); @@ -88,29 +90,36 @@ public static void main( final String[] args ) */ // Intersection with a XY plane at a fixed Z position. - final int zslice = 22; // plan + final int zslice = 16; // plan final double z = ( zslice ) * cal[ 2 ]; // um final double[][] xy = MeshPlaneIntersection.intersect( simplified, z ); - final float[] xRoi = new float[ xy[ 0 ].length ]; - final float[] yRoi = new float[ xy[ 0 ].length ]; - for ( int i = 0; i < xy[ 0 ].length; i++ ) - { - xRoi[ i ] = ( float ) ( xy[ 0 ][ i ] / cal[ 0 ] ); - yRoi[ i ] = ( float ) ( xy[ 1 ][ i ] / cal[ 1 ] ); - } - final PolygonRoi roi = new PolygonRoi( xRoi, yRoi, PolygonRoi.POLYGON ); - Overlay overlay = out.getOverlay(); - if ( overlay == null ) - { - overlay = new Overlay(); - out.setOverlay( overlay ); - } - overlay.add( roi ); + toOverlay( xy, out, cal ); } System.out.println( "Done." ); } + private static void toOverlay( final double[][] xy, final ImagePlus out, final double[] cal ) + { + final float[] xRoi = new float[ xy[ 0 ].length ]; + final float[] yRoi = new float[ xy[ 0 ].length ]; + for ( int i = 0; i < xy[ 0 ].length; i++ ) + { + xRoi[ i ] = ( float ) ( xy[ 0 ][ i ] / cal[ 0 ] + 0.5 ); + yRoi[ i ] = ( float ) ( xy[ 1 ][ i ] / cal[ 1 ] + 0.5 ); + } + final PolygonRoi roi = new PolygonRoi( xRoi, yRoi, PolygonRoi.POLYGON ); + roi.setStrokeWidth( 0.2 ); + roi.setStrokeColor( Color.RED ); + Overlay overlay = out.getOverlay(); + if ( overlay == null ) + { + overlay = new Overlay(); + out.setOverlay( overlay ); + } + overlay.add( roi ); + } + @SuppressWarnings( "unused" ) private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask2() { diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java index 5fd7ef44d..f4369bd54 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java @@ -24,9 +24,6 @@ public static double[][] intersect( final Mesh mesh, final double z ) final Vertices vertices = mesh.vertices(); final Triangles triangles = mesh.triangles(); - // Map of vertex id to list of faces they are in. - final TLongObjectHashMap< TLongArrayList > vertexList = new TLongObjectHashMap<>(); - // Map of edge (va -> vb with always va < vb) to face pair (fa, fb). // They are stored as a paired integers using Szudzik pairing. // So it won't work if the index exceeds a few 100s of millions. @@ -43,10 +40,6 @@ public static double[][] intersect( final Mesh mesh, final double z ) vs[ 2 ] = triangles.vertex2( face ); Arrays.sort( vs ); - // Insert face into vertex list. - for ( final long v : vs ) - insertFaceIntoVertexList( v, face, vertexList ); - // Deal with the 3 edges. insertEdge( vs[ 0 ], vs[ 1 ], face, edgeList, pairHolder ); insertEdge( vs[ 0 ], vs[ 2 ], face, edgeList, pairHolder ); @@ -71,7 +64,7 @@ public static double[][] intersect( final Mesh mesh, final double z ) /* * Find one edge that crosses the Z plane. */ - + final TLongIterator edgeIt = edgeList.keySet().iterator(); long start = -1; while ( edgeIt.hasNext() ) @@ -92,6 +85,10 @@ public static double[][] intersect( final Mesh mesh, final double z ) return null; } + /* + * Iterate from it, selecting faces that an edge that crosses the plane. + */ + final TDoubleArrayList intersectionX = new TDoubleArrayList(); final TDoubleArrayList intersectionY = new TDoubleArrayList(); long current = start; @@ -106,13 +103,11 @@ public static double[][] intersect( final Mesh mesh, final double z ) break; final long next = getNextEdge( mesh, edgeList, face, current, z, previousFace, edges, vs ); - if ( next < 0 ) - break; - visited.add( face ); previousFace = face; current = next; } + return new double[][] { intersectionX.toArray(), intersectionY.toArray() }; } @@ -147,12 +142,12 @@ private static long getNextEdge( final Mesh mesh, final TLongObjectHashMap< long edges[ 2 ][ 1 ] = vs[ 2 ]; for ( final long[] edge : edges ) { - final long e = pair( edge[ 0 ], edge[ 1 ]); - if ( e == current ) + final long e = pair( edge[ 0 ], edge[ 1 ] ); + if ( e == current ) continue; if ( testLineIntersectPlane( vertices, edge[ 0 ], edge[ 1 ], z ) ) - return e; + return e; } return -1; @@ -169,18 +164,25 @@ private static void addEdgeToContour( final Vertices vertices, final long edge, final double xt = vertices.x( tv ); final double yt = vertices.y( tv ); final double zt = vertices.z( tv ); + double x; + double y; if ( zs == zt ) { - cx.add( 0.5 * ( xs + xt ) ); - cy.add( 0.5 * ( ys + yt ) ); + x = 0.5 * ( xs + xt ); + y = 0.5 * ( ys + yt ); } else { final double t = ( z - zs ) / ( zt - zs ); - cx.add( xs + t * ( xt - xs ) ); - cy.add( ys + t * ( yt - ys ) ); + x = xs + t * ( xt - xs ); + y = ys + t * ( yt - ys ); } + final int np = cx.size(); + if ( np > 1 && cx.getQuick( np - 1 ) == x && cy.getQuick( np - 1 ) == y ) + return; // Don't add duplicate. + cx.add( x ); + cy.add( y ); } private static void insertEdge( final long va, final long vb, final long face, final TLongObjectHashMap< long[] > edgeList, final int[] pairHolder ) From b86e7dc4e92e19e16225086ac729dfbfd63f29c5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 16 Apr 2023 21:15:09 +0200 Subject: [PATCH 051/263] Temporary couple to SNAPSHOT version of the mesh library. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index c9c570530..0a2ff2a2f 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,7 @@ net.imagej imagej-mesh + 0.8.2-SNAPSHOT net.imagej From 0141aa703b26b84072b030e5375f756001cc5c73 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 16 Apr 2023 21:16:21 +0200 Subject: [PATCH 052/263] Rework the demo of mesh intersection. Much cleaner. Still does not work for not simple meshes. Probably because of border cases where we have vertices that lie exactly on the plane we are interesecting with. --- .../plugin/trackmate/mesh/Demo3DMesh.java | 441 +++++++----------- .../trackmate/mesh/MeshPlaneIntersection.java | 339 ++++++-------- 2 files changed, 296 insertions(+), 484 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 0cd9e9515..c3b86079e 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -7,21 +7,22 @@ import fiji.plugin.trackmate.detection.MaskUtils; import fiji.plugin.trackmate.util.TMUtils; import gnu.trove.list.array.TDoubleArrayList; -import gnu.trove.list.linked.TLongLinkedList; -import gnu.trove.procedure.TLongProcedure; -import gnu.trove.set.hash.TLongHashSet; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; import ij.gui.Overlay; +import ij.gui.PointRoi; import ij.gui.PolygonRoi; +import ij.gui.Roi; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; -import net.imagej.mesh.Triangles; import net.imagej.mesh.Vertices; import net.imagej.mesh.io.stl.STLMeshIO; +import net.imagej.mesh.naive.NaiveDoubleMesh; +import net.imagej.mesh.naive.NaiveDoubleMesh.Triangles; +import net.imagej.mesh.nio.BufferMeshEdges; import net.imglib2.RandomAccessibleInterval; import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.ImgView; @@ -35,7 +36,6 @@ import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; -import net.imglib2.util.Util; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; @@ -44,220 +44,172 @@ public class Demo3DMesh public static void main( final String[] args ) { - ImageJ.main( args ); -// final ImgPlus< BitType > mask = loadTestMask2(); - final ImgPlus< BitType > mask = loadTestMask(); - - // Convert it to labeling. - final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( mask, mask, 0.5, 1 ); - final ImagePlus out = ImageJFunctions.show( labeling.getIndexImg(), "labeling" ); - - // Iterate through all components. - final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); - final double[] cal = TMUtils.getSpatialCalibration( mask ); - - // Parse regions to create polygons on boundaries. - final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); - int j = 0; - while ( iterator.hasNext() ) + try { - final LabelRegion< Integer > region = iterator.next(); - final IntervalView< BoolType > box = Views.zeroMin( region ); - - // To mesh. - final Mesh mesh = Meshes.marchingCubes( box ); - final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 0 ); -// final Mesh simplified = Meshes.simplify( cleaned, 0.25f, 10 ); - final Mesh simplified = cleaned; - - // Scale and offset with physical coordinates. - final double[] origin = region.minAsDoubleArray(); - scale( simplified.vertices(), cal, origin ); - - // Simplify. - System.out.println( "Before cleaning: " + mesh.vertices().size() + " vertices and " + mesh.triangles().size() + " faces." ); - System.out.println( "Before simplification: " + cleaned.vertices().size() + " vertices and " + cleaned.triangles().size() + " faces." ); - System.out.println( "After simplification: " + simplified.vertices().size() + " vertices and " + simplified.triangles().size() + " faces." ); - System.out.println(); - - /* - * IO. - */ - testIO( simplified, ++j ); - - /* - * Display. - */ - - // Intersection with a XY plane at a fixed Z position. - final int zslice = 16; // plan - final double z = ( zslice ) * cal[ 2 ]; // um - - final double[][] xy = MeshPlaneIntersection.intersect( simplified, z ); - toOverlay( xy, out, cal ); - } - System.out.println( "Done." ); - } - private static void toOverlay( final double[][] xy, final ImagePlus out, final double[] cal ) - { - final float[] xRoi = new float[ xy[ 0 ].length ]; - final float[] yRoi = new float[ xy[ 0 ].length ]; - for ( int i = 0; i < xy[ 0 ].length; i++ ) - { - xRoi[ i ] = ( float ) ( xy[ 0 ][ i ] / cal[ 0 ] + 0.5 ); - yRoi[ i ] = ( float ) ( xy[ 1 ][ i ] / cal[ 1 ] + 0.5 ); - } - final PolygonRoi roi = new PolygonRoi( xRoi, yRoi, PolygonRoi.POLYGON ); - roi.setStrokeWidth( 0.2 ); - roi.setStrokeColor( Color.RED ); - Overlay overlay = out.getOverlay(); - if ( overlay == null ) - { - overlay = new Overlay(); - out.setOverlay( overlay ); - } - overlay.add( roi ); - } + ImageJ.main( args ); +// final ImgPlus< BitType > mask = loadTestMask2(); + final ImgPlus< BitType > mask = loadTestMask(); - @SuppressWarnings( "unused" ) - private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask2() - { - final String filePath = "samples/mesh/Cube.tif"; - final ImagePlus imp = IJ.openImage( filePath ); - @SuppressWarnings( "unchecked" ) - final ImgPlus< T > img = TMUtils.rawWraps( imp ); - final RandomAccessibleInterval< BitType > mask = RealTypeConverters.convert( img, new BitType() ); - return new ImgPlus<>( ImgView.wrap( mask ), img ); - } + // Convert it to labeling. + final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( mask, mask, 0.5, 1 ); + final ImagePlus out = ImageJFunctions.show( labeling.getIndexImg(), "labeling" ); - private static double[][] intersect2( final Mesh mesh, final double z ) - { - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); + // Iterate through all components. + final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); + final double[] cal = TMUtils.getSpatialCalibration( mask ); - // Find a line that intersects with z plane. - long[] start = null; - for ( long i = 0; i < triangles.size(); i++ ) - { - final long v0 = triangles.vertex0( i ); - final long v1 = triangles.vertex1( i ); - final long v2 = triangles.vertex2( i ); - if ( testLineIntersectPlane( vertices, v0, v1, z ) ) - { - start = new long[] { v0, v1 }; - break; - } - if ( testLineIntersectPlane( vertices, v0, v2, z ) ) - { - start = new long[] { v0, v2 }; - break; - } - if ( testLineIntersectPlane( vertices, v2, v1, z ) ) + // Holder for the contour coords. + final TDoubleArrayList cx = new TDoubleArrayList(); + final TDoubleArrayList cy = new TDoubleArrayList(); + + // Parse regions to create polygons on boundaries. + final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); + int j = 0; + while ( iterator.hasNext() ) { - start = new long[] { v2, v1 }; - break; + final LabelRegion< Integer > region = iterator.next(); + + // To mesh. + final IntervalView< BoolType > box = Views.zeroMin( region ); + final Mesh mesh = Meshes.marchingCubes( box ); + System.out.println( "Before cleaning: " + mesh.vertices().size() + " vertices and " + mesh.triangles().size() + " faces." ); + final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 0 ); + System.out.println( "Before simplification: " + cleaned.vertices().size() + " vertices and " + cleaned.triangles().size() + " faces." ); + final Mesh simplified = Meshes.simplify( cleaned, 0.25f, 10 ); +// final Mesh simplified = debugMesh( new long[] { 0, 0, 0 }, region.dimensionsAsLongArray() ); + + // Wrap as mesh with edges. + final BufferMeshEdges emesh = BufferMeshEdges.wrap( simplified, true ); + System.out.println( "After simplification: " + emesh.vertices().size() + " vertices and " + simplified.triangles().size() + " faces." ); + System.out.println(); + + // Scale and offset with physical coordinates. + final double[] origin = region.minAsDoubleArray(); + scale( emesh.vertices(), cal, origin ); + + // Simplify. + + /* + * IO. + */ + testIO( emesh, ++j ); + + /* + * Display. + */ + + // Intersection with a XY plane at a fixed Z position. + final int zslice = 20; // plan + final double z = ( zslice ) * cal[ 2 ]; // um + + MeshPlaneIntersection.intersect( emesh, z, cx, cy ); + toOverlay( cx, cy, out, cal ); + + break; // DEBUg } + System.out.println( "Done." ); } - if ( start == null ) + catch ( final Exception e ) { - System.out.println( "No intersection with Z = " + z + " found." ); - return null; - } - System.out.println( "Intersection with Z = " + z + ": " + Util.printCoordinates( start ) ); - - final TDoubleArrayList intersectionX = new TDoubleArrayList(); - final TDoubleArrayList intersectionY = new TDoubleArrayList(); - final TLongLinkedList queue = new TLongLinkedList(); - final TLongHashSet visited = new TLongHashSet(); - final TLongHashSet neighborVertices = new TLongHashSet( 12 ); - final LineIntersectProcedure lineIntersectProcedure = new LineIntersectProcedure( vertices, z, queue, visited, intersectionX, intersectionY ); - queue.add( start[ 0 ] ); - while ( !queue.isEmpty() ) - { - final long source = queue.removeAt( queue.size() - 1 ); - if (visited.contains( source )) - continue; - visited.add( source ); - - // Search neighbors of the current one that intersect with the Z - // plane. - searchNeighbors( mesh, source, z, neighborVertices ); - - // Check if line connecting neighbors intersect plane. - lineIntersectProcedure.setSourceV( source ); - neighborVertices.forEach( lineIntersectProcedure ); + e.printStackTrace(); } - - return new double[][] { intersectionX.toArray(), intersectionY.toArray() }; } - /** - * Finds the indices of the vertices that are connected to the vertex with - * the specified index in the specified mesh, if they make a line that - * crosses the Z plane at the specified position. - *

- * TODO This search is inefficient, and would benefit from having a data - * structure that stores this info. - * - * @param mesh - * the mesh. - * @param v - * the index of the vertex to find the neighbors of. - * @param neighbors - * an array in which to write the indices of the neighbors. Is - * reset by this method. - */ - private static void searchNeighbors( final Mesh mesh, final long v, final double z, final TLongHashSet neighbors ) - { - neighbors.clear(); - for ( long face = 0; face < mesh.triangles().size(); face++ ) - testFace( mesh, face, v, z, neighbors ); - } - - /** - * - * @param mesh - * the mesh to test. - * @param face - * the index of the face to test. - * @param v - * the index of the vertex we are searching. - * @param z - * the z position an edge needs to cross. - * @param neighbors - * the list of neighbors to add candidate to. - */ - private static final void testFace( final Mesh mesh, final long face, final long v, final double z, final TLongHashSet neighbors ) + @SuppressWarnings( "unused" ) + private static Mesh debugMesh( final long[] min, final long[] max ) { + final NaiveDoubleMesh mesh = new NaiveDoubleMesh(); + final net.imagej.mesh.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices(); final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - final long v0 = triangles.vertex0( face ); - final long v1 = triangles.vertex1( face ); - final long v2 = triangles.vertex2( face ); - testFaceVertexIs( vertices, v, v0, v1, v2, z, neighbors ); - testFaceVertexIs( vertices, v, v1, v0, v2, z, neighbors ); - testFaceVertexIs( vertices, v, v2, v0, v1, z, neighbors ); + + // Coords as X Y Z + + // Bottom square. + final double[] bnw = new double[] { min[ 0 ], min[ 1 ], min[ 2 ] }; + final double[] bne = new double[] { max[ 0 ], min[ 1 ], min[ 2 ] }; + final double[] bsw = new double[] { min[ 0 ], max[ 1 ], min[ 2 ] }; + final double[] bse = new double[] { max[ 0 ], max[ 1 ], min[ 2 ] }; + + // Top square. + final double[] tnw = new double[] { min[ 0 ], min[ 1 ], max[ 2 ] }; + final double[] tne = new double[] { max[ 0 ], min[ 1 ], max[ 2 ] }; + final double[] tsw = new double[] { min[ 0 ], max[ 1 ], max[ 2 ] }; + final double[] tse = new double[] { max[ 0 ], max[ 1 ], max[ 2 ] }; + + // Add vertices. + final long bnwi = vertices.add( bnw[ 0 ], bnw[ 1 ], bnw[ 2 ] ); + final long bnei = vertices.add( bne[ 0 ], bne[ 1 ], bne[ 2 ] ); + final long bswi = vertices.add( bsw[ 0 ], bsw[ 1 ], bsw[ 2 ] ); + final long bsei = vertices.add( bse[ 0 ], bse[ 1 ], bse[ 2 ] ); + final long tnwi = vertices.add( tnw[ 0 ], tnw[ 1 ], tnw[ 2 ] ); + final long tnei = vertices.add( tne[ 0 ], tne[ 1 ], tne[ 2 ] ); + final long tswi = vertices.add( tsw[ 0 ], tsw[ 1 ], tsw[ 2 ] ); + final long tsei = vertices.add( tse[ 0 ], tse[ 1 ], tse[ 2 ] ); + + // Add triangles for the 6 faces. + + // Bottom. + triangles.add( bnwi, bnei, bswi ); + triangles.add( bnei, bsei, bswi ); + + // Top. + triangles.add( tnwi, tnei, tswi ); + triangles.add( tnei, tsei, tswi ); + + // Front (facing south). + triangles.add( tswi, tsei, bsei ); + triangles.add( tswi, bsei, bswi ); + + // Back (facing north). + triangles.add( tnwi, tnei, bnei ); + triangles.add( tnwi, bnei, bnwi ); + + // Left (facing west). + triangles.add( tnwi, tswi, bswi ); + triangles.add( tnwi, bnwi, bswi ); + + // Right (facing east). + triangles.add( tnei, tsei, bsei ); + triangles.add( tnei, bnei, bsei ); + + return mesh; } - private static void testFaceVertexIs( final Vertices vertices, final long searched, final long source, final long v1, final long v2, final double z, final TLongHashSet neighbors ) + private static void toOverlay( final TDoubleArrayList cx, final TDoubleArrayList cy, final ImagePlus out, final double[] cal ) { - if ( source != searched ) + final int l = cx.size(); + if ( l == 0 ) return; - if ( testLineIntersectPlane( vertices, source, v1, z ) ) - neighbors.add( v1 ); - if ( testLineIntersectPlane( vertices, source, v2, z ) ) - neighbors.add( v2 ); - } + final Roi roi; + if ( l == 1 ) + { + roi = new PointRoi( + cx.get( 0 ) / cal[ 0 ] + 0.5, + cy.get( 0 ) / cal[ 1 ] + 0.5, null ); + } + else + { + final float[] xRoi = new float[ l ]; + final float[] yRoi = new float[ l ]; + for ( int i = 0; i < l; i++ ) + { + xRoi[ i ] = ( float ) ( cx.get( i ) / cal[ 0 ] + 0.5 ); + yRoi[ i ] = ( float ) ( cy.get( i ) / cal[ 1 ] + 0.5 ); + } + roi = new PolygonRoi( xRoi, yRoi, PolygonRoi.POLYGON ); +// roi.setStrokeWidth( 0.2 ); + } - private static boolean testLineIntersectPlane( final Vertices vertices, final long source, final long target, final double z ) - { - final double z0 = vertices.z( source ); - final double z1 = vertices.z( target ); - if ( ( z0 > z && z1 > z ) || ( z0 < z && z1 < z ) ) - return false; - return true; + roi.setStrokeColor( Color.RED ); + Overlay overlay = out.getOverlay(); + if ( overlay == null ) + { + overlay = new Overlay(); + out.setOverlay( overlay ); + } + overlay.add( roi ); } private static void testIO( final Mesh simplified, final int j ) @@ -286,85 +238,7 @@ private static void scale( final Vertices vertices, final double[] scale, final } } - /** - * Procedure that adds the intersection of the line made by the source - * vertex and target vertices iterated. - *

- * The intersection is added only if the target vertex has not been visited. - * New targets are also added to the queue. - */ - private static final class LineIntersectProcedure implements TLongProcedure - { - - private final Vertices vertices; - - private long sv = -1; - - private final double z; - - private final TLongLinkedList queue; - - private final TLongHashSet visited; - - private final TDoubleArrayList intersectionX; - - private final TDoubleArrayList intersectionY; - - private double zs; - - private double xs; - - private double ys; - - public LineIntersectProcedure( - final Vertices vertices, - final double z, - final TLongLinkedList queue, - final TLongHashSet visited, - final TDoubleArrayList intersectionX, - final TDoubleArrayList intersectionY ) - { - this.vertices = vertices; - this.z = z; - this.queue = queue; - this.visited = visited; - this.intersectionX = intersectionX; - this.intersectionY = intersectionY; - } - - public void setSourceV( final long sourceV ) - { - this.sv = sourceV; - this.xs = vertices.x( sv ); - this.ys = vertices.y( sv ); - this.zs = vertices.z( sv ); - } - - @Override - public boolean execute( final long tv ) - { - if ( !visited.contains( tv ) ) - { - final double xt = vertices.x( tv ); - final double yt = vertices.y( tv ); - final double zt = vertices.z( tv ); - if ( zs == zt ) - { - intersectionX.add( 0.5 * ( xs + xt ) ); - intersectionY.add( 0.5 * ( ys + yt ) ); - } - else - { - final double t = ( z - zs ) / ( zt - zs ); - intersectionX.add( xs + t * ( xt - xs ) ); - intersectionY.add( ys + t * ( yt - ys ) ); - } - queue.add( tv ); - } - return true; - } - } - + @SuppressWarnings( "unused" ) private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask() { final String filePath = "samples/mesh/CElegansMask3D.tif"; @@ -381,4 +255,15 @@ private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > final RandomAccessibleInterval< BitType > mask = RealTypeConverters.convert( t1, new BitType() ); return new ImgPlus< BitType >( ImgView.wrap( mask ), t1 ); } + + @SuppressWarnings( "unused" ) + private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask2() + { + final String filePath = "samples/mesh/Cube.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + @SuppressWarnings( "unchecked" ) + final ImgPlus< T > img = TMUtils.rawWraps( imp ); + final RandomAccessibleInterval< BitType > mask = RealTypeConverters.convert( img, new BitType() ); + return new ImgPlus<>( ImgView.wrap( mask ), img ); + } } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java index f4369bd54..90fea17bf 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java @@ -2,181 +2,202 @@ import java.util.Arrays; -import gnu.trove.iterator.TLongIterator; import gnu.trove.list.array.TDoubleArrayList; import gnu.trove.list.array.TLongArrayList; -import gnu.trove.map.hash.TLongObjectHashMap; -import gnu.trove.set.hash.TLongHashSet; +import net.imagej.mesh.Edges; import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; import net.imagej.mesh.Triangles; import net.imagej.mesh.Vertices; public class MeshPlaneIntersection { - public static double[][] intersect( final Mesh mesh, final double z ) + /** + * Only works if the {@link Mesh} supports {@link Mesh#edges()}. + * + * @param mesh + * @param z + * @return + */ + public static void intersect( + final Mesh mesh, + final double z, + final TDoubleArrayList cx, + final TDoubleArrayList cy ) { /* - * Build the edge and face maps. This could be more efficiently - * implemented in a read-only winged-edge mesh class. + * Clear contour holders. */ + cx.resetQuick(); + cy.resetQuick(); - final Vertices vertices = mesh.vertices(); - final Triangles triangles = mesh.triangles(); - - // Map of edge (va -> vb with always va < vb) to face pair (fa, fb). - // They are stored as a paired integers using Szudzik pairing. - // So it won't work if the index exceeds a few 100s of millions. - final TLongObjectHashMap< long[] > edgeList = new TLongObjectHashMap< long[] >(); - - // Iterate through all the faces. - final long startTime = System.currentTimeMillis(); - final long[] vs = new long[ 3 ]; - final int[] pairHolder = new int[ 2 ]; - for ( long face = 0; face < triangles.size(); face++ ) - { - vs[ 0 ] = triangles.vertex0( face ); - vs[ 1 ] = triangles.vertex1( face ); - vs[ 2 ] = triangles.vertex2( face ); - Arrays.sort( vs ); - - // Deal with the 3 edges. - insertEdge( vs[ 0 ], vs[ 1 ], face, edgeList, pairHolder ); - insertEdge( vs[ 0 ], vs[ 2 ], face, edgeList, pairHolder ); - insertEdge( vs[ 1 ], vs[ 2 ], face, edgeList, pairHolder ); - } - final long endTime = System.currentTimeMillis(); - System.out.println( "Built edge and face lists for " + triangles.size() + " faces in " + ( endTime - startTime ) + " ms." ); - -// edgeList.forEachEntry( new TLongObjectProcedure< long[] >() -// { -// private final int[] phK = new int[ 2 ]; -// -// @Override -// public boolean execute( final long k, final long[] v ) -// { -// unpair( k, phK ); -// System.out.println( String.format( "%d, %d -> %d, %d", phK[ 0 ], phK[ 1 ], v[ 0 ], v[ 1 ] ) ); -// return true; -// } -// } ); + /* + * Check if bounding-box intersect. TODO: use a data structure where the + * bounding-box is a field, calculated once. + */ + final float[] bb = Meshes.boundingBox( mesh ); + if ( bb[ 2 ] > z ) + return; + if ( bb[ 5 ] < z ) + return; /* * Find one edge that crosses the Z plane. */ - final TLongIterator edgeIt = edgeList.keySet().iterator(); + final Edges edges = mesh.edges(); + final Vertices vertices = mesh.vertices(); + final long nEdges = edges.size(); + long start = -1; - while ( edgeIt.hasNext() ) + for ( long e = 0; e < nEdges; e++ ) { - final long edge = edgeIt.next(); - unpair( edge, pairHolder ); - final long va = pairHolder[ 0 ]; - final long vb = pairHolder[ 1 ]; - if ( testLineIntersectPlane( vertices, va, vb, z ) ) + if ( edgeCrossPlane( vertices, edges, e, z ) ) { - start = edge; - break; + // Edge is part of a face? + final long f0 = edges.f0( e ); + if ( f0 >= 0 ) + { + start = e; + break; + } + // This edge has no face, we need another one. } } + // Cannot build contour based on edge with no faces. if ( start < 0 ) - { - System.out.println( "Could not find an edge that intersects with Z = " + z ); - return null; - } + return; - /* - * Iterate from it, selecting faces that an edge that crosses the plane. - */ + // Holder for the vertices of a triangle. + final long[] vs = new long[ 3 ]; + // Holder for the 3 edges of a triangle. + final long[] es = new long[ 3 ]; - final TDoubleArrayList intersectionX = new TDoubleArrayList(); - final TDoubleArrayList intersectionY = new TDoubleArrayList(); long current = start; - long previousFace = -1; - final long[][] edges = new long[ 3 ][ 2 ]; - final TLongHashSet visited = new TLongHashSet(); + final long startTriangle = edges.f0( start ); + long previousTriangle = startTriangle; + final TLongArrayList visited = new TLongArrayList(); +// final TLongHashSet visited = new TLongHashSet(); + visited.add( startTriangle ); while ( true ) { - addEdgeToContour( vertices, current, z, intersectionX, intersectionY, pairHolder ); - final long face = getNextFace( edgeList, current, previousFace ); - if ( visited.contains( face ) ) - break; + addEdgeToContour( vertices, edges, current, z, cx, cy ); + + final long triangle = getNextTriangle( edges, current, previousTriangle ); + System.out.println( "At triangle: " + toString( mesh, triangle ) ); + + if ( triangle < 0 || visited.contains( triangle ) ) + return; - final long next = getNextEdge( mesh, edgeList, face, current, z, previousFace, edges, vs ); - visited.add( face ); - previousFace = face; + visited.add( triangle ); + final long next = getNextEdge( mesh, triangle, current, z, vs, es ); + + if ( next < 0 || next == start ) + return; + + previousTriangle = triangle; current = next; } + } + + private static String toString( final Mesh mesh, final long triangle ) + { + // TODO Auto-generated method stub + return null; + } - return new double[][] { intersectionX.toArray(), intersectionY.toArray() }; + private static boolean edgeCrossPlane( final Vertices vertices, final Edges edges, final long e, final double z ) + { + final double z0 = vertices.z( edges.v0( e ) ); + final double z1 = vertices.z( edges.v1( e ) ); + if ( z0 > z && z1 > z ) + return false; + if ( z0 < z && z1 < z ) + return false; + return true; } - private static long getNextFace( final TLongObjectHashMap< long[] > edgeList, final long current, final long previousFace ) + private static long getNextTriangle( final Edges edges, final long e, final long previousFace ) { - // Get the faces of this edge. - final long[] faces = edgeList.get( current ); - // Retain the one we have not been visiting. - long face; - if ( faces[ 0 ] == previousFace ) - face = faces[ 1 ]; - else - face = faces[ 0 ]; - return face; + final long f0 = edges.f0( e ); + if ( f0 == previousFace ) + return edges.f1( e ); + return f0; } - private static long getNextEdge( final Mesh mesh, final TLongObjectHashMap< long[] > edgeList, final long face, final long current, final double z, final long previousFace, final long[][] edges, final long[] vs ) + /** + * Returns the index of the edge in the specified triangle that crosses the + * plane with the specified z, and that is different from the specified + * current edge. Returns -1 is such an edge cannot be found for the + * specified triangle. + * + * @param mesh + * the mesh structure. + * @param face + * the triangle to inspect. + * @param current + * the current edge, that should not be returned. + * @param z + * the value of the Z plane. + * @param vs + * holder for the vertices of the triangle (size at least 3). + * @param es + * holder for the edges of the triangle (size at least 3). + * @return the index of the next edge. + */ + private static long getNextEdge( + final Mesh mesh, + final long face, + final long current, + final double z, + final long[] vs, + final long[] es ) { final Triangles triangles = mesh.triangles(); final Vertices vertices = mesh.vertices(); + final Edges edges = mesh.edges(); // Get the edges of this face. vs[ 0 ] = triangles.vertex0( face ); vs[ 1 ] = triangles.vertex1( face ); vs[ 2 ] = triangles.vertex2( face ); Arrays.sort( vs ); - edges[ 0 ][ 0 ] = vs[ 0 ]; - edges[ 0 ][ 1 ] = vs[ 1 ]; - edges[ 1 ][ 0 ] = vs[ 0 ]; - edges[ 1 ][ 1 ] = vs[ 2 ]; - edges[ 2 ][ 0 ] = vs[ 1 ]; - edges[ 2 ][ 1 ] = vs[ 2 ]; - for ( final long[] edge : edges ) + es[ 0 ] = edges.indexOf( vs[ 0 ], vs[ 1 ] ); + es[ 1 ] = edges.indexOf( vs[ 0 ], vs[ 2 ] ); + es[ 2 ] = edges.indexOf( vs[ 1 ], vs[ 2 ] ); + for ( final long e : es ) { - final long e = pair( edge[ 0 ], edge[ 1 ] ); if ( e == current ) continue; - if ( testLineIntersectPlane( vertices, edge[ 0 ], edge[ 1 ], z ) ) - return e; + if ( edgeCrossPlane( vertices, edges, e, z ) ) + return e; } return -1; } - private static void addEdgeToContour( final Vertices vertices, final long edge, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy, final int[] pairHolder ) + private static void addEdgeToContour( + final Vertices vertices, + final Edges edges, + final long e, + final double z, + final TDoubleArrayList cx, + final TDoubleArrayList cy ) { - unpair( edge, pairHolder ); - final int sv = pairHolder[ 0 ]; - final int tv = pairHolder[ 1 ]; + final long sv = edges.v0( e ); + final long tv = edges.v1( e ); final double xs = vertices.x( sv ); final double ys = vertices.y( sv ); final double zs = vertices.z( sv ); final double xt = vertices.x( tv ); final double yt = vertices.y( tv ); final double zt = vertices.z( tv ); - double x; - double y; - if ( zs == zt ) - { - x = 0.5 * ( xs + xt ); - y = 0.5 * ( ys + yt ); - } - else - { - final double t = ( z - zs ) / ( zt - zs ); - x = xs + t * ( xt - xs ); - y = ys + t * ( yt - ys ); - } + final double t = ( zs == zt ) + ? 0.5 : ( z - zs ) / ( zt - zs ); + final double x = xs + t * ( xt - xs ); + final double y = ys + t * ( yt - ys ); final int np = cx.size(); if ( np > 1 && cx.getQuick( np - 1 ) == x && cy.getQuick( np - 1 ) == y ) return; // Don't add duplicate. @@ -184,98 +205,4 @@ private static void addEdgeToContour( final Vertices vertices, final long edge, cx.add( x ); cy.add( y ); } - - private static void insertEdge( final long va, final long vb, final long face, final TLongObjectHashMap< long[] > edgeList, final int[] pairHolder ) - { - assert va < vb; - final long edge = pair( va, vb ); - final long[] faces = edgeList.get( edge ); - if ( faces == null ) - { - edgeList.put( edge, new long[] { face, -1 } ); - return; - } - faces[ 1 ] = face; - } - - private static void insertFaceIntoVertexList( final long vertex, final long face, final TLongObjectHashMap< TLongArrayList > vertexList ) - { - TLongArrayList faceList = vertexList.get( vertex ); - if ( faceList == null ) - { - faceList = new TLongArrayList(); - vertexList.put( vertex, faceList ); - } - faceList.add( face ); - - } - - private static boolean testLineIntersectPlane( final Vertices vertices, final long source, final long target, final double z ) - { - final double z0 = vertices.z( source ); - final double z1 = vertices.z( target ); - if ( ( z0 > z && z1 > z ) || ( z0 < z && z1 < z ) ) - return false; - return true; - } - - /** - * Szudzik pairing. - * - * @param x - * the 1st int to pair. - * @param y - * the 2nd int to pair. - * @return Szudzik pairing. - */ - public static long pair( final double x, final double y ) - { - return ( long ) ( x >= y ? x * x + x + y : y * y + x ); - } - - /** - * Szudzik unpairing. - * - * @param z - * the factor to unpair. - * @param out - * where to write the results in. - */ - public static void unpair( final long z, final int[] out ) - { - final long b = ( long ) Math.sqrt( z ); - final int a = ( int ) ( z - b * b ); - if ( a < b ) - { - out[ 0 ] = a; - out[ 1 ] = ( int ) b; - } - else - { - out[ 0 ] = ( int ) b; - out[ 1 ] = ( int ) ( a - b ); - } - } - - public static void main( final String[] args ) - { - final int[][] tests = new int[][] { - { 1, 2 }, - { 100, 5 }, - { 5, 100 }, - { 0, 500 }, - { 9, 0 }, - { 120, 12345678 }, - { -1, 50 }, - { 20, -1 } - }; - final int[] out = new int[ 2 ]; - for ( final int[] test : tests ) - { - final long z = pair( test[ 0 ], test[ 1 ] ); - unpair( z, out ); - System.out.println( String.format( "%d, %d -> %d -> %d, %d", test[ 0 ], test[ 1 ], z, out[ 0 ], out[ 1 ] ) ); - } - } - } From 8eccba429d0743174acab851f30367a6e3bbf433 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:06:35 +0200 Subject: [PATCH 053/263] A simple data structure to store a 3D mesh in a Spot object. This class is the 3D counterpart of SpotRoi. It stores the object shape as a mesh, and has (for now) a few methods to facilitate painting it and creating it. The mesh are stored with coordinates relative to the spot center (mesh center is at 0,0,0). The same for the bounding box. The mesh coordinates are expected to be in physical coordinates, not pixel coordinates. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/SpotMesh.java diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java new file mode 100644 index 000000000..a57338c09 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -0,0 +1,260 @@ +package fiji.plugin.trackmate; + +import gnu.trove.list.array.TDoubleArrayList; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.Triangles; +import net.imagej.mesh.Vertices; +import net.imglib2.RealPoint; + +public class SpotMesh +{ + + /** + * The mesh representing the 3D contour of the spot. The mesh is centered on + * (0, 0, 0) and the true position of its vertices is obtained by adding the + * spot center. + */ + public final Mesh mesh; + + /** + * The bounding-box, centered on (0,0,0) of this object. + */ + public final float[] boundingBox; + + public SpotMesh( final Mesh mesh, final float[] boundingBox ) + { + this.mesh = mesh; + this.boundingBox = boundingBox; + } + + /** + * Creates a spot representing a 3D object, with the mesh specifying its + * position and shape. + *

+ * Warning: the specified mesh is modified and wrapped in the spot. + * + * @param mesh + * the mesh. + * @param quality + * the spot quality. + * @return a new {@link Spot}. + */ + public static Spot createSpot( final Mesh mesh, final double quality ) + { + final RealPoint center = Meshes.center( mesh ); + + // Shift mesh to (0, 0, 0). + final Vertices vertices = mesh.vertices(); + final long nVertices = vertices.size(); + for ( long i = 0; i < nVertices; i++ ) + vertices.setPositionf( i, + vertices.xf( i ) - center.getFloatPosition( 0 ), + vertices.yf( i ) - center.getFloatPosition( 1 ), + vertices.zf( i ) - center.getFloatPosition( 2 ) ); + + // Bounding box with respect to 0. + final float[] boundingBox = Meshes.boundingBox( mesh ); + + // Spot mesh, all relative to 0. + final SpotMesh spotMesh = new SpotMesh( mesh, boundingBox ); + + // Create spot. + final double r = spotMesh.radius(); + final Spot spot = new Spot( + center.getDoublePosition( 0 ), + center.getDoublePosition( 1 ), + center.getDoublePosition( 2 ), + r, + quality ); + spot.setMesh( spotMesh ); + return spot; + } + + private double radius() + { + return Math.pow( 3. * volume() / ( 4 * Math.PI ), 1. / 3. ); + } + + private double volume() + { + final Vertices vertices = mesh.vertices(); + final Triangles triangles = mesh.triangles(); + final long nTriangles = triangles.size(); + double sum = 0.; + for ( long t = 0; t < nTriangles; t++ ) + { + final long v1 = triangles.vertex0( t ); + final long v2 = triangles.vertex1( t ); + final long v3 = triangles.vertex2( t ); + + final double x1 = vertices.x( v1 ); + final double y1 = vertices.y( v1 ); + final double z1 = vertices.z( v1 ); + final double x2 = vertices.x( v2 ); + final double y2 = vertices.y( v2 ); + final double z2 = vertices.z( v2 ); + final double x3 = vertices.x( v3 ); + final double y3 = vertices.y( v3 ); + final double z3 = vertices.z( v3 ); + + final double v321 = x3 * y2 * z1; + final double v231 = x2 * y3 * z1; + final double v312 = x3 * y1 * z2; + final double v132 = x1 * y3 * z2; + final double v213 = x2 * y1 * z3; + final double v123 = x1 * y2 * z3; + + sum += ( 1. / 6. ) * ( -v321 + v231 + v312 - v132 - v213 + v123 ); + } + return Math.abs( sum ); + } + + public void scale(final double alpha) + { + final Vertices vertices = mesh.vertices(); + final long nVertices = vertices.size(); + for ( int v = 0; v < nVertices; v++ ) + { + final float x = vertices.xf( v ); + final float y = vertices.yf( v ); + final float z = vertices.zf( v ); + + // Spherical coords. + if ( x == 0. && y == 0. ) + { + if ( z == 0 ) + continue; + + vertices.setPositionf( v, 0f, 0f, ( float ) ( z * alpha ) ); + continue; + } + final double r = Math.sqrt( x * x + y * y + z * z ) ; + final double theta = Math.acos( z / r ); + final double phi = Math.signum( y ) * Math.acos( x / Math.sqrt( x * x + y * y ) ); + + final double ra = r * alpha; + final float xa = ( float ) ( ra * Math.sin( theta ) * Math.cos( phi ) ); + final float ya = ( float ) ( ra * Math.sin( theta ) * Math.sin( phi ) ); + final float za = ( float ) ( ra * Math.cos( theta ) ); + vertices.setPositionf( v, xa, ya, za ); + } + } + + public void slice( final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) + { + slice( mesh, z, cx, cy ); + } + + public static void slice( final Mesh mesh, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) + { + // Clear contour holders. + cx.resetQuick(); + cy.resetQuick(); + + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + for ( long f = 0; f < triangles.size(); f++ ) + { + final long v0 = triangles.vertex0( f ); + final long v1 = triangles.vertex1( f ); + final long v2 = triangles.vertex2( f ); + + final double minZ = minZ( vertices, v0, v1, v2 ); + if ( minZ > z ) + continue; + final double maxZ = maxZ( vertices, v0, v1, v2 ); + if ( maxZ < z ) + continue; + + triangleIntersection( vertices, v0, v1, v2, z, cx, cy ); + } + } + + /** + * Intersection of a triangle with a Z plane. + */ + private static void triangleIntersection( final Vertices vertices, final long v0, final long v1, final long v2, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) + { + final double z0 = vertices.z( v0 ); + final double z1 = vertices.z( v1 ); + final double z2 = vertices.z( v2 ); + + // Skip this; I don't know how to deal with this border case. + if ( z0 == z && z1 == z && z2 == z ) + { + addSegmentToContour( vertices, v0, v1, cx, cy ); + addSegmentToContour( vertices, v0, v2, cx, cy ); + addSegmentToContour( vertices, v1, v2, cx, cy ); + return; + } + + if ( z0 == z && z1 == z ) + { + addSegmentToContour( vertices, v0, v1, cx, cy ); + return; + } + if ( z0 == z && z2 == z ) + { + addSegmentToContour( vertices, v0, v2, cx, cy ); + return; + } + if ( z1 == z && z2 == z ) + { + addSegmentToContour( vertices, v1, v2, cx, cy ); + return; + } + + addEdgeIntersectionToContour( vertices, v0, v1, z, cx, cy ); + addEdgeIntersectionToContour( vertices, v0, v2, z, cx, cy ); + addEdgeIntersectionToContour( vertices, v1, v2, z, cx, cy ); + } + + private static void addSegmentToContour( final Vertices vertices, final long v0, final long v1, final TDoubleArrayList cx, final TDoubleArrayList cy ) + { + final double x0 = vertices.x( v0 ); + final double x1 = vertices.x( v1 ); + cx.add( x0 ); + cx.add( x1 ); + final double y0 = vertices.y( v0 ); + final double y1 = vertices.y( v0 ); + cy.add( y0 ); + cy.add( y1 ); + } + + private static void addEdgeIntersectionToContour( + final Vertices vertices, + final long sv, + final long tv, + final double z, + final TDoubleArrayList cx, + final TDoubleArrayList cy ) + { + final double zs = vertices.z( sv ); + final double zt = vertices.z( tv ); + if ( ( zs > z && zt > z ) || ( zs < z && zt < z ) ) + return; + + final double xs = vertices.x( sv ); + final double ys = vertices.y( sv ); + final double xt = vertices.x( tv ); + final double yt = vertices.y( tv ); + final double t = ( zs == zt ) + ? 0.5 : ( z - zs ) / ( zt - zs ); + final double x = xs + t * ( xt - xs ); + final double y = ys + t * ( yt - ys ); + cx.add( x ); + cy.add( y ); + } + + private static final double minZ( final Vertices vertices, final long v0, final long v1, final long v2 ) + { + return Math.min( vertices.z( v0 ), Math.min( vertices.z( v1 ), vertices.z( v2 ) ) ); + } + + private static final double maxZ( final Vertices vertices, final long v0, final long v1, final long v2 ) + { + return Math.max( vertices.z( v0 ), Math.max( vertices.z( v1 ), vertices.z( v2 ) ) ); + } + +} From 72ed47fe7dec1a9ba0c989f526905d0ba9ffd0fa Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:07:42 +0200 Subject: [PATCH 054/263] Spot objects may have a SpotMesh. --- src/main/java/fiji/plugin/trackmate/Spot.java | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index 00bb2f5bd..626e28528 100644 --- a/src/main/java/fiji/plugin/trackmate/Spot.java +++ b/src/main/java/fiji/plugin/trackmate/Spot.java @@ -8,12 +8,12 @@ * 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 * . @@ -75,11 +75,18 @@ public class Spot extends AbstractEuclideanSpace implements RealLocalizable, Com /** * The polygon that represents the 2D roi around the spot. Can be - * null if the detector that created this spot does not support - * ROIs or for 3D images. + * null if the spot does not contain 2D contour information or + * has a 3D shape information as a mesh. */ private SpotRoi roi; + /** + * The mesh that represents the 3D object around the spot. Can be + * null of the spot does not contain 3D shape information or + * has a 2D shape information as a contour. + */ + private SpotMesh mesh; + /* * CONSTRUCTORS */ @@ -240,6 +247,7 @@ public boolean equals( final Object other ) public void setRoi( final SpotRoi roi ) { this.roi = roi; + this.mesh = null; } public SpotRoi getRoi() @@ -247,6 +255,17 @@ public SpotRoi getRoi() return roi; } + public void setMesh( final SpotMesh mesh ) + { + this.roi = null; + this.mesh = mesh; + } + + public SpotMesh getMesh() + { + return mesh; + } + /** * @return the name for this Spot. */ @@ -257,7 +276,7 @@ public String getName() /** * Set the name of this Spot. - * + * * @param name * the name to use. */ @@ -284,7 +303,7 @@ public String toString() /** * Return a string representation of this spot, with calculated features. - * + * * @return a string representation of the spot. */ public String echo() @@ -370,9 +389,9 @@ public void putFeature( final String feature, final Double value ) /** * Copy the listed features of the spot src to the current spot - * + * */ - public void copyFeatures( Spot src, final Map< String, Double > features ) + public void copyFeatures( final Spot src, final Map< String, Double > features ) { if ( null == features || features.isEmpty() ) return; From 6b99872a496e640f8f9d7f12bacf4e8973b4bf4a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:08:30 +0200 Subject: [PATCH 055/263] Utility method for SpotRoi: generate XY coordinates and write them in holders provided by the user. --- .../java/fiji/plugin/trackmate/SpotRoi.java | 52 +++++++++++++++++-- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index 00d2556e6..9efe32ccf 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -8,12 +8,12 @@ * 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 * . @@ -23,6 +23,7 @@ import java.util.Arrays; +import gnu.trove.list.array.TDoubleArrayList; import net.imagej.ImgPlus; import net.imglib2.IterableInterval; import net.imglib2.RandomAccessibleInterval; @@ -61,7 +62,7 @@ public SpotRoi copy() /** * Returns a new int array containing the X pixel coordinates * to which to paint this polygon. - * + * * @param calibration * the pixel size in X, to convert physical coordinates to pixel * coordinates. @@ -85,7 +86,7 @@ public double[] toPolygonX( final double calibration, final double xcorner, fina /** * Returns a new int array containing the Y pixel coordinates * to which to paint this polygon. - * + * * @param calibration * the pixel size in Y, to convert physical coordinates to pixel * coordinates. @@ -106,6 +107,49 @@ public double[] toPolygonY( final double calibration, final double ycorner, fina return yp; } + /** + * Writes the X AND Y pixel coordinates of the contour of the ROI inside a + * double list, cleared first when this method is called. Similar to + * {@link #toPolygonX(double, double, double, double)} and + * {@link #toPolygonY(double, double, double, double)} but allocation-free. + * + * @param calibration + * the pixel sizes, to convert physical coordinates to pixel + * coordinates. + * @param xcorner + * the top-left X corner of the view in the image to paint. + * @param magnification + * the magnification of the view. + * @param cx + * the list in which to write the contour X coordinates. First + * reset when called. + * @param cy + * the list in which to write the contour Y coordinates. First + * reset when called. + */ + public void toPolygon( + final double calibration[], + final double xcorner, + final double ycorner, + final double spotXCenter, + final double spotYCenter, + final double magnification, + final TDoubleArrayList cx, + final TDoubleArrayList cy ) + { + cx.resetQuick(); + cy.resetQuick(); + for ( int i = 0; i < x.length; i++ ) + { + final double xc = ( spotXCenter + x[ i ] ) / calibration[ 0 ]; + final double xp = ( xc - xcorner ) * magnification; + cx.add( xp ); + final double yc = ( spotYCenter + y[ i ] ) / calibration[ 1 ]; + final double yp = ( yc - ycorner ) * magnification; + cy.add( yp ); + } + } + public < T > IterableInterval< T > sample( final Spot spot, final ImgPlus< T > img ) { return sample( spot.getDoublePosition( 0 ), spot.getDoublePosition( 1 ), img, img.averageScale( 0 ), img.averageScale( 1 ) ); From 1f215f23899d8f275efcd535aaf95b19d1ef8fe2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:10:00 +0200 Subject: [PATCH 056/263] MaskUtils have method to create spots with meshes for 3D masks and label images. --- .../detection/LabelImageDetector.java | 2 +- .../plugin/trackmate/detection/MaskUtils.java | 155 ++++++++++++++---- 2 files changed, 123 insertions(+), 34 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index 39ec68d35..653d9a320 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -137,7 +137,7 @@ private < R extends IntegerType< R > > void processIntegerImg( final RandomAcces final ImgLabeling< Integer, R > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); if ( input.numDimensions() == 2 ) - spots = MaskUtils.fromLabelingWithROI( labeling, interval, calibration, simplify, null ); + spots = MaskUtils.from2DLabelingWithROI( labeling, interval, calibration, simplify, null ); else spots = MaskUtils.fromLabeling( labeling, interval, calibration ); } diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 5ead9f1e1..e190ace96 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -8,12 +8,12 @@ * 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 * . @@ -30,14 +30,19 @@ import java.util.concurrent.ExecutorService; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.Threads; +import ij.ImagePlus; import ij.gui.PolygonRoi; import ij.process.FloatPolygon; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.Vertices; import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; @@ -51,13 +56,16 @@ import net.imglib2.histogram.Real1dBinMapper; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; +import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegionCursor; import net.imglib2.roi.labeling.LabelRegions; import net.imglib2.type.BooleanType; import net.imglib2.type.logic.BitType; +import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; +import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; import net.imglib2.util.Util; @@ -115,11 +123,11 @@ public static final long getThreshold( final Histogram1d< ? > hist ) int k, kStar; // k = the current threshold; kStar = optimal threshold final int L = histogram.length; // The total intensity of the image long N1, N; // N1 = # points with intensity <=k; N = total number of - // points + // points long Sk; // The total intensity for all histogram points <=k long S; double BCV, BCVmax; // The current Between Class Variance and maximum - // BCV + // BCV double num, denom; // temporary bookkeeping // Initialize values: @@ -148,15 +156,15 @@ public static final long getThreshold( final Histogram1d< ? > hist ) // precision and // will prevent overflow in the case of large saturated images denom = ( double ) ( N1 ) * ( N - N1 ); // Maximum value of denom is - // (N^2)/4 = - // approx. 3E10 + // (N^2)/4 = + // approx. 3E10 if ( denom != 0 ) { // Float here is to avoid loss of precision when dividing num = ( ( double ) N1 / N ) * S - Sk; // Maximum value of num = - // 255*N = - // approx 8E7 + // 255*N = + // approx 8E7 BCV = ( num * num ) / denom; } else @@ -176,7 +184,7 @@ public static final long getThreshold( final Histogram1d< ? > hist ) /** * Creates a zero-min label image from a thresholded input image. - * + * * @param * the type of the input image. Must be real, scalar. * @param input @@ -228,7 +236,7 @@ public static final < T extends RealType< T > > ImgLabeling< Integer, IntType > * Creates spots from a grayscale image, thresholded to create a mask. A * spot is created for each connected-component of the mask, with a size * that matches the mask size. - * + * * @param * the type of the input image. Must be real, scalar. * @param input @@ -260,7 +268,7 @@ public static < T extends RealType< T > > List< Spot > fromThreshold( /** * Creates spots from a label image. - * + * * @param * the type that backs-up the labeling. * @param labeling @@ -309,7 +317,7 @@ public static < R extends IntegerType< R > > List< Spot > fromLabeling( volume *= calibration[ d ]; final double radius = ( labeling.numDimensions() == 2 ) ? Math.sqrt( volume / Math.PI ) - : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); + : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); final double quality = region.size(); spots.add( new Spot( x, y, z, radius, quality ) ); } @@ -322,7 +330,7 @@ public static < R extends IntegerType< R > > List< Spot > fromLabeling( * spot is created for each connected-component of the mask, with a size * that matches the mask size. The quality of the spots is read from another * image, by taking the max pixel value of this image with the ROI. - * + * * @param * the type of the input image. Must be real, scalar. * @param input @@ -397,7 +405,7 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > final double radius = ( labeling.numDimensions() == 2 ) ? Math.sqrt( volume / Math.PI ) - : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); + : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); spots.add( new Spot( x, y, z, radius, quality ) ); } @@ -405,12 +413,12 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > } /** - * Creates spots with their ROIs from a 2D grayscale image, - * thresholded to create a mask. A spot is created for each + * Creates spots with their ROIs or meshes from a 2D or 3D + * grayscale image, thresholded to create a mask. A spot is created for each * connected-component of the mask, with a size that matches the mask size. * The quality of the spots is read from another image, by taking the max * pixel value of this image with the ROI. - * + * * @param * the type of the input image. Must be real, scalar. * @param @@ -441,12 +449,16 @@ public static final < T extends RealType< T >, S extends RealType< S > > List< S final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { - if ( input.numDimensions() != 2 ) - throw new IllegalArgumentException( "Can only process 2D images with this method, but got " + input.numDimensions() + "D." ); - // Get labeling. final ImgLabeling< Integer, IntType > labeling = toLabeling( input, interval, threshold, numThreads ); - return fromLabelingWithROI( labeling, interval, calibration, simplify, qualityImage ); + + // Process it. + if ( input.numDimensions() == 2 ) + return from2DLabelingWithROI( labeling, interval, calibration, simplify, qualityImage ); + else if ( input.numDimensions() == 3 ) + return from3DLabelingWithROI( labeling, interval, calibration, simplify, qualityImage ); + else + throw new IllegalArgumentException( "Can only process 2D or 3D images with this method, but got " + labeling.numDimensions() + "D." ); } /** @@ -472,18 +484,18 @@ public static final < T extends RealType< T >, S extends RealType< S > > List< S * the image in which to read the quality value. * @return a list of spots, with ROI. */ - public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > fromLabelingWithROI( + public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from2DLabelingWithROI( final ImgLabeling< Integer, R > labeling, final Interval interval, final double[] calibration, final boolean simplify, final RandomAccessibleInterval< S > qualityImage ) { - final Map< Integer, List< Spot > > map = fromLabelingWithROIMap( labeling, interval, calibration, simplify, qualityImage ); - final List spots = new ArrayList<>(); + final Map< Integer, List< Spot > > map = from2DLabelingWithROIMap( labeling, interval, calibration, simplify, qualityImage ); + final List< Spot > spots = new ArrayList<>(); for ( final List< Spot > s : map.values() ) spots.addAll( s ); - + return spots; } @@ -516,7 +528,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot * @return a map linking the label integer value to the list of spots, with * ROI, it corresponds to. */ - public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > fromLabelingWithROIMap( + public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > from2DLabelingWithROIMap( final ImgLabeling< Integer, R > labeling, final Interval interval, final double[] calibration, @@ -549,7 +561,6 @@ public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integ polygonsMap.put( label, pp ); } - // Storage for results. final Map< Integer, List< Spot > > output = new HashMap<>( polygonsMap.size() ); @@ -616,6 +627,73 @@ public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integ return output; } + /** + * Creates spots with meshes from a 3D label image. The + * quality value is read from a secondary image, by taking the max value in + * each ROI. + * + * @param + * the type that backs-up the labeling. + * @param + * the type of the quality image. Must be real, scalar. + * @param labeling + * the labeling, must be zero-min and 3D. + * @param interval + * the interval, used to reposition the spots from the zero-min + * labeling to the proper coordinates. + * @param calibration + * the physical calibration. + * @param simplify + * if true the meshes will be post-processed to be + * smoother and contain less points. + * @param qualityImage + * the image in which to read the quality value. + * @return a list of spots, with meshes. + */ + public static < R extends IntegerType< R >, S extends NumericType< S > > List< Spot > from3DLabelingWithROI( + final ImgLabeling< Integer, R > labeling, + final Interval interval, + final double[] calibration, + final boolean simplify, + final RandomAccessibleInterval< S > qualityImage ) + { + if ( labeling.numDimensions() != 3 ) + throw new IllegalArgumentException( "Can only process 3D images with this method, but got " + labeling.numDimensions() + "D." ); + + + // Quality image. + final ImagePlus qualityImp = ( null == qualityImage ) + ? null + : ImageJFunctions.wrap( qualityImage, "QualityImage" ); + + // Parse regions to create meshes on label. + final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); + final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); + final List< Spot > spots = new ArrayList<>( regions.getExistingLabels().size() ); + while ( iterator.hasNext() ) + { + final LabelRegion< Integer > region = iterator.next(); + + // To mesh. + final IntervalView< BoolType > box = Views.zeroMin( region ); + final Mesh mesh = Meshes.marchingCubes( box ); + final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 0 ); + final Mesh simplified = simplify + ? Meshes.simplify( cleaned, 0.25f, 10 ) + : cleaned; + // PScale to physical coords. + final double[] origin = region.minAsDoubleArray(); + scale( simplified.vertices(), calibration, origin ); + + // Measure quality. + // TODO Iterator over the mesh. + final double quality = -1; + + spots.add( SpotMesh.createSpot( simplified, quality ) ); + } + return spots; + } + private static final double distanceSquaredBetweenPoints( final double vx, final double vy, final double wx, final double wy ) { final double deltax = ( vx - wx ); @@ -696,7 +774,7 @@ private static final void douglasPeucker( final List< double[] > list, final int * the number of points in a curve that is approximated by a series of * points. *

- * + * * @see Ramer–Douglas–Peucker * Algorithm (Wikipedia) @@ -737,7 +815,7 @@ public static final PolygonRoi simplify( final PolygonRoi roi, final double smoo /** * Start at 1. - * + * * @return a new iterator that goes like 1, 2, 3, ... */ public static final Iterator< Integer > labelGenerator() @@ -769,7 +847,7 @@ public boolean hasNext() * Warning: cannot deal with holes, they are simply ignored. *

* Copied and adapted from ImageJ1 code by Wayne Rasband. - * + * * @param * the type of the mask. * @param mask @@ -1122,9 +1200,9 @@ public void prepend( final Outline o ) final int oSize = o.last - o.first; if ( size <= o.reserved - o.last && oSize > first ) { /* - * We don't have enough space in our own array but in that of - * 'o' so append our own data to that of 'o' - */ + * We don't have enough space in our own array but in that of + * 'o' so append our own data to that of 'o' + */ System.arraycopy( x, first, o.x, o.last, size ); System.arraycopy( y, first, o.y, o.last, size ); x = o.x; @@ -1212,4 +1290,15 @@ public String toString() } } + private static void scale( final Vertices vertices, final double[] scale, final double[] origin ) + { + final long nv = vertices.size(); + for ( long i = 0; i < nv; i++ ) + { + final double x = ( origin[ 0 ] + vertices.x( i ) ) * scale[ 0 ]; + final double y = ( origin[ 1 ] + vertices.y( i ) ) * scale[ 1 ]; + final double z = ( origin[ 2 ] + vertices.z( i ) ) * scale[ 2 ]; + vertices.set( i, x, y, z ); + } + } } From 2d46afb98adcefa14e390806020aa11cb9ad1603 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:10:28 +0200 Subject: [PATCH 057/263] Mask, label and threshold detectors support creating 3D meshes for their spots. --- .../trackmate/detection/LabelImageDetector.java | 12 +++++++----- .../detection/LabelImageDetectorFactory.java | 10 +++++----- .../detection/MaskDetectorFactory.java | 9 ++++----- .../trackmate/detection/ThresholdDetector.java | 17 +++-------------- .../detection/ThresholdDetectorFactory.java | 11 +++++------ 5 files changed, 24 insertions(+), 35 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index 653d9a320..d89ba879c 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -8,12 +8,12 @@ * 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 * . @@ -112,9 +112,9 @@ public boolean process() final ImgFactory< IntType > factory = Util.getArrayOrCellImgFactory( interval, new IntType() ); final Img< IntType > img = factory.create( interval ); LoopBuilder - .setImages( Views.zeroMin( rai ), img ) - .multiThreaded( false ) - .forEachPixel( ( i, o ) -> o.setReal( i.getRealDouble() ) ); + .setImages( Views.zeroMin( rai ), img ) + .multiThreaded( false ) + .forEachPixel( ( i, o ) -> o.setReal( i.getRealDouble() ) ); processIntegerImg( img ); } final long end = System.currentTimeMillis(); @@ -138,6 +138,8 @@ private < R extends IntegerType< R > > void processIntegerImg( final RandomAcces final ImgLabeling< Integer, R > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); if ( input.numDimensions() == 2 ) spots = MaskUtils.from2DLabelingWithROI( labeling, interval, calibration, simplify, null ); + else if ( input.numDimensions() == 3 ) + spots = MaskUtils.from3DLabelingWithROI( labeling, interval, calibration, simplify, null ); else spots = MaskUtils.fromLabeling( labeling, interval, calibration ); } diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java index 13738452a..c9b4ebf9d 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java @@ -8,12 +8,12 @@ * 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 * . @@ -75,8 +75,8 @@ public class LabelImageDetectorFactory< T extends RealType< T > & NativeType< T + "that is unique to the object." + "

" + "This detector reads such an image and create spots from each object. " - + "In 2D the contour of a label is imported. In 3D, spherical spots " - + "of the same volume that the label are created." + + "In 2D the contour of a label is imported. In 3D, a mesh around the " + + "label is imported." + "

" + "The spot quality stores the object area or volume in pixels." + ""; @@ -103,7 +103,7 @@ public boolean setTarget( final ImgPlus< T > img, final Map< String, Object > se this.settings = settings; return checkSettings( settings ); } - + @Override public SpotDetector< T > getDetector( final Interval interval, final int frame ) { diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java index 54675018a..8066c4d2e 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -8,12 +8,12 @@ * 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 * . @@ -72,9 +72,8 @@ public class MaskDetectorFactory< T extends RealType< T > & NativeType< T > > ex + "a value strictly larger than 0 are " + "considered as part of the foreground, " + "and used to build connected regions. In 2D, spots are created with " - + "the (possibly simplified) contour of the region. In 3D, a spherical " - + "spot is created for each region in its center, with a volume equal to the " - + "region volume." + + "the (possibly simplified) contour of the region. In 3D, a mesh is " + + "created for each region." + "

" + "The spot quality stores the object area or volume in pixels." + ""; diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java index 1fea44b66..d274ed6ee 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java @@ -8,12 +8,12 @@ * 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 * . @@ -110,20 +110,9 @@ public boolean checkInput() public boolean process() { final long start = System.currentTimeMillis(); - if ( input.numDimensions() == 2 ) + if ( input.numDimensions() == 2 || input.numDimensions() == 3 ) { - /* - * 2D: we compute and store the contour. - */ spots = MaskUtils.fromThresholdWithROI( input, interval, calibration, threshold, simplify, numThreads, null ); - - } - else if ( input.numDimensions() == 3 ) - { - /* - * 3D: We create spots of the same volume that of the region. - */ - spots = MaskUtils.fromThreshold( input, interval, calibration, threshold, numThreads ); } else { diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java index 289043950..142247eed 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -8,12 +8,12 @@ * 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 * . @@ -73,9 +73,8 @@ public class ThresholdDetectorFactory< T extends RealType< T > & NativeType< T > + "Pixels in the designated channel that have " + "a value larger than the threshold are considered as part of the foreground, " + "and used to build connected regions. In 2D, spots are created with " - + "the (possibly simplified) contour of the region. In 3D, a spherical " - + "spot is created for each region in its center, with a volume equal to the " - + "region volume." + + "the (possibly simplified) contour of the region. In 3D, a mesh is " + + "created for each region." + "

" + "The spot quality stores the object area or volume in pixels." + ""; @@ -106,7 +105,7 @@ public boolean setTarget( final ImgPlus< T > img, final Map< String, Object > se this.settings = settings; return checkSettings( settings ); } - + @Override public SpotDetector< T > getDetector( final Interval interval, final int frame ) { From 7bab2dc62462b46b2ced806f6070f9b9acefc93a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:15:10 +0200 Subject: [PATCH 058/263] Paint 3D meshes in the HyperStackViewer. We simply reslice the mesh at the Z slice currently displayed, and paint the intersection as a collection of segments. I also took the opportunity to refactor a bit the SpotOverlay. Right now this works but is not optimal: 1/ There are weird stuff happening at the *top* of the mesh: it's like we miss some part of it. 2/ The slice routine generates a list of disconnected segments. It does not show when we paint them, but maybe would be nice to reconstruct the collection of contours resulting from the intersection of a mesh with a plane. 3/ We could optimize the slice() routinemaybe by having an index that sorts triangles by their minZ value, and another index that sorts them by their maxZ value. This way we could quickly retrieve the triangles to sort by two binary-search and one set intersection. --- .../hyperstack/PaintSpotMesh.java | 106 +++++++++++++++++ .../hyperstack/PaintSpotRoi.java | 108 ++++++++++++++++++ .../hyperstack/PaintSpotSphere.java | 70 ++++++++++++ .../visualization/hyperstack/SpotOverlay.java | 106 +++++++---------- 4 files changed, 327 insertions(+), 63 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java new file mode 100644 index 000000000..80e25ed77 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -0,0 +1,106 @@ +package fiji.plugin.trackmate.visualization.hyperstack; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.geom.Path2D; +import java.awt.geom.Path2D.Double; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import gnu.trove.list.array.TDoubleArrayList; + +/** + * Utility class to paint the {@link SpotMesh} component of spots. + * + * @author Jean-Yves Tinevez + * + */ +public class PaintSpotMesh +{ + + private final double[] calibration; + + private final DisplaySettings displaySettings; + + private final TDoubleArrayList cx; + + private final TDoubleArrayList cy; + + private final Double polygon; + + public PaintSpotMesh( final double[] calibration, final DisplaySettings displaySettings ) + { + this.calibration = calibration; + this.displaySettings = displaySettings; + this.cx = new TDoubleArrayList(); + this.cy = new TDoubleArrayList(); + this.polygon = new Path2D.Double(); + } + + public int paint( + final Graphics2D g2d, + final Spot spot, + final double zslice, + final double xs, + final double ys, + final int xcorner, + final int ycorner, + final double magnification ) + { + final double x = spot.getFeature( Spot.POSITION_X ); + final double y = spot.getFeature( Spot.POSITION_Y ); + final double z = spot.getFeature( Spot.POSITION_Z ); + final double dz = zslice - z; + + final SpotMesh mesh = spot.getMesh(); + if ( mesh.boundingBox[ 2 ] > dz || mesh.boundingBox[ 5 ] < dz ) + { + g2d.fillOval( + ( int ) Math.round( xs - 2 * magnification ), + ( int ) Math.round( ys - 2 * magnification ), + ( int ) Math.round( 4 * magnification ), + ( int ) Math.round( 4 * magnification ) ); + return -1; + } + + // Slice. + mesh.slice( dz, cx, cy ); + // Scale to screen coordinates. + for ( int i = 0; i < cx.size(); i++ ) + { + // Pixel coords. + final double xc = ( x + cx.get( i ) ) / calibration[ 0 ] + 0.5; + final double yc = ( y + cy.get( i ) ) / calibration[ 1 ] + 0.5; + // Window coords. + cx.set( i, ( xc - xcorner ) * magnification ); + cy.set( i, ( yc - ycorner ) * magnification ); + } + + polygon.reset(); + for ( int i = 0; i < cx.size() - 1; i += 2 ) + { + final double x0 = cx.get( i ); + final double x1 = cx.get( i + 1 ); + final double y0 = cy.get( i ); + final double y1 = cy.get( i + 1 ); + polygon.moveTo( x0, y0 ); + polygon.lineTo( x1, y1 ); + } + + if ( displaySettings.isSpotFilled() ) + { + g2d.fill( polygon ); + g2d.setColor( Color.BLACK ); + g2d.draw( polygon ); + } + else + { + g2d.draw( polygon ); + } + + final int textPos = ( int ) ( PaintSpotRoi.max( cx ) - xs ); + return textPos; + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java new file mode 100644 index 000000000..0e864894d --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java @@ -0,0 +1,108 @@ +package fiji.plugin.trackmate.visualization.hyperstack; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.geom.Path2D; + +import fiji.plugin.trackmate.SpotRoi; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import gnu.trove.list.array.TDoubleArrayList; + +/** + * Utility class to paint the {@link SpotRoi} component of spots. + * + * @author Jean-Yves Tinevez + * + */ +public class PaintSpotRoi +{ + + private final double[] calibration; + + private final DisplaySettings displaySettings; + + private final java.awt.geom.Path2D.Double polygon; + + private final TDoubleArrayList cx; + + private final TDoubleArrayList cy; + + public PaintSpotRoi( final double[] calibration, final DisplaySettings displaySettings ) + { + this.calibration = calibration; + this.displaySettings = displaySettings; + this.polygon = new Path2D.Double(); + this.cx = new TDoubleArrayList(); + this.cy = new TDoubleArrayList(); + } + + /** + * Paint the specified spot using its {@link SpotRoi} field. The latter must + * not be null. + * + * @param g2d + * the graphics object, configured to paint the spot with. + * @param roi + * the spot roi. + * @param x + * the X spot center in physical coordinates. + * @param y + * the Y spot center in physical coordinates. + * @param xcorner + * the X position of the displayed window. + * @param ycorner + * the X position of the displayed window. + * @param magnification + * the magnification of the displayed window. + * @return the text position X indent in pixels to use to paint a string + * next to the painted contour. + */ + public int paint( + final Graphics2D g2d, + final SpotRoi roi, + final double x, + final double y, + final double xcorner, + final double ycorner, + final double magnification ) + { + // In pixel units. + final double xp = x / calibration[ 0 ] + 0.5f; + // Scale to image zoom. + final double xs = ( xp - xcorner ) * magnification; + // Contour in pixel coordinates. + roi.toPolygon( calibration, xcorner, ycorner, x, y, magnification, cx, cy ); + // The 0.5 is here so that we plot vertices at pixel centers. + polygon.reset(); + polygon.moveTo( cx.get( 0 ), cy.get( 0 ) ); + for ( int i = 1; i < cx.size(); ++i ) + polygon.lineTo( cx.get( i ), cy.get( i ) ); + polygon.closePath(); + + if ( displaySettings.isSpotFilled() ) + { + g2d.fill( polygon ); + g2d.setColor( Color.BLACK ); + g2d.draw( polygon ); + } + else + { + g2d.draw( polygon ); + } + + final int textPos = ( int ) ( max( cx ) - xs ); + return textPos; + } + + static final double max( final TDoubleArrayList l ) + { + double max = Double.NEGATIVE_INFINITY; + for ( int i = 0; i < l.size(); i++ ) + { + final double v = l.getQuick( i ); + if ( v > max ) + max = v; + } + return max; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java new file mode 100644 index 000000000..040c8ab13 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java @@ -0,0 +1,70 @@ +package fiji.plugin.trackmate.visualization.hyperstack; + +import java.awt.Graphics2D; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; + +/** + * Utility class to paint the spots as little spheres. + * + * @author Jean-Yves Tinevez + * + */ +public class PaintSpotSphere +{ + + private final double[] calibration; + + private final DisplaySettings displaySettings; + + public PaintSpotSphere( final double[] calibration, final DisplaySettings displaySettings ) + { + this.calibration = calibration; + this.displaySettings = displaySettings; + } + + public int paint( + final Graphics2D g2d, + final Spot spot, + final double zslice, + final double xs, + final double ys, + final int xcorner, + final int ycorner, + final double magnification ) + { + final double z = spot.getFeature( Spot.POSITION_Z ); + final double dz = zslice - z; + final double dz2 = dz * dz; + final double radiusRatio = displaySettings.getSpotDisplayRadius(); + final double radius = spot.getFeature( Spot.RADIUS ) * radiusRatio; + + if ( dz2 >= radius * radius ) + { + g2d.fillOval( + ( int ) Math.round( xs - 2 * magnification ), + ( int ) Math.round( ys - 2 * magnification ), + ( int ) Math.round( 4 * magnification ), + ( int ) Math.round( 4 * magnification ) ); + return -1; // Do not paint spot name. + } + + final double apparentRadius = Math.sqrt( radius * radius - dz2 ) / calibration[ 0 ] * magnification; + if ( displaySettings.isSpotFilled() ) + g2d.fillOval( + ( int ) Math.round( xs - apparentRadius ), + ( int ) Math.round( ys - apparentRadius ), + ( int ) Math.round( 2 * apparentRadius ), + ( int ) Math.round( 2 * apparentRadius ) ); + else + g2d.drawOval( + ( int ) Math.round( xs - apparentRadius ), + ( int ) Math.round( ys - apparentRadius ), + ( int ) Math.round( 2 * apparentRadius ), + ( int ) Math.round( 2 * apparentRadius ) ); + + final int textPos = ( int ) apparentRadius; + return textPos; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java index 041b12bcd..f818db6d9 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -8,12 +8,12 @@ * 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 * . @@ -32,15 +32,14 @@ import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.geom.AffineTransform; -import java.awt.geom.Path2D; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -73,6 +72,12 @@ public class SpotOverlay extends Roi protected final Model model; + private final PaintSpotRoi paintSpotRoi; + + private final PaintSpotSphere paintSpotSphere; + + private final PaintSpotMesh paintSpotMesh; + /* * CONSTRUCTOR */ @@ -84,6 +89,9 @@ public SpotOverlay( final Model model, final ImagePlus imp, final DisplaySetting this.imp = imp; this.calibration = TMUtils.getSpatialCalibration( imp ); this.displaySettings = displaySettings; + this.paintSpotSphere = new PaintSpotSphere( calibration, displaySettings ); + this.paintSpotRoi = new PaintSpotRoi( calibration, displaySettings ); + this.paintSpotMesh = new PaintSpotMesh( calibration, displaySettings ); } /* @@ -132,7 +140,7 @@ public void drawOverlay( final Graphics g ) g2d.setStroke( new BasicStroke( ( float ) displaySettings.getLineThickness() ) ); - if ( selectionOnly && null != spotSelection) + if ( selectionOnly && null != spotSelection ) { // Track display mode only displays selection. for ( final Spot spot : spotSelection ) @@ -243,86 +251,58 @@ public void setSpotSelection( final Collection< Spot > spots ) protected void drawSpot( final Graphics2D g2d, final Spot spot, final double zslice, final int xcorner, final int ycorner, final double magnification, final boolean filled ) { + // Spot center in pixel coords. final double x = spot.getFeature( Spot.POSITION_X ); final double y = spot.getFeature( Spot.POSITION_Y ); - final double z = spot.getFeature( Spot.POSITION_Z ); - final double dz2 = ( z - zslice ) * ( z - zslice ); - final double radiusRatio = displaySettings.getSpotDisplayRadius(); - final double radius = spot.getFeature( Spot.RADIUS ) * radiusRatio; - // In pixel units + // Pixel coords. final double xp = x / calibration[ 0 ] + 0.5f; final double yp = y / calibration[ 1 ] + 0.5f; - // so that spot centers are displayed on the pixel centers. - - // Scale to image zoom + // 0.5, so that spot centers are displayed on the pixel centers. + // Display window coordinates. final double xs = ( xp - xcorner ) * magnification; final double ys = ( yp - ycorner ) * magnification; - if ( dz2 >= radius * radius ) + // Spot shape. + final SpotRoi roi = spot.getRoi(); + final SpotMesh mesh = spot.getMesh(); + + final int textPos; + if ( !displaySettings.isSpotDisplayedAsRoi() || ( mesh == null && roi == null ) ) { - g2d.fillOval( ( int ) Math.round( xs - 2 * magnification ), ( int ) Math.round( ys - 2 * magnification ), ( int ) Math.round( 4 * magnification ), ( int ) Math.round( 4 * magnification ) ); - return; + textPos = paintSpotSphere.paint( g2d, spot, zslice, xs, ys, xcorner, ycorner, magnification ); } - - final SpotRoi roi = spot.getRoi(); - if ( !displaySettings.isSpotDisplayedAsRoi() || roi == null || roi.x.length < 2 ) + else if ( roi != null ) { - final double apparentRadius = Math.sqrt( radius * radius - dz2 ) / calibration[ 0 ] * magnification; - final int textPos = ( int ) apparentRadius; - if ( displaySettings.isSpotShowName() ) - drawSpotName( g2d, spot, xs, ys, textPos ); - if ( filled ) - g2d.fillOval( - ( int ) Math.round( xs - apparentRadius ), - ( int ) Math.round( ys - apparentRadius ), - ( int ) Math.round( 2 * apparentRadius ), - ( int ) Math.round( 2 * apparentRadius ) ); - else - g2d.drawOval( - ( int ) Math.round( xs - apparentRadius ), - ( int ) Math.round( ys - apparentRadius ), - ( int ) Math.round( 2 * apparentRadius ), - ( int ) Math.round( 2 * apparentRadius ) ); + textPos = paintSpotRoi.paint( g2d, roi, xs, ys, xcorner, ycorner, magnification ); } else { - final double[] polygonX = roi.toPolygonX( calibration[ 0 ], xcorner - 0.5, x, magnification ); - final double[] polygonY = roi.toPolygonY( calibration[ 1 ], ycorner - 0.5, y, magnification ); - // The 0.5 is here so that we plot vertices at pixel centers. - final Path2D polygon = new Path2D.Double(); - polygon.moveTo( polygonX[ 0 ], polygonY[ 0 ] ); - for ( int i = 1; i < polygonX.length; ++i ) - polygon.lineTo( polygonX[ i ], polygonY[ i ] ); - polygon.closePath(); - final int textPos = ( int ) ( Arrays.stream( polygonX ).max().getAsDouble() - xs ); - - if ( filled ) - { - if ( displaySettings.isSpotShowName() ) - drawSpotName( g2d, spot, xs, ys, textPos ); - g2d.fill( polygon ); - g2d.setColor( Color.BLACK ); - g2d.draw( polygon ); - } - else - { - if ( displaySettings.isSpotShowName() ) - drawSpotName( g2d, spot, xs, ys, textPos ); - g2d.draw( polygon ); - } + textPos = paintSpotMesh.paint( g2d, spot, zslice, xs, ys, xcorner, ycorner, magnification ); + } + + if ( textPos >= 0 && displaySettings.isSpotShowName() ) + { + final int windowWidth = imp.getWindow().getWidth(); + drawString( g2d, fm, windowWidth, spot.toString(), xs, ys, textPos ); } } - private final void drawSpotName( final Graphics2D g2d, final Spot spot, final double xs, final double ys, final int textPos ) + private static final void drawString( + final Graphics2D g2d, + final FontMetrics fm, + final int windowWidth, + final String str, + final double xs, + final double ys, + final int textPos ) { - final String str = spot.toString(); final int xindent = fm.stringWidth( str ); int xtext = ( int ) ( xs + textPos + 5 ); - if ( xtext + xindent > imp.getWindow().getWidth() ) + if ( xtext + xindent > windowWidth ) xtext = ( int ) ( xs - textPos - 5 - xindent ); final int yindent = fm.getAscent() / 2; final int ytext = ( int ) ys + yindent; - g2d.drawString( spot.toString(), xtext, ytext ); + g2d.drawString( str, xtext, ytext ); } } From f0118a2ca5af059dabe0088ffb2262cf2c597872 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:15:24 +0200 Subject: [PATCH 059/263] Outdated interactive tests. --- .../plugin/trackmate/mesh/Demo3DMesh.java | 6 +- .../trackmate/mesh/Demo3DMeshTrackMate.java | 28 +++++++ .../trackmate/mesh/MeshPlaneIntersection.java | 76 +++++++++++++++++++ 3 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index c3b86079e..26ae1704a 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -88,8 +88,6 @@ public static void main( final String[] args ) final double[] origin = region.minAsDoubleArray(); scale( emesh.vertices(), cal, origin ); - // Simplify. - /* * IO. */ @@ -103,10 +101,8 @@ public static void main( final String[] args ) final int zslice = 20; // plan final double z = ( zslice ) * cal[ 2 ]; // um - MeshPlaneIntersection.intersect( emesh, z, cx, cy ); + MeshPlaneIntersection.intersect2( emesh, z, cx, cy ); toOverlay( cx, cy, out, cal ); - - break; // DEBUg } System.out.println( "Done." ); } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java new file mode 100644 index 000000000..3e018dc44 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java @@ -0,0 +1,28 @@ +package fiji.plugin.trackmate.mesh; + +import fiji.plugin.trackmate.TrackMatePlugIn; +import ij.IJ; +import ij.ImageJ; +import ij.ImagePlus; + +public class Demo3DMeshTrackMate +{ + + public static void main( final String[] args ) + { + try + { + + ImageJ.main( args ); + final String filePath = "samples/mesh/CElegansMask3D.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + imp.show(); + + new TrackMatePlugIn().run( null ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } +} diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java index 90fea17bf..042492e86 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java @@ -178,6 +178,7 @@ private static long getNextEdge( } + private static void addEdgeToContour( final Vertices vertices, final Edges edges, @@ -205,4 +206,79 @@ private static void addEdgeToContour( cx.add( x ); cy.add( y ); } + + public static void intersect2( final Mesh mesh, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) + { + // Clear contour holders. + cx.resetQuick(); + cy.resetQuick(); + + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + for ( long f = 0; f < triangles.size(); f++ ) + { + final long v0 = triangles.vertex0( f ); + final long v1 = triangles.vertex1( f ); + final long v2 = triangles.vertex2( f ); + + final double minZ = minZ( vertices, v0, v1, v2 ); + if ( minZ > z ) + continue; + final double maxZ = maxZ( vertices, v0, v1, v2 ); + if ( maxZ < z ) + continue; + + segmentIntersecting( vertices, v0, v1, v2, z, cx, cy ); + } + } + + /** + * Intersection of a triangle with a Z plane. + */ + private static void segmentIntersecting( final Vertices vertices, final long v0, final long v1, final long v2, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) + { + addEdgeToContour( vertices, v0, v1, z, cx, cy ); + addEdgeToContour( vertices, v0, v2, z, cx, cy ); + addEdgeToContour( vertices, v1, v2, z, cx, cy ); + } + + private static void addEdgeToContour( + final Vertices vertices, + final long sv, + final long tv, + final double z, + final TDoubleArrayList cx, + final TDoubleArrayList cy ) + { + final double zs = vertices.z( sv ); + final double zt = vertices.z( tv ); + if ( ( zs > z && zt > z ) || ( zs < z && zt < z ) ) + return; + + final double xs = vertices.x( sv ); + final double ys = vertices.y( sv ); + final double xt = vertices.x( tv ); + final double yt = vertices.y( tv ); + final double t = ( zs == zt ) + ? 0.5 : ( z - zs ) / ( zt - zs ); + final double x = xs + t * ( xt - xs ); + final double y = ys + t * ( yt - ys ); + final int np = cx.size(); + if ( np > 1 && cx.getQuick( np - 1 ) == x && cy.getQuick( np - 1 ) == y ) + return; // Don't add duplicate. + + cx.add( x ); + cy.add( y ); + } + + private static final double minZ( final Vertices vertices, final long v0, final long v1, final long v2 ) + { + return Math.min( vertices.z( v0 ), Math.min( vertices.z( v1 ), vertices.z( v2 ) ) ); + } + + private static final double maxZ( final Vertices vertices, final long v0, final long v1, final long v2 ) + { + return Math.max( vertices.z( v0 ), Math.max( vertices.z( v1 ), vertices.z( v2 ) ) ); + } + } From 0302c28e5c560e1e5e09e5714ed169f9bbdb4efd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 23:41:21 +0200 Subject: [PATCH 060/263] Fix javadoc. --- src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index e190ace96..8fa69da97 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -424,7 +424,7 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > * @param * the type of the quality image. Must be real, scalar. * @param input - * the input image. Must be 2D. + * the input image. Can be 2D or 3D. * @param interval * the interval in the input image to analyze. * @param calibration From 7aebecdfd4f9a67f34127ca0442748ec8272241a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 23:41:49 +0200 Subject: [PATCH 061/263] Temporary store meshes in absolute physical coordinates. While we try to make better slices. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 47 +++++++++++++++---- .../hyperstack/PaintSpotMesh.java | 6 +-- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index a57338c09..6e4f06d96 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -45,13 +45,13 @@ public static Spot createSpot( final Mesh mesh, final double quality ) final RealPoint center = Meshes.center( mesh ); // Shift mesh to (0, 0, 0). - final Vertices vertices = mesh.vertices(); - final long nVertices = vertices.size(); - for ( long i = 0; i < nVertices; i++ ) - vertices.setPositionf( i, - vertices.xf( i ) - center.getFloatPosition( 0 ), - vertices.yf( i ) - center.getFloatPosition( 1 ), - vertices.zf( i ) - center.getFloatPosition( 2 ) ); +// final Vertices vertices = mesh.vertices(); +// final long nVertices = vertices.size(); +// for ( long i = 0; i < nVertices; i++ ) +// vertices.setPositionf( i, +// vertices.xf( i ) - center.getFloatPosition( 0 ), +// vertices.yf( i ) - center.getFloatPosition( 1 ), +// vertices.zf( i ) - center.getFloatPosition( 2 ) ); // Bounding box with respect to 0. final float[] boundingBox = Meshes.boundingBox( mesh ); @@ -205,6 +205,10 @@ private static void triangleIntersection( final Vertices vertices, final long v0 return; } + // Only one vertex is touching the plane -> no need to paint. + if ( z0 == z || z1 == z || z2 == z ) + return; + addEdgeIntersectionToContour( vertices, v0, v1, z, cx, cy ); addEdgeIntersectionToContour( vertices, v0, v2, z, cx, cy ); addEdgeIntersectionToContour( vertices, v1, v2, z, cx, cy ); @@ -217,7 +221,7 @@ private static void addSegmentToContour( final Vertices vertices, final long v0, cx.add( x0 ); cx.add( x1 ); final double y0 = vertices.y( v0 ); - final double y1 = vertices.y( v0 ); + final double y1 = vertices.y( v1 ); cy.add( y0 ); cy.add( y1 ); } @@ -247,6 +251,33 @@ private static void addEdgeIntersectionToContour( cy.add( y ); } + @Override + public String toString() + { + final StringBuilder str = new StringBuilder( super.toString() ); + + str.append( "\nBounding-box" ); + str.append( String.format( "\n%5s: %7.2f -> %7.2f", "X", boundingBox[ 0 ], boundingBox[ 3 ] ) ); + str.append( String.format( "\n%5s: %7.2f -> %7.2f", "Y", boundingBox[ 1 ], boundingBox[ 4 ] ) ); + str.append( String.format( "\n%5s: %7.2f -> %7.2f", "Z", boundingBox[ 2 ], boundingBox[ 5 ] ) ); + + final Vertices vertices = mesh.vertices(); + final long nVertices = vertices.size(); + str.append( "\nV (" + nVertices + "):" ); + for ( long i = 0; i < nVertices; i++ ) + str.append( String.format( "\n%5d: %7.2f %7.2f %7.2f", + i, vertices.x( i ), vertices.y( i ), vertices.z( i ) ) ); + + final Triangles triangles = mesh.triangles(); + final long nTriangles = triangles.size(); + str.append( "\nF (" + nTriangles + "):" ); + for ( long i = 0; i < nTriangles; i++ ) + str.append( String.format( "\n%5d: %5d %5d %5d", + i, triangles.vertex0( i ), triangles.vertex1( i ), triangles.vertex2( i ) ) ); + + return str.toString(); + } + private static final double minZ( final Vertices vertices, final long v0, final long v1, final long v2 ) { return Math.min( vertices.z( v0 ), Math.min( vertices.z( v1 ), vertices.z( v2 ) ) ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 80e25ed77..2e3f35158 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -51,7 +51,7 @@ public int paint( final double x = spot.getFeature( Spot.POSITION_X ); final double y = spot.getFeature( Spot.POSITION_Y ); final double z = spot.getFeature( Spot.POSITION_Z ); - final double dz = zslice - z; + final double dz = zslice; final SpotMesh mesh = spot.getMesh(); if ( mesh.boundingBox[ 2 ] > dz || mesh.boundingBox[ 5 ] < dz ) @@ -70,8 +70,8 @@ public int paint( for ( int i = 0; i < cx.size(); i++ ) { // Pixel coords. - final double xc = ( x + cx.get( i ) ) / calibration[ 0 ] + 0.5; - final double yc = ( y + cy.get( i ) ) / calibration[ 1 ] + 0.5; + final double xc = ( cx.get( i ) ) / calibration[ 0 ] + 0.5; + final double yc = ( cy.get( i ) ) / calibration[ 1 ] + 0.5; // Window coords. cx.set( i, ( xc - xcorner ) * magnification ); cy.set( i, ( yc - ycorner ) * magnification ); From 01f6eaaf2bed6d3aff7b94b2c6c611f0f611f3f7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 18 Apr 2023 22:59:32 +0200 Subject: [PATCH 062/263] Moller-Trumbore algorithm for the intersection of a ray with a triangle. --- .../plugin/trackmate/mesh/MollerTrumbore.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/MollerTrumbore.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MollerTrumbore.java b/src/test/java/fiji/plugin/trackmate/mesh/MollerTrumbore.java new file mode 100644 index 000000000..362688e5e --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/MollerTrumbore.java @@ -0,0 +1,112 @@ +package fiji.plugin.trackmate.mesh; + +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Triangles; +import net.imagej.mesh.Vertices; + +/** + * Adapted from Wikipedia. + * + * @author Jean-Yves Tinevez + * + */ +public class MollerTrumbore +{ + + private static final double EPSILON = 0.0000001; + + private final Vertices vertices; + + private final Triangles triangles; + + private final double[] tmp; + + public MollerTrumbore( final Mesh mesh ) + { + this.vertices = mesh.vertices(); + this.triangles = mesh.triangles(); + this.tmp = new double[ 3 ]; + } + + public boolean rayIntersectsTriangle( + final long id, + final double ox, + final double oy, + final double oz, + final double rx, + final double ry, + final double rz, + final double[] intersection ) + { + final long vertex0 = triangles.vertex0( id ); + final long vertex1 = triangles.vertex1( id ); + final long vertex2 = triangles.vertex2( id ); + + // Coords. + final double x0 = vertices.x( vertex0 ); + final double y0 = vertices.y( vertex0 ); + final double z0 = vertices.z( vertex0 ); + final double x1 = vertices.x( vertex1 ); + final double y1 = vertices.y( vertex1 ); + final double z1 = vertices.z( vertex1 ); + final double x2 = vertices.x( vertex2 ); + final double y2 = vertices.y( vertex2 ); + final double z2 = vertices.z( vertex2 ); + + // Edge 1 + final double e1x = x1 - x0; + final double e1y = y1 - y0; + final double e1z = z1 - z0; + // Edge 2 + final double e2x = x2 - x0; + final double e2y = y2 - y0; + final double e2z = z2 - z0; + + cross( rx, ry, rz, e2x, e2y, e2z, tmp ); + final double hx = tmp[ 0 ]; + final double hy = tmp[ 1 ]; + final double hz = tmp[ 2 ]; + final double a = dot( e1x, e1y, e1z, hx, hy, hz ); + if ( a > -EPSILON && a < EPSILON ) + return false; // This ray is parallel to this triangle. + + final double sx = ox - x0; + final double sy = oy - y0; + final double sz = oz - z0; + final double f = 1. / a; + final double u = f * dot( sx, sy, sz, hx, hy, hz ); + + if ( u < 0. || u > 1. ) + return false; + + cross( sx, sy, sz, e1x, e1y, e1z, tmp ); + final double qx = tmp[ 0 ]; + final double qy = tmp[ 1 ]; + final double qz = tmp[ 2 ]; + + final double v = f * dot( rx, ry, rz, qx, qy, qz ); + + if ( v < 0. || u + v > 1. ) + return false; + + // We have an infinite line intersection. + final double t = f * dot( e2x, e2y, e2z, qx, qy, qz ); + intersection[ 0 ] = ox + t * rx; + intersection[ 1 ] = oy + t * ry; + intersection[ 2 ] = oy + t * rz; + + return true; + } + + private double dot( final double x1, final double y1, final double z1, final double x2, final double y2, final double z2 ) + { + return x1 * x2 + y1 * y2 + z1 * z2; + } + + private void cross( final double x1, final double y1, final double z1, final double x2, final double y2, final double z2, final double[] out ) + { + out[ 0 ] = y1 * z2 - z1 * y2; + out[ 1 ] = -x1 * z2 + z1 * x2; + out[ 2 ] = x1 * y2 - y1 * x2; + } +} From 9007614ad33150dd3371c6de368be3b92b76de2d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 18 Apr 2023 23:00:16 +0200 Subject: [PATCH 063/263] Non working version of a pixel iterator. It is working for simple meshes but the ones we have have too many border cases and make it fail. --- .../trackmate/mesh/DemoPixelIteration.java | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java new file mode 100644 index 000000000..f8d52bfb2 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -0,0 +1,241 @@ +package fiji.plugin.trackmate.mesh; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.detection.MaskDetectorFactory; +import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import gnu.trove.list.array.TDoubleArrayList; +import ij.IJ; +import ij.ImageJ; +import ij.ImagePlus; +import ij.gui.NewImage; +import net.imagej.mesh.Mesh; +import net.imglib2.RandomAccess; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.iterator.LocalizingIntervalIterator; +import net.imglib2.type.numeric.RealType; + +public class DemoPixelIteration +{ + + public static class MeshIterator + { + + private final Mesh mesh; + + private final float[] bb; + + private final MollerTrumbore mollerTrumbore; + + private final double[] cal; + + private final long maxX; + + private final long minX; + + public MeshIterator( final Spot spot, final double[] cal ) + { + this.cal = cal; + final SpotMesh sm = spot.getMesh(); + this.mesh = sm.mesh; + this.bb = sm.boundingBox; + this.minX = Math.round( bb[ 0 ] / cal[ 0 ] ); + this.maxX = Math.round( bb[ 3 ] / cal[ 0 ] ); + this.mollerTrumbore = new MollerTrumbore( mesh ); + } + + public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) + { + + final long[] min = new long[ 3 ]; + final long[] max = new long[ 3 ]; + // Iterate only though Y and Z. + min[ 0 ] = minX; + max[ 0 ] = minX; + for ( int d = 1; d < 3; d++ ) + { + min[ d ] = Math.round( bb[ d ] / cal[ d ] ); + max[ d ] = Math.round( bb[ d + 3 ] / cal[ d ] ); + } + final LocalizingIntervalIterator it = new LocalizingIntervalIterator( min, max ); + + final TDoubleArrayList xs = new TDoubleArrayList(); + final double[] coords = new double[ 3 ]; + while ( it.hasNext() ) + { + it.fwd(); + ra.setPosition( it ); + + // Get all the X position where triangles cross the line. + final double y = it.getIntPosition( 1 ) * cal[ 1 ]; + final double z = it.getIntPosition( 2 ) * cal[ 2 ]; + getXIntersectingCoords( y, z, xs, coords ); + + // No intersection? + if ( xs.isEmpty() ) + continue; + + xs.sort(); + final int xsSize = xs.size(); + + final double firstIntersection = xs.min(); + final double lastIntersection = xs.max(); + for ( long ix = minX; ix <= maxX; ix++ ) + { + final double x = ix * cal[ 0 ]; + final boolean inside; + if ( x < firstIntersection || x > lastIntersection ) + { + inside = false; + } + else + { + final int i = xs.binarySearch( x, 0, xsSize ); + if ( i < 0 ) + { + final int ip = -( i + 1 ); + + // Below the first intersection or beyond the last. + if ( ip == 0 || ip == xs.size() ) + { + inside = false; + } + else + { + // Between two intersections. + inside = ( ip % 2 ) != 0; + } + } + else + { + // On an intersection. We accept. + inside = true; + } + } + if ( inside ) + { + ra.setPosition( ix, 0 ); + ra.get().setReal( 500 ); + } + } + } + } + + private int removeDuplicate( final TDoubleArrayList ts ) + { + // Sort it. + ts.sort(); + + if ( ts.size() < 2 ) + return ts.size(); + + int j = 0; + for ( int i = 0; i < ts.size() - 1; i++ ) + { + if ( ts.get( i ) != ts.get( i + 1 ) ) + { + ts.set( j++, ts.get( i ) ); + } + } + + ts.set( j++, ts.get( ts.size() - 1 ) ); + return j; + } + + /** + * Returns the list of X coordinates where the line parallel to the X + * axis and passing through (0,y,z) crosses the triangles of the mesh. + * The list is unordered and may have duplicates. + * + * @param y + * the Y coordinate of the line origin. + * @param z + * the Z coordinate of the line origin. + * @param ts + * a holder for the resulting intersections X coordinate. + * @param intersection + * a holder for intersection coordinates, messed with + * internally. + */ + private void getXIntersectingCoords( final double y, final double z, final TDoubleArrayList ts, final double[] intersection ) + { + ts.resetQuick(); + for ( long id = 0; id < mesh.triangles().size(); id++ ) + if ( mollerTrumbore.rayIntersectsTriangle( id, 0, y, z, 1., 0, 0, intersection ) ) + ts.add( intersection[ 0 ] ); + } + + } + + @SuppressWarnings( "unchecked" ) + public static < T extends RealType< T > > void main( final String[] args ) + { + try + { + ImageJ.main( args ); + +// final Mesh mesh = Demo3DMesh.debugMesh( new long[] { 4, 4, 4 }, new long[] { 10, 10, 10 } ); +// final Spot s0 = SpotMesh.createSpot( mesh, 1. ); +// final Model model = new Model(); +// model.beginUpdate(); +// try +// { +// model.addSpotTo( s0, 0 ); +// } +// finally +// { +// model.endUpdate(); +// } +// final ImagePlus imp = NewImage.createByteImage( "cube", 16, 16, 16, NewImage.FILL_BLACK ); + + final String imPath = "samples/mesh/CElegansMask3DNoScale-mask-t1.tif"; + final ImagePlus imp = IJ.openImage( imPath ); + + final Settings settings = new Settings( imp ); + settings.detectorFactory = new MaskDetectorFactory<>(); + settings.detectorSettings = settings.detectorFactory.getDefaultSettings(); + settings.detectorSettings.put( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, + true ); + + final TrackMate trackmate = new TrackMate( settings ); + trackmate.setNumThreads( 4 ); + trackmate.execDetection(); + + final Model model = trackmate.getModel(); + final SpotCollection spots = model.getSpots(); + spots.setVisible( true ); + + final ImagePlus out = NewImage.createShortImage( "OUT", imp.getWidth(), imp.getHeight(), imp.getNSlices(), NewImage.FILL_BLACK ); + out.show(); + + final double[] cal = TMUtils.getSpatialCalibration( imp ); + for ( final Spot spot : model.getSpots().iterable( true ) ) + { + final MeshIterator it = new MeshIterator( spot, cal ); + it.iterate( ( RandomAccess< T > ) ImageJFunctions.wrap( out ).randomAccess() ); + it.iterate( ( RandomAccess< T > ) ImageJFunctions.wrap( imp ).randomAccess() ); + break; + } + + imp.show(); + final SelectionModel sm = new SelectionModel( model ); + final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); + final HyperStackDisplayer view = new HyperStackDisplayer( model, sm, imp, ds ); + view.render(); + + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } +} From 94d9f4a4ae22e63253afed4ead8ea65211d68c99 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 18 Apr 2023 23:00:37 +0200 Subject: [PATCH 064/263] Utility to export meshes to STL for debugging. --- .../trackmate/mesh/ExportMeshForDemo.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java new file mode 100644 index 000000000..bee4247c0 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java @@ -0,0 +1,62 @@ +package fiji.plugin.trackmate.mesh; + +import java.io.File; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.detection.MaskDetectorFactory; +import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; +import ij.IJ; +import ij.ImagePlus; +import net.imagej.mesh.io.stl.STLMeshIO; + +public class ExportMeshForDemo +{ + + public static void main( final String[] args ) + { + try + { + final String filePath = "samples/mesh/CElegansMask3D.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + + final Settings settings = new Settings( imp ); + settings.detectorFactory = new MaskDetectorFactory<>(); + settings.detectorSettings = settings.detectorFactory.getDefaultSettings(); + settings.detectorSettings.put( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, false ); + + final TrackMate trackmate = new TrackMate( settings ); + trackmate.setNumThreads( 4 ); + trackmate.execDetection(); + + final Model model = trackmate.getModel(); + final SpotCollection spots = model.getSpots(); + spots.setVisible( true ); + + final String meshDir = "samples/mesh/io"; + for ( final File file : new File( meshDir ).listFiles() ) + if ( !file.isDirectory() ) + file.delete(); + + final STLMeshIO io = new STLMeshIO(); + for ( final Spot spot : spots.iterable( true ) ) + { + final int t = spot.getFeature( Spot.FRAME ).intValue(); + final int id = spot.ID(); + final String savePath = String.format( "%s/mesh_t%2d_id_%04d.stl", meshDir, t, id ); + final SpotMesh mesh = spot.getMesh(); + if ( mesh != null ) + io.save( mesh.mesh, savePath ); + } + System.out.println( "Export done." ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } +} From c7a420d9256fd8bcfd71c01e87a453f382c1ec61 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 18 Apr 2023 23:02:15 +0200 Subject: [PATCH 065/263] Make some debugging utilities package visible. --- src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 26ae1704a..fcb977622 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -113,7 +113,7 @@ public static void main( final String[] args ) } @SuppressWarnings( "unused" ) - private static Mesh debugMesh( final long[] min, final long[] max ) + static Mesh debugMesh( final long[] min, final long[] max ) { final NaiveDoubleMesh mesh = new NaiveDoubleMesh(); final net.imagej.mesh.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices(); @@ -234,8 +234,7 @@ private static void scale( final Vertices vertices, final double[] scale, final } } - @SuppressWarnings( "unused" ) - private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask() + static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask() { final String filePath = "samples/mesh/CElegansMask3D.tif"; final ImagePlus imp = IJ.openImage( filePath ); @@ -252,8 +251,7 @@ private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > return new ImgPlus< BitType >( ImgView.wrap( mask ), t1 ); } - @SuppressWarnings( "unused" ) - private static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask2() + static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTestMask2() { final String filePath = "samples/mesh/Cube.tif"; final ImagePlus imp = IJ.openImage( filePath ); From 68590ff1a6d3491dc281c287be21b7e91baf8b1e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 19 Apr 2023 22:57:25 +0200 Subject: [PATCH 066/263] Utility to sort Trove arrays. --- .../plugin/trackmate/mesh/SortArrays.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/SortArrays.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/SortArrays.java b/src/test/java/fiji/plugin/trackmate/mesh/SortArrays.java new file mode 100644 index 000000000..13594bcf4 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/SortArrays.java @@ -0,0 +1,143 @@ +package fiji.plugin.trackmate.mesh; + +import java.util.BitSet; +import java.util.Random; + +import gnu.trove.list.array.TDoubleArrayList; + +/** + * Utilities to sort an array and return the sorting index. + */ +public class SortArrays +{ + + public static void reorder( final TDoubleArrayList data, final int[] ind ) + { + final BitSet done = new BitSet( data.size() ); + for ( int i = 0; i < data.size() && done.cardinality() < data.size(); i++ ) + { + int ia = i; + int ib = ind[ ia ]; + if ( done.get( ia ) ) + { // index is already done + continue; + } + if ( ia == ib ) + { // element is at the right place + done.set( ia ); + continue; + } + final int x = ia; // start a loop at x = ia + // some next index will be x again eventually + final double a = data.getQuick( ia ); + // keep element a as the last value after the loop + while ( ib != x && !done.get( ia ) ) + { + final double b = data.getQuick( ib ); + // element from index b must go to index a + data.setQuick( ia, b ); + done.set( ia ); + ia = ib; + ib = ind[ ia ]; // get next index + } + data.setQuick( ia, a ); // set value a to last index + done.set( ia ); + } + } + + public static int[] quicksort( final TDoubleArrayList main ) + { + final int[] index = new int[ main.size() ]; + for ( int i = 0; i < index.length; i++ ) + index[ i ] = i; + quicksort( main, index ); + return index; + } + + public static void quicksort( final TDoubleArrayList main, final int[] index ) + { + quicksort( main, index, 0, index.length - 1 ); + } + + // quicksort a[left] to a[right] + public static void quicksort( final TDoubleArrayList a, final int[] index, final int left, final int right ) + { + if ( right <= left ) + return; + final int i = partition( a, index, left, right ); + quicksort( a, index, left, i - 1 ); + quicksort( a, index, i + 1, right ); + } + + // partition a[left] to a[right], assumes left < right + private static int partition( final TDoubleArrayList a, final int[] index, + final int left, final int right ) + { + int i = left - 1; + int j = right; + while ( true ) + { + while ( less( a.getQuick( ++i ), a.getQuick( right ) ) ) + ; + while ( less( a.getQuick( right ), a.getQuick( --j ) ) ) + if ( j == left ) + break; // don't go out-of-bounds + if ( i >= j ) + break; // check if pointers cross + exch( a, index, i, j ); // swap two elements into place + } + exch( a, index, i, right ); // swap with partition element + return i; + } + + // is x < y ? + private static boolean less( final double x, final double y ) + { + return ( x < y ); + } + + // exchange a[i] and a[j] + private static void exch( final TDoubleArrayList a, final int[] index, final int i, final int j ) + { + final double swap = a.getQuick( i ); + a.setQuick( i, a.getQuick( j ) ); + a.setQuick( j, swap ); + final int b = index[ i ]; + index[ i ] = index[ j ]; + index[ j ] = b; + } + + public static void main( final String[] args ) + { + final Random ran = new Random( 1l ); + final int n = 10; + final TDoubleArrayList arr = new TDoubleArrayList(); + for ( int i = 0; i < n; i++ ) + arr.add( ran.nextDouble() ); + + final TDoubleArrayList copy = new TDoubleArrayList( arr ); + + System.out.print( String.format( "Before sorting: %4.2f", arr.get( 0 ) ) ); + for ( int i = 1; i < arr.size(); i++ ) + System.out.print( String.format( ", %4.2f", arr.get( i ) ) ); + System.out.println(); + + final int[] index = quicksort( arr ); + System.out.print( String.format( "After sorting: %4.2f", arr.get( 0 ) ) ); + for ( int i = 1; i < arr.size(); i++ ) + System.out.print( String.format( ", %4.2f", arr.get( i ) ) ); + System.out.println(); + + System.out.print( String.format( "Index: %4d", index[ 0 ] ) ); + for ( int i = 1; i < arr.size(); i++ ) + System.out.print( String.format( ", %4d", index[ i ] ) ); + System.out.println(); + + reorder( arr, index ); + System.out.print( String.format( "Reorder copy: %4.2f", copy.get( 0 ) ) ); + for ( int i = 1; i < copy.size(); i++ ) + System.out.print( String.format( ", %4.2f", copy.get( i ) ) ); + System.out.println(); + } + +} From c72231f83ef5718fa592e6d28926a8af97c52e63 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 19 Apr 2023 22:57:35 +0200 Subject: [PATCH 067/263] Demo simple mesh. --- .../plugin/trackmate/mesh/DefaultMesh.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java new file mode 100644 index 000000000..d3e4dc6ef --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java @@ -0,0 +1,86 @@ +package fiji.plugin.trackmate.mesh; + +import java.util.List; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.detection.ThresholdDetector; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import ij.ImageJ; +import ij.ImagePlus; +import ij.gui.NewImage; +import ij.measure.Calibration; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.mesh.Mesh; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.logic.BitType; + +public class DefaultMesh +{ + + public static void main( final String[] args ) + { + ImageJ.main( args ); + final ImgPlus< BitType > img = Demo3DMesh.loadTestMask2(); + final ImagePlus imp = ImageJFunctions.show( img, "box" ); + imp.setDimensions( + img.dimensionIndex( Axes.CHANNEL ), + img.dimensionIndex( Axes.Z ), + img.dimensionIndex( Axes.TIME ) ); + final double[] calibration = new double[] { 1., 1., 1. }; + + final ThresholdDetector< BitType > detector = new ThresholdDetector< BitType >( img, img, calibration, 0, false ); + detector.process(); + final List< Spot > spots = detector.getResult(); + + final Model model = new Model(); + for ( final Spot spot : spots ) + { + model.getSpots().add( spot, 0 ); + System.out.println( spot.getMesh() ); + } + + final SelectionModel selectionModel = new SelectionModel( model ); + final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); + final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, imp, ds ); + view.render(); + } + + public static void main2( final String[] args ) + { + ImageJ.main( args ); + final ImagePlus imp = NewImage.createByteImage( "dummy", + 64, 64, 64, NewImage.FILL_RAMP ); + final Calibration cal = imp.getCalibration(); + cal.pixelWidth = 0.5; + cal.pixelHeight = 0.5; + cal.pixelDepth = 0.5; + imp.show(); + + final long[] min = new long[] { 2, 2, 2 }; + final long[] max = new long[] { 20, 20, 20 }; + final Mesh mesh = Demo3DMesh.debugMesh( min, max ); + final Spot spot = SpotMesh.createSpot( mesh, 1. ); + + final Model model = new Model(); + model.beginUpdate(); + try + { + model.addSpotTo( spot, 0 ); + } + finally + { + model.endUpdate(); + } + + final SelectionModel selectionModel = new SelectionModel( model ); + final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); + final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, imp, ds ); + view.render(); + } +} From e8486986887263d9afe62aaced26d79ac5681fdb Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 19 Apr 2023 22:58:03 +0200 Subject: [PATCH 068/263] Fix back to master branch in the imagej-mesh. --- .../plugin/trackmate/mesh/Demo3DMesh.java | 10 +- .../trackmate/mesh/MeshPlaneIntersection.java | 284 ------------------ 2 files changed, 4 insertions(+), 290 deletions(-) delete mode 100644 src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index fcb977622..6b964e9c8 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -22,7 +22,6 @@ import net.imagej.mesh.io.stl.STLMeshIO; import net.imagej.mesh.naive.NaiveDoubleMesh; import net.imagej.mesh.naive.NaiveDoubleMesh.Triangles; -import net.imagej.mesh.nio.BufferMeshEdges; import net.imglib2.RandomAccessibleInterval; import net.imglib2.converter.RealTypeConverters; import net.imglib2.img.ImgView; @@ -80,18 +79,17 @@ public static void main( final String[] args ) // final Mesh simplified = debugMesh( new long[] { 0, 0, 0 }, region.dimensionsAsLongArray() ); // Wrap as mesh with edges. - final BufferMeshEdges emesh = BufferMeshEdges.wrap( simplified, true ); - System.out.println( "After simplification: " + emesh.vertices().size() + " vertices and " + simplified.triangles().size() + " faces." ); + System.out.println( "After simplification: " + mesh.vertices().size() + " vertices and " + simplified.triangles().size() + " faces." ); System.out.println(); // Scale and offset with physical coordinates. final double[] origin = region.minAsDoubleArray(); - scale( emesh.vertices(), cal, origin ); + scale( mesh.vertices(), cal, origin ); /* * IO. */ - testIO( emesh, ++j ); + testIO( mesh, ++j ); /* * Display. @@ -101,7 +99,7 @@ public static void main( final String[] args ) final int zslice = 20; // plan final double z = ( zslice ) * cal[ 2 ]; // um - MeshPlaneIntersection.intersect2( emesh, z, cx, cy ); +// MeshPlaneIntersection.intersect2( mesh, z, cx, cy ); toOverlay( cx, cy, out, cal ); } System.out.println( "Done." ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java deleted file mode 100644 index 042492e86..000000000 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlaneIntersection.java +++ /dev/null @@ -1,284 +0,0 @@ -package fiji.plugin.trackmate.mesh; - -import java.util.Arrays; - -import gnu.trove.list.array.TDoubleArrayList; -import gnu.trove.list.array.TLongArrayList; -import net.imagej.mesh.Edges; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.Triangles; -import net.imagej.mesh.Vertices; - -public class MeshPlaneIntersection -{ - - /** - * Only works if the {@link Mesh} supports {@link Mesh#edges()}. - * - * @param mesh - * @param z - * @return - */ - public static void intersect( - final Mesh mesh, - final double z, - final TDoubleArrayList cx, - final TDoubleArrayList cy ) - { - /* - * Clear contour holders. - */ - cx.resetQuick(); - cy.resetQuick(); - - /* - * Check if bounding-box intersect. TODO: use a data structure where the - * bounding-box is a field, calculated once. - */ - final float[] bb = Meshes.boundingBox( mesh ); - if ( bb[ 2 ] > z ) - return; - if ( bb[ 5 ] < z ) - return; - - /* - * Find one edge that crosses the Z plane. - */ - - final Edges edges = mesh.edges(); - final Vertices vertices = mesh.vertices(); - final long nEdges = edges.size(); - - long start = -1; - for ( long e = 0; e < nEdges; e++ ) - { - if ( edgeCrossPlane( vertices, edges, e, z ) ) - { - // Edge is part of a face? - final long f0 = edges.f0( e ); - if ( f0 >= 0 ) - { - start = e; - break; - } - // This edge has no face, we need another one. - } - } - // Cannot build contour based on edge with no faces. - if ( start < 0 ) - return; - - // Holder for the vertices of a triangle. - final long[] vs = new long[ 3 ]; - // Holder for the 3 edges of a triangle. - final long[] es = new long[ 3 ]; - - long current = start; - final long startTriangle = edges.f0( start ); - long previousTriangle = startTriangle; - final TLongArrayList visited = new TLongArrayList(); -// final TLongHashSet visited = new TLongHashSet(); - visited.add( startTriangle ); - while ( true ) - { - addEdgeToContour( vertices, edges, current, z, cx, cy ); - - final long triangle = getNextTriangle( edges, current, previousTriangle ); - System.out.println( "At triangle: " + toString( mesh, triangle ) ); - - if ( triangle < 0 || visited.contains( triangle ) ) - return; - - visited.add( triangle ); - final long next = getNextEdge( mesh, triangle, current, z, vs, es ); - - if ( next < 0 || next == start ) - return; - - previousTriangle = triangle; - current = next; - } - } - - private static String toString( final Mesh mesh, final long triangle ) - { - // TODO Auto-generated method stub - return null; - } - - private static boolean edgeCrossPlane( final Vertices vertices, final Edges edges, final long e, final double z ) - { - final double z0 = vertices.z( edges.v0( e ) ); - final double z1 = vertices.z( edges.v1( e ) ); - if ( z0 > z && z1 > z ) - return false; - if ( z0 < z && z1 < z ) - return false; - return true; - } - - private static long getNextTriangle( final Edges edges, final long e, final long previousFace ) - { - final long f0 = edges.f0( e ); - if ( f0 == previousFace ) - return edges.f1( e ); - return f0; - } - - /** - * Returns the index of the edge in the specified triangle that crosses the - * plane with the specified z, and that is different from the specified - * current edge. Returns -1 is such an edge cannot be found for the - * specified triangle. - * - * @param mesh - * the mesh structure. - * @param face - * the triangle to inspect. - * @param current - * the current edge, that should not be returned. - * @param z - * the value of the Z plane. - * @param vs - * holder for the vertices of the triangle (size at least 3). - * @param es - * holder for the edges of the triangle (size at least 3). - * @return the index of the next edge. - */ - private static long getNextEdge( - final Mesh mesh, - final long face, - final long current, - final double z, - final long[] vs, - final long[] es ) - { - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - final Edges edges = mesh.edges(); - - // Get the edges of this face. - vs[ 0 ] = triangles.vertex0( face ); - vs[ 1 ] = triangles.vertex1( face ); - vs[ 2 ] = triangles.vertex2( face ); - Arrays.sort( vs ); - es[ 0 ] = edges.indexOf( vs[ 0 ], vs[ 1 ] ); - es[ 1 ] = edges.indexOf( vs[ 0 ], vs[ 2 ] ); - es[ 2 ] = edges.indexOf( vs[ 1 ], vs[ 2 ] ); - for ( final long e : es ) - { - if ( e == current ) - continue; - - if ( edgeCrossPlane( vertices, edges, e, z ) ) - return e; - } - return -1; - - } - - - private static void addEdgeToContour( - final Vertices vertices, - final Edges edges, - final long e, - final double z, - final TDoubleArrayList cx, - final TDoubleArrayList cy ) - { - final long sv = edges.v0( e ); - final long tv = edges.v1( e ); - final double xs = vertices.x( sv ); - final double ys = vertices.y( sv ); - final double zs = vertices.z( sv ); - final double xt = vertices.x( tv ); - final double yt = vertices.y( tv ); - final double zt = vertices.z( tv ); - final double t = ( zs == zt ) - ? 0.5 : ( z - zs ) / ( zt - zs ); - final double x = xs + t * ( xt - xs ); - final double y = ys + t * ( yt - ys ); - final int np = cx.size(); - if ( np > 1 && cx.getQuick( np - 1 ) == x && cy.getQuick( np - 1 ) == y ) - return; // Don't add duplicate. - - cx.add( x ); - cy.add( y ); - } - - public static void intersect2( final Mesh mesh, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) - { - // Clear contour holders. - cx.resetQuick(); - cy.resetQuick(); - - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - for ( long f = 0; f < triangles.size(); f++ ) - { - final long v0 = triangles.vertex0( f ); - final long v1 = triangles.vertex1( f ); - final long v2 = triangles.vertex2( f ); - - final double minZ = minZ( vertices, v0, v1, v2 ); - if ( minZ > z ) - continue; - final double maxZ = maxZ( vertices, v0, v1, v2 ); - if ( maxZ < z ) - continue; - - segmentIntersecting( vertices, v0, v1, v2, z, cx, cy ); - } - } - - /** - * Intersection of a triangle with a Z plane. - */ - private static void segmentIntersecting( final Vertices vertices, final long v0, final long v1, final long v2, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) - { - addEdgeToContour( vertices, v0, v1, z, cx, cy ); - addEdgeToContour( vertices, v0, v2, z, cx, cy ); - addEdgeToContour( vertices, v1, v2, z, cx, cy ); - } - - private static void addEdgeToContour( - final Vertices vertices, - final long sv, - final long tv, - final double z, - final TDoubleArrayList cx, - final TDoubleArrayList cy ) - { - final double zs = vertices.z( sv ); - final double zt = vertices.z( tv ); - if ( ( zs > z && zt > z ) || ( zs < z && zt < z ) ) - return; - - final double xs = vertices.x( sv ); - final double ys = vertices.y( sv ); - final double xt = vertices.x( tv ); - final double yt = vertices.y( tv ); - final double t = ( zs == zt ) - ? 0.5 : ( z - zs ) / ( zt - zs ); - final double x = xs + t * ( xt - xs ); - final double y = ys + t * ( yt - ys ); - final int np = cx.size(); - if ( np > 1 && cx.getQuick( np - 1 ) == x && cy.getQuick( np - 1 ) == y ) - return; // Don't add duplicate. - - cx.add( x ); - cy.add( y ); - } - - private static final double minZ( final Vertices vertices, final long v0, final long v1, final long v2 ) - { - return Math.min( vertices.z( v0 ), Math.min( vertices.z( v1 ), vertices.z( v2 ) ) ); - } - - private static final double maxZ( final Vertices vertices, final long v0, final long v1, final long v2 ) - { - return Math.max( vertices.z( v0 ), Math.max( vertices.z( v1 ), vertices.z( v2 ) ) ); - } - -} From e44c9fa47b489a8c3e62dc70ef30f179907cdb66 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 19 Apr 2023 22:58:17 +0200 Subject: [PATCH 069/263] Update the demo with Toby + JY ideas. Not good enough yet. --- .../trackmate/mesh/DemoPixelIteration.java | 209 ++++++++++++++---- 1 file changed, 168 insertions(+), 41 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index f8d52bfb2..d19880897 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -14,11 +14,14 @@ import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import gnu.trove.list.array.TDoubleArrayList; +import gnu.trove.list.array.TIntArrayList; +import gnu.trove.list.array.TLongArrayList; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; import ij.gui.NewImage; import net.imagej.mesh.Mesh; +import net.imagej.mesh.Triangles; import net.imglib2.RandomAccess; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.iterator.LocalizingIntervalIterator; @@ -27,6 +30,12 @@ public class DemoPixelIteration { + private static final boolean DEBUG = true; + + private static final int DEBUG_Z = 16; + + private static final int DEBUG_Y = 143; + public static class MeshIterator { @@ -68,58 +77,112 @@ public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) } final LocalizingIntervalIterator it = new LocalizingIntervalIterator( min, max ); + // Array of x position of intersections. final TDoubleArrayList xs = new TDoubleArrayList(); - final double[] coords = new double[ 3 ]; + // Array of triangle indices intersecting. + final TLongArrayList tl = new TLongArrayList(); + // Array of triangle normals projected onto the X line. + final TDoubleArrayList nxs = new TDoubleArrayList(); + // Array to store the 'inside' score. + final TIntArrayList insideScore = new TIntArrayList(); + while ( it.hasNext() ) { it.fwd(); ra.setPosition( it ); + if ( DEBUG ) + { + if ( it.getIntPosition( 2 ) != DEBUG_Z ) + continue; + if ( it.getIntPosition( 1 ) != DEBUG_Y ) + continue; + } + // Get all the X position where triangles cross the line. final double y = it.getIntPosition( 1 ) * cal[ 1 ]; final double z = it.getIntPosition( 2 ) * cal[ 2 ]; - getXIntersectingCoords( y, z, xs, coords ); + getXIntersectingCoords( y, z, tl, xs ); // No intersection? if ( xs.isEmpty() ) continue; - xs.sort(); - final int xsSize = xs.size(); + // Collect normals projection on the X line. + getNormalXProjection( tl, nxs ); + + // Sort by by X coordinate of intersections. + final int[] index = SortArrays.quicksort( xs ); + + // Sort normal array with the same order. + SortArrays.reorder( nxs, index ); + + if ( DEBUG ) + { + System.out.println(); + System.out.println( "Before removing duplicates:" ); + System.out.println( "XS: " + xs ); + System.out.println( "NS: " + nxs ); + System.out.println( "Normals running sum: " ); + for ( int i = 0; i < nxs.size(); i++ ) + System.out.print( "( " + i + " -> " + nxs.subList( 0, i + 1 ).sum() + "), " ); + System.out.println(); + } + + // Merge duplicates. + final int maxIndex = removeDuplicate( xs, nxs ); + + if ( DEBUG ) + { + System.out.println( "After removing duplicates:" ); + System.out.println( "XS: " + xs.subList( 0, maxIndex ) ); + System.out.println( "NS: " + nxs.subList( 0, maxIndex ) ); + } + + // DEBUG +// if ( maxIndex % 2 != 0 ) +// { +// System.out.println( "XS: " + xs.subList( 0, maxIndex ) ); +// System.out.println( "NS: " + nxs.subList( 0, maxIndex ) ); +// } + + // Iterate to build the inside score between each intersection. + insideScore.resetQuick(); + insideScore.add( 0 ); + for ( int i = 0; i < maxIndex; i++ ) + { + final double n = nxs.getQuick( i ); + final int prevScore = insideScore.getQuick( i ); + + // Weird case: the normal is orthogonal to X. Should not + // happen because we filtered out triangles parallel to the + // X axis. + if ( n == 0. ) + { + insideScore.add( prevScore ); + continue; + } + else + { + final int score = prevScore + ( ( n > 0 ) ? -1 : 1 ); + insideScore.add( score ); + } + } - final double firstIntersection = xs.min(); - final double lastIntersection = xs.max(); for ( long ix = minX; ix <= maxX; ix++ ) { final double x = ix * cal[ 0 ]; + final int i = xs.binarySearch( x, 0, maxIndex ); final boolean inside; - if ( x < firstIntersection || x > lastIntersection ) + if ( i < 0 ) { - inside = false; + final int ip = -( i + 1 ); + inside = insideScore.getQuick( ip ) > 0; } else { - final int i = xs.binarySearch( x, 0, xsSize ); - if ( i < 0 ) - { - final int ip = -( i + 1 ); - - // Below the first intersection or beyond the last. - if ( ip == 0 || ip == xs.size() ) - { - inside = false; - } - else - { - // Between two intersections. - inside = ( ip % 2 ) != 0; - } - } - else - { - // On an intersection. We accept. - inside = true; - } + // On an intersection. We accept. + inside = true; } if ( inside ) { @@ -130,24 +193,71 @@ public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) } } - private int removeDuplicate( final TDoubleArrayList ts ) + private int removeDuplicate( final TDoubleArrayList ts, final TDoubleArrayList nxs ) { - // Sort it. - ts.sort(); - if ( ts.size() < 2 ) return ts.size(); int j = 0; + double accum = 0.; + int nAccum = 0; + int nPos = 0; + int nNeg = 0; + final double maxN; for ( int i = 0; i < ts.size() - 1; i++ ) { - if ( ts.get( i ) != ts.get( i + 1 ) ) +// System.out.print( j + " -> " + i ); + if ( ts.getQuick( i ) != ts.getQuick( i + 1 ) ) + { + ts.setQuick( j, ts.getQuick( i ) ); + if ( nAccum == 0 ) + { + nxs.setQuick( j, nxs.getQuick( i ) ); + } + else + { + // Average. + nxs.setQuick( j, accum / nAccum ); + // Majority. +// final double vmaj; +// if ( nPos == nNeg ) +// vmaj = 0.; +// else if ( nPos > nNeg ) +// vmaj = 1.; +// else +// vmaj = -1.; +// nxs.setQuick( j, vmaj ); + } + accum = 0.; + nAccum = 0; + nPos = 0; + nNeg = 0; + j++; + } + else { - ts.set( j++, ts.get( i ) ); + final double v = nxs.getQuick( i ); + accum += v; + if ( v > 0 ) + nPos++; + if ( v < 0 ) + nNeg++; + nAccum++; +// System.out.print( ", " + accum ); } +// System.out.println(); } - ts.set( j++, ts.get( ts.size() - 1 ) ); + ts.setQuick( j, ts.getQuick( ts.size() - 1 ) ); + if ( nAccum == 0 ) + { + nxs.setQuick( j, nxs.getQuick( ts.size() - 1 ) ); + } + else + { + nxs.setQuick( j, accum / nAccum ); + } + j++; return j; } @@ -160,20 +270,33 @@ private int removeDuplicate( final TDoubleArrayList ts ) * the Y coordinate of the line origin. * @param z * the Z coordinate of the line origin. + * @param tl + * a holder for the triangle indices intersecting. * @param ts * a holder for the resulting intersections X coordinate. - * @param intersection - * a holder for intersection coordinates, messed with - * internally. */ - private void getXIntersectingCoords( final double y, final double z, final TDoubleArrayList ts, final double[] intersection ) + private void getXIntersectingCoords( final double y, final double z, + final TLongArrayList tl, final TDoubleArrayList ts ) { + final double[] intersection = new double[ 3 ]; + tl.resetQuick(); ts.resetQuick(); + // TODO optimize search of triangles with a data structure. for ( long id = 0; id < mesh.triangles().size(); id++ ) if ( mollerTrumbore.rayIntersectsTriangle( id, 0, y, z, 1., 0, 0, intersection ) ) + { + tl.add( id ); ts.add( intersection[ 0 ] ); + } } + private void getNormalXProjection( final TLongArrayList tl, final TDoubleArrayList nxs ) + { + nxs.resetQuick(); + final Triangles triangles = mesh.triangles(); + for ( int id = 0; id < tl.size(); id++ ) + nxs.add( triangles.nx( tl.getQuick( id ) ) ); + } } @SuppressWarnings( "unchecked" ) @@ -204,7 +327,7 @@ public static < T extends RealType< T > > void main( final String[] args ) settings.detectorFactory = new MaskDetectorFactory<>(); settings.detectorSettings = settings.detectorFactory.getDefaultSettings(); settings.detectorSettings.put( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, - true ); + false ); final TrackMate trackmate = new TrackMate( settings ); trackmate.setNumThreads( 4 ); @@ -216,6 +339,8 @@ public static < T extends RealType< T > > void main( final String[] args ) final ImagePlus out = NewImage.createShortImage( "OUT", imp.getWidth(), imp.getHeight(), imp.getNSlices(), NewImage.FILL_BLACK ); out.show(); + out.setSlice( DEBUG_Z + 1 ); + out.resetDisplayRange(); final double[] cal = TMUtils.getSpatialCalibration( imp ); for ( final Spot spot : model.getSpots().iterable( true ) ) @@ -227,6 +352,8 @@ public static < T extends RealType< T > > void main( final String[] args ) } imp.show(); + imp.setSlice( DEBUG_Z + 1 ); + imp.resetDisplayRange(); final SelectionModel sm = new SelectionModel( model ); final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); final HyperStackDisplayer view = new HyperStackDisplayer( model, sm, imp, ds ); From da9e34682107c3b84353c98b18d5562859e62c93 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 01:06:31 +0200 Subject: [PATCH 070/263] Yeaaahh! Working version of a pixel iterator. At least on star-convex objects, but does not assume they are. Still very tiny final border cases, where the iteration stops early at the poles of an object, but it's nothing unsurmontable. Tomorrow. --- .../trackmate/mesh/DemoPixelIteration.java | 239 +++++++++++++++--- 1 file changed, 203 insertions(+), 36 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index d19880897..3b7325f69 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -1,5 +1,7 @@ package fiji.plugin.trackmate.mesh; +import java.io.IOException; + import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; @@ -21,7 +23,11 @@ import ij.ImagePlus; import ij.gui.NewImage; import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; import net.imagej.mesh.Triangles; +import net.imagej.mesh.Vertices; +import net.imagej.mesh.io.stl.STLMeshIO; +import net.imagej.mesh.nio.BufferMesh; import net.imglib2.RandomAccess; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.iterator.LocalizingIntervalIterator; @@ -30,7 +36,7 @@ public class DemoPixelIteration { - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final int DEBUG_Z = 16; @@ -108,6 +114,11 @@ public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) if ( xs.isEmpty() ) continue; + if ( DEBUG ) + { + exportMeshSubset( tl ); + } + // Collect normals projection on the X line. getNormalXProjection( tl, nxs ); @@ -123,10 +134,6 @@ public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) System.out.println( "Before removing duplicates:" ); System.out.println( "XS: " + xs ); System.out.println( "NS: " + nxs ); - System.out.println( "Normals running sum: " ); - for ( int i = 0; i < nxs.size(); i++ ) - System.out.print( "( " + i + " -> " + nxs.subList( 0, i + 1 ).sum() + "), " ); - System.out.println(); } // Merge duplicates. @@ -139,19 +146,24 @@ public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) System.out.println( "NS: " + nxs.subList( 0, maxIndex ) ); } - // DEBUG -// if ( maxIndex % 2 != 0 ) -// { -// System.out.println( "XS: " + xs.subList( 0, maxIndex ) ); -// System.out.println( "NS: " + nxs.subList( 0, maxIndex ) ); -// } + final TDoubleArrayList outXs = new TDoubleArrayList(); + final TDoubleArrayList outNxs = new TDoubleArrayList(); + // Check we are alternating entering / leaving. + checkAlternating( xs, nxs, maxIndex, outXs, outNxs ); + + if ( DEBUG ) + { + System.out.println( "After checking alternating:" ); + System.out.println( "XS: " + outXs ); + System.out.println( "NS: " + outNxs ); + } // Iterate to build the inside score between each intersection. insideScore.resetQuick(); insideScore.add( 0 ); - for ( int i = 0; i < maxIndex; i++ ) + for ( int i = 0; i < outNxs.size(); i++ ) { - final double n = nxs.getQuick( i ); + final double n = outNxs.getQuick( i ); final int prevScore = insideScore.getQuick( i ); // Weird case: the normal is orthogonal to X. Should not @@ -172,7 +184,7 @@ public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) for ( long ix = minX; ix <= maxX; ix++ ) { final double x = ix * cal[ 0 ]; - final int i = xs.binarySearch( x, 0, maxIndex ); + final int i = outXs.binarySearch( x ); final boolean inside; if ( i < 0 ) { @@ -193,6 +205,122 @@ public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) } } + private void checkAlternating( + final TDoubleArrayList xs, final TDoubleArrayList nxs, final int maxIndex, + final TDoubleArrayList outXs, final TDoubleArrayList outNxs ) + { + outXs.resetQuick(); + outNxs.resetQuick(); + + double prevN = nxs.getQuick( 0 ); + final double prevX = xs.getQuick( 0 ); + + outXs.add( prevX ); + outNxs.add( prevN ); + + // The first one should be an entry (normal neg). + assert prevN < 0; + // The last one should be an exit (normal pos). + assert nxs.getQuick( maxIndex ) > 0; + + for ( int i = 1; i < maxIndex; i++ ) + { + final double n = nxs.getQuick( i ); + if ( n * prevN < 0. ) + { + // Sign did change. All good. + outXs.add( xs.getQuick( i ) ); + outNxs.add( n ); + } + else + { + // Sign did not change! Merge. + if ( n < 0. ) + { + // Two consecutive entries. + // Remove this one, so that the first valid entry stays. + } + else + { + // Two consecutive exits. + // Remove the previous one, so that the last exit is + // this one. + outXs.removeAt( outXs.size() - 1 ); + outNxs.removeAt( outNxs.size() - 1 ); + // And add this one. + outXs.add( xs.getQuick( i ) ); + outNxs.add( n ); + } + } + prevN = n; + } + } + + private void exportMeshSubset( final TLongArrayList tl ) + { + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + final BufferMesh out = new BufferMesh( tl.size() * 3, tl.size() ); + for ( int i = 0; i < tl.size(); i++ ) + { + final long id = tl.getQuick( i ); + + final long v0 = triangles.vertex0( id ); + final double x0 = vertices.x( v0 ); + final double y0 = vertices.y( v0 ); + final double z0 = vertices.z( v0 ); + final double v0nx = vertices.nx( v0 ); + final double v0ny = vertices.ny( v0 ); + final double v0nz = vertices.nz( v0 ); + final long nv0 = out.vertices().add( x0, y0, z0, v0nx, v0ny, v0nz, 0., 0. ); + + final long v1 = triangles.vertex1( id ); + final double x1 = vertices.x( v1 ); + final double y1 = vertices.y( v1 ); + final double z1 = vertices.z( v1 ); + final double v1nx = vertices.nx( v1 ); + final double v1ny = vertices.ny( v1 ); + final double v1nz = vertices.nz( v1 ); + final long nv1 = out.vertices().add( x1, y1, z1, v1nx, v1ny, v1nz, 0., 0. ); + + final long v2 = triangles.vertex2( id ); + final double x2 = vertices.x( v2 ); + final double y2 = vertices.y( v2 ); + final double z2 = vertices.z( v2 ); + final double v2nx = vertices.nx( v2 ); + final double v2ny = vertices.ny( v2 ); + final double v2nz = vertices.nz( v2 ); + final long nv2 = out.vertices().add( x2, y2, z2, v2nx, v2ny, v2nz, 0., 0. ); + + final double nx = triangles.nx( id ); + final double ny = triangles.ny( id ); + final double nz = triangles.nz( id ); + + out.triangles().add( nv0, nv1, nv2, nx, ny, nz ); + } + Meshes.removeDuplicateVertices( out, 0 ); + + System.out.println( out ); + + final STLMeshIO io = new STLMeshIO(); + try + { + io.save( out, "samples/mesh/io/intersect.stl" ); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + } + + /** + * Remove duplicate positions, for the normals, take the mean of normals + * at duplicate positions. + * + * @param ts + * @param nxs + * @return the new arrays length. + */ private int removeDuplicate( final TDoubleArrayList ts, final TDoubleArrayList nxs ) { if ( ts.size() < 2 ) @@ -201,12 +329,8 @@ private int removeDuplicate( final TDoubleArrayList ts, final TDoubleArrayList n int j = 0; double accum = 0.; int nAccum = 0; - int nPos = 0; - int nNeg = 0; - final double maxN; for ( int i = 0; i < ts.size() - 1; i++ ) { -// System.out.print( j + " -> " + i ); if ( ts.getQuick( i ) != ts.getQuick( i + 1 ) ) { ts.setQuick( j, ts.getQuick( i ) ); @@ -218,34 +342,17 @@ private int removeDuplicate( final TDoubleArrayList ts, final TDoubleArrayList n { // Average. nxs.setQuick( j, accum / nAccum ); - // Majority. -// final double vmaj; -// if ( nPos == nNeg ) -// vmaj = 0.; -// else if ( nPos > nNeg ) -// vmaj = 1.; -// else -// vmaj = -1.; -// nxs.setQuick( j, vmaj ); } accum = 0.; nAccum = 0; - nPos = 0; - nNeg = 0; j++; } else { final double v = nxs.getQuick( i ); accum += v; - if ( v > 0 ) - nPos++; - if ( v < 0 ) - nNeg++; nAccum++; -// System.out.print( ", " + accum ); } -// System.out.println(); } ts.setQuick( j, ts.getQuick( ts.size() - 1 ) ); @@ -288,6 +395,67 @@ private void getXIntersectingCoords( final double y, final double z, tl.add( id ); ts.add( intersection[ 0 ] ); } + else + { +// // Second chance: Is this triangle parallel to the X axis +// // and crossing the line? +// if (mesh.triangles().nx( id ) == 0.) +// { +// final long v0 = mesh.triangles().vertex0( id ); +// final double z0 = mesh.vertices().z( v0 ); +// // Right Z? +// if ( z0 != z ) +// continue; +// +// final double y0 = mesh.vertices().y( v0 ); +// final long v1 = mesh.triangles().vertex1( id ); +// final double y1 = mesh.vertices().y( v1 ); +// final long v2 = mesh.triangles().vertex2( id ); +// final double y2 = mesh.vertices().y( v2 ); +// final double minY = Math.min( y0, Math.min( y1, y2 ) ); +// final double maxY = Math.max( y0, Math.max( y1, y2 ) ); +// if ( minY > y ) +// continue; +// if ( maxY < y ) +// continue; +// +// final double avg = ( mesh.vertices().x( v0 ) + mesh.vertices().x( v1 ) +// + mesh.vertices().x( v2 ) ) / 3.; +// +// tl.add( id ); +// ts.add( avg ); +// } + } + } + + private String triangleToString( final long id ) + { + final StringBuilder str = new StringBuilder( id + ": " ); + + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + final long v0 = triangles.vertex0( id ); + final double x0 = vertices.x( v0 ); + final double y0 = vertices.y( v0 ); + final double z0 = vertices.z( v0 ); + str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x0, y0, z0 ) ); + + final long v1 = triangles.vertex1( id ); + final double x1 = vertices.x( v1 ); + final double y1 = vertices.y( v1 ); + final double z1 = vertices.z( v1 ); + str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x1, y1, z1 ) ); + + final long v2 = triangles.vertex2( id ); + final double x2 = vertices.x( v2 ); + final double y2 = vertices.y( v2 ); + final double z2 = vertices.z( v2 ); + str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x2, y2, z2 ) ); + + str.append( String.format( "N = (%4.2f, %4.2f, %4.2f) ", + triangles.nx( id ), triangles.nz( id ), triangles.nz( id ) ) ); + + return str.toString(); } private void getNormalXProjection( final TLongArrayList tl, final TDoubleArrayList nxs ) @@ -348,7 +516,6 @@ public static < T extends RealType< T > > void main( final String[] args ) final MeshIterator it = new MeshIterator( spot, cal ); it.iterate( ( RandomAccess< T > ) ImageJFunctions.wrap( out ).randomAccess() ); it.iterate( ( RandomAccess< T > ) ImageJFunctions.wrap( imp ).randomAccess() ); - break; } imp.show(); From b2b4ade0b45bfa841fc665ccb37ad02166944b19 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:15:34 +0200 Subject: [PATCH 071/263] Move mesh utility classes to a dedicated package. --- .../plugin/trackmate/util/mesh/MeshUtils.java | 118 ++++++++ .../trackmate/util}/mesh/MollerTrumbore.java | 10 +- .../trackmate/util/mesh/RayCastingX.java | 282 ++++++++++++++++++ .../trackmate/util}/mesh/SortArrays.java | 5 +- 4 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/MeshUtils.java rename src/{test/java/fiji/plugin/trackmate => main/java/fiji/plugin/trackmate/util}/mesh/MollerTrumbore.java (87%) create mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/RayCastingX.java rename src/{test/java/fiji/plugin/trackmate => main/java/fiji/plugin/trackmate/util}/mesh/SortArrays.java (96%) diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/MeshUtils.java b/src/main/java/fiji/plugin/trackmate/util/mesh/MeshUtils.java new file mode 100644 index 000000000..d8889f4ff --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/MeshUtils.java @@ -0,0 +1,118 @@ +package fiji.plugin.trackmate.util.mesh; + +import java.io.IOException; + +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.Triangles; +import net.imagej.mesh.Vertices; +import net.imagej.mesh.io.stl.STLMeshIO; +import net.imagej.mesh.nio.BufferMesh; + +/** + * A collection of small utilities to facilitate debugging issues related to + * meshes in TrackMate. + * + * @author Jean-Yves Tinevez + * + */ +public class MeshUtils +{ + + /** + * Saves a sub-mesh containing the specified triangles to a STL file. + * + * @param tl + * the list of triangles (ids in the original mesh) to save. + * @param mesh + * the original mesh. + * @param saveFilePath + * a file path for a STL file. + */ + public static void exportMeshSubset( final long[] tl, final Mesh mesh, final String saveFilePath ) + { + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + final BufferMesh out = new BufferMesh( tl.length * 3, tl.length ); + for ( int i = 0; i < tl.length; i++ ) + { + final long id = tl[ i ]; + + final long v0 = triangles.vertex0( id ); + final double x0 = vertices.x( v0 ); + final double y0 = vertices.y( v0 ); + final double z0 = vertices.z( v0 ); + final double v0nx = vertices.nx( v0 ); + final double v0ny = vertices.ny( v0 ); + final double v0nz = vertices.nz( v0 ); + final long nv0 = out.vertices().add( x0, y0, z0, v0nx, v0ny, v0nz, 0., 0. ); + + final long v1 = triangles.vertex1( id ); + final double x1 = vertices.x( v1 ); + final double y1 = vertices.y( v1 ); + final double z1 = vertices.z( v1 ); + final double v1nx = vertices.nx( v1 ); + final double v1ny = vertices.ny( v1 ); + final double v1nz = vertices.nz( v1 ); + final long nv1 = out.vertices().add( x1, y1, z1, v1nx, v1ny, v1nz, 0., 0. ); + + final long v2 = triangles.vertex2( id ); + final double x2 = vertices.x( v2 ); + final double y2 = vertices.y( v2 ); + final double z2 = vertices.z( v2 ); + final double v2nx = vertices.nx( v2 ); + final double v2ny = vertices.ny( v2 ); + final double v2nz = vertices.nz( v2 ); + final long nv2 = out.vertices().add( x2, y2, z2, v2nx, v2ny, v2nz, 0., 0. ); + + final double nx = triangles.nx( id ); + final double ny = triangles.ny( id ); + final double nz = triangles.nz( id ); + + out.triangles().add( nv0, nv1, nv2, nx, ny, nz ); + } + Meshes.removeDuplicateVertices( out, 0 ); + + final STLMeshIO io = new STLMeshIO(); + try + { + io.save( out, saveFilePath ); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + } + + public static String triangleToString( final Mesh mesh, final long id ) + { + final StringBuilder str = new StringBuilder( id + ": " ); + + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + final long v0 = triangles.vertex0( id ); + final double x0 = vertices.x( v0 ); + final double y0 = vertices.y( v0 ); + final double z0 = vertices.z( v0 ); + str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x0, y0, z0 ) ); + + final long v1 = triangles.vertex1( id ); + final double x1 = vertices.x( v1 ); + final double y1 = vertices.y( v1 ); + final double z1 = vertices.z( v1 ); + str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x1, y1, z1 ) ); + + final long v2 = triangles.vertex2( id ); + final double x2 = vertices.x( v2 ); + final double y2 = vertices.y( v2 ); + final double z2 = vertices.z( v2 ); + str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x2, y2, z2 ) ); + + str.append( String.format( "N = (%4.2f, %4.2f, %4.2f) ", + triangles.nx( id ), triangles.nz( id ), triangles.nz( id ) ) ); + + return str.toString(); + } + +} + diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MollerTrumbore.java b/src/main/java/fiji/plugin/trackmate/util/mesh/MollerTrumbore.java similarity index 87% rename from src/test/java/fiji/plugin/trackmate/mesh/MollerTrumbore.java rename to src/main/java/fiji/plugin/trackmate/util/mesh/MollerTrumbore.java index 362688e5e..6e812082f 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MollerTrumbore.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/MollerTrumbore.java @@ -1,12 +1,18 @@ -package fiji.plugin.trackmate.mesh; +package fiji.plugin.trackmate.util.mesh; import net.imagej.mesh.Mesh; import net.imagej.mesh.Triangles; import net.imagej.mesh.Vertices; /** - * Adapted from Wikipedia. + * Möller–Trumbore intersection algorithm. + *

+ * This algorithm can efficiently tells whether a ray intersects with a triangle + * in a mesh. Adapted from Wikipedia. * + * @see . * @author Jean-Yves Tinevez * */ diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/RayCastingX.java b/src/main/java/fiji/plugin/trackmate/util/mesh/RayCastingX.java new file mode 100644 index 000000000..05f46e406 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/RayCastingX.java @@ -0,0 +1,282 @@ +package fiji.plugin.trackmate.util.mesh; + +import gnu.trove.list.array.TDoubleArrayList; +import gnu.trove.list.array.TLongArrayList; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Triangles; + +/** + * Ray casting algorithm. + *

+ * Used to determine whether a point is inside or outside a mesh. This + * implementation uses rays cast along X only. + * + * @author Jean-Yves Tinevez + * + */ +public class RayCastingX +{ + + private static final boolean DEBUG = false; + + /** List of triangle ids intersecting with the current ray. */ + private final TLongArrayList intersectingTriangle = new TLongArrayList(); + + /** List of X positions of the triangle intersections with the ray. */ + private final TDoubleArrayList intersectionXs = new TDoubleArrayList(); + + /** List of the X component of normal at intersection point. */ + private final TDoubleArrayList intersectionNormals = new TDoubleArrayList(); + + private final Mesh mesh; + + private final MollerTrumbore mollerTrumbore; + + public RayCastingX( final Mesh mesh ) + { + this.mesh = mesh; + this.mollerTrumbore = new MollerTrumbore( mesh ); + } + + /** + * Returns the position of mesh entries and exists along the specified X + * ray. The lists returned are pruned for duplicate and non-alternating + * entry / exit points are resolved. + * + * @param y + * the Y position of the X ray to cast. + * @param z + * the Z position of the X ray to cast. + * @param meshXs + * List of resolved X positions where we enter / exit the mesh, + * along the specified ray. Modified by this call. + * @param meshNs + * List of X component of normals at points where we enter / exit + * the mesh. Modified by this call. + * + */ + public void cast( final double y, final double z, final TDoubleArrayList meshXs, final TDoubleArrayList meshNs ) + { + meshXs.resetQuick(); + meshNs.resetQuick(); + + // Get all the X position where triangles cross the line. + getXIntersectingCoords( y, z, intersectingTriangle, intersectionXs ); + + // No intersection? + if ( intersectionXs.isEmpty() ) + return; + + if ( DEBUG ) + MeshUtils.exportMeshSubset( intersectingTriangle.toArray(), mesh, "samples/mesh/io/subset.stl" ); + + // Collect normals projection on the X line. + getNormalXProjection( mesh, intersectingTriangle, intersectionNormals ); + + // Sort by by X coordinate of intersections. + final int[] index = SortArrays.quicksort( intersectionXs ); + + // Sort normal array with the same order. + SortArrays.reorder( intersectionNormals, index ); + + if ( DEBUG ) + { + System.out.println(); + System.out.println( "Before removing duplicates:" ); + System.out.println( "XS: " + intersectionXs ); + System.out.println( "NS: " + intersectionNormals ); + } + + // Merge duplicates. + final int maxIndex = removeDuplicate( intersectionXs, intersectionNormals ); + + if ( DEBUG ) + { + System.out.println( "After removing duplicates:" ); + System.out.println( "XS: " + intersectionXs.subList( 0, maxIndex ) ); + System.out.println( "NS: " + intersectionNormals.subList( 0, maxIndex ) ); + } + + // Check we are alternating entering / leaving. + checkAlternating( intersectionXs, intersectionNormals, maxIndex, meshXs, meshNs ); + } + + /** + * Remove duplicate positions of intersections. + *

+ * It is very likely that the ray casting along X intersects with triangle + * edges or triangle vertices. This is because in some case the mesh we + * iterate through was generated by the marching-cubes algorithm, and the + * mesh vertices lie exactly at pixel coordinates. + *

+ * Because of this they ray might intersects at one point with several, + * possibly many (3-9) triangles. This routine merges consecutive duplicate + * X position by retaining one one for a set, and taking the mean normal of + * the set. + * + * @param ts + * the X position of the intersections of the ray with triangles, + * possibly with duplicates. Will be modified by this routine. + * @param nxs + * the X component of the normal of the intersected triangles. + * Will be modified by this call. + * @return the new arrays length. That is: the actual size of the + * intersection list once it has been pruned of duplicates. + */ + private static final int removeDuplicate( final TDoubleArrayList ts, final TDoubleArrayList nxs ) + { + if ( ts.size() < 2 ) + return ts.size(); + + int j = 0; + double accum = 0.; + int nAccum = 0; + for ( int i = 0; i < ts.size() - 1; i++ ) + { + if ( ts.getQuick( i ) != ts.getQuick( i + 1 ) ) + { + ts.setQuick( j, ts.getQuick( i ) ); + if ( nAccum == 0 ) + { + nxs.setQuick( j, nxs.getQuick( i ) ); + } + else + { + // Average. + nxs.setQuick( j, accum / nAccum ); + } + accum = 0.; + nAccum = 0; + j++; + } + else + { + final double v = nxs.getQuick( i ); + accum += v; + nAccum++; + } + } + + ts.setQuick( j, ts.getQuick( ts.size() - 1 ) ); + if ( nAccum == 0 ) + nxs.setQuick( j, nxs.getQuick( ts.size() - 1 ) ); + else + nxs.setQuick( j, accum / nAccum ); + + j++; + return j; + } + + /** + * Processes entries and exists along a ray in the mesh. + *

+ * Ideally, following a ray, every time we cross a triangle at an entry, it + * should be followed by an exit and vice-versa. When it is not the case, it + * means the ray has been following triangles exactly parallels to the X + * axis. This routine resolves theses issues by returning new arrays where + * non alternating entries and exits have been pruned. It retains the + * 'leftmost' entry and the 'rightmost' exit every time several consecutive + * entries or exits are encountered. + * + * @param xs + * the array of X position of intersection points. + * @param nxs + * the array of X component of the normals at these intersection + * points. + * @param maxIndex + * the size of these arrays (actual arrays might be bigger, but + * they won't be iterated past this size). + * @param outXs + * a holder for the resulting pruned X positions of intersection + * points. Reset by this call. Must be empty when called. + * @param outNxs + * a holder for the resulting X component of the normals at + * intersection points. Reset by this call. Must be empty when + * called. + */ + private static final void checkAlternating( + final TDoubleArrayList xs, final TDoubleArrayList nxs, final int maxIndex, + final TDoubleArrayList outXs, final TDoubleArrayList outNxs ) + { + double prevN = nxs.getQuick( 0 ); + final double prevX = xs.getQuick( 0 ); + + outXs.add( prevX ); + outNxs.add( prevN ); + + // The first one should be an entry (normal neg). + assert prevN < 0; + // The last one should be an exit (normal pos). + assert nxs.getQuick( maxIndex ) > 0; + + for ( int i = 1; i < maxIndex; i++ ) + { + final double n = nxs.getQuick( i ); + if ( n * prevN < 0. ) + { + // Sign did change. All good. + outXs.add( xs.getQuick( i ) ); + outNxs.add( n ); + } + else + { + // Sign did not change! Merge. + if ( n < 0. ) + { + // Two consecutive entries. + // Remove this one, so that the first valid entry stays. + } + else + { + // Two consecutive exits. + // Remove the previous one, so that the last exit is + // this one. + outXs.removeAt( outXs.size() - 1 ); + outNxs.removeAt( outNxs.size() - 1 ); + // And add this one. + outXs.add( xs.getQuick( i ) ); + outNxs.add( n ); + } + } + prevN = n; + } + } + + /** + * Returns the list of X coordinates where the line parallel to the X axis + * and passing through (0,y,z) crosses the triangles of the mesh. The list + * is unordered and may have duplicates. + * + * @param y + * the Y coordinate of the line origin. + * @param z + * the Z coordinate of the line origin. + * @param tl + * a holder for the triangle indices intersecting. + * @param ts + * a holder for the resulting intersections X coordinate. + */ + private void getXIntersectingCoords( final double y, final double z, + final TLongArrayList tl, final TDoubleArrayList ts ) + { + final double[] intersection = new double[ 3 ]; + tl.resetQuick(); + ts.resetQuick(); + // TODO optimize search of triangles with a data structure. + for ( long id = 0; id < mesh.triangles().size(); id++ ) + if ( mollerTrumbore.rayIntersectsTriangle( id, 0, y, z, 1., 0, 0, intersection ) ) + { + tl.add( id ); + ts.add( intersection[ 0 ] ); + } + } + + private static void getNormalXProjection( final Mesh mesh, final TLongArrayList tl, final TDoubleArrayList nxs ) + { + nxs.resetQuick(); + final Triangles triangles = mesh.triangles(); + for ( int id = 0; id < tl.size(); id++ ) + nxs.add( triangles.nx( tl.getQuick( id ) ) ); + } + +} diff --git a/src/test/java/fiji/plugin/trackmate/mesh/SortArrays.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java similarity index 96% rename from src/test/java/fiji/plugin/trackmate/mesh/SortArrays.java rename to src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java index 13594bcf4..9cdf73d92 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/SortArrays.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java @@ -1,4 +1,4 @@ -package fiji.plugin.trackmate.mesh; +package fiji.plugin.trackmate.util.mesh; import java.util.BitSet; import java.util.Random; @@ -6,7 +6,8 @@ import gnu.trove.list.array.TDoubleArrayList; /** - * Utilities to sort an array and return the sorting index. + * Utilities to sort a Trove list and return the sorting index to sort other + * lists with. */ public class SortArrays { From 3e0b21277dad1c0b111ca04f9a4f08489162b41e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:15:49 +0200 Subject: [PATCH 072/263] An imglib2 cursor that iterates over the pixels inside a mesh. --- .../trackmate/util/mesh/SpotMeshCursor.java | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java new file mode 100644 index 000000000..d658a6707 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -0,0 +1,267 @@ +package fiji.plugin.trackmate.util.mesh; + +import fiji.plugin.trackmate.SpotMesh; +import gnu.trove.list.array.TDoubleArrayList; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imglib2.Cursor; +import net.imglib2.RandomAccess; +import net.imglib2.Sampler; + +/** + * A {@link Cursor} that iterates over the pixels inside a mesh. + *

+ * It is based on an implementation of the ray casting algorithm, with some + * optimization to avoid querying the mesh for every single pixel. It does its + * best to ensure that the pixels iterated inside a mesh created from a mask are + * exactly the pixels of the original mask, but does not succeed fully (yet). + * + * @author Jean-Yves Tinevez + * + * @param + * the types of the pixels iterated. + */ +public class SpotMeshCursor< T > implements Cursor< T > +{ + + private final double[] cal; + + private final float[] bb; + + private final long minX; + + private final long maxX; + + private final long minY; + + private final long maxY; + + private final long minZ; + + private final long maxZ; + + private final RandomAccess< T > ra; + + private boolean hasNext; + + private long iy; + + private long iz; + + private long ix; + + /** Ray casting algorithm. */ + private final RayCastingX rayCasting; + + /** + * List of resolved X positions where we enter / exit the mesh. Set by the + * ray casting algorithm. + */ + private final TDoubleArrayList meshXs = new TDoubleArrayList(); + + /** List of normal X component where we enter / exit the mesh. */ + private final TDoubleArrayList meshNs = new TDoubleArrayList(); + + /** X position of the next (forward in X) intersection with the mesh. */ + private double nextXIntersection; + + /** X component of the normal at the next intersection with the mesh. */ + private double nextNormal; + + /** Index of the next intersection in the {@link #meshXs} list. */ + private int indexNextXIntersection; + + private Mesh mesh; + + public SpotMeshCursor( final RandomAccess< T > ra, final SpotMesh sm, final double[] cal ) + { + this( ra, sm.mesh, sm.boundingBox, cal ); + } + + public SpotMeshCursor( final RandomAccess< T > ra, final Mesh mesh, final double[] cal ) + { + this( ra, mesh, Meshes.boundingBox( mesh ), cal ); + } + + public SpotMeshCursor( final RandomAccess< T > ra, final Mesh mesh, final float[] boundingBox, final double[] cal ) + { + this.ra = ra; + this.mesh = mesh; + this.cal = cal; + this.bb = boundingBox; + this.minX = Math.round( bb[ 0 ] / cal[ 0 ] ); + this.maxX = Math.round( bb[ 3 ] / cal[ 0 ] ); + this.minY = Math.round( bb[ 1 ] / cal[ 1 ] ); + this.maxY = Math.round( bb[ 4 ] / cal[ 1 ] ); + this.minZ = Math.round( bb[ 2 ] / cal[ 2 ] ); + this.maxZ = Math.round( bb[ 5 ] / cal[ 2 ] ); + this.rayCasting = new RayCastingX( mesh ); + reset(); + } + + + @Override + public void reset() + { + this.ix = maxX; // To force a new ray cast when we call fwd() + this.iy = minY - 1; // Then we will move to minY. + this.iz = minZ; + this.hasNext = true; + preFetch(); + } + + @Override + public void fwd() + { + ra.setPosition( ix, 0 ); + ra.setPosition( iy, 1 ); + ra.setPosition( iz, 2 ); + preFetch(); + } + + private void preFetch() + { + hasNext = false; + while ( true ) + { + // Find next position. + ix++; + if ( ix > maxX ) + { + ix = minX; + while ( true ) + { + // Next Y line, we will need to ray cast again. + ix = minX; + iy++; + if ( iy > maxY ) + { + iy = minY; + iz++; + if ( iz > maxZ ) + return; // Finished! + } + + // New ray cast. + final double z = iz * cal[ 2 ]; + final double y = iy * cal[ 1 ]; + rayCasting.cast( y, z, meshXs, meshNs ); + + // No intersection? + if ( !meshXs.isEmpty() ) + { + this.indexNextXIntersection = 0; + this.nextXIntersection = meshXs.getQuick( 0 ); + this.nextNormal = meshNs.getQuick( 0 ); + break; + } + // No intersection on this line, move to the next. + } + } + // We have found the next position. + + // Is it inside? + final double x = ix * cal[ 0 ]; + + // Special case: only one intersection. + if ( meshXs.size() == 1 ) + { + if ( x == nextXIntersection ) + { + hasNext = true; + return; + } + else + { + continue; + } + } + + if ( x >= nextXIntersection ) + { + indexNextXIntersection++; + if ( indexNextXIntersection >= meshXs.size() ) + { + final boolean inside = ( x == meshXs.get( meshXs.size() - 1 ) ); + if ( inside ) + { + hasNext = true; + return; + } + } + else + { + final boolean isEntry = ( nextNormal < 0. ) || ( ix == nextXIntersection ); + nextXIntersection = meshXs.getQuick( indexNextXIntersection ); + nextNormal = meshNs.getQuick( indexNextXIntersection ); + if ( isEntry ) + { + hasNext = true; + return; + } + } + } + else + { + if ( nextNormal > 0. ) + { + hasNext = true; + return; + } + } + + // Not inside, move to the next point. + } + } + + @Override + public boolean hasNext() + { + return hasNext; + } + + @Override + public void jumpFwd( final long steps ) + { + for ( int i = 0; i < steps; i++ ) + fwd(); + } + + @Override + public T next() + { + fwd(); + return get(); + } + + @Override + public long getLongPosition( final int d ) + { + return ra.getLongPosition( d ); + } + + @Override + public Cursor< T > copyCursor() + { + return new SpotMeshCursor<>( ra.copyRandomAccess(), mesh, bb, cal ); + } + + @Override + public Sampler< T > copy() + { + return copyCursor(); + } + + @Override + public int numDimensions() + { + return 3; + } + + @Override + public T get() + { + return ra.get(); + } + +} From 81b5b6af144bec50fca1be6b3588928aa79ff34a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:15:56 +0200 Subject: [PATCH 073/263] Update the interactive demo. --- .../trackmate/mesh/DemoPixelIteration.java | 470 +----------------- 1 file changed, 18 insertions(+), 452 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index 3b7325f69..9bbc2cc91 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -1,472 +1,29 @@ package fiji.plugin.trackmate.mesh; -import java.io.IOException; - import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; -import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.MaskDetectorFactory; import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.util.mesh.SpotMeshCursor; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import gnu.trove.list.array.TDoubleArrayList; -import gnu.trove.list.array.TIntArrayList; -import gnu.trove.list.array.TLongArrayList; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; import ij.gui.NewImage; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.Triangles; -import net.imagej.mesh.Vertices; -import net.imagej.mesh.io.stl.STLMeshIO; -import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.Cursor; import net.imglib2.RandomAccess; -import net.imglib2.img.display.imagej.ImageJFunctions; -import net.imglib2.iterator.LocalizingIntervalIterator; import net.imglib2.type.numeric.RealType; public class DemoPixelIteration { - private static final boolean DEBUG = false; - - private static final int DEBUG_Z = 16; - - private static final int DEBUG_Y = 143; - - public static class MeshIterator - { - - private final Mesh mesh; - - private final float[] bb; - - private final MollerTrumbore mollerTrumbore; - - private final double[] cal; - - private final long maxX; - - private final long minX; - - public MeshIterator( final Spot spot, final double[] cal ) - { - this.cal = cal; - final SpotMesh sm = spot.getMesh(); - this.mesh = sm.mesh; - this.bb = sm.boundingBox; - this.minX = Math.round( bb[ 0 ] / cal[ 0 ] ); - this.maxX = Math.round( bb[ 3 ] / cal[ 0 ] ); - this.mollerTrumbore = new MollerTrumbore( mesh ); - } - - public < T extends RealType< T > > void iterate( final RandomAccess< T > ra ) - { - - final long[] min = new long[ 3 ]; - final long[] max = new long[ 3 ]; - // Iterate only though Y and Z. - min[ 0 ] = minX; - max[ 0 ] = minX; - for ( int d = 1; d < 3; d++ ) - { - min[ d ] = Math.round( bb[ d ] / cal[ d ] ); - max[ d ] = Math.round( bb[ d + 3 ] / cal[ d ] ); - } - final LocalizingIntervalIterator it = new LocalizingIntervalIterator( min, max ); - - // Array of x position of intersections. - final TDoubleArrayList xs = new TDoubleArrayList(); - // Array of triangle indices intersecting. - final TLongArrayList tl = new TLongArrayList(); - // Array of triangle normals projected onto the X line. - final TDoubleArrayList nxs = new TDoubleArrayList(); - // Array to store the 'inside' score. - final TIntArrayList insideScore = new TIntArrayList(); - - while ( it.hasNext() ) - { - it.fwd(); - ra.setPosition( it ); - - if ( DEBUG ) - { - if ( it.getIntPosition( 2 ) != DEBUG_Z ) - continue; - if ( it.getIntPosition( 1 ) != DEBUG_Y ) - continue; - } - - // Get all the X position where triangles cross the line. - final double y = it.getIntPosition( 1 ) * cal[ 1 ]; - final double z = it.getIntPosition( 2 ) * cal[ 2 ]; - getXIntersectingCoords( y, z, tl, xs ); - - // No intersection? - if ( xs.isEmpty() ) - continue; - - if ( DEBUG ) - { - exportMeshSubset( tl ); - } - - // Collect normals projection on the X line. - getNormalXProjection( tl, nxs ); - - // Sort by by X coordinate of intersections. - final int[] index = SortArrays.quicksort( xs ); - - // Sort normal array with the same order. - SortArrays.reorder( nxs, index ); - - if ( DEBUG ) - { - System.out.println(); - System.out.println( "Before removing duplicates:" ); - System.out.println( "XS: " + xs ); - System.out.println( "NS: " + nxs ); - } - - // Merge duplicates. - final int maxIndex = removeDuplicate( xs, nxs ); - - if ( DEBUG ) - { - System.out.println( "After removing duplicates:" ); - System.out.println( "XS: " + xs.subList( 0, maxIndex ) ); - System.out.println( "NS: " + nxs.subList( 0, maxIndex ) ); - } - - final TDoubleArrayList outXs = new TDoubleArrayList(); - final TDoubleArrayList outNxs = new TDoubleArrayList(); - // Check we are alternating entering / leaving. - checkAlternating( xs, nxs, maxIndex, outXs, outNxs ); - - if ( DEBUG ) - { - System.out.println( "After checking alternating:" ); - System.out.println( "XS: " + outXs ); - System.out.println( "NS: " + outNxs ); - } - - // Iterate to build the inside score between each intersection. - insideScore.resetQuick(); - insideScore.add( 0 ); - for ( int i = 0; i < outNxs.size(); i++ ) - { - final double n = outNxs.getQuick( i ); - final int prevScore = insideScore.getQuick( i ); - - // Weird case: the normal is orthogonal to X. Should not - // happen because we filtered out triangles parallel to the - // X axis. - if ( n == 0. ) - { - insideScore.add( prevScore ); - continue; - } - else - { - final int score = prevScore + ( ( n > 0 ) ? -1 : 1 ); - insideScore.add( score ); - } - } - - for ( long ix = minX; ix <= maxX; ix++ ) - { - final double x = ix * cal[ 0 ]; - final int i = outXs.binarySearch( x ); - final boolean inside; - if ( i < 0 ) - { - final int ip = -( i + 1 ); - inside = insideScore.getQuick( ip ) > 0; - } - else - { - // On an intersection. We accept. - inside = true; - } - if ( inside ) - { - ra.setPosition( ix, 0 ); - ra.get().setReal( 500 ); - } - } - } - } - - private void checkAlternating( - final TDoubleArrayList xs, final TDoubleArrayList nxs, final int maxIndex, - final TDoubleArrayList outXs, final TDoubleArrayList outNxs ) - { - outXs.resetQuick(); - outNxs.resetQuick(); - - double prevN = nxs.getQuick( 0 ); - final double prevX = xs.getQuick( 0 ); - - outXs.add( prevX ); - outNxs.add( prevN ); - - // The first one should be an entry (normal neg). - assert prevN < 0; - // The last one should be an exit (normal pos). - assert nxs.getQuick( maxIndex ) > 0; - - for ( int i = 1; i < maxIndex; i++ ) - { - final double n = nxs.getQuick( i ); - if ( n * prevN < 0. ) - { - // Sign did change. All good. - outXs.add( xs.getQuick( i ) ); - outNxs.add( n ); - } - else - { - // Sign did not change! Merge. - if ( n < 0. ) - { - // Two consecutive entries. - // Remove this one, so that the first valid entry stays. - } - else - { - // Two consecutive exits. - // Remove the previous one, so that the last exit is - // this one. - outXs.removeAt( outXs.size() - 1 ); - outNxs.removeAt( outNxs.size() - 1 ); - // And add this one. - outXs.add( xs.getQuick( i ) ); - outNxs.add( n ); - } - } - prevN = n; - } - } - - private void exportMeshSubset( final TLongArrayList tl ) - { - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - final BufferMesh out = new BufferMesh( tl.size() * 3, tl.size() ); - for ( int i = 0; i < tl.size(); i++ ) - { - final long id = tl.getQuick( i ); - - final long v0 = triangles.vertex0( id ); - final double x0 = vertices.x( v0 ); - final double y0 = vertices.y( v0 ); - final double z0 = vertices.z( v0 ); - final double v0nx = vertices.nx( v0 ); - final double v0ny = vertices.ny( v0 ); - final double v0nz = vertices.nz( v0 ); - final long nv0 = out.vertices().add( x0, y0, z0, v0nx, v0ny, v0nz, 0., 0. ); - - final long v1 = triangles.vertex1( id ); - final double x1 = vertices.x( v1 ); - final double y1 = vertices.y( v1 ); - final double z1 = vertices.z( v1 ); - final double v1nx = vertices.nx( v1 ); - final double v1ny = vertices.ny( v1 ); - final double v1nz = vertices.nz( v1 ); - final long nv1 = out.vertices().add( x1, y1, z1, v1nx, v1ny, v1nz, 0., 0. ); - - final long v2 = triangles.vertex2( id ); - final double x2 = vertices.x( v2 ); - final double y2 = vertices.y( v2 ); - final double z2 = vertices.z( v2 ); - final double v2nx = vertices.nx( v2 ); - final double v2ny = vertices.ny( v2 ); - final double v2nz = vertices.nz( v2 ); - final long nv2 = out.vertices().add( x2, y2, z2, v2nx, v2ny, v2nz, 0., 0. ); - - final double nx = triangles.nx( id ); - final double ny = triangles.ny( id ); - final double nz = triangles.nz( id ); - - out.triangles().add( nv0, nv1, nv2, nx, ny, nz ); - } - Meshes.removeDuplicateVertices( out, 0 ); - - System.out.println( out ); - - final STLMeshIO io = new STLMeshIO(); - try - { - io.save( out, "samples/mesh/io/intersect.stl" ); - } - catch ( final IOException e ) - { - e.printStackTrace(); - } - } - - /** - * Remove duplicate positions, for the normals, take the mean of normals - * at duplicate positions. - * - * @param ts - * @param nxs - * @return the new arrays length. - */ - private int removeDuplicate( final TDoubleArrayList ts, final TDoubleArrayList nxs ) - { - if ( ts.size() < 2 ) - return ts.size(); - - int j = 0; - double accum = 0.; - int nAccum = 0; - for ( int i = 0; i < ts.size() - 1; i++ ) - { - if ( ts.getQuick( i ) != ts.getQuick( i + 1 ) ) - { - ts.setQuick( j, ts.getQuick( i ) ); - if ( nAccum == 0 ) - { - nxs.setQuick( j, nxs.getQuick( i ) ); - } - else - { - // Average. - nxs.setQuick( j, accum / nAccum ); - } - accum = 0.; - nAccum = 0; - j++; - } - else - { - final double v = nxs.getQuick( i ); - accum += v; - nAccum++; - } - } - - ts.setQuick( j, ts.getQuick( ts.size() - 1 ) ); - if ( nAccum == 0 ) - { - nxs.setQuick( j, nxs.getQuick( ts.size() - 1 ) ); - } - else - { - nxs.setQuick( j, accum / nAccum ); - } - j++; - return j; - } - - /** - * Returns the list of X coordinates where the line parallel to the X - * axis and passing through (0,y,z) crosses the triangles of the mesh. - * The list is unordered and may have duplicates. - * - * @param y - * the Y coordinate of the line origin. - * @param z - * the Z coordinate of the line origin. - * @param tl - * a holder for the triangle indices intersecting. - * @param ts - * a holder for the resulting intersections X coordinate. - */ - private void getXIntersectingCoords( final double y, final double z, - final TLongArrayList tl, final TDoubleArrayList ts ) - { - final double[] intersection = new double[ 3 ]; - tl.resetQuick(); - ts.resetQuick(); - // TODO optimize search of triangles with a data structure. - for ( long id = 0; id < mesh.triangles().size(); id++ ) - if ( mollerTrumbore.rayIntersectsTriangle( id, 0, y, z, 1., 0, 0, intersection ) ) - { - tl.add( id ); - ts.add( intersection[ 0 ] ); - } - else - { -// // Second chance: Is this triangle parallel to the X axis -// // and crossing the line? -// if (mesh.triangles().nx( id ) == 0.) -// { -// final long v0 = mesh.triangles().vertex0( id ); -// final double z0 = mesh.vertices().z( v0 ); -// // Right Z? -// if ( z0 != z ) -// continue; -// -// final double y0 = mesh.vertices().y( v0 ); -// final long v1 = mesh.triangles().vertex1( id ); -// final double y1 = mesh.vertices().y( v1 ); -// final long v2 = mesh.triangles().vertex2( id ); -// final double y2 = mesh.vertices().y( v2 ); -// final double minY = Math.min( y0, Math.min( y1, y2 ) ); -// final double maxY = Math.max( y0, Math.max( y1, y2 ) ); -// if ( minY > y ) -// continue; -// if ( maxY < y ) -// continue; -// -// final double avg = ( mesh.vertices().x( v0 ) + mesh.vertices().x( v1 ) -// + mesh.vertices().x( v2 ) ) / 3.; -// -// tl.add( id ); -// ts.add( avg ); -// } - } - } - - private String triangleToString( final long id ) - { - final StringBuilder str = new StringBuilder( id + ": " ); - - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - final long v0 = triangles.vertex0( id ); - final double x0 = vertices.x( v0 ); - final double y0 = vertices.y( v0 ); - final double z0 = vertices.z( v0 ); - str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x0, y0, z0 ) ); - - final long v1 = triangles.vertex1( id ); - final double x1 = vertices.x( v1 ); - final double y1 = vertices.y( v1 ); - final double z1 = vertices.z( v1 ); - str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x1, y1, z1 ) ); - - final long v2 = triangles.vertex2( id ); - final double x2 = vertices.x( v2 ); - final double y2 = vertices.y( v2 ); - final double z2 = vertices.z( v2 ); - str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x2, y2, z2 ) ); - - str.append( String.format( "N = (%4.2f, %4.2f, %4.2f) ", - triangles.nx( id ), triangles.nz( id ), triangles.nz( id ) ) ); - - return str.toString(); - } - - private void getNormalXProjection( final TLongArrayList tl, final TDoubleArrayList nxs ) - { - nxs.resetQuick(); - final Triangles triangles = mesh.triangles(); - for ( int id = 0; id < tl.size(); id++ ) - nxs.add( triangles.nx( tl.getQuick( id ) ) ); - } - } - @SuppressWarnings( "unchecked" ) public static < T extends RealType< T > > void main( final String[] args ) { @@ -507,24 +64,33 @@ public static < T extends RealType< T > > void main( final String[] args ) final ImagePlus out = NewImage.createShortImage( "OUT", imp.getWidth(), imp.getHeight(), imp.getNSlices(), NewImage.FILL_BLACK ); out.show(); - out.setSlice( DEBUG_Z + 1 ); out.resetDisplayRange(); + imp.show(); + imp.resetDisplayRange(); + final double[] cal = TMUtils.getSpatialCalibration( imp ); for ( final Spot spot : model.getSpots().iterable( true ) ) { - final MeshIterator it = new MeshIterator( spot, cal ); - it.iterate( ( RandomAccess< T > ) ImageJFunctions.wrap( out ).randomAccess() ); - it.iterate( ( RandomAccess< T > ) ImageJFunctions.wrap( imp ).randomAccess() ); + System.out.println( spot ); + final Cursor< T > cursor = new SpotMeshCursor< T >( TMUtils.rawWraps( out ).randomAccess(), spot.getMesh(), cal ); + final RandomAccess< T > ra = TMUtils.rawWraps( imp ).randomAccess(); + while ( cursor.hasNext() ) + { + cursor.fwd(); + cursor.get().setReal( 100 ); + + ra.setPosition( cursor ); + ra.get().setReal( 100 ); + } + break; } - imp.show(); - imp.setSlice( DEBUG_Z + 1 ); - imp.resetDisplayRange(); final SelectionModel sm = new SelectionModel( model ); final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); final HyperStackDisplayer view = new HyperStackDisplayer( model, sm, imp, ds ); view.render(); + System.out.println( "Done." ); } catch ( final Exception e ) From 2717711a37566406834975d859ca0f1dcea097f5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:53:20 +0200 Subject: [PATCH 074/263] Expose mesh volume and radius routines. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 6e4f06d96..09029c476 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -71,13 +71,25 @@ public static Spot createSpot( final Mesh mesh, final double quality ) return spot; } - private double radius() + /** + * Returns the radius of the equivalent sphere with the same volume that of + * the specified mesh. + * + * @return the radius in physical units. + */ + public static final double radius(final Mesh mesh) { - return Math.pow( 3. * volume() / ( 4 * Math.PI ), 1. / 3. ); + return Math.pow( 3. * volume(mesh) / ( 4 * Math.PI ), 1. / 3. ); } - private double volume() + /** + * Returns the volume of the specified mesh. + * + * @return the volume in physical units. + */ + public static double volume( final Mesh mesh ) { + final Vertices vertices = mesh.vertices(); final Triangles triangles = mesh.triangles(); final long nTriangles = triangles.size(); @@ -110,6 +122,27 @@ private double volume() return Math.abs( sum ); } + /** + * Returns the radius of the equivalent sphere with the same volume that of + * this mesh. + * + * @return the radius in physical units. + */ + public double radius() + { + return radius( mesh ); + } + + /** + * Returns the volume of this mesh. + * + * @return the volume in physical units. + */ + public double volume() + { + return volume( mesh ); + } + public void scale(final double alpha) { final Vertices vertices = mesh.vertices(); From 1406b8d5a03a7f3061522f7a0d01cc838a472e3b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:53:48 +0200 Subject: [PATCH 075/263] Implement quality measurement on the 3D object. Without using the mesh actually, we use the bitmask provided. --- .../plugin/trackmate/detection/MaskUtils.java | 33 ++++++++++++------- .../trackmate/gui/editor/LabkitImporter.java | 2 +- .../trackmate/util/mesh/SpotMeshCursor.java | 3 +- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 8fa69da97..fb1b0d690 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -34,7 +34,6 @@ import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.Threads; -import ij.ImagePlus; import ij.gui.PolygonRoi; import ij.process.FloatPolygon; import net.imagej.ImgPlus; @@ -43,6 +42,7 @@ import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; import net.imagej.mesh.Vertices; +import net.imglib2.Cursor; import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; @@ -56,7 +56,7 @@ import net.imglib2.histogram.Real1dBinMapper; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; -import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.roi.Regions; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegionCursor; @@ -65,7 +65,6 @@ import net.imglib2.type.logic.BitType; import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; -import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; import net.imglib2.util.Util; @@ -650,7 +649,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integ * the image in which to read the quality value. * @return a list of spots, with meshes. */ - public static < R extends IntegerType< R >, S extends NumericType< S > > List< Spot > from3DLabelingWithROI( + public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from3DLabelingWithROI( final ImgLabeling< Integer, R > labeling, final Interval interval, final double[] calibration, @@ -660,12 +659,6 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S if ( labeling.numDimensions() != 3 ) throw new IllegalArgumentException( "Can only process 3D images with this method, but got " + labeling.numDimensions() + "D." ); - - // Quality image. - final ImagePlus qualityImp = ( null == qualityImage ) - ? null - : ImageJFunctions.wrap( qualityImage, "QualityImage" ); - // Parse regions to create meshes on label. final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); @@ -686,8 +679,24 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S scale( simplified.vertices(), calibration, origin ); // Measure quality. - // TODO Iterator over the mesh. - final double quality = -1; + final double quality; + if ( null == qualityImage ) + { + quality = SpotMesh.volume( simplified ); + } + else + { + double max = Double.NEGATIVE_INFINITY; + final Cursor< S > cursor = Regions.sample( region, qualityImage ).cursor(); + while(cursor.hasNext()) + { + cursor.fwd(); + final double val = cursor.get().getRealDouble(); + if ( val > max ) + max = val; + } + quality = max; + } spots.add( SpotMesh.createSpot( simplified, quality ) ); } diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 45153f6e6..c587b0608 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -253,7 +253,7 @@ private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T indices.add( Integer.valueOf( i + 1 ) ); final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); - final Map< Integer, List< Spot > > spots = MaskUtils.fromLabelingWithROIMap( + final Map< Integer, List< Spot > > spots = MaskUtils.from2DLabelingWithROIMap( labeling, Views.zeroMin( labeling ), calibration, diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java index d658a6707..6dcc1c097 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -6,7 +6,6 @@ import net.imagej.mesh.Meshes; import net.imglib2.Cursor; import net.imglib2.RandomAccess; -import net.imglib2.Sampler; /** * A {@link Cursor} that iterates over the pixels inside a mesh. @@ -247,7 +246,7 @@ public Cursor< T > copyCursor() } @Override - public Sampler< T > copy() + public Cursor< T > copy() { return copyCursor(); } From 8cafcc5384282b3800ef96922a1a4fc5743c6395 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:01:55 +0200 Subject: [PATCH 076/263] Common SpotShape interface for the 2D and 3D different shape objects. --- src/main/java/fiji/plugin/trackmate/Spot.java | 30 +++++++++++++++++++ .../java/fiji/plugin/trackmate/SpotMesh.java | 29 +++++++++++++----- .../java/fiji/plugin/trackmate/SpotRoi.java | 11 ++++++- .../java/fiji/plugin/trackmate/SpotShape.java | 25 ++++++++++++++++ 4 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/SpotShape.java diff --git a/src/main/java/fiji/plugin/trackmate/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index 626e28528..7502ab58e 100644 --- a/src/main/java/fiji/plugin/trackmate/Spot.java +++ b/src/main/java/fiji/plugin/trackmate/Spot.java @@ -250,6 +250,13 @@ public void setRoi( final SpotRoi roi ) this.mesh = null; } + /** + * Return the 2D polygonal shape of this spot. Might be null if + * the spot has no shape information, or if it has but in 3D (in that case + * the {@link #mesh} field won't be null). + * + * @return the spot roi. Can be null. + */ public SpotRoi getRoi() { return roi; @@ -261,11 +268,34 @@ public void setMesh( final SpotMesh mesh ) this.mesh = mesh; } + /** + * Return the mesh shape of this spot. Might be null if the + * spot has no shape information, or if it has but in 2D (in that case the + * {@link #roi} field won't be null). + * + * @return the spot mesh. Can be null. + */ public SpotMesh getMesh() { return mesh; } + /** + * Returns the shape field of this spot as a {@link SpotShape}. + *

+ * If the spot has no shape information, this will return null. + * If the image is 2D the shape returned will be a {@link SpotRoi}. In 3D it + * will be a {@link SpotRoi}. + * + * @return the spot shape. + */ + public SpotShape getShape() + { + if ( roi != null ) + return roi; + return mesh; + } + /** * @return the name for this Spot. */ diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 09029c476..28cb73e30 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -5,9 +5,10 @@ import net.imagej.mesh.Meshes; import net.imagej.mesh.Triangles; import net.imagej.mesh.Vertices; +import net.imagej.mesh.nio.BufferMesh; import net.imglib2.RealPoint; -public class SpotMesh +public class SpotMesh implements SpotShape { /** @@ -20,7 +21,7 @@ public class SpotMesh /** * The bounding-box, centered on (0,0,0) of this object. */ - public final float[] boundingBox; + public float[] boundingBox; public SpotMesh( final Mesh mesh, final float[] boundingBox ) { @@ -122,12 +123,7 @@ public static double volume( final Mesh mesh ) return Math.abs( sum ); } - /** - * Returns the radius of the equivalent sphere with the same volume that of - * this mesh. - * - * @return the radius in physical units. - */ + @Override public double radius() { return radius( mesh ); @@ -143,6 +139,13 @@ public double volume() return volume( mesh ); } + @Override + public double size() + { + return volume(); + } + + @Override public void scale(final double alpha) { final Vertices vertices = mesh.vertices(); @@ -172,6 +175,7 @@ public void scale(final double alpha) final float za = ( float ) ( ra * Math.cos( theta ) ); vertices.setPositionf( v, xa, ya, za ); } + boundingBox = Meshes.boundingBox( mesh ); } public void slice( final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) @@ -284,6 +288,14 @@ private static void addEdgeIntersectionToContour( cy.add( y ); } + @Override + public SpotMesh copy() + { + final BufferMesh meshCopy = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); + Meshes.copy( this.mesh, meshCopy ); + return new SpotMesh( meshCopy, boundingBox.clone() ); + } + @Override public String toString() { @@ -321,4 +333,5 @@ private static final double maxZ( final Vertices vertices, final long v0, final return Math.max( vertices.z( v0 ), Math.max( vertices.z( v1 ), vertices.z( v2 ) ) ); } + } diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index 9efe32ccf..a8faa695c 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -35,7 +35,7 @@ import net.imglib2.type.logic.BoolType; import net.imglib2.view.Views; -public class SpotRoi +public class SpotRoi implements SpotShape { /** @@ -54,6 +54,7 @@ public SpotRoi( final double[] x, final double[] y ) this.y = y; } + @Override public SpotRoi copy() { return new SpotRoi( x.clone(), y.clone() ); @@ -164,6 +165,7 @@ public < T > IterableInterval< T > sample( final double spotXCenter, final doubl return Regions.sample( region, Views.extendMirrorDouble( Views.dropSingletonDimensions( img ) ) ); } + @Override public double radius() { return Math.sqrt( area() / Math.PI ); @@ -174,6 +176,13 @@ public double area() return Math.abs( signedArea( x, y ) ); } + @Override + public double size() + { + return area(); + } + + @Override public void scale( final double alpha ) { for ( int i = 0; i < x.length; i++ ) diff --git a/src/main/java/fiji/plugin/trackmate/SpotShape.java b/src/main/java/fiji/plugin/trackmate/SpotShape.java new file mode 100644 index 000000000..cf5b632a2 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/SpotShape.java @@ -0,0 +1,25 @@ +package fiji.plugin.trackmate; + +public interface SpotShape +{ + + /** + * Returns the radius of the equivalent sphere with the same volume that of + * this mesh. + * + * @return the radius in physical units. + */ + double radius(); + + void scale( double alpha ); + + SpotShape copy(); + + /** + * Returns the physical size of this shape. In 2D it is the area. In 3D it + * is the volume. + * + * @return the shape size. + */ + double size(); +} From e6ac80ea361942a33c262fcf1f3c5ca30892e042 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:02:08 +0200 Subject: [PATCH 077/263] Iterable for a spot mesh. --- .../trackmate/util/mesh/SpotMeshIterable.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java new file mode 100644 index 000000000..c3550cce0 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java @@ -0,0 +1,97 @@ +package fiji.plugin.trackmate.util.mesh; + +import java.util.Iterator; + +import fiji.plugin.trackmate.SpotMesh; +import net.imagej.mesh.Meshes; +import net.imglib2.Cursor; +import net.imglib2.IterableInterval; +import net.imglib2.Localizable; +import net.imglib2.RandomAccessible; +import net.imglib2.RealPoint; + +public class SpotMeshIterable< T > implements IterableInterval< T >, Localizable +{ + + private final double[] calibration; + + private final SpotMesh sm; + + private final RealPoint center; + + private final RandomAccessible< T > img; + + public SpotMeshIterable( final RandomAccessible< T > img, final SpotMesh sm, final double[] calibration ) + { + this.img = img; + this.sm = sm; + this.calibration = calibration; + this.center = Meshes.center( sm.mesh ); + } + + @Override + public int numDimensions() + { + return 3; + } + + @Override + public long getLongPosition( final int d ) + { + return Math.round( center.getDoublePosition( d ) / calibration[ d ] ); + } + + @Override + public long size() + { + // Costly! + long size = 0; + for ( @SuppressWarnings( "unused" ) + final T t : this ) + size++; + + return size; + } + + @Override + public T firstElement() + { + return cursor().next(); + } + + @Override + public Object iterationOrder() + { + return this; + } + + @Override + public Iterator< T > iterator() + { + return cursor(); + } + + @Override + public long min( final int d ) + { + return Math.round( sm.boundingBox[ d ] / calibration[ d ] ); + } + + @Override + public long max( final int d ) + { + return Math.round( sm.boundingBox[ 3 + d ] / calibration[ d ] ); + } + + @Override + public Cursor< T > cursor() + { + return new SpotMeshCursor<>( img.randomAccess(), sm.mesh, calibration ); + } + + @Override + public Cursor< T > localizingCursor() + { + return cursor(); + } +} From 3de035b96944981a98c5fd77488a4a15608dd83a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:02:45 +0200 Subject: [PATCH 078/263] Add methods to SpotUtil to return suitable iterables in 2D and 3D. With this simple change we get intensity measurements in 3D in a mesh for free. --- .../fiji/plugin/trackmate/util/SpotUtil.java | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java b/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java index 1fc72fc4c..bf83dbc68 100644 --- a/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java +++ b/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java @@ -8,12 +8,12 @@ * 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 * . @@ -24,8 +24,11 @@ import java.util.Iterator; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; +import fiji.plugin.trackmate.SpotShape; import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.util.mesh.SpotMeshIterable; import net.imagej.ImgPlus; import net.imglib2.Cursor; import net.imglib2.FinalInterval; @@ -34,6 +37,7 @@ import net.imglib2.Localizable; import net.imglib2.RandomAccess; import net.imglib2.RealLocalizable; +import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.util.Intervals; import net.imglib2.util.Util; @@ -43,6 +47,16 @@ public class SpotUtil { + public static final < T extends RealType< T > > IterableInterval< T > iterable( final SpotShape shape, final RealLocalizable center, final ImgPlus< T > img ) + { + if ( shape instanceof SpotRoi ) + return iterable( ( SpotRoi ) shape, center, img ); + else if ( shape instanceof SpotShape ) + return iterable( ( SpotMesh ) shape, img ); + else + throw new IllegalArgumentException( "Unsuitable shape for SpotShape: " + shape ); + } + public static final < T extends RealType< T > > IterableInterval< T > iterable( final SpotRoi roi, final RealLocalizable center, final ImgPlus< T > img ) { final SpotRoiIterable< T > neighborhood = new SpotRoiIterable<>( roi, center, img ); @@ -56,11 +70,17 @@ public static final < T extends RealType< T > > IterableInterval< T > iterable( { // Prepare neighborhood final SpotRoi roi = spot.getRoi(); + final SpotMesh mesh = spot.getMesh(); if ( null != roi && DetectionUtils.is2D( img ) ) { // Operate on ROI only if we have one and the image is 2D. return iterable( roi, spot, img ); } + else if ( mesh != null ) + { + // Operate on 3D if we have a mesh. + return iterable( mesh, img ); + } else { // Otherwise default to circle / sphere. @@ -74,6 +94,12 @@ public static final < T extends RealType< T > > IterableInterval< T > iterable( } } + public static < T extends NumericType< T > > IterableInterval< T > iterable( final SpotMesh mesh, final ImgPlus< T > img ) + { + return new SpotMeshIterable< T >( Views.extendZero( img ), + mesh, TMUtils.getSpatialCalibration( img ) ); + } + private static < T > IterableInterval< T > makeSinglePixelIterable( final RealLocalizable center, final ImgPlus< T > img ) { final double[] calibration = TMUtils.getSpatialCalibration( img ); From 90de0ffbb1ff5def9c5b7103dcd93b5b884c662a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:03:47 +0200 Subject: [PATCH 079/263] Generalize contrast and SNR feature analyzer to 3D with mesh. --- .../spot/SpotContrastAndSNRAnalyzer.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java index 2bd128663..b9055e8cc 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java @@ -8,12 +8,12 @@ * 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 * . @@ -29,8 +29,7 @@ import static fiji.plugin.trackmate.features.spot.SpotIntensityMultiCAnalyzerFactory.makeFeatureKey; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotRoi; -import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.SpotShape; import fiji.plugin.trackmate.util.SpotNeighborhood; import fiji.plugin.trackmate.util.SpotNeighborhoodCursor; import fiji.plugin.trackmate.util.SpotUtil; @@ -54,7 +53,7 @@ * Important: this analyzer relies on some results provided by the * {@link SpotIntensityMultiCAnalyzer} analyzer. Thus, it must be run * after it. - * + * * @author Jean-Yves Tinevez, 2011 - 2012. Revised December 2020. */ public class SpotContrastAndSNRAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > @@ -72,7 +71,7 @@ public class SpotContrastAndSNRAnalyzer< T extends RealType< T > > extends Abstr /** * Instantiates an analyzer for contrast and SNR. - * + * * @param img * the 2D or 3D image of the desired time-point and channel to * operate on, @@ -101,11 +100,12 @@ public final void process( final Spot spot ) // Operate on ROI only if we have one and the image is 2D. final double meanOut; - final SpotRoi roi = spot.getRoi(); - if ( null != roi && DetectionUtils.is2D( img ) ) + final SpotShape shape = spot.getShape(); + if ( null != shape ) { + // 2D or 3D cases are treated altogether. final double alpha = outterRadius / radius; - final SpotRoi outterRoi = roi.copy(); + final SpotShape outterRoi = shape.copy(); outterRoi.scale( alpha ); final IterableInterval< T > neighborhood = SpotUtil.iterable( outterRoi, spot, img ); double totalSum = 0.; From 1dc136b3827a7f3bb7ecc6ae61cab469c4e10ee3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:03:59 +0200 Subject: [PATCH 080/263] Add a TODO for ellipse fit feature. --- .../features/spot/SpotFitEllipseAnalyzer.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzer.java index 992856912..efb5870b4 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzer.java @@ -8,12 +8,12 @@ * 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 * . @@ -76,6 +76,11 @@ public void process( final Spot spot ) } else { + /* + * TODO: deal with 3D case with a mesh. Fit an ellipsoid, with extra + * parameters that are left blank for 2d? Put it in another case? + */ + x0 = Double.NaN; y0 = Double.NaN; major = Double.NaN; @@ -192,7 +197,7 @@ private static double[] getCentroid( final double[] x, final double[] y ) * ]. We always have a > b. theta in radians measure the angle of the * ellipse long axis with the x axis, in radians, and positive means * counter-clockwise. - * + * * Formulas from * https://en.wikipedia.org/wiki/Ellipse#In_Cartesian_coordinates */ From 67c8352974073d5b57d1c284dcac08b3980ca28b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 18:55:42 +0200 Subject: [PATCH 081/263] Temporary couple to imagej-mesh-io 0.1.3-SNAPSHOT. So that we can have reading from an input stream. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 0a2ff2a2f..dc19c4e65 100644 --- a/pom.xml +++ b/pom.xml @@ -179,6 +179,7 @@ net.imagej imagej-mesh-io + 0.1.3-SNAPSHOT From e5c9232445d805332aa35259d7600f8b3dab81d9 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 19:26:13 +0200 Subject: [PATCH 082/263] Serialization and deserialization of spot meshes to file. The meshes are saved in a ZIP file next to the TrackMate XML file. If the TrackMate file is called: trackmate-file.xml, then the file that stores the meshes for the model are in a zip file called trackmate-file.xml.meshes This zip file just contains the meshes as PLY files, named with the spots ID. If a spot has an ID equal to 1234, then the PLY file that stores its mesh is named 1234.ply in the zip file. If the zip file does not contain such a file then it means that this spot does not have a mesh. If there is not .meshes file, it means that no spot have a mesh. Possible improvement: read all meshes at once, instead of opening and closing the zip file for every spot. --- .../fiji/plugin/trackmate/io/TmXmlReader.java | 37 +++++++- .../fiji/plugin/trackmate/io/TmXmlWriter.java | 93 +++++++++++++++++-- 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 9f5faf46e..f2597ff21 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -91,10 +91,13 @@ import static fiji.plugin.trackmate.io.TmXmlKeys.TRACK_FILTER_COLLECTION_ELEMENT_KEY; import static fiji.plugin.trackmate.io.TmXmlKeys.TRACK_ID_ELEMENT_KEY; import static fiji.plugin.trackmate.io.TmXmlKeys.TRACK_NAME_ATTRIBUTE_NAME; +import static fiji.plugin.trackmate.io.TmXmlWriter.MESH_FILE_EXTENSION; +import static fiji.plugin.trackmate.io.TmXmlWriter.PLY_MESH_IO; import static fiji.plugin.trackmate.tracking.TrackerKeys.XML_ATTRIBUTE_TRACKER_NAME; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -104,6 +107,8 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import org.jdom2.Attribute; import org.jdom2.DataConversionException; @@ -123,6 +128,7 @@ import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase; import fiji.plugin.trackmate.features.FeatureFilter; @@ -147,6 +153,8 @@ import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; import ij.IJ; import ij.ImagePlus; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; public class TmXmlReader { @@ -179,6 +187,8 @@ public class TmXmlReader */ protected boolean ok = true; + private final File meshFile; + /* * CONSTRUCTORS */ @@ -189,6 +199,7 @@ public class TmXmlReader public TmXmlReader( final File file ) { this.file = file; + this.meshFile = new File( file.getAbsolutePath() + MESH_FILE_EXTENSION ); final SAXBuilder sb = new SAXBuilder(); Element r = null; try @@ -205,7 +216,7 @@ public TmXmlReader( final File file ) catch ( final IOException e ) { logger.error( "Problem reading " + file.getName() - + ".\nError message is:\n" + e.getLocalizedMessage() + '\n' ); + + ".\nError message is:\n" + e.getLocalizedMessage() + '\n' ); ok = false; } this.root = r; @@ -371,7 +382,6 @@ public Model getModel() ok = false; // Track features - try { final Map< Integer, Map< String, Double > > savedFeatureMap = readTrackFeatures( modelElement ); @@ -911,7 +921,6 @@ private SpotCollection getSpots( final Element modelElement ) final Map< Integer, Set< Spot > > content = new HashMap<>( frameContent.size() ); for ( final Element currentFrameContent : frameContent ) { - currentFrame = readIntAttribute( currentFrameContent, FRAME_ATTRIBUTE_NAME, logger ); final List< Element > spotContent = currentFrameContent.getChildren( SPOT_ELEMENT_KEY ); final Set< Spot > spotSet = new HashSet<>( spotContent.size() ); @@ -1168,6 +1177,28 @@ private Spot createSpotFrom( final Element spotEl ) } removeAttributeFromName( atts, ROI_N_POINTS_ATTRIBUTE_NAME ); + /* + * Try to read mesh if any and if we did not find a ROI. + */ + if ( roiNPoints <= 2 && meshFile.exists() ) + { + try (final ZipFile zipFile = new ZipFile( meshFile )) + { + final ZipEntry entry = zipFile.getEntry( ID + ".ply" ); + if ( entry != null ) + { + final InputStream is = zipFile.getInputStream( entry ); + final Mesh mesh = PLY_MESH_IO.open( is ); + final SpotMesh spotMesh = new SpotMesh( mesh, Meshes.boundingBox( mesh ) ); + spot.setMesh( spotMesh ); + } + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + } + /* * Read all other attributes -> features. */ diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index 79d0bf9ef..f001876a8 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -8,12 +8,12 @@ * 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 * . @@ -104,6 +104,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import org.jdom2.Attribute; import org.jdom2.Document; @@ -128,10 +130,21 @@ import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import gnu.trove.map.hash.TIntIntHashMap; +import gnu.trove.procedure.TIntIntProcedure; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.io.ply.PLYMeshIO; public class TmXmlWriter { + static final PLYMeshIO PLY_MESH_IO = new PLYMeshIO(); + + static final String MESH_FILE_EXTENSION = ".meshes"; + + /** Zip compression level (0-9) */ + private static final int COMPRESSION_LEVEL = 5; + /* * FIELD */ @@ -236,6 +249,8 @@ public void appendModel( final Model model ) modelElement.addContent( filteredTrackElement ); root.addContent( modelElement ); + + writeSpotMeshes( model.getSpots().iterable( false ) ); } /** @@ -695,11 +710,7 @@ protected Element echoAnalyzers( final Settings settings ) return analyzersElement; } - /* - * STATIC METHODS - */ - - private static final Element marshalSpot( final Spot spot, final FeatureModel fm ) + private final Element marshalSpot( final Spot spot, final FeatureModel fm ) { final Collection< Attribute > attributes = new ArrayList<>(); final Attribute IDattribute = new Attribute( SPOT_ID_ATTRIBUTE_NAME, "" + spot.ID() ); @@ -738,8 +749,74 @@ private static final Element marshalSpot( final Spot spot, final FeatureModel fm } spotElement.setText( str.toString() ); } - spotElement.setAttributes( attributes ); return spotElement; } + + protected void writeSpotMeshes( final Iterable< Spot > spots ) + { + // Only create the meshes file if at least one spot has a mesh. + boolean hasMesh = false; + for ( final Spot spot : spots ) + { + if ( spot.getMesh() != null ) + { + hasMesh = true; + break; + } + } + if ( !hasMesh ) + return; + + // Holder for map spot -> frame + final TIntIntHashMap frameMap = new TIntIntHashMap(); + + // Create zip output stream and write to it. + final File meshFile = new File( file.getAbsolutePath() + MESH_FILE_EXTENSION ); + logger.log( " Writing spot meshes to " + meshFile.getName() + "\n" ); + + try(final ZipOutputStream zos = new ZipOutputStream( new FileOutputStream( meshFile ) )) + { + zos.setMethod( ZipOutputStream.DEFLATED ) ; + zos.setLevel( COMPRESSION_LEVEL ); + + // Write spot meshes. + for ( final Spot spot : spots ) + { + if (spot.getMesh()!=null) + { + final Mesh mesh = spot.getMesh().mesh; + final byte[] bs = PLY_MESH_IO.writeBinary( mesh ); + + final String entryName = spot.ID() + ".ply"; + zos.putNextEntry( new ZipEntry( entryName ) ); + zos.write( bs ); + zos.closeEntry(); + + frameMap.put( spot.ID(), spot.getFeature( Spot.FRAME ).intValue() ); + } + } + + // Write dict text file. + final StringBuilder str = new StringBuilder(); + str.append( "frame,ID\n" ); + frameMap.forEachEntry( new TIntIntProcedure() + { + + @Override + public boolean execute( final int ID, final int t ) + { + str.append( String.format( "%d,%d\n", t, ID ) ); + return true; + } + }); + zos.putNextEntry( new ZipEntry( "mesh-info.txt" ) ); + zos.write( str.toString().getBytes() ); + } + catch ( final IOException e ) + { + logger.error( "Problem writing the mesh file:\n" + e.getMessage() ); + e.printStackTrace(); + } + } } From d157924b7fe297b33a7921737c0b5e7280e76b07 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 20:24:38 +0200 Subject: [PATCH 083/263] Read all spot meshes in one pass. --- .../fiji/plugin/trackmate/io/TmXmlReader.java | 77 +++++++++++++------ 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index f2597ff21..9e2c4fcb8 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -97,7 +97,6 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -107,7 +106,9 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.zip.ZipEntry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipException; import java.util.zip.ZipFile; import org.jdom2.Attribute; @@ -932,6 +933,56 @@ private SpotCollection getSpots( final Element modelElement ) } content.put( currentFrame, spotSet ); } + + // Do we have a mesh file? + if ( meshFile.exists() ) + { + // Matcher for zipped file name. + final String regex = "(\\d+)\\.ply"; + final Pattern pattern = Pattern.compile(regex); + // Iterate through entries. + try (final ZipFile zipFile = new ZipFile( meshFile )) + { + zipFile.stream().forEach( entry -> { + final String name = entry.getName(); + final Matcher matcher = pattern.matcher( name ); + if ( matcher.matches() ) + { + // Get corresponding spot. + final int id = Integer.parseInt( matcher.group( 1 ) ); + final Spot spot = cache.get( id ); + // Deserialize mesh. + try + { + final Mesh mesh = PLY_MESH_IO.open( zipFile.getInputStream( entry ) ); + final SpotMesh sm = new SpotMesh( mesh, Meshes.boundingBox( mesh ) ); + spot.setMesh( sm ); + } + catch ( final IOException e ) + { + ok = false; + logger.error( "Problem reading mesh for spot " + id + ":\n" + + e.getMessage() + '\n' ); + e.printStackTrace(); + } + } + + } ); + } + catch ( final ZipException e ) + { + ok = false; + logger.error( "Issues reading the mesh file:\n" + e.getMessage() + '\n' ); + e.printStackTrace(); + } + catch ( final IOException e ) + { + ok = false; + logger.error( "Issues reading the mesh file:\n" + e.getMessage() + '\n' ); + e.printStackTrace(); + } + } + final SpotCollection allSpots = SpotCollection.fromMap( content ); return allSpots; } @@ -1177,28 +1228,6 @@ private Spot createSpotFrom( final Element spotEl ) } removeAttributeFromName( atts, ROI_N_POINTS_ATTRIBUTE_NAME ); - /* - * Try to read mesh if any and if we did not find a ROI. - */ - if ( roiNPoints <= 2 && meshFile.exists() ) - { - try (final ZipFile zipFile = new ZipFile( meshFile )) - { - final ZipEntry entry = zipFile.getEntry( ID + ".ply" ); - if ( entry != null ) - { - final InputStream is = zipFile.getInputStream( entry ); - final Mesh mesh = PLY_MESH_IO.open( is ); - final SpotMesh spotMesh = new SpotMesh( mesh, Meshes.boundingBox( mesh ) ); - spot.setMesh( spotMesh ); - } - } - catch ( final IOException e ) - { - e.printStackTrace(); - } - } - /* * Read all other attributes -> features. */ From 15ebfdecccf85c4c6143d72440b6dd648bcbccb1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 20:41:28 +0200 Subject: [PATCH 084/263] Recompute face normals after loading mesh. They are not saved not retrieved by the PLY file reader. Should they? --- src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 9e2c4fcb8..c0f8b2892 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -156,6 +156,7 @@ import ij.ImagePlus; import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; +import net.imagej.mesh.nio.BufferMesh; public class TmXmlReader { @@ -954,7 +955,9 @@ private SpotCollection getSpots( final Element modelElement ) // Deserialize mesh. try { - final Mesh mesh = PLY_MESH_IO.open( zipFile.getInputStream( entry ) ); + final Mesh m = PLY_MESH_IO.open( zipFile.getInputStream( entry ) ); + final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() ); + Meshes.calculateNormals( m, mesh ); final SpotMesh sm = new SpotMesh( mesh, Meshes.boundingBox( mesh ) ); spot.setMesh( sm ); } From 4fb0e5983ffa2b1cbf96ce1781b5b4e9d9996f9e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 22 Apr 2023 04:35:47 +0200 Subject: [PATCH 085/263] More utility methods to sort Trove arrays. --- .../trackmate/util/mesh/SortArrays.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java index 9cdf73d92..d9d7a2711 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java @@ -1,9 +1,11 @@ package fiji.plugin.trackmate.util.mesh; import java.util.BitSet; +import java.util.Comparator; import java.util.Random; import gnu.trove.list.array.TDoubleArrayList; +import gnu.trove.list.array.TLongArrayList; /** * Utilities to sort a Trove list and return the sorting index to sort other @@ -108,6 +110,59 @@ private static void exch( final TDoubleArrayList a, final int[] index, final int index[ j ] = b; } + /* + * Sorting index arrays with a comparator. + */ + + public static void quicksort( final TLongArrayList main, final Comparator< Long > c ) + { + final int[] index = new int[ main.size() ]; + for ( int i = 0; i < index.length; i++ ) + index[ i ] = i; + quicksort( main, 0, main.size(), c ); + } + + private static void quicksort( final TLongArrayList a, final int left, final int right, final Comparator< Long > c ) + { + if ( right <= left ) + return; + final int i = partition( a, left, right, c ); + quicksort( a, left, i - 1, c ); + quicksort( a, i + 1, right, c ); + } + + // partition a[left] to a[right], assumes left < right + private static int partition( final TLongArrayList a, + final int left, final int right, final Comparator< Long > c ) + { + int i = left - 1; + int j = right; + while ( true ) + { + while ( less( a.getQuick( ++i ), a.getQuick( right ) ) ); + while ( less( a.getQuick( right ), a.getQuick( --j ) ) ) + if ( j == left ) + break; // don't go out-of-bounds + if ( i >= j ) + break; // check if pointers cross + exch( a, i, j ); // swap two elements into place + } + exch( a, i, right ); // swap with partition element + return i; + } + + // exchange a[i] and a[j] + private static void exch( final TLongArrayList a, final int i, final int j ) + { + final long swap = a.getQuick( i ); + a.setQuick( i, a.getQuick( j ) ); + a.setQuick( j, swap ); + } + + /* + * Main. + */ + public static void main( final String[] args ) { final Random ran = new Random( 1l ); @@ -141,4 +196,5 @@ public static void main( final String[] args ) System.out.println(); } + } From 0ecf26d7e7824a43db3ef0f8779bdab9ec09aa8f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 22 Apr 2023 04:37:03 +0200 Subject: [PATCH 086/263] WIP: Rework the Z-slicing of meshes. Try to build actual contours across Z sections of the object, reusing the ray casting things we have for the iteration. Very preliminary and has cases where it does not work. Also has a lot of room for optimization and refactoring. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 462 +++++++++++++++++- .../hyperstack/PaintSpotMesh.java | 84 ++-- .../hyperstack/PaintSpotRoi.java | 5 +- 3 files changed, 504 insertions(+), 47 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 28cb73e30..46c400fb5 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -1,6 +1,18 @@ package fiji.plugin.trackmate; +import java.awt.geom.Point2D; +import java.awt.geom.Point2D.Double; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; + +import fiji.plugin.trackmate.util.mesh.MeshUtils; +import fiji.plugin.trackmate.util.mesh.RayCastingX; +import gnu.trove.iterator.TIntIterator; import gnu.trove.list.array.TDoubleArrayList; +import gnu.trove.list.array.TLongArrayList; +import gnu.trove.list.linked.TDoubleLinkedList; +import gnu.trove.set.hash.TIntHashSet; import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; import net.imagej.mesh.Triangles; @@ -78,9 +90,9 @@ public static Spot createSpot( final Mesh mesh, final double quality ) * * @return the radius in physical units. */ - public static final double radius(final Mesh mesh) + public static final double radius( final Mesh mesh ) { - return Math.pow( 3. * volume(mesh) / ( 4 * Math.PI ), 1. / 3. ); + return Math.pow( 3. * volume( mesh ) / ( 4 * Math.PI ), 1. / 3. ); } /** @@ -146,7 +158,7 @@ public double size() } @Override - public void scale(final double alpha) + public void scale( final double alpha ) { final Vertices vertices = mesh.vertices(); final long nVertices = vertices.size(); @@ -165,7 +177,7 @@ public void scale(final double alpha) vertices.setPositionf( v, 0f, 0f, ( float ) ( z * alpha ) ); continue; } - final double r = Math.sqrt( x * x + y * y + z * z ) ; + final double r = Math.sqrt( x * x + y * y + z * z ); final double theta = Math.acos( z / r ); final double phi = Math.signum( y ) * Math.acos( x / Math.sqrt( x * x + y * y ) ); @@ -178,19 +190,212 @@ public void scale(final double alpha) boundingBox = Meshes.boundingBox( mesh ); } - public void slice( final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) + public List< TDoubleLinkedList[] > slice( final double z ) { - slice( mesh, z, cx, cy ); + return slice2( mesh, z ); } - public static void slice( final Mesh mesh, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) + public static List< TDoubleLinkedList[] > slice2( final Mesh mesh, final double z ) { - // Clear contour holders. - cx.resetQuick(); - cy.resetQuick(); + final double resolution = 1; // FIXME. final Triangles triangles = mesh.triangles(); final Vertices vertices = mesh.vertices(); + final TLongArrayList intersecting = new TLongArrayList(); + for ( long f = 0; f < triangles.size(); f++ ) + { + final long v0 = triangles.vertex0( f ); + final long v1 = triangles.vertex1( f ); + final long v2 = triangles.vertex2( f ); + final double minZ = minZ( vertices, v0, v1, v2 ); + if ( minZ > z ) + continue; + final double maxZ = maxZ( vertices, v0, v1, v2 ); + if ( maxZ < z ) + continue; + + intersecting.add( f ); + } + + // Holder for ray-casting results. + final TDoubleArrayList xs = new TDoubleArrayList(); + final TDoubleArrayList normals = new TDoubleArrayList(); + final List< TDoubleLinkedList[] > contours = new ArrayList<>(); + + // Adding mesh entries and exits to contours. + final TDoubleLinkedList exits = new TDoubleLinkedList(); + final TDoubleLinkedList entries = new TDoubleLinkedList(); + + // Set of contours that are still active (it is still ok to + // add points to them). + final TIntHashSet activeContours = new TIntHashSet(); // lol + + // What contours to remove from the active set. + final TIntHashSet removeFromActive = new TIntHashSet(); + + final float[] bb = Meshes.boundingBox( mesh ); + final RayCastingX ray = new RayCastingX( mesh ); + for ( double y = bb[ 1 ]; y <= bb[ 4 ]; y += resolution ) + { + ray.cast( y, z, xs, normals ); + if ( xs.isEmpty() ) + continue; + + if ( contours.isEmpty() ) + { + // Initializing. + for ( int i = 0; i < xs.size(); i++ ) + { + final double x = xs.getQuick( i ); + if ( normals.getQuick( i ) < 0. ) + { + // Entry: new contour. + final TDoubleLinkedList cx = new TDoubleLinkedList(); + final TDoubleLinkedList cy = new TDoubleLinkedList(); + cx.add( x ); + cy.add( y ); + contours.add( new TDoubleLinkedList[] { cx, cy } ); + activeContours.add( contours.size() - 1 ); + } + else + { + // Exit: add it to the end of an existing one if we have + // it. + if ( contours.isEmpty() ) + { + final TDoubleLinkedList cx = new TDoubleLinkedList(); + final TDoubleLinkedList cy = new TDoubleLinkedList(); + cx.add( x ); + cy.add( y ); + contours.add( new TDoubleLinkedList[] { cx, cy } ); + activeContours.add( contours.size() - 1 ); + } + else + { + final TDoubleLinkedList[] contour = contours.get( contours.size() - 1 ); + contour[ 0 ].add( x ); + contour[ 1 ].add( y ); + } + } + } + } + else + { + // Find to what contour to add it. Criterion: nearest. + + /* + * It's a tracking problem. We want to link a set of X position + * (mesh borders) to contour tails and heads. X positions that + * are not matched indicate a new contour should be created. + * + * We assume that there is no global disappearance of mesh + * entries. That is: there is not situation where all contours + * suddenly stops and another entry and exits appear away. + */ + + removeFromActive.clear(); + removeFromActive.addAll( activeContours ); + + entries.clear(); + exits.clear(); + for ( int j = 0; j < xs.size(); j++ ) + { + final double x = xs.get( j ); + if ( normals.get( j ) < 0 ) + entries.add( x ); + else + exits.add( x ); + } + + // Find suitable entries for contours. We only iterate over + // active contours. + final TIntIterator it = activeContours.iterator(); + while ( it.hasNext() ) + { + final int i = it.next(); + final TDoubleLinkedList cx = contours.get( i )[0]; + final TDoubleLinkedList cy = contours.get( i )[1]; + + // Entries. + double minDist = java.lang.Double.POSITIVE_INFINITY; + int bestEntry = -1; + for ( int j = 0; j < entries.size(); j++ ) + { + final double x = entries.get( j ); + final double d = Math.abs( x - cx.get( 0 ) ); + if ( d < minDist ) + { + minDist = d; + bestEntry = j; + } + } + if ( bestEntry >= 0 ) + { + cx.insert( 0, entries.get( bestEntry ) ); + cy.insert( 0, y ); + entries.removeAt( bestEntry ); + removeFromActive.remove( i ); // mark contour as active. + } + + // Exits. + minDist = java.lang.Double.POSITIVE_INFINITY; + int bestExit = -1; + for ( int j = 0; j < exits.size(); j++ ) + { + final double x = exits.get( j ); + final double d = Math.abs( x - cx.get( cx.size() - 1 ) ); + if ( d < minDist ) + { + minDist = d; + bestExit = j; + } + } + if ( bestExit >= 0 ) + { + cx.add( exits.get( bestExit ) ); + cy.add( y ); + exits.removeAt( bestExit ); + removeFromActive.remove( i ); // mark contour as active. + } + } + + // Do we still have entries and exits without a contour? + if ( !entries.isEmpty() || !exits.isEmpty() ) + { + // -> create one for them. + for ( int i = 0; i < Math.max( entries.size(), exits.size() ); i++ ) + { + final TDoubleLinkedList cx = new TDoubleLinkedList(); + final TDoubleLinkedList cy = new TDoubleLinkedList(); + if ( i < entries.size() ) + { + cx.add( entries.get( i ) ); + cy.add( y ); + } + if ( i < exits.size() ) + { + cx.add( exits.get( i ) ); + cy.add( y ); + } + contours.add( new TDoubleLinkedList[] { cx, cy } ); + activeContours.add( contours.size() - 1 ); + } + } + + // Do we have contours that did not receive a entry or an exit? + if ( !removeFromActive.isEmpty() ) + activeContours.removeAll( removeFromActive ); + + } + } + return contours; + } + + public static List< TDoubleLinkedList[] > slice( final Mesh mesh, final double z ) + { + final Triangles triangles = mesh.triangles(); + final Vertices vertices = mesh.vertices(); + final TLongArrayList intersecting = new TLongArrayList(); for ( long f = 0; f < triangles.size(); f++ ) { final long v0 = triangles.vertex0( f ); @@ -203,9 +408,206 @@ public static void slice( final Mesh mesh, final double z, final TDoubleArrayLis final double maxZ = maxZ( vertices, v0, v1, v2 ); if ( maxZ < z ) continue; + if ( minZ == maxZ ) + continue; // parallel. + + intersecting.add( f ); + } - triangleIntersection( vertices, v0, v1, v2, z, cx, cy ); + final ArrayDeque< Point2D.Double[] > segments = new ArrayDeque<>(); + for ( int i = 0; i < intersecting.size(); i++ ) + { + final long id = intersecting.getQuick( i ); + final Point2D.Double[] endPoints = triangleIntersection( mesh, id, z ); + if ( endPoints != null && endPoints[ 0 ] != null && endPoints[ 1 ] != null ) + { + final Double a = endPoints[ 0 ]; + final Double b = endPoints[ 1 ]; + if ( a.x == b.x && a.y == b.y ) + continue; + + segments.add( endPoints ); + } } + + final List< TDoubleLinkedList[] > contours = new ArrayList<>(); + SEGMENT: while ( !segments.isEmpty() ) + { + final Double[] segment = segments.pop(); + final Double a = segment[ 0 ]; + final Double b = segment[ 1 ]; + + // What contour does it belong to? + for ( final TDoubleLinkedList[] contour : contours ) + { + final TDoubleLinkedList x = contour[ 0 ]; + final TDoubleLinkedList y = contour[ 1 ]; + + // Test if connects to first point of the contour. + final double xstart = x.get( 0 ); + final double ystart = y.get( 0 ); + if ( a.x == xstart && a.y == ystart ) + { + // Insert other extremity just before the first point. + x.insert( 0, b.x ); + y.insert( 0, b.y ); + continue SEGMENT; + } + else if ( b.x == xstart && b.y == ystart ) + { + x.insert( 0, a.x ); + y.insert( 0, a.y ); + continue SEGMENT; + } + + // Test if connects to first point of the contour. + final double xend = x.get( x.size() - 1 ); + final double yend = y.get( y.size() - 1 ); + if ( a.x == xend && a.y == yend ) + { + // Add other extremity at the end. + x.add( b.x ); + y.add( b.y ); + continue SEGMENT; + } + else if ( b.x == xend && b.y == yend ) + { + // Add other extremity at the end. + x.add( a.x ); + y.add( a.y ); + continue SEGMENT; + } + } + + /* + * It does not belong to a contour. Make a new one. + */ + + final TDoubleLinkedList x = new TDoubleLinkedList(); + final TDoubleLinkedList y = new TDoubleLinkedList(); + x.add( a.x ); + x.add( b.x ); + y.add( a.y ); + y.add( b.y ); + contours.add( new TDoubleLinkedList[] { x, y } ); + } + + System.out.println( "Found " + contours.size() + " contours:" ); // DEBUG + for ( int i = 0; i < contours.size(); i++ ) + { + System.out.println( "- Contour " + ( i + 1 ) ); // DEBUG + final TDoubleLinkedList[] contour = contours.get( i ); + final TDoubleLinkedList x = contour[ 0 ]; + for ( int j = 0; j < x.size(); j++ ) + System.out.print( String.format( "%3.0f, ", x.get( j ) ) ); + System.out.println(); + final TDoubleLinkedList y = contour[ 1 ]; + for ( int j = 0; j < y.size(); j++ ) + System.out.print( String.format( "%3.0f, ", y.get( j ) ) ); + System.out.println(); + } + + return contours; + } + + private static Double[] triangleIntersection( final Mesh mesh, final long id, final double z ) + { + final long v0 = mesh.triangles().vertex0( id ); + final long v1 = mesh.triangles().vertex1( id ); + final long v2 = mesh.triangles().vertex2( id ); + + final double x0 = mesh.vertices().x( v0 ); + final double x1 = mesh.vertices().x( v1 ); + final double x2 = mesh.vertices().x( v2 ); + final double y0 = mesh.vertices().y( v0 ); + final double y1 = mesh.vertices().y( v1 ); + final double y2 = mesh.vertices().y( v2 ); + final double z0 = mesh.vertices().z( v0 ); + final double z1 = mesh.vertices().z( v1 ); + final double z2 = mesh.vertices().z( v2 ); + + Double a = null; + Double b = null; + + if ( z0 == z ) + a = new Double( x0, y0 ); + + if ( z1 == z ) + { + if ( a == null ) + { + a = new Double( x1, y1 ); + } + else + { + b = new Double( x1, y1 ); + return new Double[] { a, b }; + } + } + if ( z2 == z ) + { + if ( a == null ) + { + a = new Double( x2, y2 ); + } + else + { + b = new Double( x2, y2 ); + return new Double[] { a, b }; + } + } + + final Double p01 = edgeIntersection( x0, y0, z0, x1, y1, z1, z ); + if ( p01 != null ) + { + if ( a == null ) + { + a = p01; + } + else + { + b = p01; + return new Double[] { a, b }; + } + } + + final Double p02 = edgeIntersection( x0, y0, z0, x2, y2, z2, z ); + if ( p02 != null ) + { + if ( a == null ) + { + a = p02; + } + else + { + b = p02; + return new Double[] { a, b }; + } + } + + final Double p12 = edgeIntersection( x1, y1, z1, x2, y2, z2, z ); + if ( p12 != null ) + { + if ( a == null ) + { + a = p12; + } + else + { + b = p12; + return new Double[] { a, b }; + } + } + +// throw new IllegalStateException( "Could not find an intersection for triangle " + id ); + + System.out.println(); // DEBUG + System.out.println( "Weird triangle: " + MeshUtils.triangleToString( mesh, id ) ); // DEBUG + final double minZ = minZ( mesh.vertices(), v0, v1, v2 ); + final double maxZ = maxZ( mesh.vertices(), v0, v1, v2 ); + System.out.println( "but minZ=" + minZ + " maxZ=" + maxZ + " and z=" + z + " - equal? " + ( minZ == maxZ ) ); // DEBUG + + return null; } /** @@ -263,6 +665,19 @@ private static void addSegmentToContour( final Vertices vertices, final long v0, cy.add( y1 ); } + private static Double edgeIntersection( final double xs, final double ys, final double zs, + final double xt, final double yt, final double zt, final double z ) + { + if ( ( zs > z && zt > z ) || ( zs < z && zt < z ) ) + return null; + + assert ( zs != zt ); + final double t = ( z - zs ) / ( zt - zs ); + final double x = xs + t * ( xt - xs ); + final double y = ys + t * ( yt - ys ); + return new Double( x, y ); + } + private static void addEdgeIntersectionToContour( final Vertices vertices, final long sv, @@ -333,5 +748,30 @@ private static final double maxZ( final Vertices vertices, final long v0, final return Math.max( vertices.z( v0 ), Math.max( vertices.z( v1 ), vertices.z( v2 ) ) ); } + private static final double minY( final Vertices vertices, final Triangles triangles, final long id ) + { + final long v0 = triangles.vertex0( id ); + final long v1 = triangles.vertex1( id ); + final long v2 = triangles.vertex2( id ); + return Math.min( vertices.y( v0 ), Math.min( vertices.y( v1 ), vertices.y( v2 ) ) ); + } + + private static final double maxY( final Vertices vertices, final Triangles triangles, final long id ) + { + final long v0 = triangles.vertex0( id ); + final long v1 = triangles.vertex1( id ); + final long v2 = triangles.vertex2( id ); + return Math.max( vertices.y( v0 ), Math.max( vertices.y( v1 ), vertices.y( v2 ) ) ); + } + + private static final double minY( final Vertices vertices, final long v0, final long v1, final long v2 ) + { + return Math.min( vertices.y( v0 ), Math.min( vertices.y( v1 ), vertices.y( v2 ) ) ); + } + + private static final double maxY( final Vertices vertices, final long v0, final long v1, final long v2 ) + { + return Math.max( vertices.y( v0 ), Math.max( vertices.y( v1 ), vertices.y( v2 ) ) ); + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 2e3f35158..e62ea9645 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -1,14 +1,17 @@ package fiji.plugin.trackmate.visualization.hyperstack; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.Path2D; import java.awt.geom.Path2D.Double; +import java.util.List; +import java.util.Random; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import gnu.trove.list.array.TDoubleArrayList; +import gnu.trove.list.linked.TDoubleLinkedList; /** * Utility class to paint the {@link SpotMesh} component of spots. @@ -23,18 +26,12 @@ public class PaintSpotMesh private final DisplaySettings displaySettings; - private final TDoubleArrayList cx; - - private final TDoubleArrayList cy; - private final Double polygon; public PaintSpotMesh( final double[] calibration, final DisplaySettings displaySettings ) { this.calibration = calibration; this.displaySettings = displaySettings; - this.cx = new TDoubleArrayList(); - this.cy = new TDoubleArrayList(); this.polygon = new Path2D.Double(); } @@ -65,41 +62,60 @@ public int paint( } // Slice. - mesh.slice( dz, cx, cy ); - // Scale to screen coordinates. - for ( int i = 0; i < cx.size(); i++ ) + final List< TDoubleLinkedList[] > contours = mesh.slice( dz ); + for ( final TDoubleLinkedList[] contour : contours ) { - // Pixel coords. - final double xc = ( cx.get( i ) ) / calibration[ 0 ] + 0.5; - final double yc = ( cy.get( i ) ) / calibration[ 1 ] + 0.5; - // Window coords. - cx.set( i, ( xc - xcorner ) * magnification ); - cy.set( i, ( yc - ycorner ) * magnification ); + final TDoubleLinkedList cxs = contour[ 0 ]; + final TDoubleLinkedList cys = contour[ 1 ]; + // Scale to screen coordinates. + for ( int i = 0; i < cxs.size(); i++ ) + { + // Pixel coords. + final double xc = ( cxs.get( i ) ) / calibration[ 0 ] + 0.5; + final double yc = ( cys.get( i ) ) / calibration[ 1 ] + 0.5; + // Window coords. + cxs.set( i, ( xc - xcorner ) * magnification ); + cys.set( i, ( yc - ycorner ) * magnification ); + } } - polygon.reset(); - for ( int i = 0; i < cx.size() - 1; i += 2 ) + final Random ran = new Random( 1l ); + g2d.setStroke( new BasicStroke( 2f ) ); + for ( final TDoubleLinkedList[] contour : contours ) { - final double x0 = cx.get( i ); - final double x1 = cx.get( i + 1 ); - final double y0 = cy.get( i ); - final double y1 = cy.get( i + 1 ); - polygon.moveTo( x0, y0 ); - polygon.lineTo( x1, y1 ); + final TDoubleLinkedList cxs = contour[ 0 ]; + final TDoubleLinkedList cys = contour[ 1 ]; + if ( cxs.size() < 2 ) + continue; + + polygon.reset(); + polygon.moveTo( cxs.get( 0 ), cys.get( 0 ) ); + for ( int i = 1; i < cxs.size() - 1; i += 2 ) + polygon.lineTo( cxs.get( i ), cys.get( i ) ); + polygon.closePath(); + + g2d.setColor( new Color( + 0.5f * ( 1f + ran.nextFloat() ), + 0.5f * ( 1f + ran.nextFloat() ), + 0.5f * ( 1f + ran.nextFloat() ) ) ); + if ( displaySettings.isSpotFilled() ) + { + g2d.fill( polygon ); + g2d.setColor( Color.BLACK ); + g2d.draw( polygon ); + } + else + { + g2d.draw( polygon ); + } } - if ( displaySettings.isSpotFilled() ) + int textPos = -1; + for ( final TDoubleLinkedList[] contour : contours ) { - g2d.fill( polygon ); - g2d.setColor( Color.BLACK ); - g2d.draw( polygon ); + final TDoubleLinkedList cxs = contour[ 0 ]; + textPos = Math.max( textPos, ( int ) ( PaintSpotRoi.max( cxs ) - xs ) ); } - else - { - g2d.draw( polygon ); - } - - final int textPos = ( int ) ( PaintSpotRoi.max( cx ) - xs ); return textPos; } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java index 0e864894d..1366edee8 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java @@ -6,6 +6,7 @@ import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import gnu.trove.list.TDoubleList; import gnu.trove.list.array.TDoubleArrayList; /** @@ -94,12 +95,12 @@ public int paint( return textPos; } - static final double max( final TDoubleArrayList l ) + static final double max( final TDoubleList l ) { double max = Double.NEGATIVE_INFINITY; for ( int i = 0; i < l.size(); i++ ) { - final double v = l.getQuick( i ); + final double v = l.get( i ); if ( v > max ) max = v; } From 17584f703f8ce0d44ae33f1f55e76a7baa955cf5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 22 Apr 2023 04:37:31 +0200 Subject: [PATCH 087/263] Tweak interactive tests. When everything is bugged, they are important. --- .../plugin/trackmate/mesh/Demo3DMesh.java | 23 ++++++--- .../plugin/trackmate/mesh/DemoContour.java | 49 +++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 6b964e9c8..1e8539ec9 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -1,6 +1,7 @@ package fiji.plugin.trackmate.mesh; import java.awt.Color; +import java.io.FileWriter; import java.io.IOException; import java.util.Iterator; @@ -19,6 +20,7 @@ import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; import net.imagej.mesh.Vertices; +import net.imagej.mesh.io.ply.PLYMeshIO; import net.imagej.mesh.io.stl.STLMeshIO; import net.imagej.mesh.naive.NaiveDoubleMesh; import net.imagej.mesh.naive.NaiveDoubleMesh.Triangles; @@ -79,17 +81,17 @@ public static void main( final String[] args ) // final Mesh simplified = debugMesh( new long[] { 0, 0, 0 }, region.dimensionsAsLongArray() ); // Wrap as mesh with edges. - System.out.println( "After simplification: " + mesh.vertices().size() + " vertices and " + simplified.triangles().size() + " faces." ); + System.out.println( "After simplification: " + simplified.vertices().size() + " vertices and " + simplified.triangles().size() + " faces." ); System.out.println(); // Scale and offset with physical coordinates. final double[] origin = region.minAsDoubleArray(); - scale( mesh.vertices(), cal, origin ); + scale( simplified.vertices(), cal, origin ); /* * IO. */ - testIO( mesh, ++j ); + testIO( simplified, ++j ); /* * Display. @@ -206,13 +208,22 @@ private static void toOverlay( final TDoubleArrayList cx, final TDoubleArrayList overlay.add( roi ); } - private static void testIO( final Mesh simplified, final int j ) + private static void testIO( final Mesh mesh, final int j ) { - final STLMeshIO meshIO = new STLMeshIO(); // Serialize to disk. try { - meshIO.save( simplified, String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ) ); + new STLMeshIO().save( mesh, String.format( "samples/mesh/io/STL_%02d.stl", j ) ); + + final PLYMeshIO plyio = new PLYMeshIO(); + plyio.save( mesh, String.format( "samples/mesh/io/PLY_%02d.ply", j ) ); + final byte[] bs = plyio.writeAscii( mesh ); + final String str = new String( bs ); + try (final FileWriter writer = new FileWriter( + String.format( "samples/mesh/io/PLYTEXT_%02d.txt", j ) )) + { + writer.write( str ); + } } catch ( final IOException e ) { diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java new file mode 100644 index 000000000..963e10175 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java @@ -0,0 +1,49 @@ +package fiji.plugin.trackmate.mesh; + +import java.io.File; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.io.TmXmlReader; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import ij.ImageJ; +import ij.ImagePlus; +import math.geom2d.polygon.Polygon2D; +import math.geom2d.polygon.Polygons2D; +import math.geom2d.polygon.SimplePolygon2D; + +public class DemoContour +{ + + public static void main3( final String[] args ) + { + final SimplePolygon2D a = new SimplePolygon2D( new double[] { 0, 2, 2 }, new double[] { 0, 0, 2 } ); + final SimplePolygon2D b = new SimplePolygon2D( new double[] { 0, 0, 2 }, new double[] { 0, 2, 0 } ); + final Polygon2D c = Polygons2D.union( a, b ); + System.out.println( c.area() ); // DEBUG + + } + + public static void main( final String[] args ) + { + ImageJ.main( args ); + final String filePath = "samples/mesh/Torus-mask.xml"; + final TmXmlReader reader = new TmXmlReader( new File( filePath ) ); + final Model model = reader.getModel(); + final ImagePlus imp = reader.readImage(); + imp.show(); + + final SelectionModel selection = new SelectionModel( model ); + final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); + final HyperStackDisplayer view = new HyperStackDisplayer( model, selection, imp, ds ); + view.render(); + + final Spot spot = model.getSpots().iterable( 0, true ).iterator().next(); + final SpotMesh sm = spot.getMesh(); + sm.slice( 12. ); + } +} From 28b1da2346cbb9d8ecc1efbe642cae7fa527327d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 27 Apr 2023 19:15:45 +0200 Subject: [PATCH 088/263] Trying to work with the in-development mesh Z-slicer --- .../fiji/plugin/trackmate/ZSlicerDemo.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java diff --git a/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java b/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java new file mode 100644 index 000000000..e1b053fc4 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java @@ -0,0 +1,142 @@ +package fiji.plugin.trackmate; + +import java.awt.Color; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Random; + +import fiji.plugin.trackmate.util.TMUtils; +import ij.IJ; +import ij.ImageJ; +import ij.ImagePlus; +import ij.gui.Overlay; +import ij.gui.PolygonRoi; +import ij.plugin.Duplicator; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.MeshConnectedComponents; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.ZSlicer; +import net.imagej.mesh.ZSlicer.Contour; +import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.type.numeric.RealType; +import net.imglib2.util.Cast; +import net.imglib2.util.Util; +import net.imglib2.view.Views; + +public class ZSlicerDemo +{ + public static < T extends RealType< T > > void main( final String[] args ) throws IOException, URISyntaxException + { + ImageJ.main( args ); + System.out.println( "Opening image." ); + final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + imp.show(); + final ImgPlus< T > img = Cast.unchecked( TMUtils.rawWraps( imp ) ); + final double[] pixelSizes = new double[] { + img.averageScale( img.dimensionIndex( Axes.X ) ), + img.averageScale( img.dimensionIndex( Axes.Y ) ), + img.averageScale( img.dimensionIndex( Axes.Z ) ) }; + System.out.println( Util.printCoordinates( pixelSizes ) ); + + // First channel is the smoothed version. + System.out.println( "Marching cube on grayscale." ); + final RandomAccessibleInterval< T > smoothed; + if ( img.dimensionIndex( Axes.CHANNEL ) >= 0 ) + smoothed = Views.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 0 ); + else + smoothed = img; + + final double isoLevel = 250; + final Mesh mesh1 = Meshes.marchingCubes( smoothed, isoLevel ); + final double z = 11.0; + runMesh( imp, mesh1, z, pixelSizes, filePath, "-grayscale" ); + + System.out.println( "Finished!" ); + } + + private static void runMesh( final ImagePlus imp, Mesh mesh, final double z, final double[] pixelSizes, final String filePath, final String suffix ) throws IOException + { + final ImagePlus out = new Duplicator().run( imp, 1, 1, ( int ) z + 1, ( int ) z + 1, 1, 1 ); + out.show(); + + System.out.println( "Before removing duplicates: " + mesh ); + mesh = Meshes.removeDuplicateVertices( mesh, 2 ); + System.out.println( "After removing duplicates: " + mesh ); + System.out.println( "Scaling." ); + Meshes.scale( mesh, pixelSizes ); + + System.out.println( "N connected components: " + Meshes.nConnectedComponents( mesh ) ); + System.out.println( "Splitting in connected components:" ); + int i = 0; + final Overlay overlay = new Overlay(); + out.setOverlay( overlay ); + final Random ran = new Random( 2l ); + for ( final BufferMesh cc : MeshConnectedComponents.iterable( mesh ) ) + { + i++; + System.out.println( " # " + i + ": " + cc ); +// new PLYMeshIO().save( cc, filePath + suffix + "-" + i + ".ply" ); + +// final Model model = new Model(); +// model.beginUpdate(); + try + { + final List< Contour > contours = ZSlicer.slice( cc, z ); + for ( final Contour contour : contours ) + { + + System.out.println( contour.x ); // DEBUG + System.out.println( contour.y ); // DEBUG + final float[] xp = new float[ contour.x.size() ]; + final float[] yp = new float[ xp.length ]; + for ( int j = 0; j < xp.length; j++ ) + { + xp[ j ] = ( float ) ( 0.5 + contour.x.getQuick( j ) / pixelSizes[ 0 ] ); + yp[ j ] = ( float ) ( 0.5 + contour.y.getQuick( j ) / pixelSizes[ 1 ] ); + } + final PolygonRoi roi = new PolygonRoi( xp, yp, PolygonRoi.POLYGON ); + roi.setStrokeColor( new Color( 0.5f * ( 1 + ran.nextFloat() ), + 0.5f * ( 1 + ran.nextFloat() ), + 0.5f * ( 1 + ran.nextFloat() ) ) ); + overlay.add( roi ); + System.out.println( roi ); // DEBUG + +// final Spot spot = SpotRoi.createSpot( contour.xScaled( 1. ), contour.yScaled( 1. ), 1. ); +// model.addSpotTo( spot, 0 ); + +// System.out.println( Util.printCoordinates( spot.getRoi().x ) ); // DEBUG +// System.out.println( Util.printCoordinates( spot ) ); // DEBUG + } +// model.getSpots().setVisible( true ); + } + finally + { +// model.endUpdate(); + } + +// final SelectionModel selectionModel = new SelectionModel( model ); +// final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); +// ds.setSpotDisplayedAsRoi( true ); +// final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, out, ds ); +// view.render(); + + break; + } +// System.out.println( "Simplifying to 10%:" ); +// i = 0; +// for ( final BufferMesh cc : MeshConnectedComponents.iterable( mesh ) ) +// { +// i++; +// final Mesh simplified = Meshes.simplify( cc, 0.1f, 10 ); +// System.out.println( " # " + i + ": " + simplified ); +// new PLYMeshIO().save( simplified, filePath + suffix + "-simplified-" + i + ".ply" ); +// } + + System.out.println(); + } +} From 3f2d8d4b5df326a0d3bedf270114e0745442449e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:33:05 +0200 Subject: [PATCH 089/263] When possible use the mearching cube on grayscale to generate meshes. --- .../plugin/trackmate/detection/MaskUtils.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index fb1b0d690..0a7b2a9a3 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -40,6 +40,7 @@ import net.imagej.axis.Axes; import net.imagej.axis.AxisType; import net.imagej.mesh.Mesh; +import net.imagej.mesh.MeshConnectedComponents; import net.imagej.mesh.Meshes; import net.imagej.mesh.Vertices; import net.imglib2.Cursor; @@ -448,6 +449,9 @@ public static final < T extends RealType< T >, S extends RealType< S > > List< S final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { + if ( input.numDimensions() == 3 ) + return from3DThresholdWithROI( input, interval, threshold, calibration, simplify, qualityImage ); + // Get labeling. final ImgLabeling< Integer, IntType > labeling = toLabeling( input, interval, threshold, numThreads ); @@ -460,6 +464,40 @@ else if ( input.numDimensions() == 3 ) throw new IllegalArgumentException( "Can only process 2D or 3D images with this method, but got " + labeling.numDimensions() + "D." ); } + private static < T extends RealType< T >, S extends RealType< S > > List< Spot > from3DThresholdWithROI( + final RandomAccessible< T > input, + final Interval interval, + final double threshold, + final double[] calibration, + final boolean simplify, + final RandomAccessibleInterval< S > qualityImage ) + { + Mesh mesh = Meshes.marchingCubes( Views.interval( input, interval ), threshold ); + mesh = Meshes.removeDuplicateVertices( mesh, 2 ); + Meshes.scale( mesh, calibration ); + + final List< Spot > spots = new ArrayList<>(); + for ( Mesh cc : MeshConnectedComponents.iterable( mesh ) ) + { + if ( simplify && cc.triangles().size() > 200 ) + cc = Meshes.simplify( cc, 0.1f, 10f ); + + final Spot spot = SpotMesh.createSpot( cc, 0. ); + final double quality; + if ( qualityImage == null ) + { + quality = spot.getMesh().volume(); + } + else + { + quality = 1.; // TODO + } + spot.putFeature( Spot.QUALITY, quality ); + spots.add( spot ); + } + return spots; + } + /** * Creates spots with ROIs from a 2D label image. The quality * value is read from a secondary image, by taking the max value in each From 9334c9c38edcee326f56ece81c49794690dbab23 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:33:31 +0200 Subject: [PATCH 090/263] Utility mother class to paint things. --- .../hyperstack/TrackMatePainter.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java new file mode 100644 index 000000000..0e647c187 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java @@ -0,0 +1,70 @@ +package fiji.plugin.trackmate.visualization.hyperstack; + +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import ij.gui.ImageCanvas; + +public abstract class TrackMatePainter +{ + + protected final double[] calibration; + + protected final DisplaySettings displaySettings; + + protected final ImageCanvas canvas; + + public TrackMatePainter( final ImageCanvas canvas, final double[] calibration, final DisplaySettings displaySettings ) + { + this.canvas = canvas; + this.calibration = calibration; + this.displaySettings = displaySettings; + } + + /** + * Converts a X position in physical units (possible um) to screen + * coordinates to be used with the graphics object. + * + * @param x + * the X position to convert. + * @return the screen X coordinate. + */ + public double toScreenX( final double x ) + { + final double xp = x / calibration[ 0 ] + 0.5; // pixel coords + return canvas.screenXD( xp ); + } + + /** + * Converts a Y position in physical units (possible um) to screen + * coordinates to be used with the graphics object. + * + * @param y + * the Y position to convert. + * @return the screen Y coordinate. + */ + public double toScreenY( final double y ) + { + final double yp = y / calibration[ 0 ] + 0.5; // pixel coords + return canvas.screenYD( yp ); + } + + /** + * Returns true of the point with the specified coordinates in + * physical units lays inside the painted window. + * + * @param x + * the X coordinate in physical unit. + * @param y + * the Y coordinate in physical unit. + * @return true if (x, y) is inside the painted window. + */ + public boolean isInside( final double x, final double y ) + { + final double xs = toScreenX( x ); + if ( xs < 0 || xs > canvas.getSrcRect().width ) + return false; + final double ys = toScreenY( y ); + if ( ys < 0 || ys > canvas.getSrcRect().height ) + return false; + return true; + } +} From ddab83448bd4c33e51f10b57baf85e703a476d72 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:33:46 +0200 Subject: [PATCH 091/263] Rework painting meshes a bit. --- .../hyperstack/PaintSpotMesh.java | 113 +++++++----------- .../visualization/hyperstack/SpotOverlay.java | 4 +- 2 files changed, 47 insertions(+), 70 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index e62ea9645..87427593c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -1,17 +1,16 @@ package fiji.plugin.trackmate.visualization.hyperstack; -import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.Path2D; -import java.awt.geom.Path2D.Double; import java.util.List; -import java.util.Random; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import gnu.trove.list.linked.TDoubleLinkedList; +import ij.gui.ImageCanvas; +import net.imagej.mesh.ZSlicer; +import net.imagej.mesh.ZSlicer.Contour; /** * Utility class to paint the {@link SpotMesh} component of spots. @@ -19,40 +18,40 @@ * @author Jean-Yves Tinevez * */ -public class PaintSpotMesh +public class PaintSpotMesh extends TrackMatePainter { - private final double[] calibration; + private final Path2D.Double polygon; - private final DisplaySettings displaySettings; - - private final Double polygon; - - public PaintSpotMesh( final double[] calibration, final DisplaySettings displaySettings ) + public PaintSpotMesh( final ImageCanvas canvas, final double[] calibration, final DisplaySettings displaySettings ) { - this.calibration = calibration; - this.displaySettings = displaySettings; + super( canvas, calibration, displaySettings ); this.polygon = new Path2D.Double(); } - public int paint( - final Graphics2D g2d, - final Spot spot, - final double zslice, - final double xs, - final double ys, - final int xcorner, - final int ycorner, - final double magnification ) + public int paint( final Graphics2D g2d, final Spot spot ) { + final SpotMesh sm = spot.getMesh(); + + // Don't paint if we are out of screen. + if ( toScreenX( sm.boundingBox[ 0 ] ) > canvas.getSrcRect().width ) + return -1; + if ( toScreenX( sm.boundingBox[ 3 ] ) < 0 ) + return -1; + if ( toScreenY( sm.boundingBox[ 1 ] ) > canvas.getSrcRect().height ) + return -1; + if ( toScreenY( sm.boundingBox[ 4 ] ) < 0 ) + return -1; + + // Z plane does not cross bounding box. final double x = spot.getFeature( Spot.POSITION_X ); final double y = spot.getFeature( Spot.POSITION_Y ); - final double z = spot.getFeature( Spot.POSITION_Z ); - final double dz = zslice; - - final SpotMesh mesh = spot.getMesh(); - if ( mesh.boundingBox[ 2 ] > dz || mesh.boundingBox[ 5 ] < dz ) + final double xs = toScreenX( x ); + final double ys = toScreenY( y ); + final double dz = ( canvas.getImage().getSlice() - 1 ) * calibration[ 2 ]; + if ( sm.boundingBox[ 2 ] > dz || sm.boundingBox[ 5 ] < dz ) { + final double magnification = canvas.getMagnification(); g2d.fillOval( ( int ) Math.round( xs - 2 * magnification ), ( int ) Math.round( ys - 2 * magnification ), @@ -61,43 +60,29 @@ public int paint( return -1; } - // Slice. - final List< TDoubleLinkedList[] > contours = mesh.slice( dz ); - for ( final TDoubleLinkedList[] contour : contours ) - { - final TDoubleLinkedList cxs = contour[ 0 ]; - final TDoubleLinkedList cys = contour[ 1 ]; - // Scale to screen coordinates. - for ( int i = 0; i < cxs.size(); i++ ) - { - // Pixel coords. - final double xc = ( cxs.get( i ) ) / calibration[ 0 ] + 0.5; - final double yc = ( cys.get( i ) ) / calibration[ 1 ] + 0.5; - // Window coords. - cxs.set( i, ( xc - xcorner ) * magnification ); - cys.set( i, ( yc - ycorner ) * magnification ); - } - } - - final Random ran = new Random( 1l ); - g2d.setStroke( new BasicStroke( 2f ) ); - for ( final TDoubleLinkedList[] contour : contours ) + final List< Contour > contours = ZSlicer.slice( sm.mesh, dz ); + double maxTextPos = Double.NEGATIVE_INFINITY; + for ( final Contour contour : contours ) { - final TDoubleLinkedList cxs = contour[ 0 ]; - final TDoubleLinkedList cys = contour[ 1 ]; - if ( cxs.size() < 2 ) + if ( contour.x.size() < 2 ) continue; polygon.reset(); - polygon.moveTo( cxs.get( 0 ), cys.get( 0 ) ); - for ( int i = 1; i < cxs.size() - 1; i += 2 ) - polygon.lineTo( cxs.get( i ), cys.get( i ) ); - polygon.closePath(); + final double x0 =toScreenX( contour.x.getQuick( 0 ) ); + final double y0 =toScreenY( contour.y.getQuick( 0 ) ); + polygon.moveTo( x0, y0 ); + if ( x0 > maxTextPos ) + maxTextPos = x0; - g2d.setColor( new Color( - 0.5f * ( 1f + ran.nextFloat() ), - 0.5f * ( 1f + ran.nextFloat() ), - 0.5f * ( 1f + ran.nextFloat() ) ) ); + for ( int i = 1; i < contour.x.size(); i++ ) + { + final double xi = toScreenX( contour.x.getQuick( i ) ); + final double yi = toScreenY( contour.y.getQuick( i ) ); + polygon.lineTo( xi, yi ); + if ( xi > maxTextPos ) + maxTextPos = xi; + } + polygon.closePath(); if ( displaySettings.isSpotFilled() ) { g2d.fill( polygon ); @@ -109,14 +94,6 @@ public int paint( g2d.draw( polygon ); } } - - int textPos = -1; - for ( final TDoubleLinkedList[] contour : contours ) - { - final TDoubleLinkedList cxs = contour[ 0 ]; - textPos = Math.max( textPos, ( int ) ( PaintSpotRoi.max( cxs ) - xs ) ); - } - return textPos; + return ( int ) ( maxTextPos - xs ); } - } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java index f818db6d9..a544c4c90 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -91,7 +91,7 @@ public SpotOverlay( final Model model, final ImagePlus imp, final DisplaySetting this.displaySettings = displaySettings; this.paintSpotSphere = new PaintSpotSphere( calibration, displaySettings ); this.paintSpotRoi = new PaintSpotRoi( calibration, displaySettings ); - this.paintSpotMesh = new PaintSpotMesh( calibration, displaySettings ); + this.paintSpotMesh = new PaintSpotMesh( imp.getCanvas(), calibration, displaySettings ); } /* @@ -277,7 +277,7 @@ else if ( roi != null ) } else { - textPos = paintSpotMesh.paint( g2d, spot, zslice, xs, ys, xcorner, ycorner, magnification ); + textPos = paintSpotMesh.paint( g2d, spot ); } if ( textPos >= 0 && displaySettings.isSpotShowName() ) From 4d7f3267827b9b3623feb7a9fdf9e7b4e0a5d7a6 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:34:00 +0200 Subject: [PATCH 092/263] Update interactive tests. --- .../fiji/plugin/trackmate/ZSlicerDemo.java | 144 ++++++++++-------- .../trackmate/mesh/Demo3DMeshTrackMate.java | 3 +- 2 files changed, 79 insertions(+), 68 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java b/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java index e1b053fc4..2c8453ac0 100644 --- a/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java +++ b/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java @@ -1,17 +1,18 @@ package fiji.plugin.trackmate; -import java.awt.Color; import java.io.IOException; import java.net.URISyntaxException; import java.util.List; import java.util.Random; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; import ij.gui.Overlay; -import ij.gui.PolygonRoi; import ij.plugin.Duplicator; import net.imagej.ImgPlus; import net.imagej.axis.Axes; @@ -31,32 +32,39 @@ public class ZSlicerDemo { public static < T extends RealType< T > > void main( final String[] args ) throws IOException, URISyntaxException { - ImageJ.main( args ); - System.out.println( "Opening image." ); - final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; - final ImagePlus imp = IJ.openImage( filePath ); - imp.show(); - final ImgPlus< T > img = Cast.unchecked( TMUtils.rawWraps( imp ) ); - final double[] pixelSizes = new double[] { - img.averageScale( img.dimensionIndex( Axes.X ) ), - img.averageScale( img.dimensionIndex( Axes.Y ) ), - img.averageScale( img.dimensionIndex( Axes.Z ) ) }; - System.out.println( Util.printCoordinates( pixelSizes ) ); - - // First channel is the smoothed version. - System.out.println( "Marching cube on grayscale." ); - final RandomAccessibleInterval< T > smoothed; - if ( img.dimensionIndex( Axes.CHANNEL ) >= 0 ) - smoothed = Views.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 0 ); - else - smoothed = img; - - final double isoLevel = 250; - final Mesh mesh1 = Meshes.marchingCubes( smoothed, isoLevel ); - final double z = 11.0; - runMesh( imp, mesh1, z, pixelSizes, filePath, "-grayscale" ); - - System.out.println( "Finished!" ); + try + { + ImageJ.main( args ); + System.out.println( "Opening image." ); + final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + imp.show(); + final ImgPlus< T > img = Cast.unchecked( TMUtils.rawWraps( imp ) ); + final double[] pixelSizes = new double[] { + img.averageScale( img.dimensionIndex( Axes.X ) ), + img.averageScale( img.dimensionIndex( Axes.Y ) ), + img.averageScale( img.dimensionIndex( Axes.Z ) ) }; + System.out.println( Util.printCoordinates( pixelSizes ) ); + + // First channel is the smoothed version. + System.out.println( "Marching cube on grayscale." ); + final RandomAccessibleInterval< T > smoothed; + if ( img.dimensionIndex( Axes.CHANNEL ) >= 0 ) + smoothed = Views.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 0 ); + else + smoothed = img; + + final double isoLevel = 250; + final Mesh mesh1 = Meshes.marchingCubes( smoothed, isoLevel ); + final double z = 11.0; + runMesh( imp, mesh1, z, pixelSizes, filePath, "-grayscale" ); + + System.out.println( "Finished!" ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } } private static void runMesh( final ImagePlus imp, Mesh mesh, final double z, final double[] pixelSizes, final String filePath, final String suffix ) throws IOException @@ -76,57 +84,47 @@ private static void runMesh( final ImagePlus imp, Mesh mesh, final double z, fin final Overlay overlay = new Overlay(); out.setOverlay( overlay ); final Random ran = new Random( 2l ); - for ( final BufferMesh cc : MeshConnectedComponents.iterable( mesh ) ) + + final Model model = new Model(); + model.beginUpdate(); + try { - i++; - System.out.println( " # " + i + ": " + cc ); -// new PLYMeshIO().save( cc, filePath + suffix + "-" + i + ".ply" ); -// final Model model = new Model(); -// model.beginUpdate(); - try + for ( final BufferMesh cc : MeshConnectedComponents.iterable( mesh ) ) { + i++; + System.out.println( " # " + i + ": " + cc ); +// new PLYMeshIO().save( cc, filePath + suffix + "-" + i + ".ply" ); + final List< Contour > contours = ZSlicer.slice( cc, z ); for ( final Contour contour : contours ) { - - System.out.println( contour.x ); // DEBUG - System.out.println( contour.y ); // DEBUG - final float[] xp = new float[ contour.x.size() ]; - final float[] yp = new float[ xp.length ]; - for ( int j = 0; j < xp.length; j++ ) - { - xp[ j ] = ( float ) ( 0.5 + contour.x.getQuick( j ) / pixelSizes[ 0 ] ); - yp[ j ] = ( float ) ( 0.5 + contour.y.getQuick( j ) / pixelSizes[ 1 ] ); - } - final PolygonRoi roi = new PolygonRoi( xp, yp, PolygonRoi.POLYGON ); - roi.setStrokeColor( new Color( 0.5f * ( 1 + ran.nextFloat() ), - 0.5f * ( 1 + ran.nextFloat() ), - 0.5f * ( 1 + ran.nextFloat() ) ) ); - overlay.add( roi ); - System.out.println( roi ); // DEBUG - -// final Spot spot = SpotRoi.createSpot( contour.xScaled( 1. ), contour.yScaled( 1. ), 1. ); -// model.addSpotTo( spot, 0 ); +// final float[] xp = new float[ contour.x.size() ]; +// final float[] yp = new float[ xp.length ]; +// for ( int j = 0; j < xp.length; j++ ) +// { +// xp[ j ] = ( float ) ( 0.5 + contour.x.getQuick( j ) / pixelSizes[ 0 ] ); +// yp[ j ] = ( float ) ( 0.5 + contour.y.getQuick( j ) / pixelSizes[ 1 ] ); +// } +// final PolygonRoi roi = new PolygonRoi( xp, yp, PolygonRoi.POLYGON ); +// roi.setStrokeColor( new Color( 0.5f * ( 1 + ran.nextFloat() ), +// 0.5f * ( 1 + ran.nextFloat() ), +// 0.5f * ( 1 + ran.nextFloat() ) ) ); +// overlay.add( roi ); +// System.out.println( roi ); // DEBUG + + final Spot spot = SpotRoi.createSpot( contour.xScaled( 1. ), contour.yScaled( 1. ), 1. ); + model.addSpotTo( spot, 0 ); // System.out.println( Util.printCoordinates( spot.getRoi().x ) ); // DEBUG // System.out.println( Util.printCoordinates( spot ) ); // DEBUG } -// model.getSpots().setVisible( true ); - } - finally - { -// model.endUpdate(); - } + model.getSpots().setVisible( true ); -// final SelectionModel selectionModel = new SelectionModel( model ); -// final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); -// ds.setSpotDisplayedAsRoi( true ); -// final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, out, ds ); -// view.render(); - break; - } + +// break; + } // System.out.println( "Simplifying to 10%:" ); // i = 0; // for ( final BufferMesh cc : MeshConnectedComponents.iterable( mesh ) ) @@ -137,6 +135,18 @@ private static void runMesh( final ImagePlus imp, Mesh mesh, final double z, fin // new PLYMeshIO().save( simplified, filePath + suffix + "-simplified-" + i + ".ply" ); // } + } + finally + { + model.endUpdate(); + } + final SelectionModel selectionModel = new SelectionModel( model ); + final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); + ds.setSpotDisplayedAsRoi( true ); + final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, out, ds ); + view.render(); + view.refresh(); + System.out.println( model.getSpots() ); // DEBUG System.out.println(); } } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java index 3e018dc44..baba9a008 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java @@ -14,7 +14,8 @@ public static void main( final String[] args ) { ImageJ.main( args ); - final String filePath = "samples/mesh/CElegansMask3D.tif"; + final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; +// final String filePath = "samples/mesh/CElegansMask3D.tif"; final ImagePlus imp = IJ.openImage( filePath ); imp.show(); From 5815d27d3570a7b1c7cb4be32f8278e50939be02 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:36:13 +0200 Subject: [PATCH 093/263] Fix skipping painting of meshes if out of window. --- .../trackmate/visualization/hyperstack/PaintSpotMesh.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 87427593c..00754af85 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -34,11 +34,11 @@ public int paint( final Graphics2D g2d, final Spot spot ) final SpotMesh sm = spot.getMesh(); // Don't paint if we are out of screen. - if ( toScreenX( sm.boundingBox[ 0 ] ) > canvas.getSrcRect().width ) + if ( toScreenX( sm.boundingBox[ 0 ] ) > canvas.getWidth() ) return -1; if ( toScreenX( sm.boundingBox[ 3 ] ) < 0 ) return -1; - if ( toScreenY( sm.boundingBox[ 1 ] ) > canvas.getSrcRect().height ) + if ( toScreenY( sm.boundingBox[ 1 ] ) > canvas.getHeight() ) return -1; if ( toScreenY( sm.boundingBox[ 4 ] ) < 0 ) return -1; From 23ded95c438bcb0c4ca12abd8857e6964ab0dcd1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 28 Apr 2023 18:45:24 +0200 Subject: [PATCH 094/263] Do not include meshes smaller than 10 pixels. --- .../fiji/plugin/trackmate/detection/MaskUtils.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 0a7b2a9a3..7aa1a0c9c 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -476,17 +476,25 @@ private static < T extends RealType< T >, S extends RealType< S > > List< Spot > mesh = Meshes.removeDuplicateVertices( mesh, 2 ); Meshes.scale( mesh, calibration ); + // Min volume below which we skip spot creation. + // Discard meshes below ~ volume of 10 pixels. + final double minVolume = 10. * calibration[ 0 ] * calibration[ 1 ] * calibration[ 2 ]; + final List< Spot > spots = new ArrayList<>(); for ( Mesh cc : MeshConnectedComponents.iterable( mesh ) ) { if ( simplify && cc.triangles().size() > 200 ) cc = Meshes.simplify( cc, 0.1f, 10f ); + final double volume = Meshes.volume( cc ); + if ( volume < minVolume ) + continue; + final Spot spot = SpotMesh.createSpot( cc, 0. ); final double quality; if ( qualityImage == null ) { - quality = spot.getMesh().volume(); + quality = volume; } else { From 61b665daa11afa891da7363540eb7bd7e6a5893f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 28 Apr 2023 18:45:50 +0200 Subject: [PATCH 095/263] Tweak painting of meshes. Not important, will go away. --- .../visualization/hyperstack/PaintSpotMesh.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 00754af85..f1add66e3 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -1,6 +1,8 @@ package fiji.plugin.trackmate.visualization.hyperstack; +import java.awt.AlphaComposite; import java.awt.Color; +import java.awt.Composite; import java.awt.Graphics2D; import java.awt.geom.Path2D; import java.util.List; @@ -9,8 +11,8 @@ import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.gui.ImageCanvas; -import net.imagej.mesh.ZSlicer; -import net.imagej.mesh.ZSlicer.Contour; +import net.imagej.mesh.zslicer.ZSlicer; +import net.imagej.mesh.zslicer.ZSlicer.Contour; /** * Utility class to paint the {@link SpotMesh} component of spots. @@ -60,7 +62,8 @@ public int paint( final Graphics2D g2d, final Spot spot ) return -1; } - final List< Contour > contours = ZSlicer.slice( sm.mesh, dz ); + final double tolerance = 1e-3 * calibration[ 0 ]; + final List< Contour > contours = ZSlicer.slice( sm.mesh, dz, tolerance ); double maxTextPos = Double.NEGATIVE_INFINITY; for ( final Contour contour : contours ) { @@ -85,9 +88,12 @@ public int paint( final Graphics2D g2d, final Spot spot ) polygon.closePath(); if ( displaySettings.isSpotFilled() ) { + final Composite originalComposite = g2d.getComposite(); g2d.fill( polygon ); + g2d.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 1 ) ); g2d.setColor( Color.BLACK ); g2d.draw( polygon ); + g2d.setComposite( originalComposite ); } else { From 591b0e1d288fb24cc6a2e053a516d2d2b13781ed Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 28 Apr 2023 18:46:00 +0200 Subject: [PATCH 096/263] Update interactive tests. --- .../fiji/plugin/trackmate/ZSlicerDemo.java | 7 +- .../plugin/trackmate/mesh/DebugZSlicer.java | 68 +++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java diff --git a/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java b/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java index 2c8453ac0..06dddac87 100644 --- a/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java +++ b/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java @@ -19,9 +19,9 @@ import net.imagej.mesh.Mesh; import net.imagej.mesh.MeshConnectedComponents; import net.imagej.mesh.Meshes; -import net.imagej.mesh.ZSlicer; -import net.imagej.mesh.ZSlicer.Contour; import net.imagej.mesh.nio.BufferMesh; +import net.imagej.mesh.zslicer.ZSlicer; +import net.imagej.mesh.zslicer.ZSlicer.Contour; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.RealType; import net.imglib2.util.Cast; @@ -96,7 +96,8 @@ private static void runMesh( final ImagePlus imp, Mesh mesh, final double z, fin System.out.println( " # " + i + ": " + cc ); // new PLYMeshIO().save( cc, filePath + suffix + "-" + i + ".ply" ); - final List< Contour > contours = ZSlicer.slice( cc, z ); + final double tolerance = 1e-3 * pixelSizes[ 0 ]; + final List< Contour > contours = ZSlicer.slice( cc, z, tolerance ); for ( final Contour contour : contours ) { // final float[] xp = new float[ contour.x.size() ]; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java new file mode 100644 index 000000000..1a6962471 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java @@ -0,0 +1,68 @@ +package fiji.plugin.trackmate.mesh; + +import java.io.File; +import java.util.List; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.io.TmXmlReader; +import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import ij.ImageJ; +import ij.ImagePlus; +import net.imagej.mesh.zslicer.ZSlicer; +import net.imagej.mesh.zslicer.ZSlicer.Contour; + +public class DebugZSlicer +{ + public static void main( final String[] args ) + { + try + { + ImageJ.main( args ); + + final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.xml"; + final TmXmlReader reader = new TmXmlReader( new File( filePath ) ); + if ( !reader.isReadingOk() ) + { + System.err.println( reader.getErrorMessage() ); + return; + } + + final ImagePlus imp = reader.readImage(); + imp.show(); + final double[] calibration = TMUtils.getSpatialCalibration( imp ); + + final Model model = reader.getModel(); + final SelectionModel selection = new SelectionModel( model ); + final DisplaySettings ds = reader.getDisplaySettings(); + + final HyperStackDisplayer view = new HyperStackDisplayer( model, selection, imp, ds ); + view.render(); + + final Spot spot = model.getSpots().iterable( true ).iterator().next(); + final double z = 14.; + + imp.setZ( ( int ) Math.round( z / calibration[ 2 ] ) + 1 ); + + final double tolerance = 1e-3 * calibration[ 0 ]; + final List< Contour > contours = ZSlicer.slice( spot.getMesh().mesh, z, tolerance ); + System.out.println( "Found " + contours.size() + " contours." ); + int i = 0; + for ( final Contour contour : contours ) + { + System.out.println( "Contour " + ( ++i ) ); + System.out.println( contour ); // DEBUG + } + + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + } + +} + From 6a95a39158f42ced19a5b04851f39ba7fc15d31e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 29 Apr 2023 22:12:02 +0200 Subject: [PATCH 097/263] Update mesh painter to the new ZSlicer. --- .../hyperstack/PaintSpotMesh.java | 78 ++++++++++++------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index f1add66e3..a2510ce20 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -1,18 +1,17 @@ package fiji.plugin.trackmate.visualization.hyperstack; -import java.awt.AlphaComposite; import java.awt.Color; -import java.awt.Composite; import java.awt.Graphics2D; import java.awt.geom.Path2D; import java.util.List; +import java.util.function.DoubleUnaryOperator; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.gui.ImageCanvas; -import net.imagej.mesh.zslicer.ZSlicer; -import net.imagej.mesh.zslicer.ZSlicer.Contour; +import net.imagej.mesh.ZSlicer; +import net.imagej.mesh.ZSlicer.Contour; /** * Utility class to paint the {@link SpotMesh} component of spots. @@ -62,38 +61,26 @@ public int paint( final Graphics2D g2d, final Spot spot ) return -1; } - final double tolerance = 1e-3 * calibration[ 0 ]; - final List< Contour > contours = ZSlicer.slice( sm.mesh, dz, tolerance ); + final List< Contour > contours = ZSlicer.slice( sm.mesh, dz, calibration[ 2 ] ); + double maxTextPos = Double.NEGATIVE_INFINITY; for ( final Contour contour : contours ) { - if ( contour.x.size() < 2 ) - continue; + // Temporary set color by interior vs exterior. + if ( !contour.isInterior() ) + g2d.setColor( Color.RED ); + else + g2d.setColor( Color.GREEN ); - polygon.reset(); - final double x0 =toScreenX( contour.x.getQuick( 0 ) ); - final double y0 =toScreenY( contour.y.getQuick( 0 ) ); - polygon.moveTo( x0, y0 ); - if ( x0 > maxTextPos ) - maxTextPos = x0; + final double textPos = toPolygon( contour, polygon, this::toScreenX, this::toScreenY ); + if ( textPos > maxTextPos ) + maxTextPos = textPos; - for ( int i = 1; i < contour.x.size(); i++ ) - { - final double xi = toScreenX( contour.x.getQuick( i ) ); - final double yi = toScreenY( contour.y.getQuick( i ) ); - polygon.lineTo( xi, yi ); - if ( xi > maxTextPos ) - maxTextPos = xi; - } - polygon.closePath(); if ( displaySettings.isSpotFilled() ) { - final Composite originalComposite = g2d.getComposite(); g2d.fill( polygon ); - g2d.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_OVER, 1 ) ); g2d.setColor( Color.BLACK ); g2d.draw( polygon ); - g2d.setComposite( originalComposite ); } else { @@ -102,4 +89,43 @@ public int paint( final Graphics2D g2d, final Spot spot ) } return ( int ) ( maxTextPos - xs ); } + + /** + * Maps the coordinates of this contour to a Path2D polygon, and return the + * max X coordinate of the produced shape. + * + * @param contour + * the contour to convert. + * @param polygon + * the polygon to write. Reset by this call. + * @param toScreenX + * a function to convert the X coordinate of this contour to + * screen coordinates. + * @param toScreenY + * a function to convert the Y coordinate of this contour to + * screen coordinates. + * @return the max X position in screen units of this shape. + */ + private static final double toPolygon( final Contour contour, final Path2D polygon, final DoubleUnaryOperator toScreenX, final DoubleUnaryOperator toScreenY ) + { + double maxTextPos = Double.NEGATIVE_INFINITY; + polygon.reset(); + final double x0 = toScreenX.applyAsDouble( contour.x( 0 ) ); + final double y0 = toScreenY.applyAsDouble( contour.y( 0 ) ); + polygon.moveTo( x0, y0 ); + if ( x0 > maxTextPos ) + maxTextPos = x0; + + for ( int i = 1; i < contour.size(); i++ ) + { + final double xi = toScreenX.applyAsDouble( contour.x( i ) ); + final double yi = toScreenY.applyAsDouble( contour.y( i ) ); + polygon.lineTo( xi, yi ); + + if ( xi > maxTextPos ) + maxTextPos = xi; + } + polygon.closePath(); + return maxTextPos; + } } From 6969ba412b4c0c0d213ed9eb896a0abd3b4dde25 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 29 Apr 2023 22:12:27 +0200 Subject: [PATCH 098/263] Update interactive tests. --- .../fiji/plugin/trackmate/ZSlicerDemo.java | 153 ------------------ .../plugin/trackmate/mesh/DebugZSlicer.java | 19 +-- .../plugin/trackmate/mesh/Demo3DMesh.java | 63 +++----- 3 files changed, 31 insertions(+), 204 deletions(-) delete mode 100644 src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java diff --git a/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java b/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java deleted file mode 100644 index 06dddac87..000000000 --- a/src/test/java/fiji/plugin/trackmate/ZSlicerDemo.java +++ /dev/null @@ -1,153 +0,0 @@ -package fiji.plugin.trackmate; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.List; -import java.util.Random; - -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; -import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import ij.IJ; -import ij.ImageJ; -import ij.ImagePlus; -import ij.gui.Overlay; -import ij.plugin.Duplicator; -import net.imagej.ImgPlus; -import net.imagej.axis.Axes; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.MeshConnectedComponents; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.nio.BufferMesh; -import net.imagej.mesh.zslicer.ZSlicer; -import net.imagej.mesh.zslicer.ZSlicer.Contour; -import net.imglib2.RandomAccessibleInterval; -import net.imglib2.type.numeric.RealType; -import net.imglib2.util.Cast; -import net.imglib2.util.Util; -import net.imglib2.view.Views; - -public class ZSlicerDemo -{ - public static < T extends RealType< T > > void main( final String[] args ) throws IOException, URISyntaxException - { - try - { - ImageJ.main( args ); - System.out.println( "Opening image." ); - final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; - final ImagePlus imp = IJ.openImage( filePath ); - imp.show(); - final ImgPlus< T > img = Cast.unchecked( TMUtils.rawWraps( imp ) ); - final double[] pixelSizes = new double[] { - img.averageScale( img.dimensionIndex( Axes.X ) ), - img.averageScale( img.dimensionIndex( Axes.Y ) ), - img.averageScale( img.dimensionIndex( Axes.Z ) ) }; - System.out.println( Util.printCoordinates( pixelSizes ) ); - - // First channel is the smoothed version. - System.out.println( "Marching cube on grayscale." ); - final RandomAccessibleInterval< T > smoothed; - if ( img.dimensionIndex( Axes.CHANNEL ) >= 0 ) - smoothed = Views.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 0 ); - else - smoothed = img; - - final double isoLevel = 250; - final Mesh mesh1 = Meshes.marchingCubes( smoothed, isoLevel ); - final double z = 11.0; - runMesh( imp, mesh1, z, pixelSizes, filePath, "-grayscale" ); - - System.out.println( "Finished!" ); - } - catch ( final Exception e ) - { - e.printStackTrace(); - } - } - - private static void runMesh( final ImagePlus imp, Mesh mesh, final double z, final double[] pixelSizes, final String filePath, final String suffix ) throws IOException - { - final ImagePlus out = new Duplicator().run( imp, 1, 1, ( int ) z + 1, ( int ) z + 1, 1, 1 ); - out.show(); - - System.out.println( "Before removing duplicates: " + mesh ); - mesh = Meshes.removeDuplicateVertices( mesh, 2 ); - System.out.println( "After removing duplicates: " + mesh ); - System.out.println( "Scaling." ); - Meshes.scale( mesh, pixelSizes ); - - System.out.println( "N connected components: " + Meshes.nConnectedComponents( mesh ) ); - System.out.println( "Splitting in connected components:" ); - int i = 0; - final Overlay overlay = new Overlay(); - out.setOverlay( overlay ); - final Random ran = new Random( 2l ); - - final Model model = new Model(); - model.beginUpdate(); - try - { - - for ( final BufferMesh cc : MeshConnectedComponents.iterable( mesh ) ) - { - i++; - System.out.println( " # " + i + ": " + cc ); -// new PLYMeshIO().save( cc, filePath + suffix + "-" + i + ".ply" ); - - final double tolerance = 1e-3 * pixelSizes[ 0 ]; - final List< Contour > contours = ZSlicer.slice( cc, z, tolerance ); - for ( final Contour contour : contours ) - { -// final float[] xp = new float[ contour.x.size() ]; -// final float[] yp = new float[ xp.length ]; -// for ( int j = 0; j < xp.length; j++ ) -// { -// xp[ j ] = ( float ) ( 0.5 + contour.x.getQuick( j ) / pixelSizes[ 0 ] ); -// yp[ j ] = ( float ) ( 0.5 + contour.y.getQuick( j ) / pixelSizes[ 1 ] ); -// } -// final PolygonRoi roi = new PolygonRoi( xp, yp, PolygonRoi.POLYGON ); -// roi.setStrokeColor( new Color( 0.5f * ( 1 + ran.nextFloat() ), -// 0.5f * ( 1 + ran.nextFloat() ), -// 0.5f * ( 1 + ran.nextFloat() ) ) ); -// overlay.add( roi ); -// System.out.println( roi ); // DEBUG - - final Spot spot = SpotRoi.createSpot( contour.xScaled( 1. ), contour.yScaled( 1. ), 1. ); - model.addSpotTo( spot, 0 ); - -// System.out.println( Util.printCoordinates( spot.getRoi().x ) ); // DEBUG -// System.out.println( Util.printCoordinates( spot ) ); // DEBUG - } - model.getSpots().setVisible( true ); - - - -// break; - } -// System.out.println( "Simplifying to 10%:" ); -// i = 0; -// for ( final BufferMesh cc : MeshConnectedComponents.iterable( mesh ) ) -// { -// i++; -// final Mesh simplified = Meshes.simplify( cc, 0.1f, 10 ); -// System.out.println( " # " + i + ": " + simplified ); -// new PLYMeshIO().save( simplified, filePath + suffix + "-simplified-" + i + ".ply" ); -// } - - } - finally - { - model.endUpdate(); - } - final SelectionModel selectionModel = new SelectionModel( model ); - final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); - ds.setSpotDisplayedAsRoi( true ); - final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, out, ds ); - view.render(); - view.refresh(); - System.out.println( model.getSpots() ); // DEBUG - System.out.println(); - } -} diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java index 1a6962471..ed9090d55 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java @@ -10,10 +10,11 @@ import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import ij.CompositeImage; import ij.ImageJ; import ij.ImagePlus; -import net.imagej.mesh.zslicer.ZSlicer; -import net.imagej.mesh.zslicer.ZSlicer.Contour; +import net.imagej.mesh.ZSlicer; +import net.imagej.mesh.ZSlicer.Contour; public class DebugZSlicer { @@ -41,28 +42,22 @@ public static void main( final String[] args ) final HyperStackDisplayer view = new HyperStackDisplayer( model, selection, imp, ds ); view.render(); + imp.setDisplayMode( CompositeImage.GRAYSCALE ); final Spot spot = model.getSpots().iterable( true ).iterator().next(); - final double z = 14.; + final double z = 21.; imp.setZ( ( int ) Math.round( z / calibration[ 2 ] ) + 1 ); - final double tolerance = 1e-3 * calibration[ 0 ]; - final List< Contour > contours = ZSlicer.slice( spot.getMesh().mesh, z, tolerance ); + final List< Contour > contours = ZSlicer.slice( spot.getMesh().mesh, z, calibration[ 2 ] ); System.out.println( "Found " + contours.size() + " contours." ); - int i = 0; for ( final Contour contour : contours ) - { - System.out.println( "Contour " + ( ++i ) ); - System.out.println( contour ); // DEBUG - } - + System.out.println( contour ); } catch ( final Exception e ) { e.printStackTrace(); } } - } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 1e8539ec9..8f1aff092 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -4,22 +4,22 @@ import java.io.FileWriter; import java.io.IOException; import java.util.Iterator; +import java.util.List; import fiji.plugin.trackmate.detection.MaskUtils; import fiji.plugin.trackmate.util.TMUtils; -import gnu.trove.list.array.TDoubleArrayList; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; import ij.gui.Overlay; -import ij.gui.PointRoi; import ij.gui.PolygonRoi; -import ij.gui.Roi; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; import net.imagej.mesh.Vertices; +import net.imagej.mesh.ZSlicer; +import net.imagej.mesh.ZSlicer.Contour; import net.imagej.mesh.io.ply.PLYMeshIO; import net.imagej.mesh.io.stl.STLMeshIO; import net.imagej.mesh.naive.NaiveDoubleMesh; @@ -55,15 +55,12 @@ public static void main( final String[] args ) // Convert it to labeling. final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( mask, mask, 0.5, 1 ); final ImagePlus out = ImageJFunctions.show( labeling.getIndexImg(), "labeling" ); + out.setDimensions( mask.dimensionIndex( Axes.CHANNEL ), mask.dimensionIndex( Axes.Z ), mask.dimensionIndex( Axes.TIME ) ); // Iterate through all components. final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); final double[] cal = TMUtils.getSpatialCalibration( mask ); - // Holder for the contour coords. - final TDoubleArrayList cx = new TDoubleArrayList(); - final TDoubleArrayList cy = new TDoubleArrayList(); - // Parse regions to create polygons on boundaries. final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); int j = 0; @@ -78,7 +75,6 @@ public static void main( final String[] args ) final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 0 ); System.out.println( "Before simplification: " + cleaned.vertices().size() + " vertices and " + cleaned.triangles().size() + " faces." ); final Mesh simplified = Meshes.simplify( cleaned, 0.25f, 10 ); -// final Mesh simplified = debugMesh( new long[] { 0, 0, 0 }, region.dimensionsAsLongArray() ); // Wrap as mesh with edges. System.out.println( "After simplification: " + simplified.vertices().size() + " vertices and " + simplified.triangles().size() + " faces." ); @@ -98,11 +94,11 @@ public static void main( final String[] args ) */ // Intersection with a XY plane at a fixed Z position. - final int zslice = 20; // plan - final double z = ( zslice ) * cal[ 2 ]; // um + final int zslice = 22; // plan + final double z = ( zslice - 1 ) * cal[ 2 ]; // um -// MeshPlaneIntersection.intersect2( mesh, z, cx, cy ); - toOverlay( cx, cy, out, cal ); + final List< Contour > contours = ZSlicer.slice( simplified, z, cal[ 2 ] ); + toOverlay( contours, out, cal ); } System.out.println( "Done." ); } @@ -172,40 +168,29 @@ static Mesh debugMesh( final long[] min, final long[] max ) return mesh; } - private static void toOverlay( final TDoubleArrayList cx, final TDoubleArrayList cy, final ImagePlus out, final double[] cal ) + private static void toOverlay( final List< Contour > contours, final ImagePlus out, final double[] cal ) { - final int l = cx.size(); - if ( l == 0 ) - return; - - final Roi roi; - if ( l == 1 ) - { - roi = new PointRoi( - cx.get( 0 ) / cal[ 0 ] + 0.5, - cy.get( 0 ) / cal[ 1 ] + 0.5, null ); - } - else - { - final float[] xRoi = new float[ l ]; - final float[] yRoi = new float[ l ]; - for ( int i = 0; i < l; i++ ) - { - xRoi[ i ] = ( float ) ( cx.get( i ) / cal[ 0 ] + 0.5 ); - yRoi[ i ] = ( float ) ( cy.get( i ) / cal[ 1 ] + 0.5 ); - } - roi = new PolygonRoi( xRoi, yRoi, PolygonRoi.POLYGON ); -// roi.setStrokeWidth( 0.2 ); - } - - roi.setStrokeColor( Color.RED ); Overlay overlay = out.getOverlay(); if ( overlay == null ) { overlay = new Overlay(); out.setOverlay( overlay ); } - overlay.add( roi ); + + for ( final Contour contour : contours ) + { + System.out.println( contour ); // DEBUG + final float[] xRoi = new float[ contour.size() ]; + final float[] yRoi = new float[ contour.size() ]; + for ( int i = 0; i < contour.size(); i++ ) + { + xRoi[ i ] = ( float ) ( contour.x( i ) / cal[ 0 ] + 0.5 ); + yRoi[ i ] = ( float ) ( contour.y( i ) / cal[ 1 ] + 0.5 ); + } + final PolygonRoi roi = new PolygonRoi( xRoi, yRoi, PolygonRoi.POLYGON ); + roi.setStrokeColor( contour.isInterior() ? Color.GREEN : Color.RED ); + overlay.add( roi ); + } } private static void testIO( final Mesh mesh, final int j ) From 1c570819706dac5d051e3c92783cddbdf04ea8e9 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 17:33:13 +0200 Subject: [PATCH 099/263] Rework spot mesh iteration. Use the contours we got from the ZSlicer. Perform ray cast along the X axis. Count how many intersection we crosses to determine whether we are inside or outside. Works when the slice has several disjoint contours and when some contours are surrounding the exterior of the mesh (holes inside the slice). Relatively optimized: - Once per spot: get all the Z slices. Involves iterating once through all the triangles, sorting 2 arrays and a few binary search. - Once per X line: compute intersections of a ray along the X axis with all the contours. - Once per pixel: binary search against intersection to know how many of them we crossed. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 563 ------------------ .../trackmate/util/mesh/SpotMeshCursor.java | 154 ++--- .../trackmate/util/mesh/SpotMeshIterable.java | 2 +- .../hyperstack/PaintSpotMesh.java | 16 +- 4 files changed, 88 insertions(+), 647 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 46c400fb5..1525094dd 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -1,18 +1,5 @@ package fiji.plugin.trackmate; -import java.awt.geom.Point2D; -import java.awt.geom.Point2D.Double; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.List; - -import fiji.plugin.trackmate.util.mesh.MeshUtils; -import fiji.plugin.trackmate.util.mesh.RayCastingX; -import gnu.trove.iterator.TIntIterator; -import gnu.trove.list.array.TDoubleArrayList; -import gnu.trove.list.array.TLongArrayList; -import gnu.trove.list.linked.TDoubleLinkedList; -import gnu.trove.set.hash.TIntHashSet; import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; import net.imagej.mesh.Triangles; @@ -190,519 +177,6 @@ public void scale( final double alpha ) boundingBox = Meshes.boundingBox( mesh ); } - public List< TDoubleLinkedList[] > slice( final double z ) - { - return slice2( mesh, z ); - } - - public static List< TDoubleLinkedList[] > slice2( final Mesh mesh, final double z ) - { - final double resolution = 1; // FIXME. - - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - final TLongArrayList intersecting = new TLongArrayList(); - for ( long f = 0; f < triangles.size(); f++ ) - { - final long v0 = triangles.vertex0( f ); - final long v1 = triangles.vertex1( f ); - final long v2 = triangles.vertex2( f ); - final double minZ = minZ( vertices, v0, v1, v2 ); - if ( minZ > z ) - continue; - final double maxZ = maxZ( vertices, v0, v1, v2 ); - if ( maxZ < z ) - continue; - - intersecting.add( f ); - } - - // Holder for ray-casting results. - final TDoubleArrayList xs = new TDoubleArrayList(); - final TDoubleArrayList normals = new TDoubleArrayList(); - final List< TDoubleLinkedList[] > contours = new ArrayList<>(); - - // Adding mesh entries and exits to contours. - final TDoubleLinkedList exits = new TDoubleLinkedList(); - final TDoubleLinkedList entries = new TDoubleLinkedList(); - - // Set of contours that are still active (it is still ok to - // add points to them). - final TIntHashSet activeContours = new TIntHashSet(); // lol - - // What contours to remove from the active set. - final TIntHashSet removeFromActive = new TIntHashSet(); - - final float[] bb = Meshes.boundingBox( mesh ); - final RayCastingX ray = new RayCastingX( mesh ); - for ( double y = bb[ 1 ]; y <= bb[ 4 ]; y += resolution ) - { - ray.cast( y, z, xs, normals ); - if ( xs.isEmpty() ) - continue; - - if ( contours.isEmpty() ) - { - // Initializing. - for ( int i = 0; i < xs.size(); i++ ) - { - final double x = xs.getQuick( i ); - if ( normals.getQuick( i ) < 0. ) - { - // Entry: new contour. - final TDoubleLinkedList cx = new TDoubleLinkedList(); - final TDoubleLinkedList cy = new TDoubleLinkedList(); - cx.add( x ); - cy.add( y ); - contours.add( new TDoubleLinkedList[] { cx, cy } ); - activeContours.add( contours.size() - 1 ); - } - else - { - // Exit: add it to the end of an existing one if we have - // it. - if ( contours.isEmpty() ) - { - final TDoubleLinkedList cx = new TDoubleLinkedList(); - final TDoubleLinkedList cy = new TDoubleLinkedList(); - cx.add( x ); - cy.add( y ); - contours.add( new TDoubleLinkedList[] { cx, cy } ); - activeContours.add( contours.size() - 1 ); - } - else - { - final TDoubleLinkedList[] contour = contours.get( contours.size() - 1 ); - contour[ 0 ].add( x ); - contour[ 1 ].add( y ); - } - } - } - } - else - { - // Find to what contour to add it. Criterion: nearest. - - /* - * It's a tracking problem. We want to link a set of X position - * (mesh borders) to contour tails and heads. X positions that - * are not matched indicate a new contour should be created. - * - * We assume that there is no global disappearance of mesh - * entries. That is: there is not situation where all contours - * suddenly stops and another entry and exits appear away. - */ - - removeFromActive.clear(); - removeFromActive.addAll( activeContours ); - - entries.clear(); - exits.clear(); - for ( int j = 0; j < xs.size(); j++ ) - { - final double x = xs.get( j ); - if ( normals.get( j ) < 0 ) - entries.add( x ); - else - exits.add( x ); - } - - // Find suitable entries for contours. We only iterate over - // active contours. - final TIntIterator it = activeContours.iterator(); - while ( it.hasNext() ) - { - final int i = it.next(); - final TDoubleLinkedList cx = contours.get( i )[0]; - final TDoubleLinkedList cy = contours.get( i )[1]; - - // Entries. - double minDist = java.lang.Double.POSITIVE_INFINITY; - int bestEntry = -1; - for ( int j = 0; j < entries.size(); j++ ) - { - final double x = entries.get( j ); - final double d = Math.abs( x - cx.get( 0 ) ); - if ( d < minDist ) - { - minDist = d; - bestEntry = j; - } - } - if ( bestEntry >= 0 ) - { - cx.insert( 0, entries.get( bestEntry ) ); - cy.insert( 0, y ); - entries.removeAt( bestEntry ); - removeFromActive.remove( i ); // mark contour as active. - } - - // Exits. - minDist = java.lang.Double.POSITIVE_INFINITY; - int bestExit = -1; - for ( int j = 0; j < exits.size(); j++ ) - { - final double x = exits.get( j ); - final double d = Math.abs( x - cx.get( cx.size() - 1 ) ); - if ( d < minDist ) - { - minDist = d; - bestExit = j; - } - } - if ( bestExit >= 0 ) - { - cx.add( exits.get( bestExit ) ); - cy.add( y ); - exits.removeAt( bestExit ); - removeFromActive.remove( i ); // mark contour as active. - } - } - - // Do we still have entries and exits without a contour? - if ( !entries.isEmpty() || !exits.isEmpty() ) - { - // -> create one for them. - for ( int i = 0; i < Math.max( entries.size(), exits.size() ); i++ ) - { - final TDoubleLinkedList cx = new TDoubleLinkedList(); - final TDoubleLinkedList cy = new TDoubleLinkedList(); - if ( i < entries.size() ) - { - cx.add( entries.get( i ) ); - cy.add( y ); - } - if ( i < exits.size() ) - { - cx.add( exits.get( i ) ); - cy.add( y ); - } - contours.add( new TDoubleLinkedList[] { cx, cy } ); - activeContours.add( contours.size() - 1 ); - } - } - - // Do we have contours that did not receive a entry or an exit? - if ( !removeFromActive.isEmpty() ) - activeContours.removeAll( removeFromActive ); - - } - } - return contours; - } - - public static List< TDoubleLinkedList[] > slice( final Mesh mesh, final double z ) - { - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - final TLongArrayList intersecting = new TLongArrayList(); - for ( long f = 0; f < triangles.size(); f++ ) - { - final long v0 = triangles.vertex0( f ); - final long v1 = triangles.vertex1( f ); - final long v2 = triangles.vertex2( f ); - - final double minZ = minZ( vertices, v0, v1, v2 ); - if ( minZ > z ) - continue; - final double maxZ = maxZ( vertices, v0, v1, v2 ); - if ( maxZ < z ) - continue; - if ( minZ == maxZ ) - continue; // parallel. - - intersecting.add( f ); - } - - final ArrayDeque< Point2D.Double[] > segments = new ArrayDeque<>(); - for ( int i = 0; i < intersecting.size(); i++ ) - { - final long id = intersecting.getQuick( i ); - final Point2D.Double[] endPoints = triangleIntersection( mesh, id, z ); - if ( endPoints != null && endPoints[ 0 ] != null && endPoints[ 1 ] != null ) - { - final Double a = endPoints[ 0 ]; - final Double b = endPoints[ 1 ]; - if ( a.x == b.x && a.y == b.y ) - continue; - - segments.add( endPoints ); - } - } - - final List< TDoubleLinkedList[] > contours = new ArrayList<>(); - SEGMENT: while ( !segments.isEmpty() ) - { - final Double[] segment = segments.pop(); - final Double a = segment[ 0 ]; - final Double b = segment[ 1 ]; - - // What contour does it belong to? - for ( final TDoubleLinkedList[] contour : contours ) - { - final TDoubleLinkedList x = contour[ 0 ]; - final TDoubleLinkedList y = contour[ 1 ]; - - // Test if connects to first point of the contour. - final double xstart = x.get( 0 ); - final double ystart = y.get( 0 ); - if ( a.x == xstart && a.y == ystart ) - { - // Insert other extremity just before the first point. - x.insert( 0, b.x ); - y.insert( 0, b.y ); - continue SEGMENT; - } - else if ( b.x == xstart && b.y == ystart ) - { - x.insert( 0, a.x ); - y.insert( 0, a.y ); - continue SEGMENT; - } - - // Test if connects to first point of the contour. - final double xend = x.get( x.size() - 1 ); - final double yend = y.get( y.size() - 1 ); - if ( a.x == xend && a.y == yend ) - { - // Add other extremity at the end. - x.add( b.x ); - y.add( b.y ); - continue SEGMENT; - } - else if ( b.x == xend && b.y == yend ) - { - // Add other extremity at the end. - x.add( a.x ); - y.add( a.y ); - continue SEGMENT; - } - } - - /* - * It does not belong to a contour. Make a new one. - */ - - final TDoubleLinkedList x = new TDoubleLinkedList(); - final TDoubleLinkedList y = new TDoubleLinkedList(); - x.add( a.x ); - x.add( b.x ); - y.add( a.y ); - y.add( b.y ); - contours.add( new TDoubleLinkedList[] { x, y } ); - } - - System.out.println( "Found " + contours.size() + " contours:" ); // DEBUG - for ( int i = 0; i < contours.size(); i++ ) - { - System.out.println( "- Contour " + ( i + 1 ) ); // DEBUG - final TDoubleLinkedList[] contour = contours.get( i ); - final TDoubleLinkedList x = contour[ 0 ]; - for ( int j = 0; j < x.size(); j++ ) - System.out.print( String.format( "%3.0f, ", x.get( j ) ) ); - System.out.println(); - final TDoubleLinkedList y = contour[ 1 ]; - for ( int j = 0; j < y.size(); j++ ) - System.out.print( String.format( "%3.0f, ", y.get( j ) ) ); - System.out.println(); - } - - return contours; - } - - private static Double[] triangleIntersection( final Mesh mesh, final long id, final double z ) - { - final long v0 = mesh.triangles().vertex0( id ); - final long v1 = mesh.triangles().vertex1( id ); - final long v2 = mesh.triangles().vertex2( id ); - - final double x0 = mesh.vertices().x( v0 ); - final double x1 = mesh.vertices().x( v1 ); - final double x2 = mesh.vertices().x( v2 ); - final double y0 = mesh.vertices().y( v0 ); - final double y1 = mesh.vertices().y( v1 ); - final double y2 = mesh.vertices().y( v2 ); - final double z0 = mesh.vertices().z( v0 ); - final double z1 = mesh.vertices().z( v1 ); - final double z2 = mesh.vertices().z( v2 ); - - Double a = null; - Double b = null; - - if ( z0 == z ) - a = new Double( x0, y0 ); - - if ( z1 == z ) - { - if ( a == null ) - { - a = new Double( x1, y1 ); - } - else - { - b = new Double( x1, y1 ); - return new Double[] { a, b }; - } - } - if ( z2 == z ) - { - if ( a == null ) - { - a = new Double( x2, y2 ); - } - else - { - b = new Double( x2, y2 ); - return new Double[] { a, b }; - } - } - - final Double p01 = edgeIntersection( x0, y0, z0, x1, y1, z1, z ); - if ( p01 != null ) - { - if ( a == null ) - { - a = p01; - } - else - { - b = p01; - return new Double[] { a, b }; - } - } - - final Double p02 = edgeIntersection( x0, y0, z0, x2, y2, z2, z ); - if ( p02 != null ) - { - if ( a == null ) - { - a = p02; - } - else - { - b = p02; - return new Double[] { a, b }; - } - } - - final Double p12 = edgeIntersection( x1, y1, z1, x2, y2, z2, z ); - if ( p12 != null ) - { - if ( a == null ) - { - a = p12; - } - else - { - b = p12; - return new Double[] { a, b }; - } - } - -// throw new IllegalStateException( "Could not find an intersection for triangle " + id ); - - System.out.println(); // DEBUG - System.out.println( "Weird triangle: " + MeshUtils.triangleToString( mesh, id ) ); // DEBUG - final double minZ = minZ( mesh.vertices(), v0, v1, v2 ); - final double maxZ = maxZ( mesh.vertices(), v0, v1, v2 ); - System.out.println( "but minZ=" + minZ + " maxZ=" + maxZ + " and z=" + z + " - equal? " + ( minZ == maxZ ) ); // DEBUG - - return null; - } - - /** - * Intersection of a triangle with a Z plane. - */ - private static void triangleIntersection( final Vertices vertices, final long v0, final long v1, final long v2, final double z, final TDoubleArrayList cx, final TDoubleArrayList cy ) - { - final double z0 = vertices.z( v0 ); - final double z1 = vertices.z( v1 ); - final double z2 = vertices.z( v2 ); - - // Skip this; I don't know how to deal with this border case. - if ( z0 == z && z1 == z && z2 == z ) - { - addSegmentToContour( vertices, v0, v1, cx, cy ); - addSegmentToContour( vertices, v0, v2, cx, cy ); - addSegmentToContour( vertices, v1, v2, cx, cy ); - return; - } - - if ( z0 == z && z1 == z ) - { - addSegmentToContour( vertices, v0, v1, cx, cy ); - return; - } - if ( z0 == z && z2 == z ) - { - addSegmentToContour( vertices, v0, v2, cx, cy ); - return; - } - if ( z1 == z && z2 == z ) - { - addSegmentToContour( vertices, v1, v2, cx, cy ); - return; - } - - // Only one vertex is touching the plane -> no need to paint. - if ( z0 == z || z1 == z || z2 == z ) - return; - - addEdgeIntersectionToContour( vertices, v0, v1, z, cx, cy ); - addEdgeIntersectionToContour( vertices, v0, v2, z, cx, cy ); - addEdgeIntersectionToContour( vertices, v1, v2, z, cx, cy ); - } - - private static void addSegmentToContour( final Vertices vertices, final long v0, final long v1, final TDoubleArrayList cx, final TDoubleArrayList cy ) - { - final double x0 = vertices.x( v0 ); - final double x1 = vertices.x( v1 ); - cx.add( x0 ); - cx.add( x1 ); - final double y0 = vertices.y( v0 ); - final double y1 = vertices.y( v1 ); - cy.add( y0 ); - cy.add( y1 ); - } - - private static Double edgeIntersection( final double xs, final double ys, final double zs, - final double xt, final double yt, final double zt, final double z ) - { - if ( ( zs > z && zt > z ) || ( zs < z && zt < z ) ) - return null; - - assert ( zs != zt ); - final double t = ( z - zs ) / ( zt - zs ); - final double x = xs + t * ( xt - xs ); - final double y = ys + t * ( yt - ys ); - return new Double( x, y ); - } - - private static void addEdgeIntersectionToContour( - final Vertices vertices, - final long sv, - final long tv, - final double z, - final TDoubleArrayList cx, - final TDoubleArrayList cy ) - { - final double zs = vertices.z( sv ); - final double zt = vertices.z( tv ); - if ( ( zs > z && zt > z ) || ( zs < z && zt < z ) ) - return; - - final double xs = vertices.x( sv ); - final double ys = vertices.y( sv ); - final double xt = vertices.x( tv ); - final double yt = vertices.y( tv ); - final double t = ( zs == zt ) - ? 0.5 : ( z - zs ) / ( zt - zs ); - final double x = xs + t * ( xt - xs ); - final double y = ys + t * ( yt - ys ); - cx.add( x ); - cy.add( y ); - } - @Override public SpotMesh copy() { @@ -737,41 +211,4 @@ public String toString() return str.toString(); } - - private static final double minZ( final Vertices vertices, final long v0, final long v1, final long v2 ) - { - return Math.min( vertices.z( v0 ), Math.min( vertices.z( v1 ), vertices.z( v2 ) ) ); - } - - private static final double maxZ( final Vertices vertices, final long v0, final long v1, final long v2 ) - { - return Math.max( vertices.z( v0 ), Math.max( vertices.z( v1 ), vertices.z( v2 ) ) ); - } - - private static final double minY( final Vertices vertices, final Triangles triangles, final long id ) - { - final long v0 = triangles.vertex0( id ); - final long v1 = triangles.vertex1( id ); - final long v2 = triangles.vertex2( id ); - return Math.min( vertices.y( v0 ), Math.min( vertices.y( v1 ), vertices.y( v2 ) ) ); - } - - private static final double maxY( final Vertices vertices, final Triangles triangles, final long id ) - { - final long v0 = triangles.vertex0( id ); - final long v1 = triangles.vertex1( id ); - final long v2 = triangles.vertex2( id ); - return Math.max( vertices.y( v0 ), Math.max( vertices.y( v1 ), vertices.y( v2 ) ) ); - } - - private static final double minY( final Vertices vertices, final long v0, final long v1, final long v2 ) - { - return Math.min( vertices.y( v0 ), Math.min( vertices.y( v1 ), vertices.y( v2 ) ) ); - } - - private static final double maxY( final Vertices vertices, final long v0, final long v1, final long v2 ) - { - return Math.max( vertices.y( v0 ), Math.max( vertices.y( v1 ), vertices.y( v2 ) ) ); - } - } diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java index 6dcc1c097..fcf98a0f1 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -1,9 +1,17 @@ package fiji.plugin.trackmate.util.mesh; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import fiji.plugin.trackmate.SpotMesh; import gnu.trove.list.array.TDoubleArrayList; import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; +import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; +import net.imagej.mesh.alg.zslicer.Slice; +import net.imagej.mesh.alg.zslicer.ZSlicer; import net.imglib2.Cursor; import net.imglib2.RandomAccess; @@ -27,84 +35,69 @@ public class SpotMeshCursor< T > implements Cursor< T > private final float[] bb; - private final long minX; + private final int minX; - private final long maxX; + private final int maxX; - private final long minY; + private final int minY; - private final long maxY; + private final int maxY; - private final long minZ; + private final int minZ; - private final long maxZ; + private final int maxZ; private final RandomAccess< T > ra; private boolean hasNext; - private long iy; - - private long iz; + private int iy; - private long ix; + private int iz; - /** Ray casting algorithm. */ - private final RayCastingX rayCasting; + private int ix; /** * List of resolved X positions where we enter / exit the mesh. Set by the * ray casting algorithm. */ - private final TDoubleArrayList meshXs = new TDoubleArrayList(); - - /** List of normal X component where we enter / exit the mesh. */ - private final TDoubleArrayList meshNs = new TDoubleArrayList(); - - /** X position of the next (forward in X) intersection with the mesh. */ - private double nextXIntersection; + private final TDoubleArrayList intersectionXs = new TDoubleArrayList(); - /** X component of the normal at the next intersection with the mesh. */ - private double nextNormal; + private final Map< Integer, Slice > sliceMap; - /** Index of the next intersection in the {@link #meshXs} list. */ - private int indexNextXIntersection; + private Slice slice; - private Mesh mesh; + private final Mesh mesh; public SpotMeshCursor( final RandomAccess< T > ra, final SpotMesh sm, final double[] cal ) { this( ra, sm.mesh, sm.boundingBox, cal ); } - public SpotMeshCursor( final RandomAccess< T > ra, final Mesh mesh, final double[] cal ) - { - this( ra, mesh, Meshes.boundingBox( mesh ), cal ); - } - public SpotMeshCursor( final RandomAccess< T > ra, final Mesh mesh, final float[] boundingBox, final double[] cal ) { this.ra = ra; this.mesh = mesh; this.cal = cal; this.bb = boundingBox; - this.minX = Math.round( bb[ 0 ] / cal[ 0 ] ); - this.maxX = Math.round( bb[ 3 ] / cal[ 0 ] ); - this.minY = Math.round( bb[ 1 ] / cal[ 1 ] ); - this.maxY = Math.round( bb[ 4 ] / cal[ 1 ] ); - this.minZ = Math.round( bb[ 2 ] / cal[ 2 ] ); - this.maxZ = Math.round( bb[ 5 ] / cal[ 2 ] ); - this.rayCasting = new RayCastingX( mesh ); + this.minX = ( int ) Math.floor( bb[ 0 ] / cal[ 0 ] ); + this.maxX = ( int ) Math.ceil( bb[ 3 ] / cal[ 0 ] ); + this.minY = ( int ) Math.floor( bb[ 1 ] / cal[ 1 ] ); + this.maxY = ( int ) Math.ceil( bb[ 4 ] / cal[ 1 ] ); + this.minZ = ( int ) Math.floor( bb[ 2 ] / cal[ 2 ] ); + this.maxZ = ( int ) Math.ceil( bb[ 5 ] / cal[ 2 ] ); + + this.sliceMap = buildSliceMap( mesh, boundingBox, cal ); reset(); } - @Override public void reset() { this.ix = maxX; // To force a new ray cast when we call fwd() this.iy = minY - 1; // Then we will move to minY. this.iz = minZ; + this.slice = sliceMap.get( iz ); this.hasNext = true; preFetch(); } @@ -139,21 +132,19 @@ private void preFetch() iz++; if ( iz > maxZ ) return; // Finished! + slice = sliceMap.get( iz ); } + if ( slice == null ) + continue; // New ray cast. - final double z = iz * cal[ 2 ]; final double y = iy * cal[ 1 ]; - rayCasting.cast( y, z, meshXs, meshNs ); + slice.xRayCast( y, intersectionXs, cal[ 1 ] ); // No intersection? - if ( !meshXs.isEmpty() ) - { - this.indexNextXIntersection = 0; - this.nextXIntersection = meshXs.getQuick( 0 ); - this.nextNormal = meshNs.getQuick( 0 ); + if ( !intersectionXs.isEmpty() ) break; - } + // No intersection on this line, move to the next. } } @@ -163,9 +154,9 @@ private void preFetch() final double x = ix * cal[ 0 ]; // Special case: only one intersection. - if ( meshXs.size() == 1 ) + if ( intersectionXs.size() == 1 ) { - if ( x == nextXIntersection ) + if ( x == intersectionXs.getQuick( 0 ) ) { hasNext = true; return; @@ -176,37 +167,20 @@ private void preFetch() } } - if ( x >= nextXIntersection ) + final int i = intersectionXs.binarySearch( x ); + if ( i >= 0 ) { - indexNextXIntersection++; - if ( indexNextXIntersection >= meshXs.size() ) - { - final boolean inside = ( x == meshXs.get( meshXs.size() - 1 ) ); - if ( inside ) - { - hasNext = true; - return; - } - } - else - { - final boolean isEntry = ( nextNormal < 0. ) || ( ix == nextXIntersection ); - nextXIntersection = meshXs.getQuick( indexNextXIntersection ); - nextNormal = meshNs.getQuick( indexNextXIntersection ); - if ( isEntry ) - { - hasNext = true; - return; - } - } + // Fall on an intersection exactly. + hasNext = true; + return; } - else + final int ip = -( i + 1 ); + // Odd or even? + if ( ip % 2 != 0 ) { - if ( nextNormal > 0. ) - { - hasNext = true; - return; - } + // Odd. We are inside. + hasNext = true; + return; } // Not inside, move to the next point. @@ -263,4 +237,32 @@ public T get() return ra.get(); } + private static final Map< Integer, Slice > buildSliceMap( final Mesh mesh, final float[] boundingBox, final double[] calibration ) + { + // Pre-compute slices. + final int minZ = ( int ) Math.ceil( boundingBox[ 2 ] / calibration[ 2 ] ); + final int maxZ = ( int ) Math.floor( boundingBox[ 5 ] / calibration[ 2 ] ); + final double[] zs = new double[ maxZ - minZ + 1 ]; + final List< Integer > sliceIndices = new ArrayList<>( zs.length ); + for ( int i = 0; i < zs.length; i++ ) + { + zs[ i ] = ( minZ + i ) * calibration[ 2 ]; // physical coords. + sliceIndices.add( minZ + i ); // pixel coordinates. + } + final List< Slice > slices = ZSlicer.slices( mesh, zs, calibration[ 2 ] ); + + // Simplify below /14th of a pixel. + final double epsilon = calibration[ 0 ] * 0.25; + final List< Slice > simplifiedSlices = slices.stream() + .map( s -> RamerDouglasPeucker.simplify( s, epsilon ) ) + .collect( Collectors.toList() ); + + // Store in a map Z (integer) pos -> slice. + final Map< Integer, Slice > sliceMap = new HashMap<>(); + for ( int i = 0; i < sliceIndices.size(); i++ ) + sliceMap.put( sliceIndices.get( i ), simplifiedSlices.get( i ) ); + + return sliceMap; + } + } diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java index c3550cce0..278b5a1df 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java @@ -86,7 +86,7 @@ public long max( final int d ) @Override public Cursor< T > cursor() { - return new SpotMeshCursor<>( img.randomAccess(), sm.mesh, calibration ); + return new SpotMeshCursor<>( img.randomAccess(), sm, calibration ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index a2510ce20..504951d70 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -3,15 +3,16 @@ import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.Path2D; -import java.util.List; import java.util.function.DoubleUnaryOperator; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.gui.ImageCanvas; -import net.imagej.mesh.ZSlicer; -import net.imagej.mesh.ZSlicer.Contour; +import net.imagej.mesh.alg.zslicer.Contour; +import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; +import net.imagej.mesh.alg.zslicer.Slice; +import net.imagej.mesh.alg.zslicer.ZSlicer; /** * Utility class to paint the {@link SpotMesh} component of spots. @@ -61,11 +62,12 @@ public int paint( final Graphics2D g2d, final Spot spot ) return -1; } - final List< Contour > contours = ZSlicer.slice( sm.mesh, dz, calibration[ 2 ] ); - + final Slice slice = ZSlicer.slice( sm.mesh, dz, calibration[ 2 ] ); double maxTextPos = Double.NEGATIVE_INFINITY; - for ( final Contour contour : contours ) + for ( final Contour c : slice ) { + final Contour contour = RamerDouglasPeucker.simplify( c, calibration[ 0 ] * 0.25 ); + // Temporary set color by interior vs exterior. if ( !contour.isInterior() ) g2d.setColor( Color.RED ); @@ -93,7 +95,7 @@ public int paint( final Graphics2D g2d, final Spot spot ) /** * Maps the coordinates of this contour to a Path2D polygon, and return the * max X coordinate of the produced shape. - * + * * @param contour * the contour to convert. * @param polygon From 6ea2074dab096a11f7337d1441e3c362ed207fc2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 17:33:21 +0200 Subject: [PATCH 100/263] Remove unused classes. --- .../trackmate/util/mesh/MollerTrumbore.java | 118 -------- .../trackmate/util/mesh/RayCastingX.java | 282 ------------------ .../trackmate/util/mesh/SortArrays.java | 200 ------------- 3 files changed, 600 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/MollerTrumbore.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/RayCastingX.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/MollerTrumbore.java b/src/main/java/fiji/plugin/trackmate/util/mesh/MollerTrumbore.java deleted file mode 100644 index 6e812082f..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/MollerTrumbore.java +++ /dev/null @@ -1,118 +0,0 @@ -package fiji.plugin.trackmate.util.mesh; - -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Triangles; -import net.imagej.mesh.Vertices; - -/** - * Möller–Trumbore intersection algorithm. - *

- * This algorithm can efficiently tells whether a ray intersects with a triangle - * in a mesh. Adapted from Wikipedia. - * - * @see . - * @author Jean-Yves Tinevez - * - */ -public class MollerTrumbore -{ - - private static final double EPSILON = 0.0000001; - - private final Vertices vertices; - - private final Triangles triangles; - - private final double[] tmp; - - public MollerTrumbore( final Mesh mesh ) - { - this.vertices = mesh.vertices(); - this.triangles = mesh.triangles(); - this.tmp = new double[ 3 ]; - } - - public boolean rayIntersectsTriangle( - final long id, - final double ox, - final double oy, - final double oz, - final double rx, - final double ry, - final double rz, - final double[] intersection ) - { - final long vertex0 = triangles.vertex0( id ); - final long vertex1 = triangles.vertex1( id ); - final long vertex2 = triangles.vertex2( id ); - - // Coords. - final double x0 = vertices.x( vertex0 ); - final double y0 = vertices.y( vertex0 ); - final double z0 = vertices.z( vertex0 ); - final double x1 = vertices.x( vertex1 ); - final double y1 = vertices.y( vertex1 ); - final double z1 = vertices.z( vertex1 ); - final double x2 = vertices.x( vertex2 ); - final double y2 = vertices.y( vertex2 ); - final double z2 = vertices.z( vertex2 ); - - // Edge 1 - final double e1x = x1 - x0; - final double e1y = y1 - y0; - final double e1z = z1 - z0; - // Edge 2 - final double e2x = x2 - x0; - final double e2y = y2 - y0; - final double e2z = z2 - z0; - - cross( rx, ry, rz, e2x, e2y, e2z, tmp ); - final double hx = tmp[ 0 ]; - final double hy = tmp[ 1 ]; - final double hz = tmp[ 2 ]; - final double a = dot( e1x, e1y, e1z, hx, hy, hz ); - if ( a > -EPSILON && a < EPSILON ) - return false; // This ray is parallel to this triangle. - - final double sx = ox - x0; - final double sy = oy - y0; - final double sz = oz - z0; - final double f = 1. / a; - final double u = f * dot( sx, sy, sz, hx, hy, hz ); - - if ( u < 0. || u > 1. ) - return false; - - cross( sx, sy, sz, e1x, e1y, e1z, tmp ); - final double qx = tmp[ 0 ]; - final double qy = tmp[ 1 ]; - final double qz = tmp[ 2 ]; - - final double v = f * dot( rx, ry, rz, qx, qy, qz ); - - if ( v < 0. || u + v > 1. ) - return false; - - // We have an infinite line intersection. - final double t = f * dot( e2x, e2y, e2z, qx, qy, qz ); - intersection[ 0 ] = ox + t * rx; - intersection[ 1 ] = oy + t * ry; - intersection[ 2 ] = oy + t * rz; - - return true; - } - - private double dot( final double x1, final double y1, final double z1, final double x2, final double y2, final double z2 ) - { - return x1 * x2 + y1 * y2 + z1 * z2; - } - - private void cross( final double x1, final double y1, final double z1, final double x2, final double y2, final double z2, final double[] out ) - { - out[ 0 ] = y1 * z2 - z1 * y2; - out[ 1 ] = -x1 * z2 + z1 * x2; - out[ 2 ] = x1 * y2 - y1 * x2; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/RayCastingX.java b/src/main/java/fiji/plugin/trackmate/util/mesh/RayCastingX.java deleted file mode 100644 index 05f46e406..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/RayCastingX.java +++ /dev/null @@ -1,282 +0,0 @@ -package fiji.plugin.trackmate.util.mesh; - -import gnu.trove.list.array.TDoubleArrayList; -import gnu.trove.list.array.TLongArrayList; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Triangles; - -/** - * Ray casting algorithm. - *

- * Used to determine whether a point is inside or outside a mesh. This - * implementation uses rays cast along X only. - * - * @author Jean-Yves Tinevez - * - */ -public class RayCastingX -{ - - private static final boolean DEBUG = false; - - /** List of triangle ids intersecting with the current ray. */ - private final TLongArrayList intersectingTriangle = new TLongArrayList(); - - /** List of X positions of the triangle intersections with the ray. */ - private final TDoubleArrayList intersectionXs = new TDoubleArrayList(); - - /** List of the X component of normal at intersection point. */ - private final TDoubleArrayList intersectionNormals = new TDoubleArrayList(); - - private final Mesh mesh; - - private final MollerTrumbore mollerTrumbore; - - public RayCastingX( final Mesh mesh ) - { - this.mesh = mesh; - this.mollerTrumbore = new MollerTrumbore( mesh ); - } - - /** - * Returns the position of mesh entries and exists along the specified X - * ray. The lists returned are pruned for duplicate and non-alternating - * entry / exit points are resolved. - * - * @param y - * the Y position of the X ray to cast. - * @param z - * the Z position of the X ray to cast. - * @param meshXs - * List of resolved X positions where we enter / exit the mesh, - * along the specified ray. Modified by this call. - * @param meshNs - * List of X component of normals at points where we enter / exit - * the mesh. Modified by this call. - * - */ - public void cast( final double y, final double z, final TDoubleArrayList meshXs, final TDoubleArrayList meshNs ) - { - meshXs.resetQuick(); - meshNs.resetQuick(); - - // Get all the X position where triangles cross the line. - getXIntersectingCoords( y, z, intersectingTriangle, intersectionXs ); - - // No intersection? - if ( intersectionXs.isEmpty() ) - return; - - if ( DEBUG ) - MeshUtils.exportMeshSubset( intersectingTriangle.toArray(), mesh, "samples/mesh/io/subset.stl" ); - - // Collect normals projection on the X line. - getNormalXProjection( mesh, intersectingTriangle, intersectionNormals ); - - // Sort by by X coordinate of intersections. - final int[] index = SortArrays.quicksort( intersectionXs ); - - // Sort normal array with the same order. - SortArrays.reorder( intersectionNormals, index ); - - if ( DEBUG ) - { - System.out.println(); - System.out.println( "Before removing duplicates:" ); - System.out.println( "XS: " + intersectionXs ); - System.out.println( "NS: " + intersectionNormals ); - } - - // Merge duplicates. - final int maxIndex = removeDuplicate( intersectionXs, intersectionNormals ); - - if ( DEBUG ) - { - System.out.println( "After removing duplicates:" ); - System.out.println( "XS: " + intersectionXs.subList( 0, maxIndex ) ); - System.out.println( "NS: " + intersectionNormals.subList( 0, maxIndex ) ); - } - - // Check we are alternating entering / leaving. - checkAlternating( intersectionXs, intersectionNormals, maxIndex, meshXs, meshNs ); - } - - /** - * Remove duplicate positions of intersections. - *

- * It is very likely that the ray casting along X intersects with triangle - * edges or triangle vertices. This is because in some case the mesh we - * iterate through was generated by the marching-cubes algorithm, and the - * mesh vertices lie exactly at pixel coordinates. - *

- * Because of this they ray might intersects at one point with several, - * possibly many (3-9) triangles. This routine merges consecutive duplicate - * X position by retaining one one for a set, and taking the mean normal of - * the set. - * - * @param ts - * the X position of the intersections of the ray with triangles, - * possibly with duplicates. Will be modified by this routine. - * @param nxs - * the X component of the normal of the intersected triangles. - * Will be modified by this call. - * @return the new arrays length. That is: the actual size of the - * intersection list once it has been pruned of duplicates. - */ - private static final int removeDuplicate( final TDoubleArrayList ts, final TDoubleArrayList nxs ) - { - if ( ts.size() < 2 ) - return ts.size(); - - int j = 0; - double accum = 0.; - int nAccum = 0; - for ( int i = 0; i < ts.size() - 1; i++ ) - { - if ( ts.getQuick( i ) != ts.getQuick( i + 1 ) ) - { - ts.setQuick( j, ts.getQuick( i ) ); - if ( nAccum == 0 ) - { - nxs.setQuick( j, nxs.getQuick( i ) ); - } - else - { - // Average. - nxs.setQuick( j, accum / nAccum ); - } - accum = 0.; - nAccum = 0; - j++; - } - else - { - final double v = nxs.getQuick( i ); - accum += v; - nAccum++; - } - } - - ts.setQuick( j, ts.getQuick( ts.size() - 1 ) ); - if ( nAccum == 0 ) - nxs.setQuick( j, nxs.getQuick( ts.size() - 1 ) ); - else - nxs.setQuick( j, accum / nAccum ); - - j++; - return j; - } - - /** - * Processes entries and exists along a ray in the mesh. - *

- * Ideally, following a ray, every time we cross a triangle at an entry, it - * should be followed by an exit and vice-versa. When it is not the case, it - * means the ray has been following triangles exactly parallels to the X - * axis. This routine resolves theses issues by returning new arrays where - * non alternating entries and exits have been pruned. It retains the - * 'leftmost' entry and the 'rightmost' exit every time several consecutive - * entries or exits are encountered. - * - * @param xs - * the array of X position of intersection points. - * @param nxs - * the array of X component of the normals at these intersection - * points. - * @param maxIndex - * the size of these arrays (actual arrays might be bigger, but - * they won't be iterated past this size). - * @param outXs - * a holder for the resulting pruned X positions of intersection - * points. Reset by this call. Must be empty when called. - * @param outNxs - * a holder for the resulting X component of the normals at - * intersection points. Reset by this call. Must be empty when - * called. - */ - private static final void checkAlternating( - final TDoubleArrayList xs, final TDoubleArrayList nxs, final int maxIndex, - final TDoubleArrayList outXs, final TDoubleArrayList outNxs ) - { - double prevN = nxs.getQuick( 0 ); - final double prevX = xs.getQuick( 0 ); - - outXs.add( prevX ); - outNxs.add( prevN ); - - // The first one should be an entry (normal neg). - assert prevN < 0; - // The last one should be an exit (normal pos). - assert nxs.getQuick( maxIndex ) > 0; - - for ( int i = 1; i < maxIndex; i++ ) - { - final double n = nxs.getQuick( i ); - if ( n * prevN < 0. ) - { - // Sign did change. All good. - outXs.add( xs.getQuick( i ) ); - outNxs.add( n ); - } - else - { - // Sign did not change! Merge. - if ( n < 0. ) - { - // Two consecutive entries. - // Remove this one, so that the first valid entry stays. - } - else - { - // Two consecutive exits. - // Remove the previous one, so that the last exit is - // this one. - outXs.removeAt( outXs.size() - 1 ); - outNxs.removeAt( outNxs.size() - 1 ); - // And add this one. - outXs.add( xs.getQuick( i ) ); - outNxs.add( n ); - } - } - prevN = n; - } - } - - /** - * Returns the list of X coordinates where the line parallel to the X axis - * and passing through (0,y,z) crosses the triangles of the mesh. The list - * is unordered and may have duplicates. - * - * @param y - * the Y coordinate of the line origin. - * @param z - * the Z coordinate of the line origin. - * @param tl - * a holder for the triangle indices intersecting. - * @param ts - * a holder for the resulting intersections X coordinate. - */ - private void getXIntersectingCoords( final double y, final double z, - final TLongArrayList tl, final TDoubleArrayList ts ) - { - final double[] intersection = new double[ 3 ]; - tl.resetQuick(); - ts.resetQuick(); - // TODO optimize search of triangles with a data structure. - for ( long id = 0; id < mesh.triangles().size(); id++ ) - if ( mollerTrumbore.rayIntersectsTriangle( id, 0, y, z, 1., 0, 0, intersection ) ) - { - tl.add( id ); - ts.add( intersection[ 0 ] ); - } - } - - private static void getNormalXProjection( final Mesh mesh, final TLongArrayList tl, final TDoubleArrayList nxs ) - { - nxs.resetQuick(); - final Triangles triangles = mesh.triangles(); - for ( int id = 0; id < tl.size(); id++ ) - nxs.add( triangles.nx( tl.getQuick( id ) ) ); - } - -} diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java deleted file mode 100644 index d9d7a2711..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SortArrays.java +++ /dev/null @@ -1,200 +0,0 @@ -package fiji.plugin.trackmate.util.mesh; - -import java.util.BitSet; -import java.util.Comparator; -import java.util.Random; - -import gnu.trove.list.array.TDoubleArrayList; -import gnu.trove.list.array.TLongArrayList; - -/** - * Utilities to sort a Trove list and return the sorting index to sort other - * lists with. - */ -public class SortArrays -{ - - public static void reorder( final TDoubleArrayList data, final int[] ind ) - { - final BitSet done = new BitSet( data.size() ); - for ( int i = 0; i < data.size() && done.cardinality() < data.size(); i++ ) - { - int ia = i; - int ib = ind[ ia ]; - if ( done.get( ia ) ) - { // index is already done - continue; - } - if ( ia == ib ) - { // element is at the right place - done.set( ia ); - continue; - } - final int x = ia; // start a loop at x = ia - // some next index will be x again eventually - final double a = data.getQuick( ia ); - // keep element a as the last value after the loop - while ( ib != x && !done.get( ia ) ) - { - final double b = data.getQuick( ib ); - // element from index b must go to index a - data.setQuick( ia, b ); - done.set( ia ); - ia = ib; - ib = ind[ ia ]; // get next index - } - data.setQuick( ia, a ); // set value a to last index - done.set( ia ); - } - } - - public static int[] quicksort( final TDoubleArrayList main ) - { - final int[] index = new int[ main.size() ]; - for ( int i = 0; i < index.length; i++ ) - index[ i ] = i; - quicksort( main, index ); - return index; - } - - public static void quicksort( final TDoubleArrayList main, final int[] index ) - { - quicksort( main, index, 0, index.length - 1 ); - } - - // quicksort a[left] to a[right] - public static void quicksort( final TDoubleArrayList a, final int[] index, final int left, final int right ) - { - if ( right <= left ) - return; - final int i = partition( a, index, left, right ); - quicksort( a, index, left, i - 1 ); - quicksort( a, index, i + 1, right ); - } - - // partition a[left] to a[right], assumes left < right - private static int partition( final TDoubleArrayList a, final int[] index, - final int left, final int right ) - { - int i = left - 1; - int j = right; - while ( true ) - { - while ( less( a.getQuick( ++i ), a.getQuick( right ) ) ) - ; - while ( less( a.getQuick( right ), a.getQuick( --j ) ) ) - if ( j == left ) - break; // don't go out-of-bounds - if ( i >= j ) - break; // check if pointers cross - exch( a, index, i, j ); // swap two elements into place - } - exch( a, index, i, right ); // swap with partition element - return i; - } - - // is x < y ? - private static boolean less( final double x, final double y ) - { - return ( x < y ); - } - - // exchange a[i] and a[j] - private static void exch( final TDoubleArrayList a, final int[] index, final int i, final int j ) - { - final double swap = a.getQuick( i ); - a.setQuick( i, a.getQuick( j ) ); - a.setQuick( j, swap ); - final int b = index[ i ]; - index[ i ] = index[ j ]; - index[ j ] = b; - } - - /* - * Sorting index arrays with a comparator. - */ - - public static void quicksort( final TLongArrayList main, final Comparator< Long > c ) - { - final int[] index = new int[ main.size() ]; - for ( int i = 0; i < index.length; i++ ) - index[ i ] = i; - quicksort( main, 0, main.size(), c ); - } - - private static void quicksort( final TLongArrayList a, final int left, final int right, final Comparator< Long > c ) - { - if ( right <= left ) - return; - final int i = partition( a, left, right, c ); - quicksort( a, left, i - 1, c ); - quicksort( a, i + 1, right, c ); - } - - // partition a[left] to a[right], assumes left < right - private static int partition( final TLongArrayList a, - final int left, final int right, final Comparator< Long > c ) - { - int i = left - 1; - int j = right; - while ( true ) - { - while ( less( a.getQuick( ++i ), a.getQuick( right ) ) ); - while ( less( a.getQuick( right ), a.getQuick( --j ) ) ) - if ( j == left ) - break; // don't go out-of-bounds - if ( i >= j ) - break; // check if pointers cross - exch( a, i, j ); // swap two elements into place - } - exch( a, i, right ); // swap with partition element - return i; - } - - // exchange a[i] and a[j] - private static void exch( final TLongArrayList a, final int i, final int j ) - { - final long swap = a.getQuick( i ); - a.setQuick( i, a.getQuick( j ) ); - a.setQuick( j, swap ); - } - - /* - * Main. - */ - - public static void main( final String[] args ) - { - final Random ran = new Random( 1l ); - final int n = 10; - final TDoubleArrayList arr = new TDoubleArrayList(); - for ( int i = 0; i < n; i++ ) - arr.add( ran.nextDouble() ); - - final TDoubleArrayList copy = new TDoubleArrayList( arr ); - - System.out.print( String.format( "Before sorting: %4.2f", arr.get( 0 ) ) ); - for ( int i = 1; i < arr.size(); i++ ) - System.out.print( String.format( ", %4.2f", arr.get( i ) ) ); - System.out.println(); - - final int[] index = quicksort( arr ); - System.out.print( String.format( "After sorting: %4.2f", arr.get( 0 ) ) ); - for ( int i = 1; i < arr.size(); i++ ) - System.out.print( String.format( ", %4.2f", arr.get( i ) ) ); - System.out.println(); - - System.out.print( String.format( "Index: %4d", index[ 0 ] ) ); - for ( int i = 1; i < arr.size(); i++ ) - System.out.print( String.format( ", %4d", index[ i ] ) ); - System.out.println(); - - reorder( arr, index ); - System.out.print( String.format( "Reorder copy: %4.2f", copy.get( 0 ) ) ); - for ( int i = 1; i < copy.size(); i++ ) - System.out.print( String.format( ", %4.2f", copy.get( i ) ) ); - System.out.println(); - } - - -} From 36709742dadee1e8f0ece43fb772a58cb50fccd5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 17:33:32 +0200 Subject: [PATCH 101/263] Update mesh demos. --- .../plugin/trackmate/mesh/DebugZSlicer.java | 8 +++---- .../plugin/trackmate/mesh/Demo3DMesh.java | 10 ++++---- .../plugin/trackmate/mesh/DemoContour.java | 6 ----- .../trackmate/mesh/DemoPixelIteration.java | 23 +++++++++++++------ 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java index ed9090d55..6c2aacffe 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java @@ -1,7 +1,6 @@ package fiji.plugin.trackmate.mesh; import java.io.File; -import java.util.List; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; @@ -13,8 +12,9 @@ import ij.CompositeImage; import ij.ImageJ; import ij.ImagePlus; -import net.imagej.mesh.ZSlicer; -import net.imagej.mesh.ZSlicer.Contour; +import net.imagej.mesh.alg.zslicer.Contour; +import net.imagej.mesh.alg.zslicer.Slice; +import net.imagej.mesh.alg.zslicer.ZSlicer; public class DebugZSlicer { @@ -49,7 +49,7 @@ public static void main( final String[] args ) imp.setZ( ( int ) Math.round( z / calibration[ 2 ] ) + 1 ); - final List< Contour > contours = ZSlicer.slice( spot.getMesh().mesh, z, calibration[ 2 ] ); + final Slice contours = ZSlicer.slice( spot.getMesh().mesh, z, calibration[ 2 ] ); System.out.println( "Found " + contours.size() + " contours." ); for ( final Contour contour : contours ) System.out.println( contour ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 8f1aff092..efb1a286c 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -4,7 +4,6 @@ import java.io.FileWriter; import java.io.IOException; import java.util.Iterator; -import java.util.List; import fiji.plugin.trackmate.detection.MaskUtils; import fiji.plugin.trackmate.util.TMUtils; @@ -18,8 +17,9 @@ import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; import net.imagej.mesh.Vertices; -import net.imagej.mesh.ZSlicer; -import net.imagej.mesh.ZSlicer.Contour; +import net.imagej.mesh.alg.zslicer.Contour; +import net.imagej.mesh.alg.zslicer.Slice; +import net.imagej.mesh.alg.zslicer.ZSlicer; import net.imagej.mesh.io.ply.PLYMeshIO; import net.imagej.mesh.io.stl.STLMeshIO; import net.imagej.mesh.naive.NaiveDoubleMesh; @@ -97,7 +97,7 @@ public static void main( final String[] args ) final int zslice = 22; // plan final double z = ( zslice - 1 ) * cal[ 2 ]; // um - final List< Contour > contours = ZSlicer.slice( simplified, z, cal[ 2 ] ); + final Slice contours = ZSlicer.slice( simplified, z, cal[ 2 ] ); toOverlay( contours, out, cal ); } System.out.println( "Done." ); @@ -168,7 +168,7 @@ static Mesh debugMesh( final long[] min, final long[] max ) return mesh; } - private static void toOverlay( final List< Contour > contours, final ImagePlus out, final double[] cal ) + private static void toOverlay( final Slice contours, final ImagePlus out, final double[] cal ) { Overlay overlay = out.getOverlay(); if ( overlay == null ) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java index 963e10175..b268d0cb8 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java @@ -4,8 +4,6 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; import fiji.plugin.trackmate.io.TmXmlReader; @@ -41,9 +39,5 @@ public static void main( final String[] args ) final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); final HyperStackDisplayer view = new HyperStackDisplayer( model, selection, imp, ds ); view.render(); - - final Spot spot = model.getSpots().iterable( 0, true ).iterator().next(); - final SpotMesh sm = spot.getMesh(); - sm.slice( 12. ); } } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index 9bbc2cc91..0cdb1c004 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -1,12 +1,13 @@ package fiji.plugin.trackmate.mesh; +import java.awt.Color; + import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.detection.MaskDetectorFactory; import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; @@ -17,6 +18,7 @@ import ij.ImageJ; import ij.ImagePlus; import ij.gui.NewImage; +import ij.process.LUT; import net.imglib2.Cursor; import net.imglib2.RandomAccess; import net.imglib2.type.numeric.RealType; @@ -49,10 +51,12 @@ public static < T extends RealType< T > > void main( final String[] args ) final ImagePlus imp = IJ.openImage( imPath ); final Settings settings = new Settings( imp ); - settings.detectorFactory = new MaskDetectorFactory<>(); + settings.detectorFactory = new ThresholdDetectorFactory<>(); settings.detectorSettings = settings.detectorFactory.getDefaultSettings(); - settings.detectorSettings.put( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, - false ); + settings.detectorSettings.put( + ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, false ); + settings.detectorSettings.put( + ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD, 100. ); final TrackMate trackmate = new TrackMate( settings ); trackmate.setNumThreads( 4 ); @@ -70,6 +74,7 @@ public static < T extends RealType< T > > void main( final String[] args ) imp.resetDisplayRange(); final double[] cal = TMUtils.getSpatialCalibration( imp ); + int i = 0; for ( final Spot spot : model.getSpots().iterable( true ) ) { System.out.println( spot ); @@ -78,20 +83,24 @@ public static < T extends RealType< T > > void main( final String[] args ) while ( cursor.hasNext() ) { cursor.fwd(); - cursor.get().setReal( 100 ); + cursor.get().setReal( 1 + i++ ); ra.setPosition( cursor ); ra.get().setReal( 100 ); } - break; } final SelectionModel sm = new SelectionModel( model ); final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); final HyperStackDisplayer view = new HyperStackDisplayer( model, sm, imp, ds ); view.render(); - System.out.println( "Done." ); + imp.setSlice( 19 ); + imp.resetDisplayRange(); + imp.setLut( LUT.createLutFromColor( Color.BLUE ) ); + out.setSlice( 19 ); + out.resetDisplayRange(); + System.out.println( "Done." ); } catch ( final Exception e ) { From 1b1a3238b82d970b0e5401492c2cfd24cbc00c9e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 21:42:39 +0200 Subject: [PATCH 102/263] Meshes are stored centered at (0,0,0) They and translated on the fly when iterating over pixels, saving and diplaying. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 14 ++++---- .../fiji/plugin/trackmate/io/TmXmlReader.java | 20 ++++++++--- .../fiji/plugin/trackmate/io/TmXmlWriter.java | 15 ++++---- .../fiji/plugin/trackmate/util/SpotUtil.java | 21 +++++++----- .../trackmate/util/mesh/SpotMeshCursor.java | 22 ++++++++++-- .../trackmate/util/mesh/SpotMeshIterable.java | 23 +++++++------ .../hyperstack/PaintSpotMesh.java | 29 ++++++++++------ .../visualization/hyperstack/SpotOverlay.java | 2 +- .../hyperstack/TrackMatePainter.java | 34 ++++++++++++++----- .../trackmate/mesh/DemoPixelIteration.java | 2 +- 10 files changed, 124 insertions(+), 58 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 1525094dd..46697d786 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -45,13 +45,13 @@ public static Spot createSpot( final Mesh mesh, final double quality ) final RealPoint center = Meshes.center( mesh ); // Shift mesh to (0, 0, 0). -// final Vertices vertices = mesh.vertices(); -// final long nVertices = vertices.size(); -// for ( long i = 0; i < nVertices; i++ ) -// vertices.setPositionf( i, -// vertices.xf( i ) - center.getFloatPosition( 0 ), -// vertices.yf( i ) - center.getFloatPosition( 1 ), -// vertices.zf( i ) - center.getFloatPosition( 2 ) ); + final Vertices vertices = mesh.vertices(); + final long nVertices = vertices.size(); + for ( long i = 0; i < nVertices; i++ ) + vertices.setPositionf( i, + vertices.xf( i ) - center.getFloatPosition( 0 ), + vertices.yf( i ) - center.getFloatPosition( 1 ), + vertices.zf( i ) - center.getFloatPosition( 2 ) ); // Bounding box with respect to 0. final float[] boundingBox = Meshes.boundingBox( mesh ); diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index c0f8b2892..e85d97849 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -156,6 +156,7 @@ import ij.ImagePlus; import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; +import net.imagej.mesh.Vertices; import net.imagej.mesh.nio.BufferMesh; public class TmXmlReader @@ -189,8 +190,6 @@ public class TmXmlReader */ protected boolean ok = true; - private final File meshFile; - /* * CONSTRUCTORS */ @@ -201,7 +200,6 @@ public class TmXmlReader public TmXmlReader( final File file ) { this.file = file; - this.meshFile = new File( file.getAbsolutePath() + MESH_FILE_EXTENSION ); final SAXBuilder sb = new SAXBuilder(); Element r = null; try @@ -936,6 +934,7 @@ private SpotCollection getSpots( final Element modelElement ) } // Do we have a mesh file? + final File meshFile = new File( file.getAbsolutePath() + MESH_FILE_EXTENSION ); if ( meshFile.exists() ) { // Matcher for zipped file name. @@ -958,7 +957,20 @@ private SpotCollection getSpots( final Element modelElement ) final Mesh m = PLY_MESH_IO.open( zipFile.getInputStream( entry ) ); final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() ); Meshes.calculateNormals( m, mesh ); - final SpotMesh sm = new SpotMesh( mesh, Meshes.boundingBox( mesh ) ); + + // Shift mesh to (0, 0, 0). + final Vertices vertices = mesh.vertices(); + final long nVertices = vertices.size(); + for ( long i = 0; i < nVertices; i++ ) + vertices.setPositionf( i, + vertices.xf( i ) - spot.getFloatPosition( 0 ), + vertices.yf( i ) - spot.getFloatPosition( 1 ), + vertices.zf( i ) - spot.getFloatPosition( 2 ) ); + + // Bounding box with respect to 0. + final float[] boundingBox = Meshes.boundingBox( mesh ); + + final SpotMesh sm = new SpotMesh( mesh, boundingBox ); spot.setMesh( sm ); } catch ( final IOException e ) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index f001876a8..20afc104d 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -134,6 +134,7 @@ import gnu.trove.procedure.TIntIntProcedure; import net.imagej.mesh.Mesh; import net.imagej.mesh.io.ply.PLYMeshIO; +import net.imagej.mesh.obj.transform.TranslateMesh; public class TmXmlWriter { @@ -775,21 +776,23 @@ protected void writeSpotMeshes( final Iterable< Spot > spots ) final File meshFile = new File( file.getAbsolutePath() + MESH_FILE_EXTENSION ); logger.log( " Writing spot meshes to " + meshFile.getName() + "\n" ); - try(final ZipOutputStream zos = new ZipOutputStream( new FileOutputStream( meshFile ) )) + try (final ZipOutputStream zos = new ZipOutputStream( new FileOutputStream( meshFile ) )) { - zos.setMethod( ZipOutputStream.DEFLATED ) ; + zos.setMethod( ZipOutputStream.DEFLATED ); zos.setLevel( COMPRESSION_LEVEL ); // Write spot meshes. for ( final Spot spot : spots ) { - if (spot.getMesh()!=null) + if ( spot.getMesh() != null ) { + // Save mesh in true coordinates. final Mesh mesh = spot.getMesh().mesh; - final byte[] bs = PLY_MESH_IO.writeBinary( mesh ); + final Mesh translated = TranslateMesh.translate( mesh, spot ); + final byte[] bs = PLY_MESH_IO.writeBinary( translated ); final String entryName = spot.ID() + ".ply"; - zos.putNextEntry( new ZipEntry( entryName ) ); + zos.putNextEntry( new ZipEntry( entryName ) ); zos.write( bs ); zos.closeEntry(); @@ -809,7 +812,7 @@ public boolean execute( final int ID, final int t ) str.append( String.format( "%d,%d\n", t, ID ) ); return true; } - }); + } ); zos.putNextEntry( new ZipEntry( "mesh-info.txt" ) ); zos.write( str.toString().getBytes() ); } diff --git a/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java b/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java index bf83dbc68..232cc16d4 100644 --- a/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java +++ b/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java @@ -50,14 +50,14 @@ public class SpotUtil public static final < T extends RealType< T > > IterableInterval< T > iterable( final SpotShape shape, final RealLocalizable center, final ImgPlus< T > img ) { if ( shape instanceof SpotRoi ) - return iterable( ( SpotRoi ) shape, center, img ); - else if ( shape instanceof SpotShape ) - return iterable( ( SpotMesh ) shape, img ); + return iterableRoi( ( SpotRoi ) shape, center, img ); + else if ( shape instanceof SpotMesh ) + return iterableMesh( ( SpotMesh ) shape, center, img ); else throw new IllegalArgumentException( "Unsuitable shape for SpotShape: " + shape ); } - public static final < T extends RealType< T > > IterableInterval< T > iterable( final SpotRoi roi, final RealLocalizable center, final ImgPlus< T > img ) + public static final < T extends RealType< T > > IterableInterval< T > iterableRoi( final SpotRoi roi, final RealLocalizable center, final ImgPlus< T > img ) { final SpotRoiIterable< T > neighborhood = new SpotRoiIterable<>( roi, center, img ); if ( neighborhood.dimension( 0 ) <= 1 && neighborhood.dimension( 1 ) <= 1 ) @@ -74,12 +74,12 @@ public static final < T extends RealType< T > > IterableInterval< T > iterable( if ( null != roi && DetectionUtils.is2D( img ) ) { // Operate on ROI only if we have one and the image is 2D. - return iterable( roi, spot, img ); + return iterableRoi( roi, spot, img ); } else if ( mesh != null ) { // Operate on 3D if we have a mesh. - return iterable( mesh, img ); + return iterableMesh( mesh, spot, img ); } else { @@ -94,10 +94,13 @@ else if ( mesh != null ) } } - public static < T extends NumericType< T > > IterableInterval< T > iterable( final SpotMesh mesh, final ImgPlus< T > img ) + public static < T extends NumericType< T > > IterableInterval< T > iterableMesh( final SpotMesh sm, final RealLocalizable center, final ImgPlus< T > img ) { - return new SpotMeshIterable< T >( Views.extendZero( img ), - mesh, TMUtils.getSpatialCalibration( img ) ); + return new SpotMeshIterable< T >( + Views.extendZero( img ), + sm, + center, + TMUtils.getSpatialCalibration( img ) ); } private static < T > IterableInterval< T > makeSinglePixelIterable( final RealLocalizable center, final ImgPlus< T > img ) diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java index fcf98a0f1..b6c70d0b0 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -6,14 +6,17 @@ import java.util.Map; import java.util.stream.Collectors; +import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import gnu.trove.list.array.TDoubleArrayList; import net.imagej.mesh.Mesh; import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; import net.imagej.mesh.alg.zslicer.Slice; import net.imagej.mesh.alg.zslicer.ZSlicer; +import net.imagej.mesh.obj.transform.TranslateMesh; import net.imglib2.Cursor; import net.imglib2.RandomAccess; +import net.imglib2.RealLocalizable; /** * A {@link Cursor} that iterates over the pixels inside a mesh. @@ -69,9 +72,24 @@ public class SpotMeshCursor< T > implements Cursor< T > private final Mesh mesh; - public SpotMeshCursor( final RandomAccess< T > ra, final SpotMesh sm, final double[] cal ) + public SpotMeshCursor( final RandomAccess< T > ra, final Spot spot, final double[] cal ) { - this( ra, sm.mesh, sm.boundingBox, cal ); + this( ra, spot.getMesh(), spot, cal ); + } + + public SpotMeshCursor( final RandomAccess< T > ra, final SpotMesh sm, final RealLocalizable center, final double[] cal ) + { + this( + ra, + TranslateMesh.translate( sm.mesh, center ), + new float[] { + sm.boundingBox[ 0 ] + center.getFloatPosition( 0 ), + sm.boundingBox[ 1 ] + center.getFloatPosition( 1 ), + sm.boundingBox[ 2 ] + center.getFloatPosition( 2 ), + sm.boundingBox[ 3 ] + center.getFloatPosition( 0 ), + sm.boundingBox[ 4 ] + center.getFloatPosition( 1 ), + sm.boundingBox[ 5 ] + center.getFloatPosition( 2 ) }, + cal ); } public SpotMeshCursor( final RandomAccess< T > ra, final Mesh mesh, final float[] boundingBox, final double[] cal ) diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java index 278b5a1df..6d8b3d6b3 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java @@ -3,30 +3,33 @@ import java.util.Iterator; import fiji.plugin.trackmate.SpotMesh; -import net.imagej.mesh.Meshes; import net.imglib2.Cursor; import net.imglib2.IterableInterval; import net.imglib2.Localizable; import net.imglib2.RandomAccessible; -import net.imglib2.RealPoint; +import net.imglib2.RealLocalizable; public class SpotMeshIterable< T > implements IterableInterval< T >, Localizable { private final double[] calibration; - private final SpotMesh sm; + private final RandomAccessible< T > img; - private final RealPoint center; + private final SpotMesh sm; - private final RandomAccessible< T > img; + private final RealLocalizable center; - public SpotMeshIterable( final RandomAccessible< T > img, final SpotMesh sm, final double[] calibration ) + public SpotMeshIterable( + final RandomAccessible< T > img, + final SpotMesh sm, + final RealLocalizable center, + final double[] calibration ) { this.img = img; this.sm = sm; + this.center = center; this.calibration = calibration; - this.center = Meshes.center( sm.mesh ); } @Override @@ -74,19 +77,19 @@ public Iterator< T > iterator() @Override public long min( final int d ) { - return Math.round( sm.boundingBox[ d ] / calibration[ d ] ); + return Math.round( ( sm.boundingBox[ d ] + center.getFloatPosition( d ) ) / calibration[ d ] ); } @Override public long max( final int d ) { - return Math.round( sm.boundingBox[ 3 + d ] / calibration[ d ] ); + return Math.round( ( sm.boundingBox[ 3 + d ] + center.getFloatPosition( d ) ) / calibration[ d ] ); } @Override public Cursor< T > cursor() { - return new SpotMeshCursor<>( img.randomAccess(), sm, calibration ); + return new SpotMeshCursor<>( img.randomAccess(), sm, center, calibration ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 504951d70..1bd3350ed 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -8,11 +8,14 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import ij.ImagePlus; import ij.gui.ImageCanvas; +import net.imagej.mesh.Mesh; import net.imagej.mesh.alg.zslicer.Contour; import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; import net.imagej.mesh.alg.zslicer.Slice; import net.imagej.mesh.alg.zslicer.ZSlicer; +import net.imagej.mesh.obj.transform.TranslateMesh; /** * Utility class to paint the {@link SpotMesh} component of spots. @@ -25,33 +28,38 @@ public class PaintSpotMesh extends TrackMatePainter private final Path2D.Double polygon; - public PaintSpotMesh( final ImageCanvas canvas, final double[] calibration, final DisplaySettings displaySettings ) + public PaintSpotMesh( final ImagePlus imp, final double[] calibration, final DisplaySettings displaySettings ) { - super( canvas, calibration, displaySettings ); + super( imp, calibration, displaySettings ); this.polygon = new Path2D.Double(); } public int paint( final Graphics2D g2d, final Spot spot ) { + final ImageCanvas canvas = canvas(); + if ( canvas == null ) + return -1; + final SpotMesh sm = spot.getMesh(); + final double x = spot.getFeature( Spot.POSITION_X ); + final double y = spot.getFeature( Spot.POSITION_Y ); // Don't paint if we are out of screen. - if ( toScreenX( sm.boundingBox[ 0 ] ) > canvas.getWidth() ) + if ( toScreenX( sm.boundingBox[ 0 ] + x ) > canvas.getWidth() ) return -1; - if ( toScreenX( sm.boundingBox[ 3 ] ) < 0 ) + if ( toScreenX( sm.boundingBox[ 3 ] + x ) < 0 ) return -1; - if ( toScreenY( sm.boundingBox[ 1 ] ) > canvas.getHeight() ) + if ( toScreenY( sm.boundingBox[ 1 ] + y ) > canvas.getHeight() ) return -1; - if ( toScreenY( sm.boundingBox[ 4 ] ) < 0 ) + if ( toScreenY( sm.boundingBox[ 4 ] + y ) < 0 ) return -1; // Z plane does not cross bounding box. - final double x = spot.getFeature( Spot.POSITION_X ); - final double y = spot.getFeature( Spot.POSITION_Y ); final double xs = toScreenX( x ); final double ys = toScreenY( y ); + final double z = spot.getFeature( Spot.POSITION_Z ); final double dz = ( canvas.getImage().getSlice() - 1 ) * calibration[ 2 ]; - if ( sm.boundingBox[ 2 ] > dz || sm.boundingBox[ 5 ] < dz ) + if ( sm.boundingBox[ 2 ] + z > dz || sm.boundingBox[ 5 ] + z < dz ) { final double magnification = canvas.getMagnification(); g2d.fillOval( @@ -62,7 +70,8 @@ public int paint( final Graphics2D g2d, final Spot spot ) return -1; } - final Slice slice = ZSlicer.slice( sm.mesh, dz, calibration[ 2 ] ); + final Mesh translated = TranslateMesh.translate( sm.mesh, spot ); + final Slice slice = ZSlicer.slice( translated, dz, calibration[ 2 ] ); double maxTextPos = Double.NEGATIVE_INFINITY; for ( final Contour c : slice ) { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java index a544c4c90..f03ea1fe0 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -91,7 +91,7 @@ public SpotOverlay( final Model model, final ImagePlus imp, final DisplaySetting this.displaySettings = displaySettings; this.paintSpotSphere = new PaintSpotSphere( calibration, displaySettings ); this.paintSpotRoi = new PaintSpotRoi( calibration, displaySettings ); - this.paintSpotMesh = new PaintSpotMesh( imp.getCanvas(), calibration, displaySettings ); + this.paintSpotMesh = new PaintSpotMesh( imp, calibration, displaySettings ); } /* diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java index 0e647c187..23a6f0eeb 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java @@ -1,6 +1,7 @@ package fiji.plugin.trackmate.visualization.hyperstack; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import ij.ImagePlus; import ij.gui.ImageCanvas; public abstract class TrackMatePainter @@ -10,25 +11,34 @@ public abstract class TrackMatePainter protected final DisplaySettings displaySettings; - protected final ImageCanvas canvas; + private final ImagePlus imp; - public TrackMatePainter( final ImageCanvas canvas, final double[] calibration, final DisplaySettings displaySettings ) + public TrackMatePainter( final ImagePlus imp, final double[] calibration, final DisplaySettings displaySettings ) { - this.canvas = canvas; + this.imp = imp; this.calibration = calibration; this.displaySettings = displaySettings; } + protected ImageCanvas canvas() + { + return imp.getCanvas(); + } + /** * Converts a X position in physical units (possible um) to screen * coordinates to be used with the graphics object. - * + * * @param x * the X position to convert. * @return the screen X coordinate. */ public double toScreenX( final double x ) { + final ImageCanvas canvas = canvas(); + if ( canvas == null ) + return Double.NaN; + final double xp = x / calibration[ 0 ] + 0.5; // pixel coords return canvas.screenXD( xp ); } @@ -36,13 +46,17 @@ public double toScreenX( final double x ) /** * Converts a Y position in physical units (possible um) to screen * coordinates to be used with the graphics object. - * + * * @param y * the Y position to convert. * @return the screen Y coordinate. */ public double toScreenY( final double y ) { + final ImageCanvas canvas = canvas(); + if ( canvas == null ) + return Double.NaN; + final double yp = y / calibration[ 0 ] + 0.5; // pixel coords return canvas.screenYD( yp ); } @@ -50,7 +64,7 @@ public double toScreenY( final double y ) /** * Returns true of the point with the specified coordinates in * physical units lays inside the painted window. - * + * * @param x * the X coordinate in physical unit. * @param y @@ -59,11 +73,15 @@ public double toScreenY( final double y ) */ public boolean isInside( final double x, final double y ) { + final ImageCanvas canvas = canvas(); + if ( canvas == null ) + return false; + final double xs = toScreenX( x ); - if ( xs < 0 || xs > canvas.getSrcRect().width ) + if ( xs < 0 || xs > canvas.getWidth() ) return false; final double ys = toScreenY( y ); - if ( ys < 0 || ys > canvas.getSrcRect().height ) + if ( ys < 0 || ys > canvas.getHeight() ) return false; return true; } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index 0cdb1c004..d639fa443 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -78,7 +78,7 @@ public static < T extends RealType< T > > void main( final String[] args ) for ( final Spot spot : model.getSpots().iterable( true ) ) { System.out.println( spot ); - final Cursor< T > cursor = new SpotMeshCursor< T >( TMUtils.rawWraps( out ).randomAccess(), spot.getMesh(), cal ); + final Cursor< T > cursor = new SpotMeshCursor< T >( TMUtils.rawWraps( out ).randomAccess(), spot, cal ); final RandomAccess< T > ra = TMUtils.rawWraps( imp ).randomAccess(); while ( cursor.hasNext() ) { From f6f85527927c42f368b64a0430d64b441814fb41 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 22:29:46 +0200 Subject: [PATCH 103/263] Create a proper mask from the input before using the mask detector. A small routine builds a view of the input (that should be a mask) where all pixels greater than 0 are set to 1 and to 0 otherwise. In the case of meshes, this allows using the marching cube algorithm on real type, interpolating at 0.5 between 0 and 1, and having smoother meshes than when using the marching cube on boolean types. --- .../detection/MaskDetectorFactory.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java index 8066c4d2e..58841e06c 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -47,6 +47,8 @@ import fiji.plugin.trackmate.util.TMUtils; import net.imglib2.Interval; import net.imglib2.RandomAccessible; +import net.imglib2.converter.Converter; +import net.imglib2.converter.Converters; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; @@ -87,14 +89,15 @@ public boolean has2Dsegmentation() @Override public SpotDetector< T > getDetector( final Interval interval, final int frame ) { - final double intensityThreshold = 0.; final boolean simplifyContours = ( Boolean ) settings.get( KEY_SIMPLIFY_CONTOURS ); final double[] calibration = TMUtils.getSpatialCalibration( img ); final int channel = ( Integer ) settings.get( KEY_TARGET_CHANNEL ) - 1; final RandomAccessible< T > imFrame = DetectionUtils.prepareFrameImg( img, channel, frame ); + final RandomAccessible< T > mask = mask( imFrame ); + final double intensityThreshold = 0.5; final ThresholdDetector< T > detector = new ThresholdDetector<>( - imFrame, + mask, interval, calibration, intensityThreshold, @@ -103,6 +106,27 @@ public SpotDetector< T > getDetector( final Interval interval, final int frame ) return detector; } + /** + * Return a view of the input image where all pixels with values strictly + * larger than 0 are set to 1, and set to 0 otherwise. + * + * @param input + * the image to wrap. + * @return a view of the image. + */ + protected RandomAccessible< T > mask( final RandomAccessible< T > input ) + { + final Converter< T, T > c = new Converter< T, T >() + { + @Override + public void convert( final T input, final T output ) + { + output.setReal( input.getRealDouble() > 0. ? 1. : 0. ); + } + }; + return Converters.convert( input, c, img.firstElement().createVariable() ); + } + @Override public String getKey() { From a7ca0645f2551efcfcb1e8ef4e1c46f86974d4ed Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 23:06:34 +0200 Subject: [PATCH 104/263] Unify and use the TrackMatePainter hierarchy. --- .../hyperstack/PaintSpotMesh.java | 1 + .../hyperstack/PaintSpotRoi.java | 101 ++++++++++-------- .../hyperstack/PaintSpotSphere.java | 35 +++--- .../visualization/hyperstack/SpotOverlay.java | 38 +++---- .../hyperstack/TrackMatePainter.java | 6 ++ 5 files changed, 99 insertions(+), 82 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 1bd3350ed..3268f4c37 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -34,6 +34,7 @@ public PaintSpotMesh( final ImagePlus imp, final double[] calibration, final Dis this.polygon = new Path2D.Double(); } + @Override public int paint( final Graphics2D g2d, final Spot spot ) { final ImageCanvas canvas = canvas(); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java index 1366edee8..2379f81ce 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java @@ -3,11 +3,14 @@ import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.Path2D; +import java.util.function.DoubleUnaryOperator; +import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import gnu.trove.list.TDoubleList; -import gnu.trove.list.array.TDoubleArrayList; +import ij.ImagePlus; +import ij.gui.ImageCanvas; /** * Utility class to paint the {@link SpotRoi} component of spots. @@ -15,26 +18,15 @@ * @author Jean-Yves Tinevez * */ -public class PaintSpotRoi +public class PaintSpotRoi extends TrackMatePainter { - private final double[] calibration; - - private final DisplaySettings displaySettings; - private final java.awt.geom.Path2D.Double polygon; - private final TDoubleArrayList cx; - - private final TDoubleArrayList cy; - - public PaintSpotRoi( final double[] calibration, final DisplaySettings displaySettings ) + public PaintSpotRoi( final ImagePlus imp, final double[] calibration, final DisplaySettings displaySettings ) { - this.calibration = calibration; - this.displaySettings = displaySettings; + super( imp, calibration, displaySettings ); this.polygon = new Path2D.Double(); - this.cx = new TDoubleArrayList(); - this.cy = new TDoubleArrayList(); } /** @@ -45,41 +37,17 @@ public PaintSpotRoi( final double[] calibration, final DisplaySettings displaySe * the graphics object, configured to paint the spot with. * @param roi * the spot roi. - * @param x - * the X spot center in physical coordinates. - * @param y - * the Y spot center in physical coordinates. - * @param xcorner - * the X position of the displayed window. - * @param ycorner - * the X position of the displayed window. - * @param magnification - * the magnification of the displayed window. * @return the text position X indent in pixels to use to paint a string * next to the painted contour. */ - public int paint( - final Graphics2D g2d, - final SpotRoi roi, - final double x, - final double y, - final double xcorner, - final double ycorner, - final double magnification ) + @Override + public int paint( final Graphics2D g2d, final Spot spot ) { - // In pixel units. - final double xp = x / calibration[ 0 ] + 0.5f; - // Scale to image zoom. - final double xs = ( xp - xcorner ) * magnification; - // Contour in pixel coordinates. - roi.toPolygon( calibration, xcorner, ycorner, x, y, magnification, cx, cy ); - // The 0.5 is here so that we plot vertices at pixel centers. - polygon.reset(); - polygon.moveTo( cx.get( 0 ), cy.get( 0 ) ); - for ( int i = 1; i < cx.size(); ++i ) - polygon.lineTo( cx.get( i ), cy.get( i ) ); - polygon.closePath(); + final ImageCanvas canvas = canvas(); + if ( canvas == null ) + return -1; + final double maxTextPos = toPolygon( spot, polygon, this::toScreenX, this::toScreenY ); if ( displaySettings.isSpotFilled() ) { g2d.fill( polygon ); @@ -91,7 +59,8 @@ public int paint( g2d.draw( polygon ); } - final int textPos = ( int ) ( max( cx ) - xs ); + final double xs = toScreenX( spot.getDoublePosition( 0 ) ); + final int textPos = ( int ) ( maxTextPos - xs ); return textPos; } @@ -106,4 +75,44 @@ static final double max( final TDoubleList l ) } return max; } + + /** + * Maps the coordinates of this contour to a Path2D polygon, and return the + * max X coordinate of the produced shape. + * + * @param contour + * the contour to convert. + * @param polygon + * the polygon to write. Reset by this call. + * @param toScreenX + * a function to convert the X coordinate of this contour to + * screen coordinates. + * @param toScreenY + * a function to convert the Y coordinate of this contour to + * screen coordinates. + * @return the max X position in screen units of this shape. + */ + private static final double toPolygon( final Spot spot, final Path2D polygon, final DoubleUnaryOperator toScreenX, final DoubleUnaryOperator toScreenY ) + { + final SpotRoi roi = spot.getRoi(); + double maxTextPos = Double.NEGATIVE_INFINITY; + polygon.reset(); + final double x0 = toScreenX.applyAsDouble( roi.x[ 0 ] + spot.getDoublePosition( 0 ) ); + final double y0 = toScreenY.applyAsDouble( roi.y[ 0 ] + spot.getDoublePosition( 1 ) ); + polygon.moveTo( x0, y0 ); + if ( x0 > maxTextPos ) + maxTextPos = x0; + + for ( int i = 1; i < roi.x.length; i++ ) + { + final double xi = toScreenX.applyAsDouble( roi.x[ i ] + spot.getDoublePosition( 0 ) ); + final double yi = toScreenY.applyAsDouble( roi.y[ i ] + spot.getDoublePosition( 1 ) ); + polygon.lineTo( xi, yi ); + + if ( xi > maxTextPos ) + maxTextPos = xi; + } + polygon.closePath(); + return maxTextPos; + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java index 040c8ab13..57d15c317 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java @@ -4,6 +4,8 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import ij.ImagePlus; +import ij.gui.ImageCanvas; /** * Utility class to paint the spots as little spheres. @@ -11,35 +13,34 @@ * @author Jean-Yves Tinevez * */ -public class PaintSpotSphere +public class PaintSpotSphere extends TrackMatePainter { - private final double[] calibration; - - private final DisplaySettings displaySettings; - - public PaintSpotSphere( final double[] calibration, final DisplaySettings displaySettings ) + public PaintSpotSphere( final ImagePlus imp, final double[] calibration, final DisplaySettings displaySettings ) { - this.calibration = calibration; - this.displaySettings = displaySettings; + super( imp, calibration, displaySettings ); } - public int paint( - final Graphics2D g2d, - final Spot spot, - final double zslice, - final double xs, - final double ys, - final int xcorner, - final int ycorner, - final double magnification ) + @Override + public int paint( final Graphics2D g2d, final Spot spot ) { + final ImageCanvas canvas = canvas(); + if ( canvas == null ) + return -1; + + final double x = spot.getFeature( Spot.POSITION_X ); + final double y = spot.getFeature( Spot.POSITION_Y ); final double z = spot.getFeature( Spot.POSITION_Z ); + final double zslice = ( canvas.getImage().getSlice() - 1 ) * calibration[ 2 ]; final double dz = zslice - z; final double dz2 = dz * dz; final double radiusRatio = displaySettings.getSpotDisplayRadius(); final double radius = spot.getFeature( Spot.RADIUS ) * radiusRatio; + final double xs = toScreenX( x ); + final double ys = toScreenY( y ); + final double magnification = canvas.getMagnification(); + if ( dz2 >= radius * radius ) { g2d.fillOval( diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java index f03ea1fe0..da8e0c468 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -89,8 +89,8 @@ public SpotOverlay( final Model model, final ImagePlus imp, final DisplaySetting this.imp = imp; this.calibration = TMUtils.getSpatialCalibration( imp ); this.displaySettings = displaySettings; - this.paintSpotSphere = new PaintSpotSphere( calibration, displaySettings ); - this.paintSpotRoi = new PaintSpotRoi( calibration, displaySettings ); + this.paintSpotSphere = new PaintSpotSphere( imp, calibration, displaySettings ); + this.paintSpotRoi = new PaintSpotRoi( imp, calibration, displaySettings ); this.paintSpotMesh = new PaintSpotMesh( imp, calibration, displaySettings ); } @@ -262,23 +262,9 @@ protected void drawSpot( final Graphics2D g2d, final Spot spot, final double zsl final double xs = ( xp - xcorner ) * magnification; final double ys = ( yp - ycorner ) * magnification; - // Spot shape. - final SpotRoi roi = spot.getRoi(); - final SpotMesh mesh = spot.getMesh(); - - final int textPos; - if ( !displaySettings.isSpotDisplayedAsRoi() || ( mesh == null && roi == null ) ) - { - textPos = paintSpotSphere.paint( g2d, spot, zslice, xs, ys, xcorner, ycorner, magnification ); - } - else if ( roi != null ) - { - textPos = paintSpotRoi.paint( g2d, roi, xs, ys, xcorner, ycorner, magnification ); - } - else - { - textPos = paintSpotMesh.paint( g2d, spot ); - } + // Get a painter adequate for the spot and config we have. + final TrackMatePainter painter = getPainter( spot ); + final int textPos = painter.paint( g2d, spot ); if ( textPos >= 0 && displaySettings.isSpotShowName() ) { @@ -287,6 +273,20 @@ else if ( roi != null ) } } + private TrackMatePainter getPainter( final Spot spot ) + { + final SpotRoi roi = spot.getRoi(); + final SpotMesh mesh = spot.getMesh(); + + if ( !displaySettings.isSpotDisplayedAsRoi() || ( mesh == null && roi == null ) ) + return paintSpotSphere; + + if ( roi != null ) + return paintSpotRoi; + + return paintSpotMesh; + } + private static final void drawString( final Graphics2D g2d, final FontMetrics fm, diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java index 23a6f0eeb..034757414 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java @@ -1,5 +1,8 @@ package fiji.plugin.trackmate.visualization.hyperstack; +import java.awt.Graphics2D; + +import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; import ij.gui.ImageCanvas; @@ -20,6 +23,8 @@ public TrackMatePainter( final ImagePlus imp, final double[] calibration, final this.displaySettings = displaySettings; } + public abstract int paint( final Graphics2D g2d, final Spot spot ); + protected ImageCanvas canvas() { return imp.getCanvas(); @@ -85,4 +90,5 @@ public boolean isInside( final double x, final double y ) return false; return true; } + } From bee190ff5378f3ab62f8cf934dbdec30909d3d0c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 3 May 2023 18:04:40 +0200 Subject: [PATCH 105/263] Mesh slices can be painted filled. Holes in slices are properly handled. But only in the case of contours generated by meshes that are manifold and two-manifold. --- .../hyperstack/PaintSpotMesh.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 3268f4c37..598b772b1 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -2,6 +2,8 @@ import java.awt.Color; import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.geom.Area; import java.awt.geom.Path2D; import java.util.function.DoubleUnaryOperator; @@ -73,32 +75,33 @@ public int paint( final Graphics2D g2d, final Spot spot ) final Mesh translated = TranslateMesh.translate( sm.mesh, spot ); final Slice slice = ZSlicer.slice( translated, dz, calibration[ 2 ] ); - double maxTextPos = Double.NEGATIVE_INFINITY; + + // Convert to AWT shape. Only work in non-pathological cases, and + // because contours are sorted by decreasing area. + final Area shape = new Area(); for ( final Contour c : slice ) { final Contour contour = RamerDouglasPeucker.simplify( c, calibration[ 0 ] * 0.25 ); + toPolygon( contour, polygon, this::toScreenX, this::toScreenY ); - // Temporary set color by interior vs exterior. - if ( !contour.isInterior() ) - g2d.setColor( Color.RED ); + if ( contour.isInterior() ) + shape.add( new Area( polygon ) ); else - g2d.setColor( Color.GREEN ); - - final double textPos = toPolygon( contour, polygon, this::toScreenX, this::toScreenY ); - if ( textPos > maxTextPos ) - maxTextPos = textPos; - - if ( displaySettings.isSpotFilled() ) - { - g2d.fill( polygon ); - g2d.setColor( Color.BLACK ); - g2d.draw( polygon ); - } - else - { - g2d.draw( polygon ); - } + shape.subtract( new Area( polygon ) ); + } + + if ( displaySettings.isSpotFilled() ) + { + g2d.fill( shape ); + g2d.setColor( Color.BLACK ); + g2d.draw( shape ); + } + else + { + g2d.draw( shape ); } + final Rectangle bounds = shape.getBounds(); + final int maxTextPos = bounds.x + bounds.width; return ( int ) ( maxTextPos - xs ); } From 7acabe5d413ce7f6c53ea35101f70a00e34169b5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 3 May 2023 18:12:58 +0200 Subject: [PATCH 106/263] Code style changes. --- .../java/fiji/plugin/trackmate/Model.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java index d17b965ff..6a7a7befc 100644 --- a/src/main/java/fiji/plugin/trackmate/Model.java +++ b/src/main/java/fiji/plugin/trackmate/Model.java @@ -8,12 +8,12 @@ * 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 * . @@ -81,13 +81,13 @@ public class Model */ private int updateLevel = 0; - private final HashSet< Spot > spotsAdded = new HashSet< >(); + private final HashSet< Spot > spotsAdded = new HashSet<>(); - private final HashSet< Spot > spotsRemoved = new HashSet< >(); + private final HashSet< Spot > spotsRemoved = new HashSet<>(); - private final HashSet< Spot > spotsMoved = new HashSet< >(); + private final HashSet< Spot > spotsMoved = new HashSet<>(); - private final HashSet< Spot > spotsUpdated = new HashSet< >(); + private final HashSet< Spot > spotsUpdated = new HashSet<>(); /** * The event cache. During a transaction, some modifications might trigger @@ -96,15 +96,15 @@ public class Model * the event ID in this cache in the meantime. The event cache contains only * the int IDs of the events listed in {@link ModelChangeEvent}, namely *

* The {@link ModelChangeEvent#MODEL_MODIFIED} cannot be cached this way, * for it needs to be configured with modification spot and edge targets, so * it uses a different system (see {@link #flushUpdate()}). */ - private final HashSet< Integer > eventCache = new HashSet< >(); + private final HashSet< Integer > eventCache = new HashSet<>(); // OTHERS @@ -120,7 +120,7 @@ public class Model /** * The list of listeners listening to model content change. */ - Set< ModelChangeListener > modelChangeListeners = new LinkedHashSet< >(); + Set< ModelChangeListener > modelChangeListeners = new LinkedHashSet<>(); /* * CONSTRUCTOR @@ -154,7 +154,7 @@ protected TrackModel createTrackModel() *

* Subclassers can override this method to have the model work with their * own subclass of {@link FeatureModel}. - * + * * @return a new instance of {@link FeatureModel}. */ protected FeatureModel createFeatureModel() @@ -321,7 +321,7 @@ public void clearTracks( final boolean doNotify ) /** * Returns the {@link TrackModel} that manages the tracks for this model. - * + * * @return the track model. */ public TrackModel getTrackModel() @@ -447,7 +447,7 @@ public void notifyFeaturesComputed() /** * Set the logger that will receive the messages from the processes * occurring within this trackmate. - * + * * @param logger * the {@link Logger} to use. */ @@ -458,7 +458,7 @@ public void setLogger( final Logger logger ) /** * Return the logger currently set for this model. - * + * * @return the {@link Logger} used. */ public Logger getLogger() @@ -544,7 +544,7 @@ public synchronized Spot moveSpotFrom( final Spot spotToMove, final Integer from * model.endUpdate(); * } * - * + * * @param spotToAdd * the spot to add. * @param toFrame @@ -593,8 +593,9 @@ public synchronized Spot removeSpot( final Spot spotToRemove ) if ( DEBUG ) System.out.println( "[TrackMateModel] Removing spot " + spotToRemove + " from frame " + fromFrame ); - trackModel.removeSpot( spotToRemove ); - // changes to edges will be caught automatically by the TrackGraphModel + trackModel.removeSpot( spotToRemove ); + // changes to edges will be caught automatically by the + // TrackGraphModel return spotToRemove; } if ( DEBUG ) @@ -626,7 +627,7 @@ public synchronized Spot removeSpot( final Spot spotToRemove ) public synchronized void updateFeatures( final Spot spotToUpdate ) { spotsUpdated.add( spotToUpdate ); // Enlist for feature update when - // transaction is marked as finished + // transaction is marked as finished final Set< DefaultWeightedEdge > touchingEdges = trackModel.edgesOf( spotToUpdate ); if ( null != touchingEdges ) { @@ -766,7 +767,7 @@ public synchronized boolean setTrackVisibility( final Integer trackID, final boo * The copy is made of the same spot objects but on a different graph, that * can be safely edited. The copy does not include the feature values for * edges and tracks, but the features are declared. - * + * * @return a new model. */ public Model copy() @@ -810,7 +811,7 @@ public Model copy() featureModel.getTrackFeatureShortNames(), featureModel.getTrackFeatureDimensions(), featureModel.getTrackFeatureIsInt() ); - + // Feature values are not copied. return copy; } @@ -841,7 +842,7 @@ private void flushUpdate() final int nEdgesToSignal = trackModel.edgesAdded.size() + trackModel.edgesRemoved.size() + trackModel.edgesModified.size(); // Do we have tracks to update? - final HashSet< Integer > tracksToUpdate = new HashSet< >( trackModel.tracksUpdated ); + final HashSet< Integer > tracksToUpdate = new HashSet<>( trackModel.tracksUpdated ); // We also want to update the tracks that have edges that were modified for ( final DefaultWeightedEdge modifiedEdge : trackModel.edgesModified ) @@ -853,7 +854,7 @@ private void flushUpdate() final int nSpotsToUpdate = spotsAdded.size() + spotsMoved.size() + spotsUpdated.size(); if ( nSpotsToUpdate > 0 ) { - final HashSet< Spot > spotsToUpdate = new HashSet< >( nSpotsToUpdate ); + final HashSet< Spot > spotsToUpdate = new HashSet<>( nSpotsToUpdate ); spotsToUpdate.addAll( spotsAdded ); spotsToUpdate.addAll( spotsMoved ); spotsToUpdate.addAll( spotsUpdated ); From b44d73b3d7b9c411f33d4f586ac5c52bad357aa6 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 18:43:06 +0200 Subject: [PATCH 107/263] Precompute and store a cache of Z-slices for meshes. We don't want to recompute them every time we have to paint the spot-meshes, or iterate through their pixel. And we have an optimized version of the ZSlicer that computes Z-slices in batch with several Zs. This makes the display and iteration in the TrackMate app much much faster for large meshes. The difficulty is that we need to specify a scale in XY and in Z (to simplify the contours, and compute the Z positions of the intersections with the image stack slices). But we don't want to store these scales - which are the pixel size typically - in the model, that should be independent of a reference to an image. The solution in this commit consists in receiving these scales when we require the precomputed Z-slices: the cache is then recomputed if needed, and we have these scales in the code when we require the Z-slices --- .../java/fiji/plugin/trackmate/Model.java | 17 ++ .../java/fiji/plugin/trackmate/SpotMesh.java | 211 ++++++++++++++---- .../fiji/plugin/trackmate/io/TmXmlReader.java | 16 +- .../trackmate/util/mesh/SpotMeshCursor.java | 103 ++------- .../trackmate/util/mesh/SpotMeshIterable.java | 6 +- .../trackmate/mesh/DemoPixelIteration.java | 2 +- 6 files changed, 207 insertions(+), 148 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java index 6a7a7befc..300910456 100644 --- a/src/main/java/fiji/plugin/trackmate/Model.java +++ b/src/main/java/fiji/plugin/trackmate/Model.java @@ -130,6 +130,7 @@ public Model() { featureModel = createFeatureModel(); trackModel = createTrackModel(); + addModelChangeListener( new SpotMeshSliceCacheInvalidator() ); } /* @@ -959,4 +960,20 @@ private void flushUpdate() } } + private static class SpotMeshSliceCacheInvalidator implements ModelChangeListener + { + + @Override + public void modelChanged( final ModelChangeEvent event ) + { + if ( event.getEventID() != ModelChangeEvent.MODEL_MODIFIED ) + return; + + event.getSpots() + .stream() + .filter( s -> event.getSpotFlag( s ) == ModelChangeEvent.FLAG_SPOT_MODIFIED ) + .filter( s -> s.getMesh() != null ) + .forEach( s -> s.getMesh().resetZSliceCache( s ) ); + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 46697d786..8409a34eb 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -1,13 +1,24 @@ package fiji.plugin.trackmate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; import net.imagej.mesh.Triangles; import net.imagej.mesh.Vertices; +import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; +import net.imagej.mesh.alg.zslicer.Slice; +import net.imagej.mesh.alg.zslicer.ZSlicer; import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.RealInterval; +import net.imglib2.RealLocalizable; import net.imglib2.RealPoint; +import net.imglib2.util.Intervals; -public class SpotMesh implements SpotShape +public class SpotMesh implements SpotShape, RealLocalizable { /** @@ -17,32 +28,19 @@ public class SpotMesh implements SpotShape */ public final Mesh mesh; - /** - * The bounding-box, centered on (0,0,0) of this object. - */ - public float[] boundingBox; - public SpotMesh( final Mesh mesh, final float[] boundingBox ) - { - this.mesh = mesh; - this.boundingBox = boundingBox; - } + private Map< Integer, Slice > sliceMap; - /** - * Creates a spot representing a 3D object, with the mesh specifying its - * position and shape. - *

- * Warning: the specified mesh is modified and wrapped in the spot. - * - * @param mesh - * the mesh. - * @param quality - * the spot quality. - * @return a new {@link Spot}. - */ - public static Spot createSpot( final Mesh mesh, final double quality ) + /** The center of this object. */ + private final RealPoint center; + + /** The bounding-box, centered on (0,0,0) of this object. */ + public RealInterval boundingBox; + + public SpotMesh( final Mesh mesh ) { - final RealPoint center = Meshes.center( mesh ); + this.mesh = mesh; + this.center = Meshes.center( mesh ); // Shift mesh to (0, 0, 0). final Vertices vertices = mesh.vertices(); @@ -52,23 +50,53 @@ public static Spot createSpot( final Mesh mesh, final double quality ) vertices.xf( i ) - center.getFloatPosition( 0 ), vertices.yf( i ) - center.getFloatPosition( 1 ), vertices.zf( i ) - center.getFloatPosition( 2 ) ); + // Bounding box, also centered on (0,0,0) + this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); + } - // Bounding box with respect to 0. - final float[] boundingBox = Meshes.boundingBox( mesh ); - - // Spot mesh, all relative to 0. - final SpotMesh spotMesh = new SpotMesh( mesh, boundingBox ); - - // Create spot. - final double r = spotMesh.radius(); - final Spot spot = new Spot( - center.getDoublePosition( 0 ), - center.getDoublePosition( 1 ), - center.getDoublePosition( 2 ), - r, - quality ); - spot.setMesh( spotMesh ); - return spot; + /** + * Gets the slice resulting from the intersection of the mesh with the XY + * plane with the specified z position, in pixel coordinates, 0-based. + *

+ * Relies on a sort of Z-slice cache. To regenerate it if needed, we need + * the specification of a scale in XY and Z specified here. + * + * @param zSlice + * the Z position of the slice, in pixel coordinates, 0-based. + * @param xyScale + * a measure of the mesh scale along XY, for instance the pixel + * size in XY that it was generated from. Used to correct and + * simplify the slice contours. + * @param zScale + * the pixel size in Z, used to generate the Z planes spacing. + * @return the slice, or null if the mesh does not intersect + * with the specified XY plane. The slice XY coordinates are + * centered so (0,0) corresponds to the mesh center. + */ + public Slice getZSlice( final int zSlice, final double xyScale, final double zScale ) + { + if ( sliceMap == null ) + sliceMap = buildSliceMap( mesh, boundingBox, center, xyScale, zScale ); + + return sliceMap.get( Integer.valueOf( zSlice ) ); + } + + /** + * Invalidates the Z-slices cache. This will force its recomputation. To be + * called after the spot has changed size or Z position. + * + * @param newPosition + * the new position of the spot. + */ + public void resetZSliceCache( final RealLocalizable newPosition ) + { + center.setPosition( newPosition ); + if ( newPosition.getDoublePosition( 2 ) == center.getDoublePosition( 2 ) ) + { + // No need to recompute the cache. Invariant by X and Y. + return; + } + sliceMap = null; } /** @@ -174,7 +202,7 @@ public void scale( final double alpha ) final float za = ( float ) ( ra * Math.cos( theta ) ); vertices.setPositionf( v, xa, ya, za ); } - boundingBox = Meshes.boundingBox( mesh ); + this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); } @Override @@ -182,7 +210,7 @@ public SpotMesh copy() { final BufferMesh meshCopy = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); Meshes.copy( this.mesh, meshCopy ); - return new SpotMesh( meshCopy, boundingBox.clone() ); + return new SpotMesh( meshCopy ); } @Override @@ -191,9 +219,9 @@ public String toString() final StringBuilder str = new StringBuilder( super.toString() ); str.append( "\nBounding-box" ); - str.append( String.format( "\n%5s: %7.2f -> %7.2f", "X", boundingBox[ 0 ], boundingBox[ 3 ] ) ); - str.append( String.format( "\n%5s: %7.2f -> %7.2f", "Y", boundingBox[ 1 ], boundingBox[ 4 ] ) ); - str.append( String.format( "\n%5s: %7.2f -> %7.2f", "Z", boundingBox[ 2 ], boundingBox[ 5 ] ) ); + str.append( String.format( "\n%5s: %7.2f -> %7.2f", "X", boundingBox.realMin( 0 ), boundingBox.realMax( 0 ) ) ); + str.append( String.format( "\n%5s: %7.2f -> %7.2f", "Y", boundingBox.realMin( 1 ), boundingBox.realMax( 1 ) ) ); + str.append( String.format( "\n%5s: %7.2f -> %7.2f", "Z", boundingBox.realMin( 2 ), boundingBox.realMax( 2 ) ) ); final Vertices vertices = mesh.vertices(); final long nVertices = vertices.size(); @@ -211,4 +239,97 @@ public String toString() return str.toString(); } + + /** + * Computes the intersections of the specified mesh with the multiple + * Z-slice at integer coordinates corresponding to 1-pixel spacing in + * Z. This is why we need to have the calibration array. The + * slices are centered on (0,0) the mesh center. + * + * @param mesh + * the mesh to reslice, centered on (0,0,0). + * @param boundingBox + * its bounding box, also centered on (0,0,0). + * @param center + * the mesh center true position. Needed to reposition it in Z. + * @param calibration + * the pixel size array, needed to compute the 1-pixel spacing. + * @return a map from slice position (integer, pixel coordinates) to slices. + */ + private static final Map< Integer, Slice > buildSliceMap( + final Mesh mesh, + final RealInterval boundingBox, + final RealLocalizable center, + final double xyScale, + final double zScale ) + { + /* + * Let's try to have everything relative to (0,0,0), so that we do not + * have to recompute the Z slices when the mesh is moved in X and Y. + */ + + /* + * Compute the Z integers, in pixel coordinates, of the mesh + * intersection. These coordinates are absolute value (relative to mesh + * center). + */ + final double zc = center.getDoublePosition( 2 ); + final int minZ = ( int ) Math.ceil( ( boundingBox.realMin( 2 ) + zc ) / zScale ); + final int maxZ = ( int ) Math.floor( ( boundingBox.realMax( 2 ) + zc ) / zScale ); + final int[] zSlices = new int[ maxZ - minZ + 1 ]; + for ( int i = 0; i < zSlices.length; i++ ) + zSlices[ i ] = ( minZ + i );// pixel coords, absolute value + + /* + * Compute equivalent Z positions in physical units, relative to + * (0,0,0), of these intersections. + */ + final double[] zPos = new double[ zSlices.length ]; + for ( int i = 0; i < zPos.length; i++ ) + zPos[ i ] = zSlices[ i ] * zScale - zc; + + // Compute the slices. They will be centered on (0,0) in XY. + final List< Slice > slices = ZSlicer.slices( + mesh, + zPos, + zScale ); + + // Simplify below 1/4th of a pixel. + final double epsilon = xyScale * 0.25; + final List< Slice > simplifiedSlices = slices.stream() + .map( s -> RamerDouglasPeucker.simplify( s, epsilon ) ) + .collect( Collectors.toList() ); + + // Store in a map of Z slice -> slice. + final Map< Integer, Slice > sliceMap = new HashMap<>(); + for ( int i = 0; i < zSlices.length; i++ ) + sliceMap.put( Integer.valueOf( zSlices[ i ] ), simplifiedSlices.get( i ) ); + + return sliceMap; + } + + private static final RealInterval toRealInterval( final float[] bb ) + { + return Intervals.createMinMaxReal( bb[ 0 ], bb[ 1 ], bb[ 2 ], bb[ 3 ], bb[ 4 ], bb[ 5 ] ); + } + + public static Spot createSpot( final Mesh mesh, final double quality ) + { + final SpotMesh sm = new SpotMesh( mesh ); + final Spot spot = new Spot( sm.center, sm.radius(), quality ); + spot.setMesh( sm ); + return spot; + } + + @Override + public int numDimensions() + { + return 3; + } + + @Override + public double getDoublePosition( final int d ) + { + return center.getDoublePosition( d ); + } } diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index e85d97849..b74232f95 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -156,7 +156,6 @@ import ij.ImagePlus; import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; -import net.imagej.mesh.Vertices; import net.imagej.mesh.nio.BufferMesh; public class TmXmlReader @@ -957,20 +956,7 @@ private SpotCollection getSpots( final Element modelElement ) final Mesh m = PLY_MESH_IO.open( zipFile.getInputStream( entry ) ); final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() ); Meshes.calculateNormals( m, mesh ); - - // Shift mesh to (0, 0, 0). - final Vertices vertices = mesh.vertices(); - final long nVertices = vertices.size(); - for ( long i = 0; i < nVertices; i++ ) - vertices.setPositionf( i, - vertices.xf( i ) - spot.getFloatPosition( 0 ), - vertices.yf( i ) - spot.getFloatPosition( 1 ), - vertices.zf( i ) - spot.getFloatPosition( 2 ) ); - - // Bounding box with respect to 0. - final float[] boundingBox = Meshes.boundingBox( mesh ); - - final SpotMesh sm = new SpotMesh( mesh, boundingBox ); + final SpotMesh sm = new SpotMesh( mesh ); spot.setMesh( sm ); } catch ( final IOException e ) diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java index b6c70d0b0..0f2099736 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -1,22 +1,10 @@ package fiji.plugin.trackmate.util.mesh; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import gnu.trove.list.array.TDoubleArrayList; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; import net.imagej.mesh.alg.zslicer.Slice; -import net.imagej.mesh.alg.zslicer.ZSlicer; -import net.imagej.mesh.obj.transform.TranslateMesh; import net.imglib2.Cursor; import net.imglib2.RandomAccess; -import net.imglib2.RealLocalizable; /** * A {@link Cursor} that iterates over the pixels inside a mesh. @@ -36,8 +24,6 @@ public class SpotMeshCursor< T > implements Cursor< T > private final double[] cal; - private final float[] bb; - private final int minX; private final int maxX; @@ -52,6 +38,8 @@ public class SpotMeshCursor< T > implements Cursor< T > private final RandomAccess< T > ra; + private final SpotMesh sm; + private boolean hasNext; private int iy; @@ -66,46 +54,19 @@ public class SpotMeshCursor< T > implements Cursor< T > */ private final TDoubleArrayList intersectionXs = new TDoubleArrayList(); - private final Map< Integer, Slice > sliceMap; - private Slice slice; - private final Mesh mesh; - - public SpotMeshCursor( final RandomAccess< T > ra, final Spot spot, final double[] cal ) - { - this( ra, spot.getMesh(), spot, cal ); - } - - public SpotMeshCursor( final RandomAccess< T > ra, final SpotMesh sm, final RealLocalizable center, final double[] cal ) - { - this( - ra, - TranslateMesh.translate( sm.mesh, center ), - new float[] { - sm.boundingBox[ 0 ] + center.getFloatPosition( 0 ), - sm.boundingBox[ 1 ] + center.getFloatPosition( 1 ), - sm.boundingBox[ 2 ] + center.getFloatPosition( 2 ), - sm.boundingBox[ 3 ] + center.getFloatPosition( 0 ), - sm.boundingBox[ 4 ] + center.getFloatPosition( 1 ), - sm.boundingBox[ 5 ] + center.getFloatPosition( 2 ) }, - cal ); - } - - public SpotMeshCursor( final RandomAccess< T > ra, final Mesh mesh, final float[] boundingBox, final double[] cal ) + public SpotMeshCursor( final RandomAccess< T > ra, final SpotMesh sm, final double[] cal ) { this.ra = ra; - this.mesh = mesh; + this.sm = sm; this.cal = cal; - this.bb = boundingBox; - this.minX = ( int ) Math.floor( bb[ 0 ] / cal[ 0 ] ); - this.maxX = ( int ) Math.ceil( bb[ 3 ] / cal[ 0 ] ); - this.minY = ( int ) Math.floor( bb[ 1 ] / cal[ 1 ] ); - this.maxY = ( int ) Math.ceil( bb[ 4 ] / cal[ 1 ] ); - this.minZ = ( int ) Math.floor( bb[ 2 ] / cal[ 2 ] ); - this.maxZ = ( int ) Math.ceil( bb[ 5 ] / cal[ 2 ] ); - - this.sliceMap = buildSliceMap( mesh, boundingBox, cal ); + this.minX = ( int ) Math.floor( ( sm.boundingBox.realMin( 0 ) + sm.getDoublePosition( 0 ) ) / cal[ 0 ] ); + this.maxX = ( int ) Math.ceil( ( sm.boundingBox.realMax( 0 ) + sm.getDoublePosition( 0 ) ) / cal[ 0 ] ); + this.minY = ( int ) Math.floor( ( sm.boundingBox.realMin( 1 ) + sm.getDoublePosition( 1 ) ) / cal[ 1 ] ); + this.maxY = ( int ) Math.ceil( ( sm.boundingBox.realMax( 1 ) + sm.getDoublePosition( 1 ) ) / cal[ 1 ] ); + this.minZ = ( int ) Math.floor( ( sm.boundingBox.realMin( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); + this.maxZ = ( int ) Math.ceil( ( sm.boundingBox.realMin( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); reset(); } @@ -115,7 +76,7 @@ public void reset() this.ix = maxX; // To force a new ray cast when we call fwd() this.iy = minY - 1; // Then we will move to minY. this.iz = minZ; - this.slice = sliceMap.get( iz ); + this.slice = sm.getZSlice( iz, cal[ 0 ], cal[ 2 ] ); this.hasNext = true; preFetch(); } @@ -150,13 +111,13 @@ private void preFetch() iz++; if ( iz > maxZ ) return; // Finished! - slice = sliceMap.get( iz ); + slice = sm.getZSlice( iz, cal[ 0 ], cal[ 2 ] ); } if ( slice == null ) continue; - // New ray cast. - final double y = iy * cal[ 1 ]; + // New ray cast, relative to slice center + final double y = iy * cal[ 1 ] - sm.getDoublePosition( 1 ); slice.xRayCast( y, intersectionXs, cal[ 1 ] ); // No intersection? @@ -169,7 +130,7 @@ private void preFetch() // We have found the next position. // Is it inside? - final double x = ix * cal[ 0 ]; + final double x = ix * cal[ 0 ] - sm.getDoublePosition( 0 ); // Special case: only one intersection. if ( intersectionXs.size() == 1 ) @@ -234,7 +195,10 @@ public long getLongPosition( final int d ) @Override public Cursor< T > copyCursor() { - return new SpotMeshCursor<>( ra.copyRandomAccess(), mesh, bb, cal ); + return new SpotMeshCursor<>( + ra.copyRandomAccess(), + sm.copy(), + cal.clone() ); } @Override @@ -254,33 +218,4 @@ public T get() { return ra.get(); } - - private static final Map< Integer, Slice > buildSliceMap( final Mesh mesh, final float[] boundingBox, final double[] calibration ) - { - // Pre-compute slices. - final int minZ = ( int ) Math.ceil( boundingBox[ 2 ] / calibration[ 2 ] ); - final int maxZ = ( int ) Math.floor( boundingBox[ 5 ] / calibration[ 2 ] ); - final double[] zs = new double[ maxZ - minZ + 1 ]; - final List< Integer > sliceIndices = new ArrayList<>( zs.length ); - for ( int i = 0; i < zs.length; i++ ) - { - zs[ i ] = ( minZ + i ) * calibration[ 2 ]; // physical coords. - sliceIndices.add( minZ + i ); // pixel coordinates. - } - final List< Slice > slices = ZSlicer.slices( mesh, zs, calibration[ 2 ] ); - - // Simplify below /14th of a pixel. - final double epsilon = calibration[ 0 ] * 0.25; - final List< Slice > simplifiedSlices = slices.stream() - .map( s -> RamerDouglasPeucker.simplify( s, epsilon ) ) - .collect( Collectors.toList() ); - - // Store in a map Z (integer) pos -> slice. - final Map< Integer, Slice > sliceMap = new HashMap<>(); - for ( int i = 0; i < sliceIndices.size(); i++ ) - sliceMap.put( sliceIndices.get( i ), simplifiedSlices.get( i ) ); - - return sliceMap; - } - } diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java index 6d8b3d6b3..a402222e7 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java @@ -77,19 +77,19 @@ public Iterator< T > iterator() @Override public long min( final int d ) { - return Math.round( ( sm.boundingBox[ d ] + center.getFloatPosition( d ) ) / calibration[ d ] ); + return Math.round( ( sm.boundingBox.realMin( d ) + center.getFloatPosition( d ) ) / calibration[ d ] ); } @Override public long max( final int d ) { - return Math.round( ( sm.boundingBox[ 3 + d ] + center.getFloatPosition( d ) ) / calibration[ d ] ); + return Math.round( ( sm.boundingBox.realMax( d ) + center.getFloatPosition( d ) ) / calibration[ d ] ); } @Override public Cursor< T > cursor() { - return new SpotMeshCursor<>( img.randomAccess(), sm, center, calibration ); + return new SpotMeshCursor<>( img.randomAccess(), sm, calibration ); } @Override diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index d639fa443..0cdb1c004 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -78,7 +78,7 @@ public static < T extends RealType< T > > void main( final String[] args ) for ( final Spot spot : model.getSpots().iterable( true ) ) { System.out.println( spot ); - final Cursor< T > cursor = new SpotMeshCursor< T >( TMUtils.rawWraps( out ).randomAccess(), spot, cal ); + final Cursor< T > cursor = new SpotMeshCursor< T >( TMUtils.rawWraps( out ).randomAccess(), spot.getMesh(), cal ); final RandomAccess< T > ra = TMUtils.rawWraps( imp ).randomAccess(); while ( cursor.hasNext() ) { From 55ada3bf73e5c8a9b3b0bcaf3a5ef0f6ab18808b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 18:43:44 +0200 Subject: [PATCH 108/263] Rework a bit the painting of spots. --- .../hyperstack/PaintSpotMesh.java | 63 +++++++--------- .../hyperstack/PaintSpotRoi.java | 28 ++++++- .../hyperstack/PaintSpotSphere.java | 22 +++--- .../hyperstack/TrackMatePainter.java | 74 +++++++++++-------- 4 files changed, 109 insertions(+), 78 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 598b772b1..9e6f9446f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -11,13 +11,10 @@ import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; -import ij.gui.ImageCanvas; -import net.imagej.mesh.Mesh; import net.imagej.mesh.alg.zslicer.Contour; import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; import net.imagej.mesh.alg.zslicer.Slice; -import net.imagej.mesh.alg.zslicer.ZSlicer; -import net.imagej.mesh.obj.transform.TranslateMesh; +import net.imglib2.RealLocalizable; /** * Utility class to paint the {@link SpotMesh} component of spots. @@ -30,59 +27,53 @@ public class PaintSpotMesh extends TrackMatePainter private final Path2D.Double polygon; + private final Area shape; + public PaintSpotMesh( final ImagePlus imp, final double[] calibration, final DisplaySettings displaySettings ) { super( imp, calibration, displaySettings ); this.polygon = new Path2D.Double(); + this.shape = new Area(); + } @Override public int paint( final Graphics2D g2d, final Spot spot ) { - final ImageCanvas canvas = canvas(); - if ( canvas == null ) - return -1; - final SpotMesh sm = spot.getMesh(); - final double x = spot.getFeature( Spot.POSITION_X ); - final double y = spot.getFeature( Spot.POSITION_Y ); - // Don't paint if we are out of screen. - if ( toScreenX( sm.boundingBox[ 0 ] + x ) > canvas.getWidth() ) - return -1; - if ( toScreenX( sm.boundingBox[ 3 ] + x ) < 0 ) - return -1; - if ( toScreenY( sm.boundingBox[ 1 ] + y ) > canvas.getHeight() ) - return -1; - if ( toScreenY( sm.boundingBox[ 4 ] + y ) < 0 ) + if ( !intersect( sm.boundingBox, spot ) ) return -1; // Z plane does not cross bounding box. + final double x = spot.getFeature( Spot.POSITION_X ); + final double y = spot.getFeature( Spot.POSITION_Y ); final double xs = toScreenX( x ); final double ys = toScreenY( y ); final double z = spot.getFeature( Spot.POSITION_Z ); - final double dz = ( canvas.getImage().getSlice() - 1 ) * calibration[ 2 ]; - if ( sm.boundingBox[ 2 ] + z > dz || sm.boundingBox[ 5 ] + z < dz ) + final int zSlice = imp.getSlice() - 1; + final double dz = zSlice * calibration[ 2 ]; + if ( sm.boundingBox.realMin( 2 ) + z > dz || sm.boundingBox.realMax( 2 ) + z < dz ) { - final double magnification = canvas.getMagnification(); - g2d.fillOval( - ( int ) Math.round( xs - 2 * magnification ), - ( int ) Math.round( ys - 2 * magnification ), - ( int ) Math.round( 4 * magnification ), - ( int ) Math.round( 4 * magnification ) ); + paintOutOfFocus( g2d, xs, ys ); return -1; } - final Mesh translated = TranslateMesh.translate( sm.mesh, spot ); - final Slice slice = ZSlicer.slice( translated, dz, calibration[ 2 ] ); - // Convert to AWT shape. Only work in non-pathological cases, and // because contours are sorted by decreasing area. - final Area shape = new Area(); + final Slice slice = sm.getZSlice( zSlice, calibration[ 0 ], calibration[ 2 ] ); + if ( slice == null ) + { + paintOutOfFocus( g2d, xs, ys ); + return -1; + } + + // Should not be null. + shape.reset(); for ( final Contour c : slice ) { final Contour contour = RamerDouglasPeucker.simplify( c, calibration[ 0 ] * 0.25 ); - toPolygon( contour, polygon, this::toScreenX, this::toScreenY ); + toPolygon( spot, contour, polygon, this::toScreenX, this::toScreenY ); if ( contour.isInterior() ) shape.add( new Area( polygon ) ); @@ -121,20 +112,20 @@ public int paint( final Graphics2D g2d, final Spot spot ) * screen coordinates. * @return the max X position in screen units of this shape. */ - private static final double toPolygon( final Contour contour, final Path2D polygon, final DoubleUnaryOperator toScreenX, final DoubleUnaryOperator toScreenY ) + private static final double toPolygon( final RealLocalizable center, final Contour contour, final Path2D polygon, final DoubleUnaryOperator toScreenX, final DoubleUnaryOperator toScreenY ) { double maxTextPos = Double.NEGATIVE_INFINITY; polygon.reset(); - final double x0 = toScreenX.applyAsDouble( contour.x( 0 ) ); - final double y0 = toScreenY.applyAsDouble( contour.y( 0 ) ); + final double x0 = toScreenX.applyAsDouble( contour.x( 0 ) + center.getDoublePosition( 0 ) ); + final double y0 = toScreenY.applyAsDouble( contour.y( 0 ) + center.getDoublePosition( 1 ) ); polygon.moveTo( x0, y0 ); if ( x0 > maxTextPos ) maxTextPos = x0; for ( int i = 1; i < contour.size(); i++ ) { - final double xi = toScreenX.applyAsDouble( contour.x( i ) ); - final double yi = toScreenY.applyAsDouble( contour.y( i ) ); + final double xi = toScreenX.applyAsDouble( contour.x( i ) + center.getDoublePosition( 0 ) ); + final double yi = toScreenY.applyAsDouble( contour.y( i ) + center.getDoublePosition( 1 ) ); polygon.lineTo( xi, yi ); if ( xi > maxTextPos ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java index 2379f81ce..4a091e91d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java @@ -10,7 +10,8 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import gnu.trove.list.TDoubleList; import ij.ImagePlus; -import ij.gui.ImageCanvas; +import net.imglib2.RealInterval; +import net.imglib2.util.Intervals; /** * Utility class to paint the {@link SpotRoi} component of spots. @@ -43,8 +44,7 @@ public PaintSpotRoi( final ImagePlus imp, final double[] calibration, final Disp @Override public int paint( final Graphics2D g2d, final Spot spot ) { - final ImageCanvas canvas = canvas(); - if ( canvas == null ) + if ( !intersect( boundingBox( spot.getRoi() ), spot ) ) return -1; final double maxTextPos = toPolygon( spot, polygon, this::toScreenX, this::toScreenY ); @@ -64,6 +64,28 @@ public int paint( final Graphics2D g2d, final Spot spot ) return textPos; } + private static final RealInterval boundingBox( final SpotRoi roi ) + { + double minX = roi.x[ 0 ]; + double maxX = roi.x[ 0 ]; + double minY = roi.y[ 0 ]; + double maxY = roi.y[ 0 ]; + for ( int i = 0; i < roi.x.length; i++ ) + { + final double x = roi.x[ i ]; + if ( x > maxX ) + maxX = x; + if ( x < minX ) + minX = x; + final double y = roi.y[ i ]; + if ( y > maxY ) + maxY = y; + if ( y < minY ) + minY = y; + } + return Intervals.createMinMaxReal( minX, minY, maxX, maxY ); + } + static final double max( final TDoubleList l ) { double max = Double.NEGATIVE_INFINITY; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java index 57d15c317..77b32e3b7 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java @@ -5,7 +5,8 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; -import ij.gui.ImageCanvas; +import net.imglib2.RealInterval; +import net.imglib2.util.Intervals; /** * Utility class to paint the spots as little spheres. @@ -24,14 +25,13 @@ public PaintSpotSphere( final ImagePlus imp, final double[] calibration, final D @Override public int paint( final Graphics2D g2d, final Spot spot ) { - final ImageCanvas canvas = canvas(); - if ( canvas == null ) + if ( !intersect( boundingBox( spot ), spot ) ) return -1; final double x = spot.getFeature( Spot.POSITION_X ); final double y = spot.getFeature( Spot.POSITION_Y ); final double z = spot.getFeature( Spot.POSITION_Z ); - final double zslice = ( canvas.getImage().getSlice() - 1 ) * calibration[ 2 ]; + final double zslice = ( imp.getSlice() - 1 ) * calibration[ 2 ]; final double dz = zslice - z; final double dz2 = dz * dz; final double radiusRatio = displaySettings.getSpotDisplayRadius(); @@ -39,15 +39,11 @@ public int paint( final Graphics2D g2d, final Spot spot ) final double xs = toScreenX( x ); final double ys = toScreenY( y ); - final double magnification = canvas.getMagnification(); + final double magnification = getMagnification(); if ( dz2 >= radius * radius ) { - g2d.fillOval( - ( int ) Math.round( xs - 2 * magnification ), - ( int ) Math.round( ys - 2 * magnification ), - ( int ) Math.round( 4 * magnification ), - ( int ) Math.round( 4 * magnification ) ); + paintOutOfFocus( g2d, xs, ys ); return -1; // Do not paint spot name. } @@ -68,4 +64,10 @@ public int paint( final Graphics2D g2d, final Spot spot ) final int textPos = ( int ) apparentRadius; return textPos; } + + private static final RealInterval boundingBox( final Spot spot ) + { + final double r = spot.getFeature( Spot.RADIUS ).doubleValue(); + return Intervals.createMinMaxReal( -r, -r, r, r ); + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java index 034757414..3d96f1ef1 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java @@ -6,6 +6,8 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; import ij.gui.ImageCanvas; +import net.imglib2.RealInterval; +import net.imglib2.RealLocalizable; public abstract class TrackMatePainter { @@ -14,7 +16,7 @@ public abstract class TrackMatePainter protected final DisplaySettings displaySettings; - private final ImagePlus imp; + protected final ImagePlus imp; public TrackMatePainter( final ImagePlus imp, final double[] calibration, final DisplaySettings displaySettings ) { @@ -25,9 +27,32 @@ public TrackMatePainter( final ImagePlus imp, final double[] calibration, final public abstract int paint( final Graphics2D g2d, final Spot spot ); - protected ImageCanvas canvas() + /** + * Returns true if the specified bounding-box, shifted by the + * Specified amount, intersects with the display window. + * + * @param boundingBox + * the bounding box, centered at (0,0), in physical coordinates. + * @param center + * the center of the bounding-box, in physical coordinates. + * @return + */ + protected boolean intersect( final RealInterval boundingBox, final RealLocalizable center ) { - return imp.getCanvas(); + final ImageCanvas canvas = imp.getCanvas(); + if ( canvas == null ) + return false; + + if ( toScreenX( boundingBox.realMin( 0 ) + center.getDoublePosition( 0 ) ) > canvas.getWidth() ) + return false; + if ( toScreenX( boundingBox.realMax( 0 ) + center.getDoublePosition( 0 ) ) < 0 ) + return false; + if ( toScreenY( boundingBox.realMin( 1 ) + center.getDoublePosition( 1 ) ) > canvas.getHeight() ) + return false; + if ( toScreenY( boundingBox.realMax( 1 ) + center.getDoublePosition( 1 ) ) < 0 ) + return false; + return true; + } /** @@ -38,9 +63,9 @@ protected ImageCanvas canvas() * the X position to convert. * @return the screen X coordinate. */ - public double toScreenX( final double x ) + protected double toScreenX( final double x ) { - final ImageCanvas canvas = canvas(); + final ImageCanvas canvas = imp.getCanvas(); if ( canvas == null ) return Double.NaN; @@ -56,9 +81,9 @@ public double toScreenX( final double x ) * the Y position to convert. * @return the screen Y coordinate. */ - public double toScreenY( final double y ) + protected double toScreenY( final double y ) { - final ImageCanvas canvas = canvas(); + final ImageCanvas canvas = imp.getCanvas(); if ( canvas == null ) return Double.NaN; @@ -66,29 +91,20 @@ public double toScreenY( final double y ) return canvas.screenYD( yp ); } - /** - * Returns true of the point with the specified coordinates in - * physical units lays inside the painted window. - * - * @param x - * the X coordinate in physical unit. - * @param y - * the Y coordinate in physical unit. - * @return true if (x, y) is inside the painted window. - */ - public boolean isInside( final double x, final double y ) + protected void paintOutOfFocus( final Graphics2D g2d, final double xs, final double ys ) { - final ImageCanvas canvas = canvas(); - if ( canvas == null ) - return false; - - final double xs = toScreenX( x ); - if ( xs < 0 || xs > canvas.getWidth() ) - return false; - final double ys = toScreenY( y ); - if ( ys < 0 || ys > canvas.getHeight() ) - return false; - return true; + final double magnification = getMagnification(); + g2d.fillOval( + ( int ) Math.round( xs - 2 * magnification ), + ( int ) Math.round( ys - 2 * magnification ), + ( int ) Math.round( 4 * magnification ), + ( int ) Math.round( 4 * magnification ) ); } + protected double getMagnification() + { + if ( imp.getCanvas() == null ) + return 1.; + return imp.getCanvas().getMagnification(); + } } From 34f314e7220b5ce2f651e440efa2aff3d854d3a9 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 18:44:23 +0200 Subject: [PATCH 109/263] Can manually scale meshes in size. Not sure it is useful, but we can do it for normal spots, rois and now meshes. --- .../visualization/hyperstack/ModelEditActions.java | 13 +++++++------ .../visualization/hyperstack/SpotEditTool.java | 10 +++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java index 671cd839a..50173291c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java @@ -38,7 +38,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotRoi; +import fiji.plugin.trackmate.SpotShape; import fiji.plugin.trackmate.detection.semiauto.SemiAutoTracker; import fiji.plugin.trackmate.util.ModelTools; import fiji.plugin.trackmate.util.TMUtils; @@ -279,29 +279,30 @@ public void changeSpotRadius( final boolean increase, final boolean fast ) return; final double radius = target.getFeature( Spot.RADIUS ); - final int factor = ( increase ) ? 1 : -1; + final int factor = ( increase ) ? -1 : 1; final double dx = imp.getCalibration().pixelWidth; final double newRadius = ( fast ) ? radius + factor * dx * COARSE_STEP : radius + factor * dx * FINE_STEP; + if ( newRadius <= dx ) return; // Store new value of radius for next spot creation. previousRadius = newRadius; - final SpotRoi roi = target.getRoi(); - if ( null == roi ) + final SpotShape shape = target.getShape(); + if ( null == shape ) { target.putFeature( Spot.RADIUS, newRadius ); } else { final double alpha = newRadius / radius; - roi.scale( alpha ); - target.putFeature( Spot.RADIUS, roi.radius() ); + shape.scale( alpha ); + target.putFeature( Spot.RADIUS, shape.radius() ); } model.beginUpdate(); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java index d091862d2..b5e2a0a73 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java @@ -8,12 +8,12 @@ * 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 * . @@ -159,17 +159,17 @@ protected void registerTool( final ImageCanvas canvas ) { /* * Double check! Since TrackMate v7 there the following bug: - * + * * Sometimes the listeners of this tool get added to the target image * canvas TWICE. This causes an unspeakable mess where all events are * triggered twice for e.g. a single click. For instance you cannot * shift-click on a spot to add it to the selection, because the event * is fired TWICE, which results in the spot being de-selected * immediately after being selected. - * + * * But the double registration seems to happen randomly. Sometimes the * listeners are added only once, *sometimes* (more often) twice. - * + * * To work around this mess, we overload the registerTool(ImageCanvas) * method and skip the registration if we find that the mouse listener * has already been added to the canvas. It fixes the issue, regardless From b559b28834b811122dc82b5285268573ae5c0a7d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:14:34 +0200 Subject: [PATCH 110/263] Use ImgLib2 Cast util to wrap an ImagePlus in an ImgPlus. --- .../fiji/plugin/trackmate/util/TMUtils.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index 2896511f5..af5a8e482 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -8,12 +8,12 @@ * 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 * . @@ -55,7 +55,7 @@ import net.imglib2.img.ImagePlusAdapter; import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.type.Type; -import net.imglib2.type.numeric.real.DoubleType; +import net.imglib2.util.Cast; import net.imglib2.util.Util; /** @@ -134,16 +134,12 @@ else if ( obj instanceof Logger ) } /** - * Wraps an IJ {@link ImagePlus} in an imglib2 {@link ImgPlus}, without - * parameterized types. The only way I have found to beat javac constraints - * on bounded multiple wildcard. + * Wraps an IJ {@link ImagePlus} in an imglib2 {@link ImgPlus}, abinding to + * a returned type. */ - @SuppressWarnings( "rawtypes" ) - public static final ImgPlus rawWraps( final ImagePlus imp ) + public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) { - final ImgPlus< DoubleType > img = ImagePlusAdapter.wrapImgPlus( imp ); - final ImgPlus raw = img; - return raw; + return Cast.unchecked( ImagePlusAdapter.wrapImgPlus( imp ) ); } /** From 0c39749553d2bdb02146e5697624389c7d53ed58 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:15:38 +0200 Subject: [PATCH 111/263] Fix mistake with mesh cursor. --- .../java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java index 0f2099736..0c88e45ed 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -66,7 +66,7 @@ public SpotMeshCursor( final RandomAccess< T > ra, final SpotMesh sm, final doub this.minY = ( int ) Math.floor( ( sm.boundingBox.realMin( 1 ) + sm.getDoublePosition( 1 ) ) / cal[ 1 ] ); this.maxY = ( int ) Math.ceil( ( sm.boundingBox.realMax( 1 ) + sm.getDoublePosition( 1 ) ) / cal[ 1 ] ); this.minZ = ( int ) Math.floor( ( sm.boundingBox.realMin( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); - this.maxZ = ( int ) Math.ceil( ( sm.boundingBox.realMin( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); + this.maxZ = ( int ) Math.ceil( ( sm.boundingBox.realMax( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); reset(); } From 5352cf056829bcf92938f1df0d8696aec7cf5500 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:16:33 +0200 Subject: [PATCH 112/263] Paint not-filled mesh with polygons, not shapes. --- .../hyperstack/PaintSpotMesh.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 9e6f9446f..87031943a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -12,7 +12,6 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; import net.imagej.mesh.alg.zslicer.Contour; -import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; import net.imagej.mesh.alg.zslicer.Slice; import net.imglib2.RealLocalizable; @@ -68,28 +67,30 @@ public int paint( final Graphics2D g2d, final Spot spot ) return -1; } - // Should not be null. - shape.reset(); - for ( final Contour c : slice ) - { - final Contour contour = RamerDouglasPeucker.simplify( c, calibration[ 0 ] * 0.25 ); - toPolygon( spot, contour, polygon, this::toScreenX, this::toScreenY ); - - if ( contour.isInterior() ) - shape.add( new Area( polygon ) ); - else - shape.subtract( new Area( polygon ) ); - } if ( displaySettings.isSpotFilled() ) { + // Should not be null. + shape.reset(); + for ( final Contour c : slice ) + { + toPolygon( spot, c, polygon, this::toScreenX, this::toScreenY ); + if ( c.isInterior() ) + shape.add( new Area( polygon ) ); + else + shape.subtract( new Area( polygon ) ); + } g2d.fill( shape ); g2d.setColor( Color.BLACK ); g2d.draw( shape ); } else { - g2d.draw( shape ); + for ( final Contour c : slice ) + { + toPolygon( spot, c, polygon, this::toScreenX, this::toScreenY ); + g2d.draw( polygon ); + } } final Rectangle bounds = shape.getBounds(); final int maxTextPos = bounds.x + bounds.width; From 5766a3b04ec7007c83f6442ea29ce5d56c1c11bc Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:17:30 +0200 Subject: [PATCH 113/263] Forgot to commit SpotMesh changes, storing its center. --- .../fiji/plugin/trackmate/util/SpotUtil.java | 16 ++++++++++++---- .../trackmate/util/mesh/SpotMeshIterable.java | 11 +++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java b/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java index 232cc16d4..8195b337e 100644 --- a/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java +++ b/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java @@ -36,6 +36,7 @@ import net.imglib2.IterableInterval; import net.imglib2.Localizable; import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealLocalizable; import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; @@ -52,7 +53,7 @@ public static final < T extends RealType< T > > IterableInterval< T > iterable( if ( shape instanceof SpotRoi ) return iterableRoi( ( SpotRoi ) shape, center, img ); else if ( shape instanceof SpotMesh ) - return iterableMesh( ( SpotMesh ) shape, center, img ); + return iterableMesh( ( SpotMesh ) shape, img ); else throw new IllegalArgumentException( "Unsuitable shape for SpotShape: " + shape ); } @@ -79,7 +80,7 @@ public static final < T extends RealType< T > > IterableInterval< T > iterable( else if ( mesh != null ) { // Operate on 3D if we have a mesh. - return iterableMesh( mesh, spot, img ); + return iterableMesh( mesh, img ); } else { @@ -94,15 +95,22 @@ else if ( mesh != null ) } } - public static < T extends NumericType< T > > IterableInterval< T > iterableMesh( final SpotMesh sm, final RealLocalizable center, final ImgPlus< T > img ) + public static < T extends NumericType< T > > IterableInterval< T > iterableMesh( final SpotMesh sm, final ImgPlus< T > img ) { return new SpotMeshIterable< T >( Views.extendZero( img ), sm, - center, TMUtils.getSpatialCalibration( img ) ); } + public static < T extends NumericType< T > > IterableInterval< T > iterableMesh( final SpotMesh sm, final RandomAccessibleInterval< T > img, final double[] calibration ) + { + return new SpotMeshIterable< T >( + Views.extendZero( img ), + sm, + calibration ); + } + private static < T > IterableInterval< T > makeSinglePixelIterable( final RealLocalizable center, final ImgPlus< T > img ) { final double[] calibration = TMUtils.getSpatialCalibration( img ); diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java index a402222e7..bce81f950 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java @@ -7,7 +7,6 @@ import net.imglib2.IterableInterval; import net.imglib2.Localizable; import net.imglib2.RandomAccessible; -import net.imglib2.RealLocalizable; public class SpotMeshIterable< T > implements IterableInterval< T >, Localizable { @@ -18,17 +17,13 @@ public class SpotMeshIterable< T > implements IterableInterval< T >, Localizable private final SpotMesh sm; - private final RealLocalizable center; - public SpotMeshIterable( final RandomAccessible< T > img, final SpotMesh sm, - final RealLocalizable center, final double[] calibration ) { this.img = img; this.sm = sm; - this.center = center; this.calibration = calibration; } @@ -41,7 +36,7 @@ public int numDimensions() @Override public long getLongPosition( final int d ) { - return Math.round( center.getDoublePosition( d ) / calibration[ d ] ); + return Math.round( sm.getDoublePosition( d ) / calibration[ d ] ); } @Override @@ -77,13 +72,13 @@ public Iterator< T > iterator() @Override public long min( final int d ) { - return Math.round( ( sm.boundingBox.realMin( d ) + center.getFloatPosition( d ) ) / calibration[ d ] ); + return Math.round( ( sm.boundingBox.realMin( d ) + sm.getFloatPosition( d ) ) / calibration[ d ] ); } @Override public long max( final int d ) { - return Math.round( ( sm.boundingBox.realMax( d ) + center.getFloatPosition( d ) ) / calibration[ d ] ); + return Math.round( ( sm.boundingBox.realMax( d ) + sm.getFloatPosition( d ) ) / calibration[ d ] ); } @Override From abf748f90500b13c5f33671aca9fcf19e354baf4 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:18:12 +0200 Subject: [PATCH 114/263] Support making single spots with possibly hollow meshes. That is: we do not rely on connected components to separate mesh, but instead simply use the ImgLib2 regions. --- .../plugin/trackmate/detection/MaskUtils.java | 74 +++++-------------- 1 file changed, 17 insertions(+), 57 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 7aa1a0c9c..7b97865c0 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -40,10 +40,8 @@ import net.imagej.axis.Axes; import net.imagej.axis.AxisType; import net.imagej.mesh.Mesh; -import net.imagej.mesh.MeshConnectedComponents; import net.imagej.mesh.Meshes; import net.imagej.mesh.Vertices; -import net.imglib2.Cursor; import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; @@ -57,7 +55,6 @@ import net.imglib2.histogram.Real1dBinMapper; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; -import net.imglib2.roi.Regions; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegionCursor; @@ -449,9 +446,6 @@ public static final < T extends RealType< T >, S extends RealType< S > > List< S final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { - if ( input.numDimensions() == 3 ) - return from3DThresholdWithROI( input, interval, threshold, calibration, simplify, qualityImage ); - // Get labeling. final ImgLabeling< Integer, IntType > labeling = toLabeling( input, interval, threshold, numThreads ); @@ -464,48 +458,6 @@ else if ( input.numDimensions() == 3 ) throw new IllegalArgumentException( "Can only process 2D or 3D images with this method, but got " + labeling.numDimensions() + "D." ); } - private static < T extends RealType< T >, S extends RealType< S > > List< Spot > from3DThresholdWithROI( - final RandomAccessible< T > input, - final Interval interval, - final double threshold, - final double[] calibration, - final boolean simplify, - final RandomAccessibleInterval< S > qualityImage ) - { - Mesh mesh = Meshes.marchingCubes( Views.interval( input, interval ), threshold ); - mesh = Meshes.removeDuplicateVertices( mesh, 2 ); - Meshes.scale( mesh, calibration ); - - // Min volume below which we skip spot creation. - // Discard meshes below ~ volume of 10 pixels. - final double minVolume = 10. * calibration[ 0 ] * calibration[ 1 ] * calibration[ 2 ]; - - final List< Spot > spots = new ArrayList<>(); - for ( Mesh cc : MeshConnectedComponents.iterable( mesh ) ) - { - if ( simplify && cc.triangles().size() > 200 ) - cc = Meshes.simplify( cc, 0.1f, 10f ); - - final double volume = Meshes.volume( cc ); - if ( volume < minVolume ) - continue; - - final Spot spot = SpotMesh.createSpot( cc, 0. ); - final double quality; - if ( qualityImage == null ) - { - quality = volume; - } - else - { - quality = 1.; // TODO - } - spot.putFeature( Spot.QUALITY, quality ); - spots.add( spot ); - } - return spots; - } - /** * Creates spots with ROIs from a 2D label image. The quality * value is read from a secondary image, by taking the max value in each @@ -715,15 +667,24 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot // To mesh. final IntervalView< BoolType > box = Views.zeroMin( region ); - final Mesh mesh = Meshes.marchingCubes( box ); - final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 0 ); + final Mesh mesh = Meshes.marchingCubes( box, 0.5 ); + final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 2 ); final Mesh simplified = simplify ? Meshes.simplify( cleaned, 0.25f, 10 ) : cleaned; - // PScale to physical coords. + + // Remove meshes that are too small + final double volumeThreshold = 10. * calibration[ 0 ] * calibration[ 1 ] * calibration[ 2 ]; + if ( SpotMesh.volume( mesh ) < volumeThreshold ) + continue; + + // Scale to physical coords. final double[] origin = region.minAsDoubleArray(); scale( simplified.vertices(), calibration, origin ); + // Make spot with default quality. + final Spot spot = SpotMesh.createSpot( simplified, 0. ); + // Measure quality. final double quality; if ( null == qualityImage ) @@ -732,19 +693,18 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot } else { + final IterableInterval< S > iterable = SpotUtil.iterableMesh( spot.getMesh(), qualityImage, calibration ); double max = Double.NEGATIVE_INFINITY; - final Cursor< S > cursor = Regions.sample( region, qualityImage ).cursor(); - while(cursor.hasNext()) + for ( final S s : iterable ) { - cursor.fwd(); - final double val = cursor.get().getRealDouble(); + final double val = s.getRealDouble(); if ( val > max ) max = val; } quality = max; } - - spots.add( SpotMesh.createSpot( simplified, quality ) ); + spot.putFeature( Spot.QUALITY, Double.valueOf( quality ) ); + spots.add( spot ); } return spots; } From 617150a742d5f3ffe55d0df344528dee6d82eaaa Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:18:34 +0200 Subject: [PATCH 115/263] Demo of the hollow mesh support in TrackMate. --- .../plugin/trackmate/mesh/DemoHollowMesh.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java new file mode 100644 index 000000000..d2530dada --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java @@ -0,0 +1,68 @@ +package fiji.plugin.trackmate.mesh; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.util.SpotUtil; +import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import ij.ImageJ; +import ij.ImagePlus; +import ij.gui.NewImage; +import net.imagej.ImgPlus; +import net.imglib2.RealPoint; +import net.imglib2.type.numeric.integer.UnsignedByteType; + +public class DemoHollowMesh +{ + + public static void main( final String[] args ) + { + ImageJ.main( args ); + final ImagePlus imp = makeImg(); + + final Settings settings = new Settings( imp ); + settings.detectorFactory = new ThresholdDetectorFactory<>(); + settings.detectorSettings = settings.detectorFactory.getDefaultSettings(); + settings.detectorSettings.put( ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD, 120. ); + settings.detectorSettings.put( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, false ); + + final TrackMate trackmate = new TrackMate( settings ); + trackmate.execDetection(); + + final Model model = trackmate.getModel(); + model.getSpots().setVisible( true ); + final SelectionModel selection = new SelectionModel( model ); + final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); + + final HyperStackDisplayer view = new HyperStackDisplayer( model, selection, imp, ds ); + view.render(); + } + + public static ImagePlus makeImg() + { + final ImagePlus imp = NewImage.createByteImage( "Hollow", 256, 256, 256, NewImage.FILL_BLACK ); + final ImgPlus< UnsignedByteType > img = TMUtils.rawWraps( imp ); + + final RealPoint center = RealPoint.wrap( new double[] { + imp.getWidth() / 2., + imp.getHeight() / 2., + imp.getNSlices() / 2. + } ); + final double r1 = imp.getWidth() / 4.; + final double r2 = imp.getWidth() / 8.; + final double r3 = imp.getWidth() / 16.; + final Spot s1 = new Spot( center, r1, 1. ); + final Spot s2 = new Spot( center, r2, 1. ); + final Spot s3 = new Spot( center, r3, 1. ); + SpotUtil.iterable( s1, img ).forEach( p -> p.setReal( 250. ) ); + SpotUtil.iterable( s2, img ).forEach( p -> p.setZero() ); + SpotUtil.iterable( s3, img ).forEach( p -> p.setReal( 250. ) ); + return imp; + } +} From f017db6876ad9789304f34f88f3f964c47a04ed8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:18:48 +0200 Subject: [PATCH 116/263] Update demo. --- .../fiji/plugin/trackmate/mesh/DemoPixelIteration.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index 0cdb1c004..ee6a75370 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -19,6 +19,7 @@ import ij.ImagePlus; import ij.gui.NewImage; import ij.process.LUT; +import net.imagej.ImgPlus; import net.imglib2.Cursor; import net.imglib2.RandomAccess; import net.imglib2.type.numeric.RealType; @@ -26,7 +27,6 @@ public class DemoPixelIteration { - @SuppressWarnings( "unchecked" ) public static < T extends RealType< T > > void main( final String[] args ) { try @@ -78,8 +78,9 @@ public static < T extends RealType< T > > void main( final String[] args ) for ( final Spot spot : model.getSpots().iterable( true ) ) { System.out.println( spot ); - final Cursor< T > cursor = new SpotMeshCursor< T >( TMUtils.rawWraps( out ).randomAccess(), spot.getMesh(), cal ); - final RandomAccess< T > ra = TMUtils.rawWraps( imp ).randomAccess(); + final ImgPlus< T > img = TMUtils.rawWraps( out ); + final Cursor< T > cursor = new SpotMeshCursor< T >( img.randomAccess(), spot.getMesh(), cal ); + final RandomAccess< T > ra = img.randomAccess(); while ( cursor.hasNext() ) { cursor.fwd(); From 9d90aaead0dcfc39e4c86edf40328e90d66f8d8f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 10:28:05 +0200 Subject: [PATCH 117/263] Refactor a bit the MaskUtil class, which is becoming too long. --- .../plugin/trackmate/detection/MaskUtils.java | 67 ++----------------- 1 file changed, 5 insertions(+), 62 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 7b97865c0..9f10e2332 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -30,7 +30,6 @@ import java.util.concurrent.ExecutorService; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.Threads; @@ -39,9 +38,6 @@ import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.Vertices; import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; @@ -61,7 +57,6 @@ import net.imglib2.roi.labeling.LabelRegions; import net.imglib2.type.BooleanType; import net.imglib2.type.logic.BitType; -import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; @@ -72,14 +67,10 @@ public class MaskUtils { - /** - * Smoothing interval for ROIs. - */ + /** Smoothing interval for ROIs. */ private static final double SMOOTH_INTERVAL = 2.; - /** - * Douglas-Peucker polygon simplification max distance. - */ + /** Douglas-Peucker polygon simplification max distance. */ private static final double DOUGLAS_PEUCKER_MAX_DISTANCE = 0.5; public static final < T extends RealType< T > > double otsuThreshold( final RandomAccessibleInterval< T > img ) @@ -664,46 +655,10 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot while ( iterator.hasNext() ) { final LabelRegion< Integer > region = iterator.next(); - - // To mesh. - final IntervalView< BoolType > box = Views.zeroMin( region ); - final Mesh mesh = Meshes.marchingCubes( box, 0.5 ); - final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, 2 ); - final Mesh simplified = simplify - ? Meshes.simplify( cleaned, 0.25f, 10 ) - : cleaned; - - // Remove meshes that are too small - final double volumeThreshold = 10. * calibration[ 0 ] * calibration[ 1 ] * calibration[ 2 ]; - if ( SpotMesh.volume( mesh ) < volumeThreshold ) + final Spot spot = regionToSpotMesh( region, simplify, calibration, qualityImage ); + if ( spot == null ) continue; - // Scale to physical coords. - final double[] origin = region.minAsDoubleArray(); - scale( simplified.vertices(), calibration, origin ); - - // Make spot with default quality. - final Spot spot = SpotMesh.createSpot( simplified, 0. ); - - // Measure quality. - final double quality; - if ( null == qualityImage ) - { - quality = SpotMesh.volume( simplified ); - } - else - { - final IterableInterval< S > iterable = SpotUtil.iterableMesh( spot.getMesh(), qualityImage, calibration ); - double max = Double.NEGATIVE_INFINITY; - for ( final S s : iterable ) - { - final double val = s.getRealDouble(); - if ( val > max ) - max = val; - } - quality = max; - } - spot.putFeature( Spot.QUALITY, Double.valueOf( quality ) ); spots.add( spot ); } return spots; @@ -795,7 +750,7 @@ private static final void douglasPeucker( final List< double[] > list, final int * Algorithm (Wikipedia) * @author Justin Wetherell * @param list - * List of Double[] points (x,y) + * List of double[] points (x,y) * @param epsilon * Distance dimension * @return Similar curve with fewer points @@ -1304,16 +1259,4 @@ public String toString() return res + "]"; } } - - private static void scale( final Vertices vertices, final double[] scale, final double[] origin ) - { - final long nv = vertices.size(); - for ( long i = 0; i < nv; i++ ) - { - final double x = ( origin[ 0 ] + vertices.x( i ) ) * scale[ 0 ]; - final double y = ( origin[ 1 ] + vertices.y( i ) ) * scale[ 1 ]; - final double z = ( origin[ 2 ] + vertices.z( i ) ) * scale[ 2 ]; - vertices.set( i, x, y, z ); - } - } } From c468c422d5b6aeaa7e1535d222cf4d6ff6e71b1c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 10:33:03 +0200 Subject: [PATCH 118/263] Recompute the Z-slices when changing the radius. Nevermind the XY changes optimization. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 8409a34eb..49d5f4385 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -91,11 +91,6 @@ public Slice getZSlice( final int zSlice, final double xyScale, final double zSc public void resetZSliceCache( final RealLocalizable newPosition ) { center.setPosition( newPosition ); - if ( newPosition.getDoublePosition( 2 ) == center.getDoublePosition( 2 ) ) - { - // No need to recompute the cache. Invariant by X and Y. - return; - } sliceMap = null; } From b3cf8b54e42fff500a755bc15704924daf6bafa6 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 17:57:12 +0200 Subject: [PATCH 119/263] Rename 2D morphology analyzers. To make room for the 3D analyzers. --- .../java/fiji/plugin/trackmate/Settings.java | 24 +++++++++++++++---- .../{ConvexHull.java => ConvexHull2D.java} | 2 +- ...zer.java => Spot2DFitEllipseAnalyzer.java} | 16 ++++++------- ...a => Spot2DFitEllipseAnalyzerFactory.java} | 6 ++--- ...a => Spot2DMorphologyAnalyzerFactory.java} | 8 +++---- ...Analyzer.java => Spot2DShapeAnalyzer.java} | 16 ++++++------- ...y.java => Spot2DShapeAnalyzerFactory.java} | 6 ++--- .../descriptors/SpotFilterDescriptor.java | 15 ++++++------ .../fiji/plugin/trackmate/io/TmXmlReader.java | 8 +++---- ... => Spot2DMorphologyAnalyzerProvider.java} | 14 +++++------ .../SpotFeatureComputationBenchmark.java | 4 ++-- .../TmXmlReaderTestDrive.java | 4 ++-- 12 files changed, 69 insertions(+), 54 deletions(-) rename src/main/java/fiji/plugin/trackmate/features/spot/{ConvexHull.java => ConvexHull2D.java} (99%) rename src/main/java/fiji/plugin/trackmate/features/spot/{SpotFitEllipseAnalyzer.java => Spot2DFitEllipseAnalyzer.java} (93%) rename src/main/java/fiji/plugin/trackmate/features/spot/{SpotFitEllipseAnalyzerFactory.java => Spot2DFitEllipseAnalyzerFactory.java} (94%) rename src/main/java/fiji/plugin/trackmate/features/spot/{SpotMorphologyAnalyzerFactory.java => Spot2DMorphologyAnalyzerFactory.java} (86%) rename src/main/java/fiji/plugin/trackmate/features/spot/{SpotShapeAnalyzer.java => Spot2DShapeAnalyzer.java} (81%) rename src/main/java/fiji/plugin/trackmate/features/spot/{SpotShapeAnalyzerFactory.java => Spot2DShapeAnalyzerFactory.java} (94%) rename src/main/java/fiji/plugin/trackmate/providers/{SpotMorphologyAnalyzerProvider.java => Spot2DMorphologyAnalyzerProvider.java} (68%) diff --git a/src/main/java/fiji/plugin/trackmate/Settings.java b/src/main/java/fiji/plugin/trackmate/Settings.java index 22cad10fa..8a5f011d9 100644 --- a/src/main/java/fiji/plugin/trackmate/Settings.java +++ b/src/main/java/fiji/plugin/trackmate/Settings.java @@ -8,12 +8,12 @@ * 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 * . @@ -35,8 +35,9 @@ import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactoryBase; import fiji.plugin.trackmate.features.track.TrackAnalyzer; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot3DMorphologyAnalyzerProvider; import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; -import fiji.plugin.trackmate.providers.SpotMorphologyAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; import fiji.plugin.trackmate.tracking.SpotTrackerFactory; import ij.ImagePlus; @@ -270,7 +271,7 @@ public int getYend() * copied, as well as filters, etc. The exception are analyzers: all the * analyzers that are found at runtime are added, regardless of the content * of the instance to copy. - * + * * @param newImp * the image to copy the settings for. * @return a new settings object. @@ -531,24 +532,37 @@ public String getErrorMessage() */ public void addAllAnalyzers() { + // Base spot analyzers. final SpotAnalyzerProvider spotAnalyzerProvider = new SpotAnalyzerProvider( imp == null ? 1 : imp.getNChannels() ); final List< String > spotAnalyzerKeys = spotAnalyzerProvider.getKeys(); for ( final String key : spotAnalyzerKeys ) addSpotAnalyzerFactory( spotAnalyzerProvider.getFactory( key ) ); + // Shall we add 2D morphology analyzers? if ( imp != null && DetectionUtils.is2D( imp ) && detectorFactory != null && detectorFactory.has2Dsegmentation() ) { - final SpotMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new SpotMorphologyAnalyzerProvider( imp.getNChannels() ); + final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot2DMorphologyAnalyzerProvider( imp.getNChannels() ); + final List< String > spotMorphologyAnaylyzerKeys = spotMorphologyAnalyzerProvider.getKeys(); + for ( final String key : spotMorphologyAnaylyzerKeys ) + addSpotAnalyzerFactory( spotMorphologyAnalyzerProvider.getFactory( key ) ); + } + + // Shall we add 3D morphology analyzers? + if ( imp != null && !DetectionUtils.is2D( imp ) && detectorFactory != null && detectorFactory.has3Dsegmentation() ) + { + final Spot3DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot3DMorphologyAnalyzerProvider( imp.getNChannels() ); final List< String > spotMorphologyAnaylyzerKeys = spotMorphologyAnalyzerProvider.getKeys(); for ( final String key : spotMorphologyAnaylyzerKeys ) addSpotAnalyzerFactory( spotMorphologyAnalyzerProvider.getFactory( key ) ); } + // Edge analyzers. final EdgeAnalyzerProvider edgeAnalyzerProvider = new EdgeAnalyzerProvider(); final List< String > edgeAnalyzerKeys = edgeAnalyzerProvider.getKeys(); for ( final String key : edgeAnalyzerKeys ) addEdgeAnalyzer( edgeAnalyzerProvider.getFactory( key ) ); + // Track analyzers. final TrackAnalyzerProvider trackAnalyzerProvider = new TrackAnalyzerProvider(); final List< String > trackAnalyzerKeys = trackAnalyzerProvider.getKeys(); for ( final String key : trackAnalyzerKeys ) diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull.java b/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java similarity index 99% rename from src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull.java rename to src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java index b1387fed5..6e306de7c 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java @@ -31,7 +31,7 @@ * Adapted from a code by Kirill Artemov, * https://github.com/DoctorGester/cia-stats. */ -public final class ConvexHull +public final class ConvexHull2D { private static List< Point > makeHull( final List< Point > points ) diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java similarity index 93% rename from src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzer.java rename to src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java index efb5870b4..e76f24e8c 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java @@ -29,12 +29,12 @@ import net.imglib2.type.numeric.RealType; import net.imglib2.util.Util; -public class SpotFitEllipseAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > +public class Spot2DFitEllipseAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > { private final boolean is2D; - public SpotFitEllipseAnalyzer( final boolean is2D ) + public Spot2DFitEllipseAnalyzer( final boolean is2D ) { this.is2D = is2D; } @@ -88,12 +88,12 @@ public void process( final Spot spot ) theta = Double.NaN; aspectRatio = Double.NaN; } - spot.putFeature( SpotFitEllipseAnalyzerFactory.X0, x0 ); - spot.putFeature( SpotFitEllipseAnalyzerFactory.Y0, y0 ); - spot.putFeature( SpotFitEllipseAnalyzerFactory.MAJOR, major ); - spot.putFeature( SpotFitEllipseAnalyzerFactory.MINOR, minor ); - spot.putFeature( SpotFitEllipseAnalyzerFactory.THETA, theta ); - spot.putFeature( SpotFitEllipseAnalyzerFactory.ASPECTRATIO, aspectRatio ); + spot.putFeature( Spot2DFitEllipseAnalyzerFactory.X0, x0 ); + spot.putFeature( Spot2DFitEllipseAnalyzerFactory.Y0, y0 ); + spot.putFeature( Spot2DFitEllipseAnalyzerFactory.MAJOR, major ); + spot.putFeature( Spot2DFitEllipseAnalyzerFactory.MINOR, minor ); + spot.putFeature( Spot2DFitEllipseAnalyzerFactory.THETA, theta ); + spot.putFeature( Spot2DFitEllipseAnalyzerFactory.ASPECTRATIO, aspectRatio ); } /** diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzerFactory.java similarity index 94% rename from src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzerFactory.java rename to src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzerFactory.java index 23f8f1462..bcff9cab4 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotFitEllipseAnalyzerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzerFactory.java @@ -36,8 +36,8 @@ import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; -@Plugin( type = SpotMorphologyAnalyzerFactory.class ) -public class SpotFitEllipseAnalyzerFactory< T extends RealType< T > & NativeType< T > > implements SpotMorphologyAnalyzerFactory< T > +@Plugin( type = Spot2DMorphologyAnalyzerFactory.class ) +public class Spot2DFitEllipseAnalyzerFactory< T extends RealType< T > & NativeType< T > > implements Spot2DMorphologyAnalyzerFactory< T > { public static final String KEY = "Spot fit 2D ellipse"; @@ -94,7 +94,7 @@ public SpotAnalyzer< T > getAnalyzer( final ImgPlus< T > img, final int frame, f if ( channel != 0 ) return SpotAnalyzer.dummyAnalyzer(); - return new SpotFitEllipseAnalyzer<>( DetectionUtils.is2D( img ) ); + return new Spot2DFitEllipseAnalyzer<>( DetectionUtils.is2D( img ) ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotMorphologyAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DMorphologyAnalyzerFactory.java similarity index 86% rename from src/main/java/fiji/plugin/trackmate/features/spot/SpotMorphologyAnalyzerFactory.java rename to src/main/java/fiji/plugin/trackmate/features/spot/Spot2DMorphologyAnalyzerFactory.java index 5becf6b1d..a63a7604f 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotMorphologyAnalyzerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DMorphologyAnalyzerFactory.java @@ -8,12 +8,12 @@ * 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 * . @@ -26,9 +26,9 @@ /** * Special interface for spot analyzers that can compute feature values based on - * the contour of spots. + * the 2D contour of spots. * * @author Jean-Yves Tinevez - 2020 */ -public interface SpotMorphologyAnalyzerFactory< T extends RealType< T > & NativeType< T > > extends SpotAnalyzerFactoryBase< T > +public interface Spot2DMorphologyAnalyzerFactory< T extends RealType< T > & NativeType< T > > extends SpotAnalyzerFactoryBase< T > {} diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java similarity index 81% rename from src/main/java/fiji/plugin/trackmate/features/spot/SpotShapeAnalyzer.java rename to src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java index 3e7004e56..f4be68aca 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotShapeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java @@ -25,12 +25,12 @@ import fiji.plugin.trackmate.SpotRoi; import net.imglib2.type.numeric.RealType; -public class SpotShapeAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > +public class Spot2DShapeAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > { private final boolean is2D; - public SpotShapeAnalyzer( final boolean is2D ) + public Spot2DShapeAnalyzer( final boolean is2D ) { this.is2D = is2D; } @@ -49,7 +49,7 @@ public void process( final Spot spot ) { area = roi.area(); perimeter = getLength( roi ); - final SpotRoi convexHull = ConvexHull.convexHull( roi ); + final SpotRoi convexHull = ConvexHull2D.convexHull( roi ); convexArea = convexHull.area(); } else @@ -71,11 +71,11 @@ public void process( final Spot spot ) final double solidity = area / convexArea; final double shapeIndex = ( area <= 0. ) ? Double.NaN : perimeter / Math.sqrt( area ); - spot.putFeature( SpotShapeAnalyzerFactory.AREA, area ); - spot.putFeature( SpotShapeAnalyzerFactory.PERIMETER, perimeter ); - spot.putFeature( SpotShapeAnalyzerFactory.CIRCULARITY, circularity ); - spot.putFeature( SpotShapeAnalyzerFactory.SOLIDITY, solidity ); - spot.putFeature( SpotShapeAnalyzerFactory.SHAPE_INDEX, shapeIndex ); + spot.putFeature( Spot2DShapeAnalyzerFactory.AREA, area ); + spot.putFeature( Spot2DShapeAnalyzerFactory.PERIMETER, perimeter ); + spot.putFeature( Spot2DShapeAnalyzerFactory.CIRCULARITY, circularity ); + spot.putFeature( Spot2DShapeAnalyzerFactory.SOLIDITY, solidity ); + spot.putFeature( Spot2DShapeAnalyzerFactory.SHAPE_INDEX, shapeIndex ); } private static final double getLength( final SpotRoi roi ) diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotShapeAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzerFactory.java similarity index 94% rename from src/main/java/fiji/plugin/trackmate/features/spot/SpotShapeAnalyzerFactory.java rename to src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzerFactory.java index 25c2d3bf2..62a653b39 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotShapeAnalyzerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzerFactory.java @@ -36,8 +36,8 @@ import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; -@Plugin( type = SpotMorphologyAnalyzerFactory.class ) -public class SpotShapeAnalyzerFactory< T extends RealType< T > & NativeType< T > > implements SpotMorphologyAnalyzerFactory< T > +@Plugin( type = Spot2DMorphologyAnalyzerFactory.class ) +public class Spot2DShapeAnalyzerFactory< T extends RealType< T > & NativeType< T > > implements Spot2DMorphologyAnalyzerFactory< T > { public static final String KEY = "Spot 2D shape descriptors"; @@ -88,7 +88,7 @@ public SpotAnalyzer< T > getAnalyzer( final ImgPlus< T > img, final int frame, f if ( channel != 0 ) return SpotAnalyzer.dummyAnalyzer(); - return new SpotShapeAnalyzer<>( DetectionUtils.is2D( img ) ); + return new Spot2DShapeAnalyzer<>( DetectionUtils.is2D( img ) ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java index 4d628b893..e3ff8697d 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java @@ -8,12 +8,12 @@ * 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 * . @@ -36,13 +36,13 @@ import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.features.FeatureFilter; -import fiji.plugin.trackmate.features.spot.SpotMorphologyAnalyzerFactory; +import fiji.plugin.trackmate.features.spot.Spot2DMorphologyAnalyzerFactory; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.FilterGuiPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; import fiji.plugin.trackmate.io.SettingsPersistence; -import fiji.plugin.trackmate.providers.SpotMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; public class SpotFilterDescriptor extends WizardPanelDescriptor @@ -106,15 +106,16 @@ public void run() * Should we add morphology feature analyzers? */ + // 2D. if ( trackmate.getSettings().detectorFactory != null && trackmate.getSettings().detectorFactory.has2Dsegmentation() && DetectionUtils.is2D( trackmate.getSettings().imp ) ) { - logger.log( "\nAdding morphology analyzers...\n", Logger.BLUE_COLOR ); + logger.log( "\nAdding 2D morphology analyzers...\n", Logger.BLUE_COLOR ); final Settings settings = trackmate.getSettings(); - final SpotMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new SpotMorphologyAnalyzerProvider( settings.imp.getNChannels() ); + final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot2DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); @SuppressWarnings( "rawtypes" ) - final List< SpotMorphologyAnalyzerFactory > factories = spotMorphologyAnalyzerProvider + final List< Spot2DMorphologyAnalyzerFactory > factories = spotMorphologyAnalyzerProvider .getKeys() .stream() .map( key -> spotMorphologyAnalyzerProvider.getFactory( key ) ) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index b74232f95..c1fc80d94 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -144,7 +144,7 @@ import fiji.plugin.trackmate.providers.DetectorProvider; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; -import fiji.plugin.trackmate.providers.SpotMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackerProvider; import fiji.plugin.trackmate.providers.ViewProvider; @@ -432,7 +432,7 @@ public Settings readSettings( final ImagePlus imp ) new SpotAnalyzerProvider( ( imp == null ) ? 1 : imp.getNChannels() ), new EdgeAnalyzerProvider(), new TrackAnalyzerProvider(), - new SpotMorphologyAnalyzerProvider( ( imp == null ) ? 1 : imp.getNChannels() ) ); + new Spot2DMorphologyAnalyzerProvider( ( imp == null ) ? 1 : imp.getNChannels() ) ); } /** @@ -471,7 +471,7 @@ public Settings readSettings( final SpotAnalyzerProvider spotAnalyzerProvider, final EdgeAnalyzerProvider edgeAnalyzerProvider, final TrackAnalyzerProvider trackAnalyzerProvider, - final SpotMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider ) + final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider ) { final Element settingsElement = root.getChild( SETTINGS_ELEMENT_KEY ); if ( null == settingsElement ) @@ -1358,7 +1358,7 @@ private void readAnalyzers( final SpotAnalyzerProvider spotAnalyzerProvider, final EdgeAnalyzerProvider edgeAnalyzerProvider, final TrackAnalyzerProvider trackAnalyzerProvider, - final SpotMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider ) + final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider ) { final Element analyzersEl = settingsElement.getChild( ANALYZER_COLLECTION_ELEMENT_KEY ); diff --git a/src/main/java/fiji/plugin/trackmate/providers/SpotMorphologyAnalyzerProvider.java b/src/main/java/fiji/plugin/trackmate/providers/Spot2DMorphologyAnalyzerProvider.java similarity index 68% rename from src/main/java/fiji/plugin/trackmate/providers/SpotMorphologyAnalyzerProvider.java rename to src/main/java/fiji/plugin/trackmate/providers/Spot2DMorphologyAnalyzerProvider.java index 6f36f99c8..9738c4271 100644 --- a/src/main/java/fiji/plugin/trackmate/providers/SpotMorphologyAnalyzerProvider.java +++ b/src/main/java/fiji/plugin/trackmate/providers/Spot2DMorphologyAnalyzerProvider.java @@ -21,24 +21,24 @@ */ package fiji.plugin.trackmate.providers; -import fiji.plugin.trackmate.features.spot.SpotMorphologyAnalyzerFactory; +import fiji.plugin.trackmate.features.spot.Spot2DMorphologyAnalyzerFactory; @SuppressWarnings( "rawtypes" ) -public class SpotMorphologyAnalyzerProvider extends AbstractProvider< SpotMorphologyAnalyzerFactory > +public class Spot2DMorphologyAnalyzerProvider extends AbstractProvider< Spot2DMorphologyAnalyzerFactory > { private final int nChannels; - public SpotMorphologyAnalyzerProvider( final int nChannels ) + public Spot2DMorphologyAnalyzerProvider( final int nChannels ) { - super( SpotMorphologyAnalyzerFactory.class ); + super( Spot2DMorphologyAnalyzerFactory.class ); this.nChannels = nChannels; } @Override - public SpotMorphologyAnalyzerFactory getFactory( final String key ) + public Spot2DMorphologyAnalyzerFactory getFactory( final String key ) { - final SpotMorphologyAnalyzerFactory factory = super.getFactory( key ); + final Spot2DMorphologyAnalyzerFactory factory = super.getFactory( key ); if ( factory == null ) return null; @@ -48,7 +48,7 @@ public SpotMorphologyAnalyzerFactory getFactory( final String key ) public static void main( final String[] args ) { - final SpotMorphologyAnalyzerProvider provider = new SpotMorphologyAnalyzerProvider( 2 ); + final Spot2DMorphologyAnalyzerProvider provider = new Spot2DMorphologyAnalyzerProvider( 2 ); System.out.println( provider.echo() ); } } diff --git a/src/test/java/fiji/plugin/trackmate/features/SpotFeatureComputationBenchmark.java b/src/test/java/fiji/plugin/trackmate/features/SpotFeatureComputationBenchmark.java index a89e1b3a4..fecad1adc 100644 --- a/src/test/java/fiji/plugin/trackmate/features/SpotFeatureComputationBenchmark.java +++ b/src/test/java/fiji/plugin/trackmate/features/SpotFeatureComputationBenchmark.java @@ -32,7 +32,7 @@ import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactoryBase; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; -import fiji.plugin.trackmate.providers.SpotMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; import ij.ImagePlus; import net.imglib2.util.Util; @@ -67,7 +67,7 @@ public static void main( final String[] args ) for ( final String key : provider1.getVisibleKeys() ) factories.add( provider1.getFactory( key ) ); - final SpotMorphologyAnalyzerProvider provider2 = new SpotMorphologyAnalyzerProvider( 1 ); + final Spot2DMorphologyAnalyzerProvider provider2 = new Spot2DMorphologyAnalyzerProvider( 1 ); for ( final String key : provider2.getVisibleKeys() ) factories.add( provider2.getFactory( key ) ); diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/TmXmlReaderTestDrive.java b/src/test/java/fiji/plugin/trackmate/interactivetests/TmXmlReaderTestDrive.java index 8ad49b2e9..c9dff0ef1 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/TmXmlReaderTestDrive.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/TmXmlReaderTestDrive.java @@ -32,7 +32,7 @@ import fiji.plugin.trackmate.providers.DetectorProvider; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; -import fiji.plugin.trackmate.providers.SpotMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackerProvider; import ij.ImagePlus; @@ -57,7 +57,7 @@ public static void main( final String args[] ) new SpotAnalyzerProvider( imp.getNChannels() ), new EdgeAnalyzerProvider(), new TrackAnalyzerProvider(), - new SpotMorphologyAnalyzerProvider( imp.getNChannels() ) ); + new Spot2DMorphologyAnalyzerProvider( imp.getNChannels() ) ); System.out.println( settings ); System.out.println( model ); From dc9a6d9774ab7bc42474185e6cd5b5b9a9766a1f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 17:58:28 +0200 Subject: [PATCH 120/263] Add flag for detectors that can return 3D shape, and interfaces for analyzers that can exploit it. --- .../detection/LabelImageDetectorFactory.java | 6 ++ .../detection/MaskDetectorFactory.java | 6 ++ .../detection/SpotDetectorFactoryBase.java | 30 ++++++++-- .../detection/ThresholdDetectorFactory.java | 6 ++ .../spot/Spot3DMorphologyAnalyzerFactory.java | 34 +++++++++++ .../descriptors/SpotFilterDescriptor.java | 22 +++++++ .../Spot3DMorphologyAnalyzerProvider.java | 58 +++++++++++++++++++ 7 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/features/spot/Spot3DMorphologyAnalyzerFactory.java create mode 100644 src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java index c9b4ebf9d..6033257f0 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java @@ -126,6 +126,12 @@ public boolean has2Dsegmentation() return true; } + @Override + public boolean has3Dsegmentation() + { + return true; + } + @Override public String getKey() { diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java index 58841e06c..ff3d31501 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -86,6 +86,12 @@ public boolean has2Dsegmentation() return true; } + @Override + public boolean has3Dsegmentation() + { + return true; + } + @Override public SpotDetector< T > getDetector( final Interval interval, final int frame ) { diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java index d4a9bf41f..d4ef93d61 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java @@ -8,12 +8,12 @@ * 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 * . @@ -112,7 +112,7 @@ public interface SpotDetectorFactoryBase< T extends RealType< T > & NativeType< /** * Returns a new default settings map suitable for the target detector. * Settings are instantiated with default values. - * + * * @return a new map. */ public Map< String, Object > getDefaultSettings(); @@ -132,10 +132,10 @@ public interface SpotDetectorFactoryBase< T extends RealType< T > & NativeType< *

* This flag may be used by clients to exploit the fact that the spots * created with this detector will have a contour that can be used - * e.g. to compute morphological features. The default is + * e.g. to compute 2D morphological features. The default is * false, indicating that this detector provides spots as a X, * Y, Z, radius tuple. - * + * * @return true if the spots created by this detector have a 2D * contour. */ @@ -144,9 +144,27 @@ public default boolean has2Dsegmentation() return false; } + /** + * Return true for the detectors that can provide a spot with a + * 3D SpotMesh when they operate on 3D images. + *

+ * This flag may be used by clients to exploit the fact that the spots + * created with this detector will have a 3D mesh that can be used + * e.g. to compute 3D morphological features. The default is + * false, indicating that this detector provides spots as a X, + * Y, Z, radius tuple. + * + * @return true if the spots created by this detector have a 2D + * contour. + */ + public default boolean has3Dsegmentation() + { + return false; + } + /** * Returns a copy the current instance. - * + * * @return a new instance of this detector factory. */ public SpotDetectorFactoryBase< T > copy(); diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java index 142247eed..70ec9be7d 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -131,6 +131,12 @@ public boolean has2Dsegmentation() return true; } + @Override + public boolean has3Dsegmentation() + { + return true; + } + @Override public String getKey() { diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DMorphologyAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DMorphologyAnalyzerFactory.java new file mode 100644 index 000000000..da4f3d75a --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DMorphologyAnalyzerFactory.java @@ -0,0 +1,34 @@ +/*- + * #%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.features.spot; + +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; + +/** + * Special interface for spot analyzers that can compute feature values based on + * the 3D mesh building the 3D shape of spots. + * + * @author Jean-Yves Tinevez - 2023 + */ +public interface Spot3DMorphologyAnalyzerFactory< T extends RealType< T > & NativeType< T > > extends SpotAnalyzerFactoryBase< T > +{} diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java index e3ff8697d..d4bb32c44 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java @@ -37,12 +37,14 @@ import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.features.spot.Spot2DMorphologyAnalyzerFactory; +import fiji.plugin.trackmate.features.spot.Spot3DMorphologyAnalyzerFactory; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.FilterGuiPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; import fiji.plugin.trackmate.io.SettingsPersistence; import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot3DMorphologyAnalyzerProvider; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; public class SpotFilterDescriptor extends WizardPanelDescriptor @@ -126,6 +128,26 @@ public void run() logger.log( strb.toString() ); } + // 3D. + if ( trackmate.getSettings().detectorFactory != null + && trackmate.getSettings().detectorFactory.has3Dsegmentation() + && !DetectionUtils.is2D( trackmate.getSettings().imp ) ) + { + logger.log( "\nAdding 3D morphology analyzers...\n", Logger.BLUE_COLOR ); + final Settings settings = trackmate.getSettings(); + final Spot3DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot3DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); + @SuppressWarnings( "rawtypes" ) + final List< Spot3DMorphologyAnalyzerFactory > factories = spotMorphologyAnalyzerProvider + .getKeys() + .stream() + .map( key -> spotMorphologyAnalyzerProvider.getFactory( key ) ) + .collect( Collectors.toList() ); + factories.forEach( settings::addSpotAnalyzerFactory ); + final StringBuilder strb = new StringBuilder(); + Settings.prettyPrintFeatureAnalyzer( factories, strb ); + logger.log( strb.toString() ); + } + /* * Show and log to progress bar in the filter GUI panel. */ diff --git a/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java b/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java new file mode 100644 index 000000000..ca74f5235 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java @@ -0,0 +1,58 @@ +/*- + * #%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.providers; + +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.features.spot.Spot3DMorphologyAnalyzerFactory; + +/** + * Provider for 3D morphology analyzers, working on {@link SpotMesh}. + */ +@SuppressWarnings( "rawtypes" ) +public class Spot3DMorphologyAnalyzerProvider extends AbstractProvider< Spot3DMorphologyAnalyzerFactory > +{ + + private final int nChannels; + + public Spot3DMorphologyAnalyzerProvider( final int nChannels ) + { + super( Spot3DMorphologyAnalyzerFactory.class ); + this.nChannels = nChannels; + } + + @Override + public Spot3DMorphologyAnalyzerFactory getFactory( final String key ) + { + final Spot3DMorphologyAnalyzerFactory factory = super.getFactory( key ); + if ( factory == null ) + return null; + + factory.setNChannels( nChannels ); + return factory; + } + + public static void main( final String[] args ) + { + final Spot3DMorphologyAnalyzerProvider provider = new Spot3DMorphologyAnalyzerProvider( 2 ); + System.out.println( provider.echo() ); + } +} From 036ddc6a7f56358f4b14a10e022cb55a6090988b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 20:53:16 +0200 Subject: [PATCH 121/263] Add ops as a dependency. --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index dc19c4e65..564a21017 100644 --- a/pom.xml +++ b/pom.xml @@ -181,6 +181,10 @@ imagej-mesh-io 0.1.3-SNAPSHOT + + net.imagej + imagej-ops + From fa44f79470ed1195f4a254343992c3f1f8af6401 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 20:53:30 +0200 Subject: [PATCH 122/263] Add VOLUME as a dimension. --- src/main/java/fiji/plugin/trackmate/Dimension.java | 13 +++++++++---- .../java/fiji/plugin/trackmate/util/TMUtils.java | 2 ++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Dimension.java b/src/main/java/fiji/plugin/trackmate/Dimension.java index ece2b7d00..b715ccc34 100644 --- a/src/main/java/fiji/plugin/trackmate/Dimension.java +++ b/src/main/java/fiji/plugin/trackmate/Dimension.java @@ -8,12 +8,12 @@ * 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 * . @@ -31,8 +31,13 @@ public enum Dimension POSITION, VELOCITY, LENGTH, - AREA, TIME, ANGLE, RATE, // count per frames - ANGLE_RATE, STRING; // for non-numeric features + AREA, + VOLUME, + TIME, + ANGLE, + RATE, // count per frames + ANGLE_RATE, + STRING; // for non-numeric features /* * We separated length and position so that x,y,z are plotted on a different diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index af5a8e482..d611a9163 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -481,6 +481,8 @@ public static final String getUnitsFor( final Dimension dimension, final String return spaceUnits; case AREA: return spaceUnits + "^2"; + case VOLUME: + return spaceUnits + "^3"; case QUALITY: return "quality"; case COST: From 0c3951398047e5f760eb9591af0ed9e1442fc313 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 20:53:57 +0200 Subject: [PATCH 123/263] Add basic 3D shape analyzers. TODO: Ellipsoid fitting, boxity. --- .../features/spot/Spot3DShapeAnalyzer.java | 98 +++++++++++ .../spot/Spot3DShapeAnalyzerFactory.java | 153 ++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java create mode 100644 src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzerFactory.java diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java new file mode 100644 index 000000000..e853febe9 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java @@ -0,0 +1,98 @@ +/*- + * #%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.features.spot; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.ops.geom.geom3d.DefaultConvexHull3D; +import net.imagej.ops.geom.geom3d.DefaultSurfaceArea; +import net.imglib2.type.numeric.RealType; + +public class Spot3DShapeAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > +{ + + private final boolean is3D; + + private final DefaultConvexHull3D convexHull; + + private final DefaultSurfaceArea surfaceArea; + + public Spot3DShapeAnalyzer( final boolean is3D ) + { + this.is3D = is3D; + this.convexHull = new DefaultConvexHull3D(); + this.surfaceArea = new DefaultSurfaceArea(); + } + + @Override + public void process( final Spot spot ) + { + double volume; + double sa; + double solidity; + double convexity; + double sphericity; + if ( is3D ) + { + final SpotMesh sm = spot.getMesh(); + if ( sm != null ) + { + final Mesh ch = convexHull.calculate( sm.mesh ); + volume = sm.volume(); + final double volumeCH = Meshes.volume( ch ); + solidity = volume / volumeCH; + + sa = surfaceArea.calculate( sm.mesh ).get(); + final double saCH = surfaceArea.calculate( ch ).get(); + convexity = sa / saCH; + + final double sphereArea = Math.pow( Math.PI, 1. / 3. ) + * Math.pow( 6. * volume, 2. / 3. ); + sphericity = sphereArea / sa; + } + else + { + final double radius = spot.getFeature( Spot.RADIUS ); + volume = 4. / 3. * Math.PI * radius * radius * radius; + sa = 4. * Math.PI * radius * radius; + solidity = 1.; + convexity = 1.; + sphericity = 1.; + } + } + else + { + volume = Double.NaN; + sa = Double.NaN; + solidity = Double.NaN; + convexity = Double.NaN; + sphericity = Double.NaN; + } + spot.putFeature( Spot3DShapeAnalyzerFactory.VOLUME, volume ); + spot.putFeature( Spot3DShapeAnalyzerFactory.SURFACE_AREA, sa ); + spot.putFeature( Spot3DShapeAnalyzerFactory.SPHERICITY, sphericity ); + spot.putFeature( Spot3DShapeAnalyzerFactory.SOLIDITY, solidity ); + spot.putFeature( Spot3DShapeAnalyzerFactory.CONVEXITY, convexity ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzerFactory.java new file mode 100644 index 000000000..ca3898deb --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzerFactory.java @@ -0,0 +1,153 @@ +/*- + * #%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.features.spot; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.ImageIcon; + +import org.scijava.plugin.Plugin; + +import fiji.plugin.trackmate.Dimension; +import fiji.plugin.trackmate.detection.DetectionUtils; +import net.imagej.ImgPlus; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; + +@Plugin( type = Spot3DMorphologyAnalyzerFactory.class ) +public class Spot3DShapeAnalyzerFactory< T extends RealType< T > & NativeType< T > > implements Spot3DMorphologyAnalyzerFactory< T > +{ + + public static final String KEY = "Spot 3D shape descriptors"; + + public static final String VOLUME = "VOLUME"; + public static final String SURFACE_AREA = "SURFACE_AREA"; + public static final String SPHERICITY = "SPHERICITY"; + public static final String SOLIDITY = "SOLIDITY"; + public static final String CONVEXITY = "CONVEXITY"; + + private static final List< String > FEATURES = Arrays.asList( new String[] { + VOLUME, SURFACE_AREA, SPHERICITY, SOLIDITY, CONVEXITY } ); + private static final Map< String, String > FEATURE_SHORTNAMES = new HashMap< >(); + private static final Map< String, String > FEATURE_NAMES = new HashMap< >(); + private static final Map< String, Dimension > FEATURE_DIMENSIONS = new HashMap< >(); + private static final Map< String, Boolean > FEATURE_ISINTS = new HashMap< >(); + static + { + FEATURE_SHORTNAMES.put( VOLUME, "Volume" ); + FEATURE_SHORTNAMES.put( SURFACE_AREA, "Surf. area" ); + FEATURE_SHORTNAMES.put( SPHERICITY, "Sphericity" ); + FEATURE_SHORTNAMES.put( CONVEXITY, "Conv." ); + FEATURE_SHORTNAMES.put( SOLIDITY, "Solidity" ); + + FEATURE_NAMES.put( VOLUME, "Volume" ); + FEATURE_NAMES.put( SURFACE_AREA, "Surface area" ); + FEATURE_NAMES.put( SPHERICITY, "Sphericity" ); + FEATURE_NAMES.put( CONVEXITY, "Convexity" ); + FEATURE_NAMES.put( SOLIDITY, "Solidity" ); + + FEATURE_DIMENSIONS.put( SURFACE_AREA, Dimension.AREA ); + FEATURE_DIMENSIONS.put( VOLUME, Dimension.VOLUME ); + FEATURE_DIMENSIONS.put( SPHERICITY, Dimension.NONE ); + FEATURE_DIMENSIONS.put( CONVEXITY, Dimension.NONE ); + FEATURE_DIMENSIONS.put( SOLIDITY, Dimension.NONE ); + + FEATURE_ISINTS.put( VOLUME, Boolean.FALSE ); + FEATURE_ISINTS.put( SURFACE_AREA, Boolean.FALSE ); + FEATURE_ISINTS.put( SPHERICITY, Boolean.FALSE ); + FEATURE_ISINTS.put( CONVEXITY, Boolean.FALSE ); + FEATURE_ISINTS.put( SOLIDITY, Boolean.FALSE ); + } + + @Override + public SpotAnalyzer< T > getAnalyzer( final ImgPlus< T > img, final int frame, final int channel ) + { + // Don't run more than once. + if ( channel != 0 ) + return SpotAnalyzer.dummyAnalyzer(); + + return new Spot3DShapeAnalyzer<>( !DetectionUtils.is2D( img ) ); + } + + @Override + public List< String > getFeatures() + { + return FEATURES; + } + + @Override + public Map< String, String > getFeatureShortNames() + { + return FEATURE_SHORTNAMES; + } + + @Override + public Map< String, String > getFeatureNames() + { + return FEATURE_NAMES; + } + + @Override + public Map< String, Dimension > getFeatureDimensions() + { + return FEATURE_DIMENSIONS; + } + + @Override + public Map< String, Boolean > getIsIntFeature() + { + return FEATURE_ISINTS; + } + + @Override + public boolean isManualFeature() + { + return false; + } + + @Override + public String getInfoText() + { + return null; + } + + @Override + public ImageIcon getIcon() + { + return null; + } + + @Override + public String getKey() + { + return KEY; + } + + @Override + public String getName() + { + return KEY; + } +} From 03d63e12822d835aa283c7e053c8edfb152431db Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 6 May 2023 19:37:48 +0200 Subject: [PATCH 124/263] Remove unused class. --- .../plugin/trackmate/util/mesh/MeshUtils.java | 118 ------------------ 1 file changed, 118 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/MeshUtils.java diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/MeshUtils.java b/src/main/java/fiji/plugin/trackmate/util/mesh/MeshUtils.java deleted file mode 100644 index d8889f4ff..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/MeshUtils.java +++ /dev/null @@ -1,118 +0,0 @@ -package fiji.plugin.trackmate.util.mesh; - -import java.io.IOException; - -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.Triangles; -import net.imagej.mesh.Vertices; -import net.imagej.mesh.io.stl.STLMeshIO; -import net.imagej.mesh.nio.BufferMesh; - -/** - * A collection of small utilities to facilitate debugging issues related to - * meshes in TrackMate. - * - * @author Jean-Yves Tinevez - * - */ -public class MeshUtils -{ - - /** - * Saves a sub-mesh containing the specified triangles to a STL file. - * - * @param tl - * the list of triangles (ids in the original mesh) to save. - * @param mesh - * the original mesh. - * @param saveFilePath - * a file path for a STL file. - */ - public static void exportMeshSubset( final long[] tl, final Mesh mesh, final String saveFilePath ) - { - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - final BufferMesh out = new BufferMesh( tl.length * 3, tl.length ); - for ( int i = 0; i < tl.length; i++ ) - { - final long id = tl[ i ]; - - final long v0 = triangles.vertex0( id ); - final double x0 = vertices.x( v0 ); - final double y0 = vertices.y( v0 ); - final double z0 = vertices.z( v0 ); - final double v0nx = vertices.nx( v0 ); - final double v0ny = vertices.ny( v0 ); - final double v0nz = vertices.nz( v0 ); - final long nv0 = out.vertices().add( x0, y0, z0, v0nx, v0ny, v0nz, 0., 0. ); - - final long v1 = triangles.vertex1( id ); - final double x1 = vertices.x( v1 ); - final double y1 = vertices.y( v1 ); - final double z1 = vertices.z( v1 ); - final double v1nx = vertices.nx( v1 ); - final double v1ny = vertices.ny( v1 ); - final double v1nz = vertices.nz( v1 ); - final long nv1 = out.vertices().add( x1, y1, z1, v1nx, v1ny, v1nz, 0., 0. ); - - final long v2 = triangles.vertex2( id ); - final double x2 = vertices.x( v2 ); - final double y2 = vertices.y( v2 ); - final double z2 = vertices.z( v2 ); - final double v2nx = vertices.nx( v2 ); - final double v2ny = vertices.ny( v2 ); - final double v2nz = vertices.nz( v2 ); - final long nv2 = out.vertices().add( x2, y2, z2, v2nx, v2ny, v2nz, 0., 0. ); - - final double nx = triangles.nx( id ); - final double ny = triangles.ny( id ); - final double nz = triangles.nz( id ); - - out.triangles().add( nv0, nv1, nv2, nx, ny, nz ); - } - Meshes.removeDuplicateVertices( out, 0 ); - - final STLMeshIO io = new STLMeshIO(); - try - { - io.save( out, saveFilePath ); - } - catch ( final IOException e ) - { - e.printStackTrace(); - } - } - - public static String triangleToString( final Mesh mesh, final long id ) - { - final StringBuilder str = new StringBuilder( id + ": " ); - - final Triangles triangles = mesh.triangles(); - final Vertices vertices = mesh.vertices(); - final long v0 = triangles.vertex0( id ); - final double x0 = vertices.x( v0 ); - final double y0 = vertices.y( v0 ); - final double z0 = vertices.z( v0 ); - str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x0, y0, z0 ) ); - - final long v1 = triangles.vertex1( id ); - final double x1 = vertices.x( v1 ); - final double y1 = vertices.y( v1 ); - final double z1 = vertices.z( v1 ); - str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x1, y1, z1 ) ); - - final long v2 = triangles.vertex2( id ); - final double x2 = vertices.x( v2 ); - final double y2 = vertices.y( v2 ); - final double z2 = vertices.z( v2 ); - str.append( String.format( "(%5.1f, %5.1f, %5.1f) - ", x2, y2, z2 ) ); - - str.append( String.format( "N = (%4.2f, %4.2f, %4.2f) ", - triangles.nx( id ), triangles.nz( id ), triangles.nz( id ) ) ); - - return str.toString(); - } - -} - From b05ed560f4baa74c752ec9a25e22c5acaeee27a6 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 6 May 2023 19:39:01 +0200 Subject: [PATCH 125/263] WIP: Ellipsoid fitter. Fit an ellipsoid to the convex-Hull of a 3D mesh. Adapted from Yury Petrov's EllipsoidFit MATLAB function and KalebKE ellipsoidfit (Apache license) TODO: Test! --- .../trackmate/util/mesh/EllipsoidFitter.java | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java b/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java new file mode 100644 index 000000000..8f004b37e --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java @@ -0,0 +1,258 @@ +package fiji.plugin.trackmate.util.mesh; + +import org.apache.commons.math3.linear.Array2DRowRealMatrix; +import org.apache.commons.math3.linear.ArrayRealVector; +import org.apache.commons.math3.linear.DecompositionSolver; +import org.apache.commons.math3.linear.EigenDecomposition; +import org.apache.commons.math3.linear.MatrixUtils; +import org.apache.commons.math3.linear.RealMatrix; +import org.apache.commons.math3.linear.RealVector; +import org.apache.commons.math3.linear.SingularValueDecomposition; + +import net.imagej.mesh.Mesh; +import net.imagej.ops.geom.geom3d.DefaultConvexHull3D; +import net.imglib2.RealLocalizable; +import net.imglib2.RealPoint; + +/** + * Fit an ellipsoid to the convex-Hull of a 3D mesh. + *

+ * Adapted from Yury Petrov's Ellipsoid + * Fit MATLAB function and KalebKE ellipsoidfit. + * + * @author Jean-Yves Tinevez + */ +public class EllipsoidFitter +{ + + public static final class EllipsoidFit + { + public final RealLocalizable center; + + public final RealLocalizable ev1; + + public final RealLocalizable ev2; + + public final RealLocalizable ev3; + + public final double r1; + + public final double r2; + + public final double r3; + + private EllipsoidFit( final RealLocalizable center, + final RealLocalizable ev1, + final RealLocalizable ev2, + final RealLocalizable ev3, + final double r1, + final double r2, + final double r3 ) + { + this.center = center; + this.ev1 = ev1; + this.ev2 = ev2; + this.ev3 = ev3; + this.r1 = r1; + this.r2 = r2; + this.r3 = r3; + } + } + + private static final DefaultConvexHull3D cHull = new DefaultConvexHull3D(); + + public static final EllipsoidFit fit( final Mesh mesh ) + { + final Mesh ch = cHull.calculate( mesh ); + return fitOnConvexHull( ch ); + } + + public static final EllipsoidFit fitOnConvexHull( final Mesh mesh ) + { + return fit( mesh.vertices(), ( int ) mesh.vertices().size() ); + } + + public static EllipsoidFit fit( final Iterable< ? extends RealLocalizable > points, final int nPoints ) + { + final RealVector V = solve( points, nPoints ); + + // To algebraix form. + final RealMatrix A = toAlgebraicForm( V ); + + // Find the center of the ellipsoid. + final RealVector C = findCenter( A ); + + // Translate the algebraic form of the ellipsoid to the center. + final RealMatrix R = translateToCenter( C, A ); + + // Ellipsoid eigenvectors and eigenvalues. + final EllipsoidFit fit = getFit( R, C ); + return fit; + } + + private static EllipsoidFit getFit( final RealMatrix R, final RealVector C ) + { + final RealMatrix subr = R.getSubMatrix( 0, 2, 0, 2 ); + + // subr[i][j] = subr[i][j] / -r[3][3]). + final double divr = -R.getEntry( 3, 3 ); + for ( int i = 0; i < subr.getRowDimension(); i++ ) + for ( int j = 0; j < subr.getRowDimension(); j++ ) + subr.setEntry( i, j, subr.getEntry( i, j ) / divr ); + + // Get the eigenvalues and eigenvectors. + final EigenDecomposition ed = new EigenDecomposition( subr ); + final double[] eigenvalues = ed.getRealEigenvalues(); + final RealVector e1 = ed.getEigenvector( 0 ); + final RealVector e2 = ed.getEigenvector( 1 ); + final RealVector e3 = ed.getEigenvector( 2 ); + + // Semi-axis length (radius). + final RealVector SAL = new ArrayRealVector( eigenvalues.length ); + for ( int i = 0; i < eigenvalues.length; i++ ) + SAL.setEntry( i, Math.sqrt( 1. / eigenvalues[ i ] ) ); + + // Put everything in a fit object. + final RealPoint center = new RealPoint( C.getEntry( 0 ), C.getEntry( 1 ), C.getEntry( 2 ) ); + final RealPoint ev1 = new RealPoint( e1.getEntry( 0 ), e1.getEntry( 1 ), e1.getEntry( 2 ) ); + final RealPoint ev2 = new RealPoint( e2.getEntry( 0 ), e2.getEntry( 1 ), e2.getEntry( 2 ) ); + final RealPoint ev3 = new RealPoint( e3.getEntry( 0 ), e3.getEntry( 1 ), e3.getEntry( 2 ) ); + return new EllipsoidFit( center, ev1, ev2, ev3, SAL.getEntry( 0 ), SAL.getEntry( 1 ), SAL.getEntry( 2 ) ); + } + + /** + * Translate the algebraic form of the ellipsoid to the center. + * + * @param C + * the center of the ellipsoid. + * @param A + * the ellipsoid matrix. + * @return the center translated form of the algebraic ellipsoid. + */ + private static final RealMatrix translateToCenter( final RealVector C, final RealMatrix A ) + { + final RealMatrix T = MatrixUtils.createRealIdentityMatrix( 4 ); + final RealMatrix centerMatrix = new Array2DRowRealMatrix( 1, 3 ); + centerMatrix.setRowVector( 0, C ); + T.setSubMatrix( centerMatrix.getData(), 3, 0 ); + final RealMatrix R = T.multiply( A ).multiply( T.transpose() ); + return R; + } + + /** + * Find the center of the ellipsoid. + * + * @param a + * the algebraic from of the polynomial. + * @return a vector containing the center of the ellipsoid. + */ + private static final RealVector findCenter( final RealMatrix A ) + { + final RealMatrix subA = A.getSubMatrix( 0, 2, 0, 2 ); + + for ( int q = 0; q < subA.getRowDimension(); q++ ) + for ( int s = 0; s < subA.getColumnDimension(); s++ ) + subA.multiplyEntry( q, s, -1.0 ); + + final RealVector subV = A.getRowVector( 3 ).getSubVector( 0, 3 ); + + final DecompositionSolver solver = new SingularValueDecomposition( subA ).getSolver(); + final RealMatrix subAi = solver.getInverse(); + return subAi.operate( subV ); + } + + /** + * Solve for Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + + * 2Iz = 1. + * + * @param points + * an iterable over 3D points. + * @param nPoints + * the number of points in the iterable. + * @return + */ + private static final RealVector solve( final Iterable< ? extends RealLocalizable > points, final int nPoints ) + { + final RealMatrix M = new Array2DRowRealMatrix( nPoints, 9 ); + int i = 0; + for ( final RealLocalizable point : points ) + { + final double x = point.getDoublePosition( 0 ); + final double y = point.getDoublePosition( 1 ); + final double z = point.getDoublePosition( 2 ); + + final double xx = x * x; + final double yy = y * y; + final double zz = z * z; + + final double xy = 2. * x * y; + final double xz = 2. * x * z; + final double yz = 2. * y * z; + + M.setEntry( i, 0, xx ); + M.setEntry( i, 1, yy ); + M.setEntry( i, 2, zz ); + M.setEntry( i, 3, xy ); + M.setEntry( i, 4, xz ); + M.setEntry( i, 5, yz ); + M.setEntry( i, 6, 2. * x ); + M.setEntry( i, 7, 2. * y ); + M.setEntry( i, 8, 2. * z ); + + i++; + if ( i >= nPoints ) + break; + } + + final RealMatrix M2 = M.transpose().multiply( M ); + + final RealVector O = new ArrayRealVector( nPoints ); + O.mapAddToSelf( 1 ); + + final RealVector MO = M.transpose().operate( O ); + + final DecompositionSolver solver = new SingularValueDecomposition( M2 ).getSolver(); + final RealMatrix I = solver.getInverse(); + + final RealVector V = I.operate( MO ); + return V; + } + + /** + * Reshape the fit result vector in the shape of an algebraic matrix. + * + *

+	 * A = 		[ Ax2 	2Dxy 	2Exz 	2Gx ] 
+	 * 		[ 2Dxy 	By2 	2Fyz 	2Hy ] 
+	 * 		[ 2Exz 	2Fyz 	Cz2 	2Iz ] 
+	 * 		[ 2Gx 	2Hy 	2Iz 	-1 ] ]
+	 * 
+	 * 
+	 * @param V the fit result.
+	 * @return a new 4x4 real matrix.
+	 */
+	private static final RealMatrix toAlgebraicForm( final RealVector V )
+	{
+		final RealMatrix A = new Array2DRowRealMatrix( 4, 4 );
+
+		A.setEntry( 0, 0, V.getEntry( 0 ) );
+		A.setEntry( 0, 1, V.getEntry( 3 ) );
+		A.setEntry( 0, 2, V.getEntry( 4 ) );
+		A.setEntry( 0, 3, V.getEntry( 6 ) );
+		A.setEntry( 1, 0, V.getEntry( 3 ) );
+		A.setEntry( 1, 1, V.getEntry( 1 ) );
+		A.setEntry( 1, 2, V.getEntry( 5 ) );
+		A.setEntry( 1, 3, V.getEntry( 7 ) );
+		A.setEntry( 2, 0, V.getEntry( 4 ) );
+		A.setEntry( 2, 1, V.getEntry( 5 ) );
+		A.setEntry( 2, 2, V.getEntry( 2 ) );
+		A.setEntry( 2, 3, V.getEntry( 8 ) );
+		A.setEntry( 3, 0, V.getEntry( 6 ) );
+		A.setEntry( 3, 1, V.getEntry( 7 ) );
+		A.setEntry( 3, 2, V.getEntry( 8 ) );
+		A.setEntry( 3, 3, -1 );
+		return A;
+	}
+}

From 20c2cbfa39c08dd57c8314aa3be154a8dda7bd92 Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Sun, 7 May 2023 11:52:55 +0200
Subject: [PATCH 126/263] toString method for the ellipsoid fit.

---
 .../plugin/trackmate/util/mesh/EllipsoidFitter.java  | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java b/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java
index 8f004b37e..20d96de41 100644
--- a/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java
+++ b/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java
@@ -13,6 +13,7 @@
 import net.imagej.ops.geom.geom3d.DefaultConvexHull3D;
 import net.imglib2.RealLocalizable;
 import net.imglib2.RealPoint;
+import net.imglib2.util.Util;
 
 /**
  * Fit an ellipsoid to the convex-Hull of a 3D mesh.
@@ -59,6 +60,17 @@ private EllipsoidFit( final RealLocalizable center,
 			this.r2 = r2;
 			this.r3 = r3;
 		}
+
+		@Override
+		public String toString()
+		{
+			final StringBuilder str = new StringBuilder( super.toString() );
+			str.append( "\n - center: " + Util.printCoordinates( center ) );
+			str.append( String.format( "\n - axis 1: radius = %.2f, vector = %s", r1, ev1 ) );
+			str.append( String.format( "\n - axis 2: radius = %.2f, vector = %s", r2, ev2 ) );
+			str.append( String.format( "\n - axis 3: radius = %.2f, vector = %s", r3, ev3 ) );
+			return str.toString();
+		}
 	}
 
 	private static final DefaultConvexHull3D cHull = new DefaultConvexHull3D();

From e1c3e3117b6834cbcd8ff3eba6b9274256a4dbad Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Sun, 7 May 2023 11:53:27 +0200
Subject: [PATCH 127/263] JUnit test for the ellipsoid fitter.

Contains a routine to generate the mesh of an ellipsoid
programmatically.
---
 .../trackmate/mesh/TestEllipsoidFit.java      | 101 ++++++++++++++++++
 1 file changed, 101 insertions(+)
 create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java

diff --git a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java
new file mode 100644
index 000000000..c5fabdd62
--- /dev/null
+++ b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java
@@ -0,0 +1,101 @@
+package fiji.plugin.trackmate.mesh;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import fiji.plugin.trackmate.util.mesh.EllipsoidFitter;
+import fiji.plugin.trackmate.util.mesh.EllipsoidFitter.EllipsoidFit;
+import net.imagej.mesh.Mesh;
+import net.imagej.mesh.naive.NaiveDoubleMesh;
+
+public class TestEllipsoidFit
+{
+
+	@Test
+	public void testSimpleEllipsoids()
+	{
+		final double TOLERANCE = 1e-6;
+		
+		final double ra = 1.;
+		final double rb = 2.;
+		for ( double rc = 3.; rc < 10.; rc++ )
+		{
+			final Mesh mesh = generateEllipsoidMesh( ra, rb, rc, -1, -1 );
+			final EllipsoidFit fit = EllipsoidFitter.fit( mesh );
+
+			final double[] arr = new double[ 3 ];
+
+			// Center on 0.
+			fit.center.localize( arr );
+			assertArrayEquals( "Ellipsoid center should be close to 0.", arr, new double[] { 0., 0., 0. }, TOLERANCE );
+
+			// Proper radius, ordered by increasing absolute value.
+			assertEquals( "Smallest radius has unexpected value.", ra, fit.r1, TOLERANCE );
+			assertEquals( "Mid radius has unexpected value.", rb, fit.r2, TOLERANCE );
+			assertEquals( "Largest radius has unexpected value.", rc, fit.r3, TOLERANCE );
+
+			// Vectors, aligned with axes.
+			fit.ev1.localize( arr );
+			for ( int d = 0; d < arr.length; d++ )
+				arr[ d ] = Math.abs( arr[ d ] );
+			assertArrayEquals( "Smallest eigenvector should be aligned with X axis.", arr, new double[] { 1., 0., 0. }, TOLERANCE );
+
+			fit.ev2.localize( arr );
+			for ( int d = 0; d < arr.length; d++ )
+				arr[ d ] = Math.abs( arr[ d ] );
+			assertArrayEquals( "Mid eigenvector should be aligned with Y axis.", arr, new double[] { 0., 1., 0. }, TOLERANCE );
+
+			fit.ev3.localize( arr );
+			for ( int d = 0; d < arr.length; d++ )
+				arr[ d ] = Math.abs( arr[ d ] );
+			assertArrayEquals( "Largest eigenvector should be aligned with Z axis.", arr, new double[] { 0., 0., 1. }, TOLERANCE );
+		}
+	}
+
+	private static Mesh generateEllipsoidMesh( final double ra, final double rb, final double rc, int numLongitudes, int numLatitudes )
+	{
+		if ( numLongitudes < 4 )
+			numLongitudes = 36; // Number of longitudinal divisions
+		if ( numLatitudes < 4 )
+			numLatitudes = 18; // Number of latitudinal divisions
+
+		final NaiveDoubleMesh mesh = new NaiveDoubleMesh();
+		for ( int lat = 0; lat < numLatitudes; lat++ )
+		{
+			final double theta1 = ( double ) lat / numLatitudes * Math.PI;
+			final double theta2 = ( double ) ( lat + 1 ) / numLatitudes * Math.PI;
+
+			for ( int lon = 0; lon < numLongitudes; lon++ )
+			{
+				final double phi1 = ( double ) lon / numLongitudes * 2 * Math.PI;
+				final double phi2 = ( double ) ( lon + 1 ) / numLongitudes * 2 * Math.PI;
+
+				// Calculate the vertices of each triangle
+				final long p1 = addVertex( mesh, ra, rb, rc, theta1, phi1 );
+				final long p2 = addVertex( mesh, ra, rb, rc, theta1, phi2 );
+				final long p3 = addVertex( mesh, ra, rb, rc, theta2, phi1 );
+				final long p4 = addVertex( mesh, ra, rb, rc, theta2, phi2 );
+
+				// Draw the triangles
+				addTriangle( mesh, p1, p3, p2 );
+				addTriangle( mesh, p2, p3, p4 );
+			}
+		}
+		return mesh;
+	}
+
+	private static long addVertex( final Mesh mesh, final double ra, final double rb, final double rc, final double theta, final double phi )
+	{
+		final double x = ra * Math.sin( theta ) * Math.cos( phi );
+		final double y = rb * Math.sin( theta ) * Math.sin( phi );
+		final double z = rc * Math.cos( theta );
+		return mesh.vertices().add( x, y, z );
+	}
+
+	private static long addTriangle( final Mesh mesh, final long p1, final long p2, final long p3 )
+	{
+		return mesh.triangles().add( p1, p2, p3 );
+	}
+}

From 6b54c96b64afb6122f8ee0f415f551c8a67c0e86 Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Sun, 7 May 2023 12:53:00 +0200
Subject: [PATCH 128/263] Javadoc for the EllipsoidFitter

---
 .../trackmate/util/mesh/EllipsoidFitter.java  | 60 +++++++++++++++----
 1 file changed, 50 insertions(+), 10 deletions(-)

diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java b/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java
index 20d96de41..6f0deb46d 100644
--- a/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java
+++ b/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java
@@ -28,20 +28,30 @@
 public class EllipsoidFitter
 {
 
+	/**
+	 * The results of fitting an ellpsoid to a mesh or a collection of points.
+	 */
 	public static final class EllipsoidFit
 	{
+		/** The ellipsoid center. */
 		public final RealLocalizable center;
 
+		/** The eigenvector of the smallest axis of the ellipsoid. */
 		public final RealLocalizable ev1;
 
+		/** The eigenvector of the middle axis of the ellipsoid. */
 		public final RealLocalizable ev2;
 
+		/** The eigenvector of the largest axis of the ellipsoid. */
 		public final RealLocalizable ev3;
 
+		/** The radius of the smallest axis of the ellipsoid. */
 		public final double r1;
 
+		/** The radius of the middle axis of the ellipsoid. */
 		public final double r2;
 
+		/** The radius of the largest axis of the ellipsoid. */
 		public final double r3;
 
 		private EllipsoidFit( final RealLocalizable center,
@@ -75,17 +85,42 @@ public String toString()
 
 	private static final DefaultConvexHull3D cHull = new DefaultConvexHull3D();
 
+	/**
+	 * Fit an ellipsoid to the convex-Hull of a 3D mesh.
+	 * 
+	 * @param mesh
+	 *            the mesh to fit.
+	 * @return the fit results.
+	 */
 	public static final EllipsoidFit fit( final Mesh mesh )
 	{
 		final Mesh ch = cHull.calculate( mesh );
 		return fitOnConvexHull( ch );
 	}
 
+	/**
+	 * Fit an ellipsoid to a 3D mesh, assuming it is the convex-Hull.
+	 * 
+	 * @param mesh
+	 *            the convex-Hull of the mesh to fit.
+	 * @return the fit results.
+	 */
 	public static final EllipsoidFit fitOnConvexHull( final Mesh mesh )
 	{
 		return fit( mesh.vertices(), ( int ) mesh.vertices().size() );
 	}
 
+	/**
+	 * Fit an ellipsoid to a collection of 3D points.
+	 * 
+	 * @param points
+	 *            an iterable over the points to fit.
+	 * @param nPoints
+	 *            the number of points to include in the fit. The fit will
+	 *            consider at most the first nPoints of the iterable, or all the
+	 *            points in the iterable, whatever comes first.
+	 * @return the fit results.
+	 */
 	public static EllipsoidFit fit( final Iterable< ? extends RealLocalizable > points, final int nPoints )
 	{
 		final RealVector V = solve( points, nPoints );
@@ -187,7 +222,7 @@ private static final RealVector findCenter( final RealMatrix A )
 	 */
 	private static final RealVector solve( final Iterable< ? extends RealLocalizable > points, final int nPoints )
 	{
-		final RealMatrix M = new Array2DRowRealMatrix( nPoints, 9 );
+		final RealMatrix M0 = new Array2DRowRealMatrix( nPoints, 9 );
 		int i = 0;
 		for ( final RealLocalizable point : points )
 		{
@@ -203,20 +238,25 @@ private static final RealVector solve( final Iterable< ? extends RealLocalizable
 			final double xz = 2. * x * z;
 			final double yz = 2. * y * z;
 
-			M.setEntry( i, 0, xx );
-			M.setEntry( i, 1, yy );
-			M.setEntry( i, 2, zz );
-			M.setEntry( i, 3, xy );
-			M.setEntry( i, 4, xz );
-			M.setEntry( i, 5, yz );
-			M.setEntry( i, 6, 2. * x );
-			M.setEntry( i, 7, 2. * y );
-			M.setEntry( i, 8, 2. * z );
+			M0.setEntry( i, 0, xx );
+			M0.setEntry( i, 1, yy );
+			M0.setEntry( i, 2, zz );
+			M0.setEntry( i, 3, xy );
+			M0.setEntry( i, 4, xz );
+			M0.setEntry( i, 5, yz );
+			M0.setEntry( i, 6, 2. * x );
+			M0.setEntry( i, 7, 2. * y );
+			M0.setEntry( i, 8, 2. * z );
 
 			i++;
 			if ( i >= nPoints )
 				break;
 		}
+		final RealMatrix M;
+		if ( i == nPoints )
+			M = M0;
+		else
+			M = M0.getSubMatrix( 0, i, 0, 9 );
 
 		final RealMatrix M2 = M.transpose().multiply( M );
 

From 59ad9711d68ad07fcb6d2ae561a4c32e545a979f Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Sun, 7 May 2023 12:53:25 +0200
Subject: [PATCH 129/263] Shape measurement via ellipsoid fit for 3D mesh.

---
 .../spot/Spot3DFitEllipsoidAnalyzer.java      | 158 ++++++++++++
 .../Spot3DFitEllipsoidAnalyzerFactory.java    | 237 ++++++++++++++++++
 2 files changed, 395 insertions(+)
 create mode 100644 src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java
 create mode 100644 src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzerFactory.java

diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java
new file mode 100644
index 000000000..255a1e046
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java
@@ -0,0 +1,158 @@
+package fiji.plugin.trackmate.features.spot;
+
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.ASPECTRATIO;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.ELLIPSOID_SHAPE;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MAJOR;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MAJOR_PHI;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MAJOR_THETA;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MEDIAN;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MEDIAN_PHI;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MEDIAN_THETA;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MINOR;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MINOR_PHI;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.MINOR_THETA;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.SHAPE_CLASS_TOLERANCE;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.SHAPE_ELLIPSOID;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.SHAPE_OBLATE;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.SHAPE_PROLATE;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.SHAPE_SPHERE;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.X0;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.Y0;
+import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.Z0;
+
+import fiji.plugin.trackmate.Spot;
+import fiji.plugin.trackmate.SpotMesh;
+import fiji.plugin.trackmate.util.mesh.EllipsoidFitter;
+import fiji.plugin.trackmate.util.mesh.EllipsoidFitter.EllipsoidFit;
+import net.imglib2.RealLocalizable;
+import net.imglib2.type.numeric.RealType;
+
+public class Spot3DFitEllipsoidAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T >
+{
+
+	private final boolean is3D;
+
+	public Spot3DFitEllipsoidAnalyzer( final boolean is3D )
+	{
+		this.is3D = is3D;
+	}
+
+	@Override
+	public void process( final Spot spot )
+	{
+		final double x0;
+		final double y0;
+		final double z0;
+		final double rA;
+		final double rB;
+		final double rC;
+		final double phiA;
+		final double thetaA;
+		final double phiB;
+		final double thetaB;
+		final double phiC;
+		final double thetaC;
+		final double aspectRatio;
+		final int shapeIndex;
+
+		if ( is3D )
+		{
+			final SpotMesh sm = spot.getMesh();
+			if ( sm != null )
+			{
+				final EllipsoidFit fit = EllipsoidFitter.fit( sm.mesh );
+				x0 = fit.center.getDoublePosition( 0 );
+				y0 = fit.center.getDoublePosition( 1 );
+				z0 = fit.center.getDoublePosition( 2 );
+				rA = Math.abs( fit.r1 );
+				rB = Math.abs( fit.r2 );
+				rC = Math.abs( fit.r3 );
+				aspectRatio = rA / rC;
+				final double drAB = ( rB - rA ) / rB;
+				final double drBC = ( rC - rB ) / rC;
+				if ( drAB < SHAPE_CLASS_TOLERANCE && drBC < SHAPE_CLASS_TOLERANCE )
+					shapeIndex = SHAPE_SPHERE;
+				else if ( drBC < SHAPE_CLASS_TOLERANCE )
+					shapeIndex = SHAPE_OBLATE;
+				else if ( drAB < SHAPE_CLASS_TOLERANCE )
+					shapeIndex = SHAPE_PROLATE;
+				else
+					shapeIndex = SHAPE_ELLIPSOID;
+
+				phiA = phi( fit.ev1 );
+				phiB = phi( fit.ev2 );
+				phiC = phi( fit.ev3 );
+				thetaA = theta( fit.ev1 );
+				thetaB = theta( fit.ev2 );
+				thetaC = theta( fit.ev3 );
+
+			}
+			else
+			{ 
+				// Assume plain sphere.
+				x0 = 0.;
+				y0 = 0.;
+				z0 = 0.;
+				final double radius = spot.getFeature( Spot.RADIUS );
+				rA = radius;
+				rB = radius;
+				rC = radius;
+				aspectRatio = 1.;
+				shapeIndex = SHAPE_ELLIPSOID;
+
+				phiA = 0.;
+				phiB = 0.;
+				phiC = 0.;
+				thetaA = 0.;
+				thetaB = 0.;
+				thetaC = 0.;
+			}
+		}
+		else
+		{
+			// Undefined for 2D: default to NaN.
+			x0 = Double.NaN;
+			y0 = Double.NaN;
+			z0 = Double.NaN;
+			rA = Double.NaN;
+			rB = Double.NaN;
+			rC = Double.NaN;
+			aspectRatio = Double.NaN;
+			shapeIndex = SHAPE_ELLIPSOID;
+
+			phiA = Double.NaN;
+			phiB = Double.NaN;
+			phiC = Double.NaN;
+			thetaA = Double.NaN;
+			thetaB = Double.NaN;
+			thetaC = Double.NaN;
+		}
+		spot.putFeature( X0, x0 );
+		spot.putFeature( Y0, y0 );
+		spot.putFeature( Z0, z0 );
+		spot.putFeature( MINOR, rA );
+		spot.putFeature( MEDIAN, rB );
+		spot.putFeature( MAJOR, rC );
+		spot.putFeature( MINOR_PHI, phiA );
+		spot.putFeature( MEDIAN_PHI, phiB );
+		spot.putFeature( MAJOR_PHI, phiC );
+		spot.putFeature( MINOR_THETA, thetaA );
+		spot.putFeature( MEDIAN_THETA, thetaB );
+		spot.putFeature( MAJOR_THETA, thetaC );
+		spot.putFeature( ASPECTRATIO, aspectRatio );
+		spot.putFeature( ELLIPSOID_SHAPE, ( double ) shapeIndex );
+	}
+
+	private double theta( final RealLocalizable v )
+	{
+		final double z = v.getDoublePosition( 2 );
+		return Math.acos( z );
+	}
+
+	private static final double phi( final RealLocalizable v )
+	{
+		final double x = v.getDoublePosition( 0 );
+		final double y = v.getDoublePosition( 1 );
+		return Math.atan2( y, x );
+	}
+}
diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzerFactory.java
new file mode 100644
index 000000000..fd1060346
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzerFactory.java
@@ -0,0 +1,237 @@
+/*-
+ * #%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.features.spot;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.ImageIcon;
+
+import org.scijava.plugin.Plugin;
+
+import fiji.plugin.trackmate.Dimension;
+import fiji.plugin.trackmate.detection.DetectionUtils;
+import net.imagej.ImgPlus;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.RealType;
+
+@Plugin( type = Spot3DMorphologyAnalyzerFactory.class )
+public class Spot3DFitEllipsoidAnalyzerFactory< T extends RealType< T > & NativeType< T > > implements Spot3DMorphologyAnalyzerFactory< T >
+{
+
+	public static final String KEY = "Spot fit 3D ellipsoid";
+	
+	public static final String X0 = "ELLIPSOID_X0";
+	public static final String Y0 = "ELLIPSOID_Y0";
+	public static final String Z0 = "ELLIPSOID_Z0";
+	public static final String MAJOR = "ELLIPSOID_MAJOR_LENGTH";
+	public static final String MEDIAN = "ELLIPSOID_MEDIAN_LENGTH";
+	public static final String MINOR = "ELLIPSOID_MINOR_LENGTH";
+	public static final String MAJOR_PHI = "ELLIPSOID_MAJOR_PHI";
+	public static final String MAJOR_THETA = "ELLIPSOID_MAJOR_THETA";
+	public static final String MEDIAN_PHI = "ELLIPSOID_MEDIAN_PHI";
+	public static final String MEDIAN_THETA = "ELLIPSOID_MEDIAN_THETA";
+	public static final String MINOR_PHI = "ELLIPSOID_MINOR_PHI";
+	public static final String MINOR_THETA = "ELLIPSOID_MINOR_THETA";
+	public static final String ASPECTRATIO = "ELLIPSOID_ASPECTRATIO";
+	public static final String ELLIPSOID_SHAPE = "ELLIPSOID_SHAPE";
+	
+	/** Denotes an ellipsoid with no particular shape. */
+	public static final int SHAPE_ELLIPSOID = 0;
+
+	/**
+	 * Denotes an ellipsoid with the oblate shape. The two largest radii are
+	 * roughly equal. Resembles a lentil.
+	 */
+	public static final int SHAPE_OBLATE = 1;
+
+	/**
+	 * Denotes an ellipsoid with the prolate shape. The two smallest radii are
+	 * roughly equal. Resembles a rugby balloon.
+	 */
+	public static final int SHAPE_PROLATE = 2;
+
+	/**
+	 * Denotes an ellipsoid with the spherical shape. The three radii are
+	 * roughly equal. Resembles a sphere
+	 */
+	public static final int SHAPE_SPHERE = 3;
+
+	/**
+	 * Tolerance in percentage on the radii values to be considered roughly
+	 * equals.
+	 * 
+	 * @see #SHAPE_ELLIPSOID
+	 * @see #SHAPE_OBLATE
+	 * @see #SHAPE_PROLATE
+	 * @see #SHAPE_SPHERE
+	 */
+	public static final double SHAPE_CLASS_TOLERANCE = 0.1;
+
+	private static final List< String > FEATURES = Arrays.asList( new String[] {
+			X0, Y0, Z0,
+			MAJOR, MEDIAN, MINOR,
+			MAJOR_PHI, MAJOR_THETA,
+			MEDIAN_PHI, MEDIAN_THETA,
+			MINOR_PHI, MINOR_THETA,
+			ASPECTRATIO,
+			ELLIPSOID_SHAPE } );
+	private static final Map< String, String > FEATURE_SHORTNAMES = new HashMap< >();
+	private static final Map< String, String > FEATURE_NAMES = new HashMap< >();
+	private static final Map< String, Dimension > FEATURE_DIMENSIONS = new HashMap< >();
+	private static final Map< String, Boolean > FEATURE_ISINTS = new HashMap< >();
+	static
+	{
+		FEATURE_SHORTNAMES.put( X0, "El. x0" );
+		FEATURE_SHORTNAMES.put( Y0, "El. y0" );
+		FEATURE_SHORTNAMES.put( Z0, "El. z0" );
+		FEATURE_SHORTNAMES.put( MAJOR, "El. long axis" );
+		FEATURE_SHORTNAMES.put( MEDIAN, "El. med. axis" );
+		FEATURE_SHORTNAMES.put( MINOR, "El. sh. axis" );
+		FEATURE_SHORTNAMES.put( MAJOR_PHI, "El. l.a. phi" );
+		FEATURE_SHORTNAMES.put( MEDIAN_PHI, "El. m.a. phi" );
+		FEATURE_SHORTNAMES.put( MINOR_PHI, "El. s.a. phi" );
+		FEATURE_SHORTNAMES.put( MAJOR_THETA, "El. l.a. theta" );
+		FEATURE_SHORTNAMES.put( MEDIAN_THETA, "El. m.a. theta" );
+		FEATURE_SHORTNAMES.put( MINOR_THETA, "El. s.a. theta" );
+		FEATURE_SHORTNAMES.put( ASPECTRATIO, "El. a.r." );
+		FEATURE_SHORTNAMES.put( ELLIPSOID_SHAPE, "El. shape" );
+
+		FEATURE_NAMES.put( X0, "Ellipsoid center x0" );
+		FEATURE_NAMES.put( Y0, "Ellipsoid center y0" );
+		FEATURE_NAMES.put( Z0, "Ellipsoid center z0" );
+		FEATURE_NAMES.put( MAJOR, "Ellipsoid long axis" );
+		FEATURE_NAMES.put( MEDIAN, "Ellipsoid long axis" );
+		FEATURE_NAMES.put( MINOR, "Ellipsoid short axis" );
+		FEATURE_NAMES.put( MAJOR_PHI, "Ellipsoid long axis phi" );
+		FEATURE_NAMES.put( MEDIAN_PHI, "Ellipsoid long axis. phi" );
+		FEATURE_NAMES.put( MINOR_PHI, "Ellipsoid short axis phi" );
+		FEATURE_NAMES.put( MAJOR_THETA, "Ellipsoid long axis theta" );
+		FEATURE_NAMES.put( MEDIAN_THETA, "Ellipsoid long axis theta" );
+		FEATURE_NAMES.put( MINOR_THETA, "Ellipsoid short axis theta" );
+		FEATURE_NAMES.put( ASPECTRATIO, "Ellipsoid aspect ratio" );
+		FEATURE_NAMES.put( ELLIPSOID_SHAPE, "Ellipsoid shape class" );
+
+		FEATURE_DIMENSIONS.put( X0, Dimension.LENGTH );
+		FEATURE_DIMENSIONS.put( Y0, Dimension.LENGTH );
+		FEATURE_DIMENSIONS.put( Z0, Dimension.LENGTH );
+		FEATURE_DIMENSIONS.put( MAJOR, Dimension.LENGTH );
+		FEATURE_DIMENSIONS.put( MEDIAN, Dimension.LENGTH );
+		FEATURE_DIMENSIONS.put( MINOR, Dimension.LENGTH );
+		FEATURE_DIMENSIONS.put( MAJOR_PHI, Dimension.ANGLE );
+		FEATURE_DIMENSIONS.put( MAJOR_THETA, Dimension.ANGLE );
+		FEATURE_DIMENSIONS.put( MEDIAN_PHI, Dimension.ANGLE );
+		FEATURE_DIMENSIONS.put( MEDIAN_THETA, Dimension.ANGLE );
+		FEATURE_DIMENSIONS.put( MINOR_PHI, Dimension.ANGLE );
+		FEATURE_DIMENSIONS.put( MINOR_THETA, Dimension.ANGLE );
+		FEATURE_DIMENSIONS.put( ASPECTRATIO, Dimension.NONE );
+		FEATURE_DIMENSIONS.put( ELLIPSOID_SHAPE, Dimension.NONE );
+
+		FEATURE_ISINTS.put( X0, Boolean.FALSE );
+		FEATURE_ISINTS.put( Y0, Boolean.FALSE );
+		FEATURE_ISINTS.put( Z0, Boolean.FALSE );
+		FEATURE_ISINTS.put( MAJOR, Boolean.FALSE );
+		FEATURE_ISINTS.put( MEDIAN, Boolean.FALSE );
+		FEATURE_ISINTS.put( MINOR, Boolean.FALSE );
+		FEATURE_ISINTS.put( MAJOR_PHI, Boolean.FALSE );
+		FEATURE_ISINTS.put( MAJOR_THETA, Boolean.FALSE );
+		FEATURE_ISINTS.put( MEDIAN_PHI, Boolean.FALSE );
+		FEATURE_ISINTS.put( MEDIAN_THETA, Boolean.FALSE );
+		FEATURE_ISINTS.put( MINOR_PHI, Boolean.FALSE );
+		FEATURE_ISINTS.put( MINOR_THETA, Boolean.FALSE );
+		FEATURE_ISINTS.put( ASPECTRATIO, Boolean.FALSE );
+		FEATURE_ISINTS.put( ELLIPSOID_SHAPE, Boolean.TRUE );
+	}
+
+
+	@Override
+	public SpotAnalyzer< T > getAnalyzer( final ImgPlus< T > img, final int frame, final int channel )
+	{
+		// Don't run more than once.
+		if ( channel != 0 )
+			return SpotAnalyzer.dummyAnalyzer();
+
+		return new Spot3DFitEllipsoidAnalyzer<>( !DetectionUtils.is2D( img ) );
+	}
+
+	@Override
+	public List< String > getFeatures()
+	{
+		return FEATURES;
+	}
+
+	@Override
+	public Map< String, String > getFeatureShortNames()
+	{
+		return FEATURE_SHORTNAMES;
+	}
+
+	@Override
+	public Map< String, String > getFeatureNames()
+	{
+		return FEATURE_NAMES;
+	}
+
+	@Override
+	public Map< String, Dimension > getFeatureDimensions()
+	{
+		return FEATURE_DIMENSIONS;
+	}
+
+	@Override
+	public Map< String, Boolean > getIsIntFeature()
+	{
+		return FEATURE_ISINTS;
+	}
+
+	@Override
+	public boolean isManualFeature()
+	{
+		return false;
+	}
+
+	@Override
+	public String getInfoText()
+	{
+		return null;
+	}
+
+	@Override
+	public ImageIcon getIcon()
+	{
+		return null;
+	}
+
+	@Override
+	public String getKey()
+	{
+		return KEY;
+	}
+
+	@Override
+	public String getName()
+	{
+		return KEY;
+	}
+}

From c26debec786a33ecf725a6120712b36567f68172 Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Mon, 8 May 2023 21:55:39 +0200
Subject: [PATCH 130/263] Spot is now an interface, with 3 derived class.

Spot -> the main interface, used by default in trackers. Define
basic methods to get and store feature values.
SpotBase -> Plain spots, like for TrackMate v<7
SpotRoi -> spot has a polygon as a contour in 2D
SpotMesh -> spot has a 3D mesh

More elegant and extensible to app consuming TrackMate trackers
with special objects.
---
 .../java/fiji/plugin/trackmate/Model.java     |   4 +-
 src/main/java/fiji/plugin/trackmate/Spot.java | 654 ++++++++----------
 .../java/fiji/plugin/trackmate/SpotBase.java  | 330 +++++++++
 .../java/fiji/plugin/trackmate/SpotMesh.java  | 119 ++--
 .../java/fiji/plugin/trackmate/SpotRoi.java   |  91 ++-
 .../java/fiji/plugin/trackmate/SpotShape.java |  25 -
 .../plugin/trackmate/action/CTCExporter.java  |  22 +-
 .../trackmate/action/IJRoiExporter.java       |   4 +-
 .../trackmate/action/LabelImgExporter.java    |  12 +-
 .../trackmate/action/MergeFileAction.java     |   3 +-
 .../action/closegaps/GapClosingMethod.java    |   3 +-
 .../action/fit/AbstractSpotFitter.java        |   1 -
 .../trackmate/detection/DetectionUtils.java   |  14 +-
 .../plugin/trackmate/detection/MaskUtils.java | 118 +++-
 .../detection/semiauto/SemiAutoTracker.java   |   1 -
 .../trackmate/features/FeatureUtils.java      |   3 +-
 .../trackmate/features/spot/ConvexHull2D.java |   8 +-
 .../spot/Spot2DFitEllipseAnalyzer.java        |   4 +-
 .../features/spot/Spot2DShapeAnalyzer.java    |   4 +-
 .../spot/Spot3DFitEllipsoidAnalyzer.java      |   4 +-
 .../features/spot/Spot3DShapeAnalyzer.java    |   4 +-
 .../spot/SpotContrastAndSNRAnalyzer.java      |  97 +--
 .../spot/SpotIntensityMultiCAnalyzer.java     |   3 +-
 .../ThresholdDetectorConfigurationPanel.java  |   6 +-
 .../plugin/trackmate/io/TGMMImporter.java     |  14 +-
 .../fiji/plugin/trackmate/io/TmXmlReader.java |  42 +-
 .../fiji/plugin/trackmate/io/TmXmlWriter.java |  12 +-
 .../Spot3DMorphologyAnalyzerProvider.java     |   3 +-
 .../tracking/kalman/KalmanTracker.java        |   7 +-
 .../tracking/overlap/OverlapTracker.java      |  28 +-
 .../trackmate/util/SpotNeighborhood.java      |  45 +-
 .../fiji/plugin/trackmate/util/SpotUtil.java  | 368 ----------
 .../hyperstack/PaintSpotMesh.java             |  12 +-
 .../hyperstack/PaintSpotRoi.java              |  18 +-
 .../hyperstack/PaintSpotSphere.java           |   5 +-
 .../visualization/hyperstack/SpotOverlay.java |  16 +-
 .../hyperstack/TrackMatePainter.java          |   4 +-
 .../java/fiji/plugin/trackmate/ModelTest.java |  50 +-
 .../plugin/trackmate/SpotCollectionTest.java  |  16 +-
 .../fiji/plugin/trackmate/TrackModelTest.java |  14 +-
 ...seGapsByLinearInterpolationActionTest.java |   4 +-
 .../detection/HessianDetectorTestDrive1.java  |   1 -
 .../features/edge/EdgeTargetAnalyzerTest.java |  14 +-
 .../edge/EdgeTimeAndLocationAnalyzerTest.java |  16 +-
 .../edge/EdgeVelocityAnalyzerTest.java        |  19 +-
 .../spot/SpotIntensityAnalyzerTest.java       |  13 +-
 .../track/TrackBranchingAnalyzerTest.java     |  19 +-
 .../track/TrackDurationAnalyzerTest.java      |  18 +-
 .../track/TrackIndexAnalyzerTest.java         |   5 +-
 .../track/TrackLocationAnalyzerTest.java      |  16 +-
 .../TrackSpeedStatisticsAnalyzerTest.java     |   9 +-
 .../graph/ConvexBranchDecompositionDebug.java |  19 +-
 .../graph/SortedDepthFirstIteratorTest.java   |  12 +-
 .../trackmate/interactivetests/GraphTest.java |  31 +-
 .../SpotFeatureGrapherExample.java            |   3 +-
 .../SpotNeighborhoodTest.java                 |  24 +-
 .../plugin/trackmate/mesh/DebugZSlicer.java   |   3 +-
 .../plugin/trackmate/mesh/DefaultMesh.java    |   4 +-
 .../plugin/trackmate/mesh/Demo3DMesh.java     |   2 -
 .../trackmate/mesh/Demo3DMeshTrackMate.java   |   4 +-
 .../plugin/trackmate/mesh/DemoHollowMesh.java |  14 +-
 .../trackmate/mesh/DemoPixelIteration.java    |   3 +-
 .../trackmate/mesh/ExportMeshForDemo.java     |   6 +-
 .../kalman/KalmanTrackerInteractiveTest.java  |  11 +-
 .../kalman/KalmanTrackerInteractiveTest3.java |   9 +-
 .../trackmate/util/SpotRoiIterableTest.java   |   4 +-
 66 files changed, 1265 insertions(+), 1206 deletions(-)
 create mode 100644 src/main/java/fiji/plugin/trackmate/SpotBase.java
 delete mode 100644 src/main/java/fiji/plugin/trackmate/SpotShape.java
 delete mode 100644 src/main/java/fiji/plugin/trackmate/util/SpotUtil.java

diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java
index 300910456..45c7a6762 100644
--- a/src/main/java/fiji/plugin/trackmate/Model.java
+++ b/src/main/java/fiji/plugin/trackmate/Model.java
@@ -972,8 +972,8 @@ public void modelChanged( final ModelChangeEvent event )
 			event.getSpots()
 					.stream()
 					.filter( s -> event.getSpotFlag( s ) == ModelChangeEvent.FLAG_SPOT_MODIFIED )
-					.filter( s -> s.getMesh() != null )
-					.forEach( s -> s.getMesh().resetZSliceCache( s ) );
+					.filter( s -> ( s instanceof SpotMesh ) )
+					.forEach( s -> ( ( SpotMesh ) s ).resetZSliceCache() );
 		}
 	}
 }
diff --git a/src/main/java/fiji/plugin/trackmate/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java
index 7502ab58e..a55d24dc3 100644
--- a/src/main/java/fiji/plugin/trackmate/Spot.java
+++ b/src/main/java/fiji/plugin/trackmate/Spot.java
@@ -23,39 +23,44 @@
 
 import static fiji.plugin.trackmate.SpotCollection.VISIBILITY;
 
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import com.google.common.collect.ImmutableMap;
+
 import fiji.plugin.trackmate.util.AlphanumComparator;
-import net.imglib2.AbstractEuclideanSpace;
+import fiji.plugin.trackmate.util.TMUtils;
+import net.imagej.ImgPlus;
+import net.imglib2.EuclideanSpace;
+import net.imglib2.IterableInterval;
+import net.imglib2.Localizable;
+import net.imglib2.RandomAccessible;
+import net.imglib2.RealInterval;
 import net.imglib2.RealLocalizable;
+import net.imglib2.RealPositionable;
+import net.imglib2.type.numeric.RealType;
 import net.imglib2.util.Util;
+import net.imglib2.view.Views;
 
 /**
- * A {@link RealLocalizable} implementation, used in TrackMate to represent a
- * detection.
+ * Interface for spots, used in TrackMate to represent a detection, or an object
+ * to be tracked.
  * 

- * On top of being a {@link RealLocalizable}, it can store additional numerical - * named features, with a {@link Map}-like syntax. Constructors enforce the - * specification of the spot location in 3D space (if Z is unused, put 0), the - * spot radius, and the spot quality. This somewhat cumbersome syntax is made to - * avoid any bad surprise with missing features in a subsequent use. The spot - * temporal features ({@link #FRAME} and {@link #POSITION_T}) are set upon - * adding to a {@link SpotCollection}. + * This interface privileges a map of String->Double organization of + * numerical feature, with the X, Y and Z coordinates stored in this map. This + * allows for default implementations for many of the {@link RealLocalizable} + * and {@link RealPositionable} methods of this interface. *

- * Each spot received at creation a unique ID (as an int), used - * later for saving, retrieving and loading. Interfering with this value will - * predictively cause undesired behavior. - * - * @author Jean-Yves Tinevez <jeanyves.tinevez@gmail.com> 2010, 2013 + * They are mainly a 3D {@link RealLocalizable}, that store the object position + * in physical coordinates (um, mm, etc). 2D detections are treated by setting + * the Z coordinate to 0. Time is treated separately, as a feature. * + * @author Jean-Yves Tinevez */ -public class Spot extends AbstractEuclideanSpace implements RealLocalizable, Comparable< Spot > +public interface Spot extends RealLocalizable, RealPositionable, RealInterval, Comparable< Spot >, EuclideanSpace { /* @@ -64,245 +69,68 @@ public class Spot extends AbstractEuclideanSpace implements RealLocalizable, Com public static AtomicInteger IDcounter = new AtomicInteger( -1 ); - /** Store the individual features, and their values. */ - private final ConcurrentHashMap< String, Double > features = new ConcurrentHashMap<>(); - - /** A user-supplied name for this spot. */ - private String name; - - /** This spot ID. */ - private final int ID; - - /** - * The polygon that represents the 2D roi around the spot. Can be - * null if the spot does not contain 2D contour information or - * has a 3D shape information as a mesh. - */ - private SpotRoi roi; - - /** - * The mesh that represents the 3D object around the spot. Can be - * null of the spot does not contain 3D shape information or - * has a 2D shape information as a contour. - */ - private SpotMesh mesh; - - /* - * CONSTRUCTORS - */ - - /** - * Creates a new spot. - * - * @param x - * the spot X coordinates, in image units. - * @param y - * the spot Y coordinates, in image units. - * @param z - * the spot Z coordinates, in image units. - * @param radius - * the spot radius, in image units. - * @param quality - * the spot quality. - * @param name - * the spot name. - */ - public Spot( final double x, final double y, final double z, final double radius, final double quality, final String name ) - { - super( 3 ); - this.ID = IDcounter.incrementAndGet(); - putFeature( POSITION_X, Double.valueOf( x ) ); - putFeature( POSITION_Y, Double.valueOf( y ) ); - putFeature( POSITION_Z, Double.valueOf( z ) ); - putFeature( RADIUS, Double.valueOf( radius ) ); - putFeature( QUALITY, Double.valueOf( quality ) ); - if ( null == name ) - { - this.name = "ID" + ID; - } - else - { - this.name = name; - } - } - - /** - * Creates a new spot, and gives it a default name. - * - * @param x - * the spot X coordinates, in image units. - * @param y - * the spot Y coordinates, in image units. - * @param z - * the spot Z coordinates, in image units. - * @param radius - * the spot radius, in image units. - * @param quality - * the spot quality. - */ - public Spot( final double x, final double y, final double z, final double radius, final double quality ) - { - this( x, y, z, radius, quality, null ); - } - - /** - * Creates a new spot, taking its 3D coordinates from a - * {@link RealLocalizable}. The {@link RealLocalizable} must have at least 3 - * dimensions, and must return coordinates in image units. - * - * @param location - * the {@link RealLocalizable} that contains the spot locatiob. - * @param radius - * the spot radius, in image units. - * @param quality - * the spot quality. - * @param name - * the spot name. - */ - public Spot( final RealLocalizable location, final double radius, final double quality, final String name ) - { - this( location.getDoublePosition( 0 ), location.getDoublePosition( 1 ), location.getDoublePosition( 2 ), radius, quality, name ); - } - - /** - * Creates a new spot, taking its 3D coordinates from a - * {@link RealLocalizable}. The {@link RealLocalizable} must have at least 3 - * dimensions, and must return coordinates in image units. The spot will get - * a default name. - * - * @param location - * the {@link RealLocalizable} that contains the spot locatiob. - * @param radius - * the spot radius, in image units. - * @param quality - * the spot quality. - */ - public Spot( final RealLocalizable location, final double radius, final double quality ) - { - this( location, radius, quality, null ); - } - - /** - * Creates a new spot, taking its location, its radius, its quality value - * and its name from the specified spot. - * - * @param spot - * the spot to read from. - */ - public Spot( final Spot spot ) - { - this( spot, spot.getFeature( RADIUS ), spot.getFeature( QUALITY ), spot.getName() ); - } - - /** - * Blank constructor meant to be used when loading a spot collection from a - * file. Will mess with the {@link #IDcounter} field, so this - * constructor should not be used for normal spot creation. - * - * @param ID - * the spot ID to set - */ - public Spot( final int ID ) - { - super( 3 ); - this.ID = ID; - synchronized ( IDcounter ) - { - if ( IDcounter.get() < ID ) - { - IDcounter.set( ID ); - } - } - } - /* * PUBLIC METHODS */ @Override - public int hashCode() + public default int compareTo( final Spot o ) { - return ID; + return ID() - o.ID(); } - @Override - public int compareTo( final Spot o ) - { - return ID - o.ID; - } - - @Override - public boolean equals( final Object other ) - { - if ( other == null ) - return false; - if ( other == this ) - return true; - if ( !( other instanceof Spot ) ) - return false; - final Spot os = ( Spot ) other; - return os.ID == this.ID; - } - - public void setRoi( final SpotRoi roi ) - { - this.roi = roi; - this.mesh = null; - } /** - * Return the 2D polygonal shape of this spot. Might be null if - * the spot has no shape information, or if it has but in 3D (in that case - * the {@link #mesh} field won't be null). - * - * @return the spot roi. Can be null. + * Returns a copy of this spot. The class and all fields will be identical, + * except for the {@link #ID()}. + * + * @return a new spot. */ - public SpotRoi getRoi() - { - return roi; - } + public Spot copy(); - public void setMesh( final SpotMesh mesh ) - { - this.roi = null; - this.mesh = mesh; - } + /** + * Scales the size of this spot by the specified ratio. + * + * @param alpha + * the scale. + */ + public void scale( double alpha ); /** - * Return the mesh shape of this spot. Might be null if the - * spot has no shape information, or if it has but in 2D (in that case the - * {@link #roi} field won't be null). - * - * @return the spot mesh. Can be null. + * Returns an iterable that will iterate over all the pixels contained in + * this spot. + * + * @param ra + * the {@link RandomAccessible} to iterate over. + * @param calibration + * the pixel size array, use to map pixel integer coordinates to + * the spot physical coordinates. + * @param + * the type of pixels in the {@link RandomAccessible}. + * @return an iterable. + * @return */ - public SpotMesh getMesh() - { - return mesh; - } + public < T extends RealType< T > > IterableInterval< T > iterable( RandomAccessible< T > ra, double calibration[] ); /** - * Returns the shape field of this spot as a {@link SpotShape}. - *

- * If the spot has no shape information, this will return null. - * If the image is 2D the shape returned will be a {@link SpotRoi}. In 3D it - * will be a {@link SpotRoi}. - * - * @return the spot shape. + * Returns an iterable that will iterate over all the pixels contained in + * this spot. + * + * @param img + * the ImgPlus to iterate over. + * @param + * the type of pixels in the {@link RandomAccessible}. + * @return an iterable. */ - public SpotShape getShape() + public default < T extends RealType< T > > IterableInterval< T > iterable( final ImgPlus< T > img ) { - if ( roi != null ) - return roi; - return mesh; + return iterable( Views.extendMirrorSingle( img ), TMUtils.getSpatialCalibration( img ) ); } /** * @return the name for this Spot. */ - public String getName() - { - return this.name; - } + public String getName(); /** * Set the name of this Spot. @@ -310,36 +138,24 @@ public String getName() * @param name * the name to use. */ - public void setName( final String name ) - { - this.name = name; - } + public void setName( final String name ); - public int ID() - { - return ID; - } - - @Override - public String toString() - { - String str; - if ( null == name || name.equals( "" ) ) - str = "ID" + ID; - else - str = name; - return str; - } + /** + * Returns the unique ID of this spot. The ID is unique within a session. + * + * @return the spot ID. + */ + public int ID(); /** * Return a string representation of this spot, with calculated features. * * @return a string representation of the spot. */ - public String echo() + public default String echo() { final StringBuilder s = new StringBuilder(); - + final String name = getName(); // Name if ( null == name ) s.append( "Spot: \n" ); @@ -355,6 +171,7 @@ public String echo() s.append( "Position: " + Util.printCoordinates( coordinates ) + "\n" ); // Feature list + final Map< String, Double > features = getFeatures(); if ( null == features || features.size() < 1 ) s.append( "No features calculated\n" ); else @@ -385,10 +202,7 @@ public String echo() * * @return a map of {@link String}s to {@link Double}s. */ - public Map< String, Double > getFeatures() - { - return features; - } + public Map< String, Double > getFeatures(); /** * Returns the value corresponding to the specified spot feature. @@ -398,10 +212,7 @@ public Map< String, Double > getFeatures() * @return the feature value, as a {@link Double}. Will be null * if it has not been set. */ - public Double getFeature( final String feature ) - { - return features.get( feature ); - } + public Double getFeature( final String feature ); /** * Stores the specified feature value for this spot. @@ -412,24 +223,36 @@ public Double getFeature( final String feature ) * the value to store, as a {@link Double}. Using * null will have unpredicted outcomes. */ - public void putFeature( final String feature, final Double value ) - { - features.put( feature, value ); - } + public void putFeature( final String feature, final Double value ); /** - * Copy the listed features of the spot src to the current spot - * + * Copy some of the features values of the specified spot to this spot. + * + * @param src + * the spot to copy feature values from. + * @param features + * the collection of feature keys to copy. */ - public void copyFeatures( final Spot src, final Map< String, Double > features ) + public default void copyFeaturesFrom( final Spot src, final Collection< String > features ) { if ( null == features || features.isEmpty() ) return; - for ( final String feat : features.keySet() ) + for ( final String feat : features ) putFeature( feat, src.getFeature( feat ) ); } + /** + * Copy all the features value from the specified spot to this spot. + * + * @param src + * the spot to copy feature values from. + */ + public default void copyFeaturesFrom( final Spot src ) + { + copyFeaturesFrom( src, src.getFeatures().keySet() ); + } + /** * Returns the difference of the feature value for this spot with the one of * the specified spot. By construction, this operation is anti-symmetric ( @@ -444,9 +267,9 @@ public void copyFeatures( final Spot src, final Map< String, Double > features ) * the name of the feature to use for calculation. * @return the difference in feature value. */ - public double diffTo( final Spot s, final String feature ) + public default double diffTo( final Spot s, final String feature ) { - final double f1 = features.get( feature ).doubleValue(); + final double f1 = getFeature( feature ).doubleValue(); final double f2 = s.getFeature( feature ).doubleValue(); return f1 - f2; } @@ -472,9 +295,9 @@ public double diffTo( final Spot s, final String feature ) * the name of the feature to use for calculation. * @return the absolute normalized difference feature value. */ - public double normalizeDiffTo( final Spot s, final String feature ) + public default double normalizeDiffTo( final Spot s, final String feature ) { - final double a = features.get( feature ).doubleValue(); + final double a = getFeature( feature ).doubleValue(); final double b = s.getFeature( feature ).doubleValue(); if ( a == -b ) return 0d; @@ -489,7 +312,7 @@ public double normalizeDiffTo( final Spot s, final String feature ) * the spot to compute the square distance to. * @return the square distance as a double. */ - public double squareDistanceTo( final RealLocalizable s ) + public default double squareDistanceTo( final RealLocalizable s ) { double sumSquared = 0d; for ( int d = 0; d < 3; d++ ) @@ -533,97 +356,232 @@ public double squareDistanceTo( final RealLocalizable s ) public final static String[] POSITION_FEATURES = new String[] { POSITION_X, POSITION_Y, POSITION_Z }; /** - * The 7 privileged spot features that must be set by a spot detector: + * The 8 privileged spot features that must be set by a spot detector: * {@link #QUALITY}, {@link #POSITION_X}, {@link #POSITION_Y}, - * {@link #POSITION_Z}, {@link #POSITION_Z}, {@link #RADIUS}, {@link #FRAME} - * . + * {@link #POSITION_Z}, {@link #POSITION_Z}, {@link #RADIUS}, + * {@link #FRAME}, {@link SpotCollection#VISIBILITY}. */ - public final static Collection< String > FEATURES = new ArrayList<>( 7 ); - - /** The 7 privileged spot feature names. */ - public final static Map< String, String > FEATURE_NAMES = new HashMap<>( 7 ); - - /** The 7 privileged spot feature short names. */ - public final static Map< String, String > FEATURE_SHORT_NAMES = new HashMap<>( 7 ); - - /** The 7 privileged spot feature dimensions. */ - public final static Map< String, Dimension > FEATURE_DIMENSIONS = new HashMap<>( 7 ); - - /** The 7 privileged spot feature isInt flags. */ - public final static Map< String, Boolean > IS_INT = new HashMap<>( 7 ); - - static - { - FEATURES.add( QUALITY ); - FEATURES.add( POSITION_X ); - FEATURES.add( POSITION_Y ); - FEATURES.add( POSITION_Z ); - FEATURES.add( POSITION_T ); - FEATURES.add( FRAME ); - FEATURES.add( RADIUS ); - FEATURES.add( SpotCollection.VISIBILITY ); - - FEATURE_NAMES.put( POSITION_X, "X" ); - FEATURE_NAMES.put( POSITION_Y, "Y" ); - FEATURE_NAMES.put( POSITION_Z, "Z" ); - FEATURE_NAMES.put( POSITION_T, "T" ); - FEATURE_NAMES.put( FRAME, "Frame" ); - FEATURE_NAMES.put( RADIUS, "Radius" ); - FEATURE_NAMES.put( QUALITY, "Quality" ); - FEATURE_NAMES.put( VISIBILITY, "Visibility" ); - - FEATURE_SHORT_NAMES.put( POSITION_X, "X" ); - FEATURE_SHORT_NAMES.put( POSITION_Y, "Y" ); - FEATURE_SHORT_NAMES.put( POSITION_Z, "Z" ); - FEATURE_SHORT_NAMES.put( POSITION_T, "T" ); - FEATURE_SHORT_NAMES.put( FRAME, "Frame" ); - FEATURE_SHORT_NAMES.put( RADIUS, "R" ); - FEATURE_SHORT_NAMES.put( QUALITY, "Quality" ); - FEATURE_SHORT_NAMES.put( VISIBILITY, "Visibility" ); - - FEATURE_DIMENSIONS.put( POSITION_X, Dimension.POSITION ); - FEATURE_DIMENSIONS.put( POSITION_Y, Dimension.POSITION ); - FEATURE_DIMENSIONS.put( POSITION_Z, Dimension.POSITION ); - FEATURE_DIMENSIONS.put( POSITION_T, Dimension.TIME ); - FEATURE_DIMENSIONS.put( FRAME, Dimension.NONE ); - FEATURE_DIMENSIONS.put( RADIUS, Dimension.LENGTH ); - FEATURE_DIMENSIONS.put( QUALITY, Dimension.QUALITY ); - FEATURE_DIMENSIONS.put( VISIBILITY, Dimension.NONE ); - - IS_INT.put( POSITION_X, Boolean.FALSE ); - IS_INT.put( POSITION_Y, Boolean.FALSE ); - IS_INT.put( POSITION_Z, Boolean.FALSE ); - IS_INT.put( POSITION_T, Boolean.FALSE ); - IS_INT.put( FRAME, Boolean.TRUE ); - IS_INT.put( RADIUS, Boolean.FALSE ); - IS_INT.put( QUALITY, Boolean.FALSE ); - IS_INT.put( VISIBILITY, Boolean.TRUE ); + public final static Collection< String > FEATURES = Arrays.asList( QUALITY, + POSITION_X, POSITION_Y, POSITION_Z, POSITION_T, FRAME, RADIUS, SpotCollection.VISIBILITY ); + + /** The 8 privileged spot feature names. */ + public final static Map< String, String > FEATURE_NAMES = ImmutableMap.of( + POSITION_X, "X", + POSITION_Y, "Y", + POSITION_Z, "Z", + POSITION_T, "T", + FRAME, "Frame", + RADIUS, "Radius", + QUALITY, "Quality", + VISIBILITY, "Visibility" ); + + /** The 8 privileged spot feature short names. */ + public final static Map< String, String > FEATURE_SHORT_NAMES = ImmutableMap.of( + POSITION_X, "X", + POSITION_Y, "Y", + POSITION_Z, "Z", + POSITION_T, "T", + FRAME, "Frame", + RADIUS, "R", + QUALITY, "Quality", + VISIBILITY, "Visibility" ); + + /** The 8 privileged spot feature dimensions. */ + public final static Map< String, Dimension > FEATURE_DIMENSIONS = ImmutableMap.of( + POSITION_X, Dimension.POSITION, + POSITION_Y, Dimension.POSITION, + POSITION_Z, Dimension.POSITION, + POSITION_T, Dimension.TIME, + FRAME, Dimension.NONE, + RADIUS, Dimension.LENGTH, + QUALITY, Dimension.QUALITY, + VISIBILITY, Dimension.NONE ); + + /** The 8 privileged spot feature isInt flags. */ + public final static Map< String, Boolean > IS_INT = ImmutableMap.of( + POSITION_X, Boolean.FALSE, + POSITION_Y, Boolean.FALSE, + POSITION_Z, Boolean.FALSE, + POSITION_T, Boolean.FALSE, + FRAME, Boolean.TRUE, + RADIUS, Boolean.FALSE, + QUALITY, Boolean.FALSE, + VISIBILITY, Boolean.TRUE ); + + /* + * REALPOSITIONABLE, REAlLOCALIZABLE + */ + + @Override + default int numDimensions() + { + return 3; + } + + @Override + public default void move( final float distance, final int d ) + { + putFeature( POSITION_FEATURES[d], getFeature( POSITION_FEATURES[d] + distance ) ); + } + + @Override + public default void move( final double distance, final int d ) + { + putFeature( POSITION_FEATURES[d], getFeature( POSITION_FEATURES[d] + distance ) ); + } + + @Override + public default void move( final RealLocalizable distance ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] + distance ) ); + } + + @Override + public default void move( final float[] distance ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] + distance[ d ] ) ); + } + + @Override + public default void move( final double[] distance ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] + distance[ d ] ) ); + } + + @Override + public default void setPosition( final RealLocalizable position ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], position.getDoublePosition( d ) ); + } + + @Override + public default void setPosition( final float[] position ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], ( double ) position[ d ] ); + } + + @Override + public default void setPosition( final double[] position ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], position[ d ] ); + } + + @Override + public default void setPosition( final float position, final int d ) + { + putFeature( POSITION_FEATURES[ d ], ( double ) position ); + } + + @Override + public default void setPosition( final double position, final int d ) + { + putFeature( POSITION_FEATURES[ d ], position ); + } + + @Override + public default void fwd( final int d ) + { + move( 1., d ); + } + + @Override + public default void bck( final int d ) + { + move( -1., d ); + } + + @Override + public default void move( final int distance, final int d ) + { + move( ( double ) distance, d ); + } + + @Override + public default void move( final long distance, final int d ) + { + move( ( double ) distance, d ); + } + + @Override + public default void move( final Localizable distance ) + { + move( ( RealLocalizable ) distance ); + } + + @Override + public default void move( final int[] distance ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] + distance[ d ] ) ); + } + + @Override + public default void move( final long[] distance ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] + distance[ d ] ) ); + } + + @Override + public default void setPosition( final Localizable position ) + { + setPosition( ( RealLocalizable ) position ); + } + + @Override + public default void setPosition( final int[] position ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], ( double ) position[ d ] ); + } + + @Override + public default void setPosition( final long[] position ) + { + for ( int d = 0; d < 3; d++ ) + putFeature( POSITION_FEATURES[ d ], ( double ) position[ d ] ); + } + + @Override + public default void setPosition( final int position, final int d ) + { + putFeature( POSITION_FEATURES[ d ], ( double ) position ); + } + + @Override + public default void setPosition( final long position, final int d ) + { + putFeature( POSITION_FEATURES[ d ], ( double ) position ); } @Override - public void localize( final float[] position ) + public default void localize( final float[] position ) { - assert ( position.length >= n ); - for ( int d = 0; d < n; ++d ) + for ( int d = 0; d < 3; ++d ) position[ d ] = getFloatPosition( d ); } @Override - public void localize( final double[] position ) + public default void localize( final double[] position ) { - assert ( position.length >= n ); - for ( int d = 0; d < n; ++d ) + for ( int d = 0; d < 3; ++d ) position[ d ] = getDoublePosition( d ); } @Override - public float getFloatPosition( final int d ) + public default float getFloatPosition( final int d ) { return ( float ) getDoublePosition( d ); } @Override - public double getDoublePosition( final int d ) + public default double getDoublePosition( final int d ) { return getFeature( POSITION_FEATURES[ d ] ); } @@ -641,7 +599,7 @@ public double getDoublePosition( final int d ) * feature. * @return a new {@link Comparator}. */ - public final static Comparator< Spot > featureComparator( final String feature ) + public static Comparator< Spot > featureComparator( final String feature ) { final Comparator< Spot > comparator = new Comparator< Spot >() { diff --git a/src/main/java/fiji/plugin/trackmate/SpotBase.java b/src/main/java/fiji/plugin/trackmate/SpotBase.java new file mode 100644 index 000000000..3cdd632dc --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/SpotBase.java @@ -0,0 +1,330 @@ +/*- + * #%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; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import fiji.plugin.trackmate.util.SpotNeighborhood; +import net.imglib2.AbstractEuclideanSpace; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.IterableInterval; +import net.imglib2.RandomAccessible; +import net.imglib2.RealLocalizable; +import net.imglib2.type.numeric.RealType; +import net.imglib2.view.Views; + +/** + * A {@link RealLocalizable} implementation of {@link SpotI}, used in TrackMate + * to represent a detection. This concrete implementation has the simplest + * shape: a spot is a sphere of fixed radius. + *

+ * On top of being a {@link RealLocalizable}, it can store additional numerical + * named features, with a {@link Map}-like syntax. Constructors enforce the + * specification of the spot location in 3D space (if Z is unused, put 0), the + * spot radius, and the spot quality. This somewhat cumbersome syntax is made to + * avoid any bad surprise with missing features in a subsequent use. The spot + * temporal features ({@link #FRAME} and {@link #POSITION_T}) are set upon + * adding to a {@link SpotCollection}. + *

+ * Each spot received at creation a unique ID (as an int), used + * later for saving, retrieving and loading. Interfering with this value will + * predictively cause undesired behavior. + * + * @author Jean-Yves Tinevez + * + */ +public class SpotBase extends AbstractEuclideanSpace implements Spot +{ + + /* + * FIELDS + */ + + public static AtomicInteger IDcounter = new AtomicInteger( -1 ); + + /** Store the individual features, and their values. */ + private final ConcurrentHashMap< String, Double > features = new ConcurrentHashMap<>(); + + /** A user-supplied name for this spot. */ + private String name; + + /** This spot ID. */ + private final int ID; + + /* + * CONSTRUCTORS + */ + + /** + * Creates a new spot. + * + * @param x + * the spot X coordinates, in image units. + * @param y + * the spot Y coordinates, in image units. + * @param z + * the spot Z coordinates, in image units. + * @param radius + * the spot radius, in image units. + * @param quality + * the spot quality. + * @param name + * the spot name. + */ + public SpotBase( final double x, final double y, final double z, final double radius, final double quality, final String name ) + { + super( 3 ); + this.ID = IDcounter.incrementAndGet(); + putFeature( POSITION_X, Double.valueOf( x ) ); + putFeature( POSITION_Y, Double.valueOf( y ) ); + putFeature( POSITION_Z, Double.valueOf( z ) ); + putFeature( RADIUS, Double.valueOf( radius ) ); + putFeature( QUALITY, Double.valueOf( quality ) ); + if ( null == name ) + { + this.name = "ID" + ID; + } + else + { + this.name = name; + } + } + + /** + * Creates a new spot, and gives it a default name. + * + * @param x + * the spot X coordinates, in image units. + * @param y + * the spot Y coordinates, in image units. + * @param z + * the spot Z coordinates, in image units. + * @param radius + * the spot radius, in image units. + * @param quality + * the spot quality. + */ + public SpotBase( final double x, final double y, final double z, final double radius, final double quality ) + { + this( x, y, z, radius, quality, null ); + } + + /** + * Creates a new spot, taking its 3D coordinates from a + * {@link RealLocalizable}. The {@link RealLocalizable} must have at least 3 + * dimensions, and must return coordinates in image units. + * + * @param location + * the {@link RealLocalizable} that contains the spot locatiob. + * @param radius + * the spot radius, in image units. + * @param quality + * the spot quality. + * @param name + * the spot name. + */ + public SpotBase( final RealLocalizable location, final double radius, final double quality, final String name ) + { + this( location.getDoublePosition( 0 ), location.getDoublePosition( 1 ), location.getDoublePosition( 2 ), radius, quality, name ); + } + + /** + * Creates a new spot, taking its 3D coordinates from a + * {@link RealLocalizable}. The {@link RealLocalizable} must have at least 3 + * dimensions, and must return coordinates in image units. The spot will get + * a default name. + * + * @param location + * the {@link RealLocalizable} that contains the spot locatiob. + * @param radius + * the spot radius, in image units. + * @param quality + * the spot quality. + */ + public SpotBase( final RealLocalizable location, final double radius, final double quality ) + { + this( location, radius, quality, null ); + } + + /** + * Creates a new spot, taking its location, its radius, its quality value + * and its name from the specified spot. + * + * @param spot + * the spot to read from. + */ + public SpotBase( final Spot oldSpot ) + { + this( oldSpot, oldSpot.getFeature( RADIUS ), oldSpot.getFeature( QUALITY ), oldSpot.getName() ); + } + + /** + * Blank constructor meant to be used when loading a spot collection from a + * file. Will mess with the {@link #IDcounter} field, so this + * constructor should not be used for normal spot creation. + * + * @param ID + * the spot ID to set + */ + public SpotBase( final int ID ) + { + super( 3 ); + this.ID = ID; + synchronized ( IDcounter ) + { + if ( IDcounter.get() < ID ) + { + IDcounter.set( ID ); + } + } + } + + /* + * PUBLIC METHODS + */ + + @Override + public SpotBase copy() + { + final SpotBase o = new SpotBase( this ); + o.copyFeaturesFrom( this ); + return o; + } + + @Override + public void scale( final double alpha ) + { + final double radius = getFeature( Spot.RADIUS ); + final double newRadius = radius * alpha; + putFeature( Spot.RADIUS, newRadius ); + } + + @Override + public int hashCode() + { + return ID; + } + + @Override + public boolean equals( final Object other ) + { + if ( other == null ) + return false; + if ( other == this ) + return true; + if ( !( other instanceof SpotBase ) ) + return false; + final SpotBase os = ( SpotBase ) other; + return os.ID == this.ID; + } + + @Override + public String getName() + { + return this.name; + } + + @Override + public void setName( final String name ) + { + this.name = name; + } + + @Override + public int ID() + { + return ID; + } + + @Override + public String toString() + { + String str; + if ( null == name || name.equals( "" ) ) + str = "ID" + ID; + else + str = name; + return str; + } + + /* + * FEATURE RELATED METHODS + */ + + @Override + public Map< String, Double > getFeatures() + { + return features; + } + + @Override + public Double getFeature( final String feature ) + { + return features.get( feature ); + } + + @Override + public void putFeature( final String feature, final Double value ) + { + features.put( feature, value ); + } + + @Override + public double realMin( final int d ) + { + return getDoublePosition( d ) - getFeature( SpotBase.RADIUS ); + } + + @Override + public double realMax( final int d ) + { + return getDoublePosition( d ) + getFeature( SpotBase.RADIUS ); + } + + @Override + public < T extends RealType< T > > IterableInterval< T > iterable( final RandomAccessible< T > ra, final double[] calibration ) + { + final double r = features.get( Spot.RADIUS ).doubleValue(); + if ( r / calibration[ 0 ] <= 1. && r / calibration[ 2 ] <= 1. ) + return makeSinglePixelIterable( this, ra, calibration ); + + return new SpotNeighborhood<>( this, ra, calibration ); + } + + private static < T > IterableInterval< T > makeSinglePixelIterable( final RealLocalizable center, final RandomAccessible< T > img, final double[] calibration ) + { + final long[] min = new long[ img.numDimensions() ]; + final long[] max = new long[ img.numDimensions() ]; + for ( int d = 0; d < min.length; d++ ) + { + final long cx = Math.round( center.getDoublePosition( d ) / calibration[ d ] ); + min[ d ] = cx; + max[ d ] = cx + 1; + } + + final Interval interval = new FinalInterval( min, max ); + return Views.interval( img, interval ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 49d5f4385..16ad4f42c 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.stream.Collectors; +import fiji.plugin.trackmate.util.mesh.SpotMeshIterable; import net.imagej.mesh.Mesh; import net.imagej.mesh.Meshes; import net.imagej.mesh.Triangles; @@ -13,12 +14,15 @@ import net.imagej.mesh.alg.zslicer.Slice; import net.imagej.mesh.alg.zslicer.ZSlicer; import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.IterableInterval; +import net.imglib2.RandomAccessible; import net.imglib2.RealInterval; import net.imglib2.RealLocalizable; import net.imglib2.RealPoint; +import net.imglib2.type.numeric.RealType; import net.imglib2.util.Intervals; -public class SpotMesh implements SpotShape, RealLocalizable +public class SpotMesh extends SpotBase { /** @@ -28,19 +32,38 @@ public class SpotMesh implements SpotShape, RealLocalizable */ public final Mesh mesh; - private Map< Integer, Slice > sliceMap; - /** The center of this object. */ - private final RealPoint center; - /** The bounding-box, centered on (0,0,0) of this object. */ public RealInterval boundingBox; - public SpotMesh( final Mesh mesh ) + public SpotMesh( + final Mesh mesh, + final double quality ) + { + this( mesh, quality, null ); + } + + /** + * Creates a new spot from the specified mesh. Its position and radius are + * calculated from the mesh. + * + * @param quality + * @param name + * @param mesh + */ + public SpotMesh( + final Mesh mesh, + final double quality, + final String name ) { + // Dummy coordinates and radius. + super( 0., 0., 0., 0., quality, name ); this.mesh = mesh; - this.center = Meshes.center( mesh ); + final RealPoint center = Meshes.center( mesh ); + + // Reposition the spot. + setPosition( center ); // Shift mesh to (0, 0, 0). final Vertices vertices = mesh.vertices(); @@ -50,10 +73,55 @@ public SpotMesh( final Mesh mesh ) vertices.xf( i ) - center.getFloatPosition( 0 ), vertices.yf( i ) - center.getFloatPosition( 1 ), vertices.zf( i ) - center.getFloatPosition( 2 ) ); + + // Compute radius. + final double r = radius( mesh ); + putFeature( Spot.RADIUS, r ); + // Bounding box, also centered on (0,0,0) this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); } + /** + * This constructor is only used for deserializing a model from a TrackMate + * file. It messes with the ID of the spots and should be not used + * otherwise. + * + * @param ID + * @param mesh + */ + public SpotMesh( final int ID, final BufferMesh mesh ) + { + super( ID ); + this.mesh = mesh; + final RealPoint center = Meshes.center( mesh ); + + // Reposition the spot. + setPosition( center ); + + // Shift mesh to (0, 0, 0). + final Vertices vertices = mesh.vertices(); + final long nVertices = vertices.size(); + for ( long i = 0; i < nVertices; i++ ) + vertices.setPositionf( i, + vertices.xf( i ) - center.getFloatPosition( 0 ), + vertices.yf( i ) - center.getFloatPosition( 1 ), + vertices.zf( i ) - center.getFloatPosition( 2 ) ); + + // Compute radius. + final double r = radius( mesh ); + putFeature( Spot.RADIUS, r ); + + // Bounding box, also centered on (0,0,0) + this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); + } + + @Override + public < T extends RealType< T > > IterableInterval< T > iterable( final RandomAccessible< T > ra, final double[] calibration ) + { + return new SpotMeshIterable<>( ra, this, calibration ); + } + /** * Gets the slice resulting from the intersection of the mesh with the XY * plane with the specified z position, in pixel coordinates, 0-based. @@ -76,7 +144,7 @@ public SpotMesh( final Mesh mesh ) public Slice getZSlice( final int zSlice, final double xyScale, final double zScale ) { if ( sliceMap == null ) - sliceMap = buildSliceMap( mesh, boundingBox, center, xyScale, zScale ); + sliceMap = buildSliceMap( mesh, boundingBox, this, xyScale, zScale ); return sliceMap.get( Integer.valueOf( zSlice ) ); } @@ -84,13 +152,9 @@ public Slice getZSlice( final int zSlice, final double xyScale, final double zSc /** * Invalidates the Z-slices cache. This will force its recomputation. To be * called after the spot has changed size or Z position. - * - * @param newPosition - * the new position of the spot. */ - public void resetZSliceCache( final RealLocalizable newPosition ) + public void resetZSliceCache() { - center.setPosition( newPosition ); sliceMap = null; } @@ -145,7 +209,6 @@ public static double volume( final Mesh mesh ) return Math.abs( sum ); } - @Override public double radius() { return radius( mesh ); @@ -161,12 +224,6 @@ public double volume() return volume( mesh ); } - @Override - public double size() - { - return volume(); - } - @Override public void scale( final double alpha ) { @@ -205,7 +262,7 @@ public SpotMesh copy() { final BufferMesh meshCopy = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); Meshes.copy( this.mesh, meshCopy ); - return new SpotMesh( meshCopy ); + return new SpotMesh( meshCopy, getFeature( Spot.QUALITY ), getName() ); } @Override @@ -307,24 +364,4 @@ private static final RealInterval toRealInterval( final float[] bb ) { return Intervals.createMinMaxReal( bb[ 0 ], bb[ 1 ], bb[ 2 ], bb[ 3 ], bb[ 4 ], bb[ 5 ] ); } - - public static Spot createSpot( final Mesh mesh, final double quality ) - { - final SpotMesh sm = new SpotMesh( mesh ); - final Spot spot = new Spot( sm.center, sm.radius(), quality ); - spot.setMesh( sm ); - return spot; - } - - @Override - public int numDimensions() - { - return 3; - } - - @Override - public double getDoublePosition( final int d ) - { - return center.getDoublePosition( d ); - } } diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index a8faa695c..df5b856f2 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -24,32 +24,55 @@ import java.util.Arrays; import gnu.trove.list.array.TDoubleArrayList; -import net.imagej.ImgPlus; import net.imglib2.IterableInterval; -import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RandomAccessible; import net.imglib2.roi.IterableRegion; import net.imglib2.roi.Masks; import net.imglib2.roi.Regions; import net.imglib2.roi.geom.GeomMasks; import net.imglib2.roi.geom.real.WritablePolygon2D; import net.imglib2.type.logic.BoolType; -import net.imglib2.view.Views; +import net.imglib2.type.numeric.RealType; -public class SpotRoi implements SpotShape +public class SpotRoi extends SpotBase { - /** - * Polygon points X coordinates, in physical units. - */ + /** Polygon points X coordinates, in physical units, centered (0,0). */ public final double[] x; - /** - * Polygon points Y coordinates, in physical units. - */ + /** Polygon points Y coordinates, in physical units, centered (0,0). */ public final double[] y; - public SpotRoi( final double[] x, final double[] y ) + public SpotRoi( + final double xc, + final double yc, + final double zc, + final double r, + final double quality, + final String name, + final double[] x, + final double[] y ) + { + super( xc, yc, zc, r, quality, name ); + this.x = x; + this.y = y; + } + + /** + * This constructor is only used for deserializing a model from a TrackMate + * file. It messes with the ID of the spots and should be not used + * otherwise. + * + * @param ID + * @param x + * @param y + */ + public SpotRoi( + final int ID, + final double[] x, + final double[] y ) { + super( ID ); this.x = x; this.y = y; } @@ -57,7 +80,12 @@ public SpotRoi( final double[] x, final double[] y ) @Override public SpotRoi copy() { - return new SpotRoi( x.clone(), y.clone() ); + final double xc = getDoublePosition( 0 ); + final double yc = getDoublePosition( 1 ); + final double zc = getDoublePosition( 2 ); + final double r = getFeature( Spot.RADIUS ); + final double quality = getFeature( Spot.QUALITY ); + return new SpotRoi( xc, yc, zc, r, quality, getName(), x.clone(), y.clone() ); } /** @@ -150,36 +178,30 @@ public void toPolygon( cy.add( yp ); } } - - public < T > IterableInterval< T > sample( final Spot spot, final ImgPlus< T > img ) - { - return sample( spot.getDoublePosition( 0 ), spot.getDoublePosition( 1 ), img, img.averageScale( 0 ), img.averageScale( 1 ) ); - } - - public < T > IterableInterval< T > sample( final double spotXCenter, final double spotYCenter, final RandomAccessibleInterval< T > img, final double xScale, final double yScale ) + + @Override + public < T extends RealType< T > > IterableInterval< T > iterable( final RandomAccessible< T > ra, final double[] calibration ) { - final double[] xp = toPolygonX( xScale, 0, spotXCenter, 1. ); - final double[] yp = toPolygonY( yScale, 0, spotYCenter, 1. ); + final double[] xp = toPolygonX( calibration[ 0 ], 0, this.getDoublePosition( 0 ), 1. ); + final double[] yp = toPolygonY( calibration[ 1 ], 0, this.getDoublePosition( 1 ), 1. ); final WritablePolygon2D polygon = GeomMasks.closedPolygon2D( xp, yp ); final IterableRegion< BoolType > region = Masks.toIterableRegion( polygon ); - return Regions.sample( region, Views.extendMirrorDouble( Views.dropSingletonDimensions( img ) ) ); + return Regions.sample( region, ra ); } - @Override - public double radius() + private static double radius( final double[] x, final double[] y ) { - return Math.sqrt( area() / Math.PI ); + return Math.sqrt( area( x, y ) / Math.PI ); } - public double area() + private static double area( final double[] x, final double[] y ) { return Math.abs( signedArea( x, y ) ); } - @Override - public double size() + public double area() { - return area(); + return area( x, y ); } @Override @@ -197,7 +219,7 @@ public void scale( final double alpha ) } } - public static Spot createSpot( final double[] x, final double[] y, final double quality ) + public static SpotRoi createSpot( final double[] x, final double[] y, final double quality ) { // Put polygon coordinates with respect to centroid. final double[] centroid = centroid( x, y ); @@ -206,15 +228,10 @@ public static Spot createSpot( final double[] x, final double[] y, final double final double[] xr = Arrays.stream( x ).map( x0 -> x0 - xc ).toArray(); final double[] yr = Arrays.stream( y ).map( y0 -> y0 - yc ).toArray(); - // Create roi. - final SpotRoi roi = new SpotRoi( xr, yr ); - // Create spot. final double z = 0.; - final double r = roi.radius(); - final Spot spot = new Spot( xc, yc, z, r, quality ); - spot.setRoi( roi ); - return spot; + final double r = radius( xr, yr ); + return new SpotRoi( xc, yc, z, r, quality, null, xr, yr ); } /* diff --git a/src/main/java/fiji/plugin/trackmate/SpotShape.java b/src/main/java/fiji/plugin/trackmate/SpotShape.java deleted file mode 100644 index cf5b632a2..000000000 --- a/src/main/java/fiji/plugin/trackmate/SpotShape.java +++ /dev/null @@ -1,25 +0,0 @@ -package fiji.plugin.trackmate; - -public interface SpotShape -{ - - /** - * Returns the radius of the equivalent sphere with the same volume that of - * this mesh. - * - * @return the radius in physical units. - */ - double radius(); - - void scale( double alpha ); - - SpotShape copy(); - - /** - * Returns the physical size of this shape. In 2D it is the area. In 3D it - * is the volume. - * - * @return the shape size. - */ - double size(); -} diff --git a/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java b/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java index 8ddb63ac9..bd3f473db 100644 --- a/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java @@ -50,9 +50,10 @@ import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.TrackModel; -import fiji.plugin.trackmate.action.LabelImgExporter.SpotRoiWriter; +import fiji.plugin.trackmate.action.LabelImgExporter.SpotShapeWriter; import fiji.plugin.trackmate.graph.ConvexBranchesDecomposition; import fiji.plugin.trackmate.graph.ConvexBranchesDecomposition.TrackBranchDecomposition; import fiji.plugin.trackmate.graph.GraphUtils; @@ -314,15 +315,16 @@ public static void exportSegmentationData( final String exportRootFolder, final for ( int frame = 0; frame < dims[ 3 ]; frame++ ) { final ImgPlus< UnsignedShortType > imgCT = TMUtils.hyperSlice( labelImg, 0, frame ); - final SpotRoiWriter< UnsignedShortType > spotWriter = new SpotRoiWriter<>( imgCT ); + final SpotShapeWriter< UnsignedShortType > spotWriter = new SpotShapeWriter<>( imgCT ); for ( final Spot spot : model.getSpots().iterable( frame, true ) ) { - if ( spot.getRoi() == null ) - continue; - final int id = idGen.getAndIncrement(); - spotWriter.write( spot, id ); - framesToWrite.add( Integer.valueOf( frame ) ); + if ( spot instanceof SpotRoi ) + { + final int id = idGen.getAndIncrement(); + spotWriter.write( spot, id ); + framesToWrite.add( Integer.valueOf( frame ) ); + } } } @@ -409,8 +411,8 @@ public static String exportTrackingData( final String exportRootFolder, final in Files.createDirectories( path.getParent() ); logger.log( "Exporting tracking text file to " + path.toString() ); - try (FileOutputStream fos = new FileOutputStream( path.toFile() ); - BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( fos ) )) + try (final FileOutputStream fos = new FileOutputStream( path.toFile() ); + final BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( fos ) )) { for ( final Integer trackID : trackModel.trackIDs( true ) ) @@ -446,7 +448,7 @@ public static String exportTrackingData( final String exportRootFolder, final in { final long frame = spot.getFeature( Spot.FRAME ).longValue(); final ImgPlus< UnsignedShortType > imgCT = TMUtils.hyperSlice( labelImg, 0, frame ); - final SpotRoiWriter< UnsignedShortType > spotRoiWriter = new SpotRoiWriter<>( imgCT ); + final SpotShapeWriter< UnsignedShortType > spotRoiWriter = new SpotShapeWriter<>( imgCT ); spotRoiWriter.write( spot, currentID ); } diff --git a/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java index d1c18e4ea..0e7c0ee13 100644 --- a/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java @@ -86,10 +86,10 @@ public void export( final Iterable< Spot > spots ) public void export( final Spot spot ) { - final SpotRoi sroi = spot.getRoi(); final Roi roi; - if ( sroi != null ) + if ( spot instanceof SpotRoi ) { + final SpotRoi sroi = ( SpotRoi ) spot; final double[] xs = sroi.toPolygonX( dx, 0., spot.getDoublePosition( 0 ), 1. ); final double[] ys = sroi.toPolygonY( dy, 0., spot.getDoublePosition( 1 ), 1. ); final float[] xp = toFloat( xs ); diff --git a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java index 8b286be51..d801f2c01 100644 --- a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java @@ -41,7 +41,6 @@ import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.TrackModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.GlasbeyLut; import ij.ImagePlus; @@ -454,8 +453,7 @@ public static final Img< FloatType > createLabelImg( final ImgPlus< FloatType > imgCT = TMUtils.hyperSlice( imgPlus, 0, frame ); final SpotWriter spotWriter = exportSpotsAsDots ? new SpotAsDotWriter<>( imgCT ) - : new SpotRoiWriter<>( imgCT ); - idGenerator.nextFrame(); + : new SpotShapeWriter<>( imgCT ); for ( final Spot spot : model.getSpots().iterable( frame, true ) ) { @@ -546,7 +544,7 @@ public static < T extends RealType< T > & NativeType< T > > ImgPlus< T > createL final ImgPlus< T > imgCT = TMUtils.hyperSlice( imgPlus, 0, frame ); final SpotWriter spotWriter = exportSpotsAsDots ? new SpotAsDotWriter<>( imgCT ) - : new SpotRoiWriter<>( imgCT ); + : new SpotShapeWriter<>( imgCT ); idGenerator.nextFrame(); for ( final Spot spot : spots.iterable( frame, true ) ) @@ -604,12 +602,12 @@ public static interface SpotWriter public void write( Spot spot, int id ); } - public static final class SpotRoiWriter< T extends RealType< T > > implements SpotWriter + public static final class SpotShapeWriter< T extends RealType< T > > implements SpotWriter { private final ImgPlus< T > img; - public SpotRoiWriter( final ImgPlus< T > img ) + public SpotShapeWriter( final ImgPlus< T > img ) { this.img = img; } @@ -617,7 +615,7 @@ public SpotRoiWriter( final ImgPlus< T > img ) @Override public void write( final Spot spot, final int id ) { - for ( final T pixel : SpotUtil.iterable( spot, img ) ) + for ( final T pixel : spot.iterable( img ) ) pixel.setReal( id ); } } diff --git a/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java b/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java index af0027b9f..924bfc7df 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java @@ -39,6 +39,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.io.IOUtils; @@ -117,7 +118,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo * An awkward way to avoid spot ID conflicts after loading * two files */ - newSpot = new Spot( oldSpot ); + newSpot = new SpotBase( oldSpot ); for ( final String feature : oldSpot.getFeatures().keySet() ) newSpot.putFeature( feature, oldSpot.getFeature( feature ) ); diff --git a/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java b/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java index 17b8d2b15..65dce45e0 100644 --- a/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java +++ b/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java @@ -33,6 +33,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.TrackModel; import fiji.plugin.trackmate.detection.DetectionUtils; @@ -173,7 +174,7 @@ public static List< Spot > interpolate( final Model model, final DefaultWeighted position[ d ] = weight * sPos[ d ] + ( 1.0 - weight ) * tPos[ d ]; final RealPoint rp = new RealPoint( position ); - final Spot newSpot = new Spot( rp, 0, 0 ); + final Spot newSpot = new SpotBase( rp, 0, 0 ); newSpot.putFeature( Spot.FRAME, Double.valueOf( f ) ); // Set some properties of the new spot diff --git a/src/main/java/fiji/plugin/trackmate/action/fit/AbstractSpotFitter.java b/src/main/java/fiji/plugin/trackmate/action/fit/AbstractSpotFitter.java index d329725e3..35588e86c 100644 --- a/src/main/java/fiji/plugin/trackmate/action/fit/AbstractSpotFitter.java +++ b/src/main/java/fiji/plugin/trackmate/action/fit/AbstractSpotFitter.java @@ -69,7 +69,6 @@ public abstract class AbstractSpotFitter implements SpotFitter private long processingTime = -1; - @SuppressWarnings( "unchecked" ) public AbstractSpotFitter( final ImagePlus imp, final int channel ) { this.channel = channel; diff --git a/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java b/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java index b01746bf7..8ccdfb210 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java @@ -36,6 +36,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.util.MedianFilter2D; @@ -408,7 +409,7 @@ public static final < T extends RealType< T > > List< Spot > findLocalMaxima( final double x = refinedPeak.getDoublePosition( 0 ) * calibration[ 0 ]; final double y = refinedPeak.getDoublePosition( 1 ) * calibration[ 1 ]; final double z = refinedPeak.getDoublePosition( 2 ) * calibration[ 2 ]; - final Spot spot = new Spot( x, y, z, radius, quality ); + final Spot spot = new SpotBase( x, y, z, radius, quality ); spots.add( spot ); } } @@ -421,7 +422,7 @@ else if ( source.numDimensions() > 1 ) final double quality = ra.get().getRealDouble(); final double x = refinedPeak.getDoublePosition( 0 ) * calibration[ 0 ]; final double y = refinedPeak.getDoublePosition( 1 ) * calibration[ 1 ]; - final Spot spot = new Spot( x, y, z, radius, quality ); + final Spot spot = new SpotBase( x, y, z, radius, quality ); spots.add( spot ); } } @@ -434,7 +435,7 @@ else if ( source.numDimensions() > 1 ) ra.setPosition( refinedPeak.getOriginalPeak() ); final double quality = ra.get().getRealDouble(); final double x = refinedPeak.getDoublePosition( 0 ) * calibration[ 0 ]; - final Spot spot = new Spot( x, y, z, radius, quality ); + final Spot spot = new SpotBase( x, y, z, radius, quality ); spots.add( spot ); } @@ -453,7 +454,7 @@ else if ( source.numDimensions() > 1 ) final double x = peak.getDoublePosition( 0 ) * calibration[ 0 ]; final double y = peak.getDoublePosition( 1 ) * calibration[ 1 ]; final double z = peak.getDoublePosition( 2 ) * calibration[ 2 ]; - final Spot spot = new Spot( x, y, z, radius, quality ); + final Spot spot = new SpotBase( x, y, z, radius, quality ); spots.add( spot ); } } @@ -466,7 +467,7 @@ else if ( source.numDimensions() > 1 ) final double quality = ra.get().getRealDouble(); final double x = peak.getDoublePosition( 0 ) * calibration[ 0 ]; final double y = peak.getDoublePosition( 1 ) * calibration[ 1 ]; - final Spot spot = new Spot( x, y, z, radius, quality ); + final Spot spot = new SpotBase( x, y, z, radius, quality ); spots.add( spot ); } } @@ -479,10 +480,9 @@ else if ( source.numDimensions() > 1 ) ra.setPosition( peak ); final double quality = ra.get().getRealDouble(); final double x = peak.getDoublePosition( 0 ) * calibration[ 0 ]; - final Spot spot = new Spot( x, y, z, radius, quality ); + final Spot spot = new SpotBase( x, y, z, radius, quality ); spots.add( spot ); } - } } diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 9f10e2332..af7d5377f 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -30,14 +30,18 @@ import java.util.concurrent.ExecutorService; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; -import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.Threads; import ij.gui.PolygonRoi; import ij.process.FloatPolygon; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.Vertices; import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; @@ -57,6 +61,7 @@ import net.imglib2.roi.labeling.LabelRegions; import net.imglib2.type.BooleanType; import net.imglib2.type.logic.BitType; +import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; @@ -307,7 +312,7 @@ public static < R extends IntegerType< R > > List< Spot > fromLabeling( ? Math.sqrt( volume / Math.PI ) : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); final double quality = region.size(); - spots.add( new Spot( x, y, z, radius, quality ) ); + spots.add( new SpotBase( x, y, z, radius, quality ) ); } return spots; @@ -394,7 +399,7 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > final double radius = ( labeling.numDimensions() == 2 ) ? Math.sqrt( volume / Math.PI ) : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); - spots.add( new Spot( x, y, z, radius, quality ) ); + spots.add( new SpotBase( x, y, z, radius, quality ) ); } return spots; @@ -598,7 +603,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integ final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] }; final String[] units = new String[] { "unitX", "unitY" }; final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units ); - final IterableInterval< S > iterable = SpotUtil.iterable( spot, qualityImgPlus ); + final IterableInterval< S > iterable = spot.iterable( qualityImgPlus ); double max = Double.NEGATIVE_INFINITY; for ( final S s : iterable ) { @@ -1259,4 +1264,109 @@ public String toString() return res + "]"; } } + + private static void scale( final Vertices vertices, final double[] scale, final double[] origin ) + { + final long nv = vertices.size(); + for ( long i = 0; i < nv; i++ ) + { + final double x = ( origin[ 0 ] + vertices.x( i ) ) * scale[ 0 ]; + final double y = ( origin[ 1 ] + vertices.y( i ) ) * scale[ 1 ]; + final double z = ( origin[ 2 ] + vertices.z( i ) ) * scale[ 2 ]; + vertices.set( i, x, y, z ); + } + } + + /** + * Returns a new {@link Spot} with a {@link SpotMesh} as shape, built from + * the specified bit-mask. + * + * @param + * the type of pixels in the quality image. + * @param region + * the bit-mask to build the mesh from. + * @param simplify + * if true the mesh will be simplified. + * @param calibration + * the pixel size array, used to scale the mesh to physical + * coordinates. + * @param qualityImage + * an image from which to read the quality value. If not + * null, the quality of the spot will be the max + * value of this image inside the mesh. If null, the + * quality will be the mesh volume. + * + * @return a new spot. + */ + private static < S extends RealType< S > > Spot regionToSpotMesh( + final RandomAccessibleInterval< BoolType > region, + final boolean simplify, + final double[] calibration, + final RandomAccessibleInterval< S > qualityImage ) + { + // To mesh. + final IntervalView< BoolType > box = Views.zeroMin( region ); + final Mesh mesh = Meshes.marchingCubes( box, 0.5 ); + final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, VERTEX_DUPLICATE_REMOVAL_PRECISION ); + final Mesh simplified; + if (simplify) + { + // Dont't go below a certain number of triangles. + final int nTriangles = ( int ) cleaned.triangles().size(); + if ( nTriangles < MIN_N_TRIANGLES ) + { + simplified = cleaned; + } + else + { + // Crude heuristics. + final float targetRatio; + if ( nTriangles < 2 * MIN_N_TRIANGLES ) + targetRatio = 0.5f; + else if ( nTriangles < 10_000 ) + targetRatio = 0.2f; + else if ( nTriangles < 1_000_000 ) + targetRatio = 0.1f; + else + targetRatio = 0.05f; + simplified = Meshes.simplify( cleaned, targetRatio, SIMPLIFY_AGGRESSIVENESS ); + } + } + else + { + simplified = cleaned; + } + // Remove meshes that are too small + final double volumeThreshold = MIN_MESH_PIXEL_VOLUME * calibration[ 0 ] * calibration[ 1 ] * calibration[ 2 ]; + if ( SpotMesh.volume( mesh ) < volumeThreshold ) + return null; + + // Scale to physical coords. + final double[] origin = region.minAsDoubleArray(); + scale( simplified.vertices(), calibration, origin ); + + // Make spot with default quality. + final SpotMesh spot = new SpotMesh( simplified, 0. ); + + // Measure quality. + final double quality; + if ( null == qualityImage ) + { + quality = SpotMesh.volume( simplified ); + } + else + { + final IterableInterval< S > iterable = spot.iterable( qualityImage, calibration ); + double max = Double.NEGATIVE_INFINITY; + for ( final S s : iterable ) + { + final double val = s.getRealDouble(); + if ( val > max ) + max = val; + } + quality = max; + } + spot.putFeature( Spot.QUALITY, Double.valueOf( quality ) ); + return spot; + } } diff --git a/src/main/java/fiji/plugin/trackmate/detection/semiauto/SemiAutoTracker.java b/src/main/java/fiji/plugin/trackmate/detection/semiauto/SemiAutoTracker.java index db871d4ae..10ed6b6b4 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/semiauto/SemiAutoTracker.java +++ b/src/main/java/fiji/plugin/trackmate/detection/semiauto/SemiAutoTracker.java @@ -45,7 +45,6 @@ public class SemiAutoTracker< T extends RealType< T > & NativeType< T > > extend private final ImagePlus imp; - @SuppressWarnings( "unchecked" ) public SemiAutoTracker( final Model model, final SelectionModel selectionModel, final ImagePlus imp, final Logger logger ) { super( model, selectionModel, logger ); diff --git a/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java b/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java index 733fbd0d5..4c8cb71eb 100644 --- a/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java +++ b/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java @@ -36,6 +36,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.features.edges.EdgeAnalyzer; import fiji.plugin.trackmate.features.manual.ManualEdgeColorAnalyzer; import fiji.plugin.trackmate.features.manual.ManualSpotColorAnalyzerFactory; @@ -383,7 +384,7 @@ public static final FeatureColorGenerator< Integer > createWholeTrackColorGenera final double z = ran.nextDouble(); final double r = ran.nextDouble(); final double q = ran.nextDouble(); - final Spot spot = new Spot( x, y, z, r, q ); + final Spot spot = new SpotBase( x, y, z, r, q ); DUMMY_MODEL.addSpotTo( spot, t ); if ( previous != null ) DUMMY_MODEL.addEdge( previous, spot, ran.nextDouble() ); diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java b/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java index 6e306de7c..c8f26a65a 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.List; +import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotRoi; /** @@ -133,6 +134,11 @@ public static SpotRoi convexHull( final SpotRoi roi ) xhull[ i ] = hull.get( i ).x; yhull[ i ] = hull.get( i ).y; } - return new SpotRoi( xhull, yhull ); + final double xc = roi.getDoublePosition( 0 ); + final double yc = roi.getDoublePosition( 1 ); + final double zc = roi.getDoublePosition( 2 ); + final double r = roi.getFeature( Spot.RADIUS ); + final double quality = roi.getFeature( Spot.QUALITY ); + return new SpotRoi( xc, yc, zc, r, quality, roi.getName(), xhull, yhull ); } } diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java index e76f24e8c..331a34d88 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java @@ -51,9 +51,9 @@ public void process( final Spot spot ) if ( is2D ) { - final SpotRoi roi = spot.getRoi(); - if ( roi != null ) + if ( spot instanceof SpotRoi ) { + final SpotRoi roi = ( SpotRoi ) spot; final double[] Q = fitEllipse( roi.x, roi.y ); final double[] A = quadraticToCartesian( Q ); x0 = A[ 0 ]; diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java index f4be68aca..4b318a62a 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java @@ -44,9 +44,9 @@ public void process( final Spot spot ) if ( is2D ) { - final SpotRoi roi = spot.getRoi(); - if ( roi != null ) + if ( spot instanceof SpotRoi ) { + final SpotRoi roi = ( SpotRoi ) spot; area = roi.area(); perimeter = getLength( roi ); final SpotRoi convexHull = ConvexHull2D.convexHull( roi ); diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java index 255a1e046..2080e55dc 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java @@ -57,9 +57,9 @@ public void process( final Spot spot ) if ( is3D ) { - final SpotMesh sm = spot.getMesh(); - if ( sm != null ) + if ( spot instanceof SpotMesh ) { + final SpotMesh sm = ( SpotMesh ) spot; final EllipsoidFit fit = EllipsoidFitter.fit( sm.mesh ); x0 = fit.center.getDoublePosition( 0 ); y0 = fit.center.getDoublePosition( 1 ); diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java index e853febe9..8ff80b8a1 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java @@ -55,9 +55,9 @@ public void process( final Spot spot ) double sphericity; if ( is3D ) { - final SpotMesh sm = spot.getMesh(); - if ( sm != null ) + if ( spot instanceof SpotMesh ) { + final SpotMesh sm = ( SpotMesh ) spot; final Mesh ch = convexHull.calculate( sm.mesh ); volume = sm.volume(); final double volumeCH = Meshes.volume( ch ); diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java index b9055e8cc..f023beb21 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java @@ -29,10 +29,6 @@ import static fiji.plugin.trackmate.features.spot.SpotIntensityMultiCAnalyzerFactory.makeFeatureKey; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotShape; -import fiji.plugin.trackmate.util.SpotNeighborhood; -import fiji.plugin.trackmate.util.SpotNeighborhoodCursor; -import fiji.plugin.trackmate.util.SpotUtil; import net.imagej.ImgPlus; import net.imglib2.IterableInterval; import net.imglib2.type.numeric.RealType; @@ -98,77 +94,33 @@ public final void process( final Spot spot ) final double radius = spot.getFeature( Spot.RADIUS ); final double outterRadius = 2. * radius; - // Operate on ROI only if we have one and the image is 2D. - final double meanOut; - final SpotShape shape = spot.getShape(); - if ( null != shape ) + final double alpha = outterRadius / radius; + final Spot outterRoi = spot.copy(); + outterRoi.scale( alpha ); + final IterableInterval< T > neighborhood = outterRoi.iterable( img ); + double totalSum = 0.; + int nTotal = 0; + for ( final T t : neighborhood ) { - // 2D or 3D cases are treated altogether. - final double alpha = outterRadius / radius; - final SpotShape outterRoi = shape.copy(); - outterRoi.scale( alpha ); - final IterableInterval< T > neighborhood = SpotUtil.iterable( outterRoi, spot, img ); - double totalSum = 0.; - int nTotal = 0; // Total number of non-NaN pixels - - // Iterate over the big ROI. - for ( final T t : neighborhood ) - { - final double val = t.getRealDouble(); - if ( Double.isNaN( val ) ) - continue; - nTotal++; - totalSum += val; - } - - // Sum intensity inside (over non-NaN pixels). - final String sumFeature = makeFeatureKey( TOTAL_INTENSITY, channel ); - final double innerSum = spot.getFeature( sumFeature ); - - // Compute number of non-NaN pixels in the inner roi. - final int nInner = ( int ) ( innerSum / meanIn ); - - // Total number of non-NaN pixels in the outer roi. - final int nOut = nTotal - nInner; - - final double outterSum = totalSum - innerSum; - meanOut = outterSum / nOut; - } - else - { - // Otherwise default to circle / sphere. - final Spot largeSpot = new Spot( spot ); - largeSpot.putFeature( Spot.RADIUS, outterRadius ); - final SpotNeighborhood< T > neighborhood = new SpotNeighborhood<>( largeSpot, img ); - if ( neighborhood.size() <= 1 ) - { - spot.putFeature( makeFeatureKey( CONTRAST, channel ), Double.NaN ); - spot.putFeature( makeFeatureKey( SNR, channel ), Double.NaN ); - return; - } - - final double radius2 = radius * radius; - int nOut = 0; // Outer number of non-NaN pixels. - double sumOut = 0; - - // Compute mean in the outer ring - final SpotNeighborhoodCursor< T > cursor = neighborhood.cursor(); - while ( cursor.hasNext() ) - { - cursor.fwd(); - final double dist2 = cursor.getDistanceSquared(); - if ( dist2 > radius2 ) - { - final double val = cursor.get().getRealDouble(); - if ( Double.isNaN( val ) ) - continue; - nOut++; - sumOut += val; - } - } - meanOut = sumOut / nOut; + final double val = t.getRealDouble(); + if ( Double.isNaN( val ) ) + continue; + nTotal++; + totalSum += val; } + final String sumFeature = makeFeatureKey( TOTAL_INTENSITY, channel ); + final double innerSum = spot.getFeature( sumFeature ); + + // Compute number of non-NaN pixels in the inner roi. + final int nInner = ( int ) ( innerSum / meanIn ); + + // Total number of non-NaN pixels in the outer roi. + final int nOut = nTotal - nInner; + + final double outterSum = totalSum - innerSum; + final double meanOut = outterSum / nOut; + // Compute contrast final double contrast = ( meanIn - meanOut ) / ( meanIn + meanOut ); @@ -179,4 +131,3 @@ public final void process( final Spot spot ) spot.putFeature( makeFeatureKey( SNR, channel ), snr ); } } - diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotIntensityMultiCAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/SpotIntensityMultiCAnalyzer.java index a09b38a51..dd6c510e2 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotIntensityMultiCAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/SpotIntensityMultiCAnalyzer.java @@ -31,7 +31,6 @@ import org.scijava.util.DoubleArray; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; import net.imagej.ImgPlus; import net.imglib2.IterableInterval; @@ -54,7 +53,7 @@ public SpotIntensityMultiCAnalyzer( final ImgPlus< T > imgCT, final int channel @Override public void process( final Spot spot ) { - final IterableInterval< T > neighborhood = SpotUtil.iterable( spot, imgCT ); + final IterableInterval< T > neighborhood = spot.iterable( imgCT ); final DoubleArray intensities = new DoubleArray(); for ( final T pixel : neighborhood ) diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java index c77183971..fc34d7386 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java @@ -52,8 +52,8 @@ import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.components.ConfigurationPanel; import fiji.plugin.trackmate.util.DetectionPreview; -import fiji.plugin.trackmate.util.Threads; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.util.Threads; import ij.ImagePlus; import net.imagej.ImgPlus; import net.imglib2.Interval; @@ -267,11 +267,9 @@ protected ThresholdDetectorConfigurationPanel( private < T extends RealType< T > & NativeType< T > > void autoThreshold() { btnAutoThreshold.setEnabled( false ); - Threads.run( "TrackMate compute threshold thread", () -> - { + Threads.run( "TrackMate compute threshold thread", () -> { try { - @SuppressWarnings( "unchecked" ) final ImgPlus< T > img = TMUtils.rawWraps( settings.imp ); final int channel = ( ( Number ) sliderChannel.getValue() ).intValue() - 1; final int frame = settings.imp.getT() - 1; diff --git a/src/main/java/fiji/plugin/trackmate/io/TGMMImporter.java b/src/main/java/fiji/plugin/trackmate/io/TGMMImporter.java index bb02c1803..bd00281ca 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TGMMImporter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TGMMImporter.java @@ -32,12 +32,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.imglib2.algorithm.Benchmark; -import net.imglib2.algorithm.OutputAlgorithm; -import net.imglib2.realtransform.AffineTransform3D; -import net.imglib2.util.LinAlgHelpers; -import net.imglib2.util.Util; - import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; @@ -50,7 +44,13 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotCollection; +import net.imglib2.algorithm.Benchmark; +import net.imglib2.algorithm.OutputAlgorithm; +import net.imglib2.realtransform.AffineTransform3D; +import net.imglib2.util.LinAlgHelpers; +import net.imglib2.util.Util; public class TGMMImporter implements OutputAlgorithm< Model >, Benchmark { @@ -377,7 +377,7 @@ public boolean process() * Make a spot and add it to this frame collection. */ - final Spot spot = new Spot( mx, my, mz, radius, score, lineage + " (" + id + ")" ); + final Spot spot = new SpotBase( mx, my, mz, radius, score, lineage + " (" + id + ")" ); spots.add( spot ); currentSpotID.put( Integer.valueOf( id ), spot ); diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index c1fc80d94..b5ced3626 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -128,6 +128,7 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; @@ -143,8 +144,8 @@ import fiji.plugin.trackmate.gui.wizard.descriptors.ConfigureViewsDescriptor; import fiji.plugin.trackmate.providers.DetectorProvider; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; -import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackerProvider; import fiji.plugin.trackmate.providers.ViewProvider; @@ -215,7 +216,7 @@ public TmXmlReader( final File file ) catch ( final IOException e ) { logger.error( "Problem reading " + file.getName() - + ".\nError message is:\n" + e.getLocalizedMessage() + '\n' ); + + ".\nError message is:\n" + e.getLocalizedMessage() + '\n' ); ok = false; } this.root = r; @@ -938,7 +939,7 @@ private SpotCollection getSpots( final Element modelElement ) { // Matcher for zipped file name. final String regex = "(\\d+)\\.ply"; - final Pattern pattern = Pattern.compile(regex); + final Pattern pattern = Pattern.compile( regex ); // Iterate through entries. try (final ZipFile zipFile = new ZipFile( meshFile )) { @@ -956,8 +957,13 @@ private SpotCollection getSpots( final Element modelElement ) final Mesh m = PLY_MESH_IO.open( zipFile.getInputStream( entry ) ); final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() ); Meshes.calculateNormals( m, mesh ); - final SpotMesh sm = new SpotMesh( mesh ); - spot.setMesh( sm ); + + // Create new spot in the mesh and replace it in the + // cache. + final SpotMesh spotMesh = new SpotMesh( id, mesh ); + spotMesh.copyFeaturesFrom( spot ); + spotMesh.setName( spot.getName() ); + cache.put( id, spotMesh ); } catch ( final IOException e ) { @@ -1194,23 +1200,15 @@ private Spot createSpotFrom( final Element spotEl ) { // Read id. final int ID = readIntAttribute( spotEl, SPOT_ID_ATTRIBUTE_NAME, logger ); - final Spot spot = new Spot( ID ); - +// final List< Attribute > atts = spotEl.getAttributes(); removeAttributeFromName( atts, SPOT_ID_ATTRIBUTE_NAME ); - // Read name. - String name = spotEl.getAttributeValue( SPOT_NAME_ATTRIBUTE_NAME ); - if ( null == name || name.equals( "" ) ) - name = "ID" + ID; - - spot.setName( name ); - removeAttributeFromName( atts, SPOT_NAME_ATTRIBUTE_NAME ); - /* * Try to read ROI if any. */ final int roiNPoints = readIntAttribute( spotEl, ROI_N_POINTS_ATTRIBUTE_NAME, Logger.VOID_LOGGER ); + final Spot spot; if ( roiNPoints > 2 ) { final double[] xrois = new double[ roiNPoints ]; @@ -1225,10 +1223,22 @@ private Spot createSpotFrom( final Element spotEl ) final double y = Double.parseDouble( vals[ index++ ] ); yrois[ i ] = y; } - spot.setRoi( new SpotRoi( xrois, yrois ) ); + spot = new SpotRoi( ID, xrois, yrois ); + } + else + { + spot = new SpotBase( ID ); } removeAttributeFromName( atts, ROI_N_POINTS_ATTRIBUTE_NAME ); + // Read name. + String name = spotEl.getAttributeValue( SPOT_NAME_ATTRIBUTE_NAME ); + if ( null == name || name.equals( "" ) ) + name = "ID" + ID; + + spot.setName( name ); + removeAttributeFromName( atts, SPOT_NAME_ATTRIBUTE_NAME ); + /* * Read all other attributes -> features. */ diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index 20afc104d..0ba5d513b 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -121,6 +121,7 @@ import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.features.edges.EdgeAnalyzer; @@ -735,9 +736,9 @@ private final Element marshalSpot( final Spot spot, final FeatureModel fm ) } final Element spotElement = new Element( SPOT_ELEMENT_KEY ); - final SpotRoi roi = spot.getRoi(); - if ( roi != null ) + if ( spot instanceof SpotRoi ) { + final SpotRoi roi = ( SpotRoi ) spot; final int nPoints = roi.x.length; attributes.add( new Attribute( ROI_N_POINTS_ATTRIBUTE_NAME, Integer.toString( nPoints ) ) ); final StringBuilder str = new StringBuilder(); @@ -760,7 +761,7 @@ protected void writeSpotMeshes( final Iterable< Spot > spots ) boolean hasMesh = false; for ( final Spot spot : spots ) { - if ( spot.getMesh() != null ) + if ( spot instanceof SpotMesh ) { hasMesh = true; break; @@ -784,10 +785,11 @@ protected void writeSpotMeshes( final Iterable< Spot > spots ) // Write spot meshes. for ( final Spot spot : spots ) { - if ( spot.getMesh() != null ) + if ( spot instanceof SpotMesh ) { // Save mesh in true coordinates. - final Mesh mesh = spot.getMesh().mesh; + final SpotMesh sm = ( SpotMesh ) spot; + final Mesh mesh = sm.mesh; final Mesh translated = TranslateMesh.translate( mesh, spot ); final byte[] bs = PLY_MESH_IO.writeBinary( translated ); diff --git a/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java b/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java index ca74f5235..86d9ec124 100644 --- a/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java +++ b/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java @@ -21,11 +21,10 @@ */ package fiji.plugin.trackmate.providers; -import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.features.spot.Spot3DMorphologyAnalyzerFactory; /** - * Provider for 3D morphology analyzers, working on {@link SpotMesh}. + * Provider for 3D morphology analyzers, working on SpotMesh. */ @SuppressWarnings( "rawtypes" ) public class Spot3DMorphologyAnalyzerProvider extends AbstractProvider< Spot3DMorphologyAnalyzerFactory > diff --git a/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java b/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java index 7477b9609..1bee98031 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java @@ -36,6 +36,7 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.tracking.SpotTracker; import fiji.plugin.trackmate.tracking.jaqaman.JaqamanLinker; @@ -236,17 +237,17 @@ public boolean process() { final double[] X = kf.predict(); final Spot s = kalmanFiltersMap.get( kf ); - final Spot predSpot = new Spot( X[ 0 ], X[ 1 ], X[ 2 ], s.getFeature( Spot.RADIUS ), s.getFeature( Spot.QUALITY ) ); + final Spot predSpot = new SpotBase( X[ 0 ], X[ 1 ], X[ 2 ], s.getFeature( Spot.RADIUS ), s.getFeature( Spot.QUALITY ) ); // copy the necessary features of original spot to the predicted // spot if ( null != featurePenalties ) - predSpot.copyFeatures( s, featurePenalties ); + predSpot.copyFeaturesFrom( s, featurePenalties.keySet() ); predictionMap.put( predSpot, kf ); if ( savePredictions ) { - final Spot pred = new Spot( X[ 0 ], X[ 1 ], X[ 2 ], s.getFeature( Spot.RADIUS ), s.getFeature( Spot.QUALITY ) ); + final Spot pred = new SpotBase( X[ 0 ], X[ 1 ], X[ 2 ], s.getFeature( Spot.RADIUS ), s.getFeature( Spot.QUALITY ) ); pred.setName( "Pred_" + s.getName() ); pred.putFeature( Spot.RADIUS, s.getFeature( Spot.RADIUS ) ); predictionsCollection.add( predSpot, frame ); diff --git a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java index 628560a96..79e78e18c 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java @@ -298,19 +298,19 @@ private static SimplePolygon2D toPolygon( final Spot spot, final double scale ) { 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 + if ( spot instanceof SpotRoi ) { + final SpotRoi roi = ( SpotRoi ) spot; final double[] xcoords = roi.toPolygonX( 1., 0., xc, 1. ); final double[] ycoords = roi.toPolygonY( 1., 0., yc, 1. ); poly = new SimplePolygon2D( xcoords, ycoords ); } + else + { + final double radius = spot.getFeature( Spot.RADIUS ).doubleValue(); + poly = new SimplePolygon2D( new Circle2D( xc, yc, radius ).asPolyline( 32 ) ); + } return poly.transform( AffineTransform2D.createScaling( new Point2D( xc, yc ), scale, scale ) ); } @@ -318,20 +318,20 @@ private static Rectangle2D toBoundingBox( final Spot spot, final double scale ) { final double xc = spot.getDoublePosition( 0 ); final double yc = spot.getDoublePosition( 1 ); - final SpotRoi roi = spot.getRoi(); - if ( roi == null ) - { - final double radius = spot.getFeature( Spot.RADIUS ).doubleValue() * scale; - return new Rectangle2D( xc - radius, yc - radius, 2 * radius, 2 * radius ); - } - else + if ( spot instanceof SpotRoi ) { + final SpotRoi roi = ( SpotRoi ) spot; final double minX = Arrays.stream( roi.x ).min().getAsDouble() * scale; final double maxX = Arrays.stream( roi.x ).max().getAsDouble() * scale; final double minY = Arrays.stream( roi.y ).min().getAsDouble() * scale; final double maxY = Arrays.stream( roi.y ).max().getAsDouble() * scale; return new Rectangle2D( xc + minX, yc + minY, maxX - minX, maxY - minY ); } + else + { + final double radius = spot.getFeature( Spot.RADIUS ).doubleValue() * scale; + return new Rectangle2D( xc - radius, yc - radius, 2 * radius, 2 * radius ); + } } private static final class FindBestSourceTask implements Callable< IoULink > diff --git a/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhood.java b/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhood.java index 5efdc98dd..7b17c0346 100644 --- a/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhood.java +++ b/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhood.java @@ -22,10 +22,10 @@ package fiji.plugin.trackmate.util; import fiji.plugin.trackmate.Spot; -import net.imagej.ImgPlus; import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.Positionable; +import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealPositionable; import net.imglib2.algorithm.neighborhood.Neighborhood; @@ -35,6 +35,7 @@ import net.imglib2.algorithm.region.localneighborhood.RectangleNeighborhoodGPL; import net.imglib2.outofbounds.OutOfBoundsMirrorExpWindowingFactory; import net.imglib2.type.numeric.RealType; +import net.imglib2.view.Views; public class SpotNeighborhood< T extends RealType< T > > implements Neighborhood< T > { @@ -53,18 +54,22 @@ public class SpotNeighborhood< T extends RealType< T > > implements Neighborhood * CONSTRUCTOR */ - public SpotNeighborhood( final Spot spot, final ImgPlus< T > img ) + public SpotNeighborhood( final Spot spot, final RandomAccessible< T > ra, final double[] calibration ) { - this.calibration = TMUtils.getSpatialCalibration( img ); - // Center - this.center = new long[ img.numDimensions() ]; + this.calibration = calibration; + // Center, span and interval. + this.center = new long[ ra.numDimensions() ]; + final long[] span = new long[ ra.numDimensions() ]; + final long[] min = new long[ra.numDimensions()]; + final long[] max = new long[ ra.numDimensions() ]; for ( int d = 0; d < center.length; d++ ) - center[ d ] = Math.round( spot.getFeature( Spot.POSITION_FEATURES[ d ] ).doubleValue() / calibration[ d ] ); - - // Span - final long[] span = new long[ img.numDimensions() ]; - for ( int d = 0; d < span.length; d++ ) + { + center[ d ] = Math.round( spot.getDoublePosition( d ) / calibration[ d ] ); span[ d ] = Math.round( spot.getFeature( Spot.RADIUS ) / calibration[ d ] ); + min[d] = center[d] - span[d]; + max[d] = center[d] + span[d]; + } + final FinalInterval interval = new FinalInterval( min, max ); // Neighborhood @@ -74,28 +79,26 @@ public SpotNeighborhood( final Spot spot, final ImgPlus< T > img ) * have to test pedantically. */ + final RandomAccessibleInterval< T > rai = Views.interval( ra, interval ); final OutOfBoundsMirrorExpWindowingFactory< T, RandomAccessibleInterval< T > > oob = new OutOfBoundsMirrorExpWindowingFactory<>(); - if ( img.numDimensions() == 2 && img.dimension( 0 ) < 2 || img.dimension( 1 ) < 2 ) + if ( ra.numDimensions() == 1 ) { - if ( img.dimension( 0 ) < 2 ) - span[ 0 ] = 0; - else - span[ 1 ] = 0; - this.neighborhood = new RectangleNeighborhoodGPL<>( img, oob ); + span[ 0 ] = 0; + this.neighborhood = new RectangleNeighborhoodGPL<>( rai, oob ); neighborhood.setPosition( center ); neighborhood.setSpan( span ); } - else if ( img.numDimensions() == 2 ) + else if ( ra.numDimensions() == 2 ) { - this.neighborhood = new EllipseNeighborhood<>( img, center, span, oob ); + this.neighborhood = new EllipseNeighborhood<>( rai, center, span, oob ); } - else if ( img.numDimensions() == 3 ) + else if ( ra.numDimensions() == 3 ) { - this.neighborhood = new EllipsoidNeighborhood<>( img, center, span, oob ); + this.neighborhood = new EllipsoidNeighborhood<>( rai, center, span, oob ); } else { - throw new IllegalArgumentException( "Source input must be 1D, 2D or 3D, got nDims = " + img.numDimensions() ); + throw new IllegalArgumentException( "Source input must be 1D, 2D or 3D, got nDims = " + ra.numDimensions() ); } } diff --git a/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java b/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java deleted file mode 100644 index 8195b337e..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/SpotUtil.java +++ /dev/null @@ -1,368 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 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.util; - -import java.util.Iterator; - -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotMesh; -import fiji.plugin.trackmate.SpotRoi; -import fiji.plugin.trackmate.SpotShape; -import fiji.plugin.trackmate.detection.DetectionUtils; -import fiji.plugin.trackmate.util.mesh.SpotMeshIterable; -import net.imagej.ImgPlus; -import net.imglib2.Cursor; -import net.imglib2.FinalInterval; -import net.imglib2.Interval; -import net.imglib2.IterableInterval; -import net.imglib2.Localizable; -import net.imglib2.RandomAccess; -import net.imglib2.RandomAccessibleInterval; -import net.imglib2.RealLocalizable; -import net.imglib2.type.numeric.NumericType; -import net.imglib2.type.numeric.RealType; -import net.imglib2.util.Intervals; -import net.imglib2.util.Util; -import net.imglib2.view.IntervalView; -import net.imglib2.view.Views; - -public class SpotUtil -{ - - public static final < T extends RealType< T > > IterableInterval< T > iterable( final SpotShape shape, final RealLocalizable center, final ImgPlus< T > img ) - { - if ( shape instanceof SpotRoi ) - return iterableRoi( ( SpotRoi ) shape, center, img ); - else if ( shape instanceof SpotMesh ) - return iterableMesh( ( SpotMesh ) shape, img ); - else - throw new IllegalArgumentException( "Unsuitable shape for SpotShape: " + shape ); - } - - public static final < T extends RealType< T > > IterableInterval< T > iterableRoi( final SpotRoi roi, final RealLocalizable center, final ImgPlus< T > img ) - { - final SpotRoiIterable< T > neighborhood = new SpotRoiIterable<>( roi, center, img ); - if ( neighborhood.dimension( 0 ) <= 1 && neighborhood.dimension( 1 ) <= 1 ) - return makeSinglePixelIterable( center, img ); - else - return neighborhood; - } - - public static final < T extends RealType< T > > IterableInterval< T > iterable( final Spot spot, final ImgPlus< T > img ) - { - // Prepare neighborhood - final SpotRoi roi = spot.getRoi(); - final SpotMesh mesh = spot.getMesh(); - if ( null != roi && DetectionUtils.is2D( img ) ) - { - // Operate on ROI only if we have one and the image is 2D. - return iterableRoi( roi, spot, img ); - } - else if ( mesh != null ) - { - // Operate on 3D if we have a mesh. - return iterableMesh( mesh, img ); - } - else - { - // Otherwise default to circle / sphere. - final SpotNeighborhood< T > neighborhood = new SpotNeighborhood<>( spot, img ); - - final int npixels = ( int ) neighborhood.size(); - if ( npixels <= 1 ) - return makeSinglePixelIterable( spot, img ); - else - return neighborhood; - } - } - - public static < T extends NumericType< T > > IterableInterval< T > iterableMesh( final SpotMesh sm, final ImgPlus< T > img ) - { - return new SpotMeshIterable< T >( - Views.extendZero( img ), - sm, - TMUtils.getSpatialCalibration( img ) ); - } - - public static < T extends NumericType< T > > IterableInterval< T > iterableMesh( final SpotMesh sm, final RandomAccessibleInterval< T > img, final double[] calibration ) - { - return new SpotMeshIterable< T >( - Views.extendZero( img ), - sm, - calibration ); - } - - private static < T > IterableInterval< T > makeSinglePixelIterable( final RealLocalizable center, final ImgPlus< T > img ) - { - final double[] calibration = TMUtils.getSpatialCalibration( img ); - final long[] min = new long[ img.numDimensions() ]; - final long[] max = new long[ img.numDimensions() ]; - for ( int d = 0; d < min.length; d++ ) - { - final long cx = Math.round( center.getDoublePosition( d ) / calibration[ d ] ); - min[ d ] = cx; - max[ d ] = cx + 1; - } - - final Interval interval = new FinalInterval( min, max ); - return Views.interval( img, interval ); - } - - private static final class SpotRoiIterable< T extends RealType< T > > implements IterableInterval< T > - { - - private final SpotRoi roi; - - private final RealLocalizable center; - - private final ImgPlus< T > img; - - private final FinalInterval interval; - - public SpotRoiIterable( final SpotRoi roi, final RealLocalizable center, final ImgPlus< T > img ) - { - this.roi = roi; - this.center = center; - this.img = img; - final double[] x = roi.toPolygonX( img.averageScale( 0 ), 0, center.getDoublePosition( 0 ), 1. ); - final double[] y = roi.toPolygonX( img.averageScale( 1 ), 0, center.getDoublePosition( 1 ), 1. ); - final long minX = ( long ) Math.floor( Util.min( x ) ); - final long maxX = ( long ) Math.ceil( Util.max( x ) ); - final long minY = ( long ) Math.floor( Util.min( y ) ); - final long maxY = ( long ) Math.ceil( Util.max( y ) ); - interval = Intervals.createMinMax( minX, minY, maxX, maxY ); - } - - @Override - public long size() - { - int n = 0; - final Cursor< T > cursor = cursor(); - while ( cursor.hasNext() ) - { - cursor.fwd(); - n++; - } - return n; - } - - @Override - public T firstElement() - { - return cursor().next(); - } - - @Override - public Object iterationOrder() - { - return this; - } - - @Override - public double realMin( final int d ) - { - return interval.realMin( d ); - } - - @Override - public double realMax( final int d ) - { - return interval.realMax( d ); - } - - @Override - public int numDimensions() - { - return 2; - } - - @Override - public long min( final int d ) - { - return interval.min( d ); - } - - @Override - public long max( final int d ) - { - return interval.max( d ); - } - - @Override - public Cursor< T > cursor() - { - return new MyCursor< T >( roi, center, img ); - } - - @Override - public Cursor< T > localizingCursor() - { - return cursor(); - } - - @Override - public Iterator< T > iterator() - { - return cursor(); - } - } - - private static final class MyCursor< T extends RealType< T > > implements Cursor< T > - { - - private final SpotRoi roi; - - private final RealLocalizable center; - - private final ImgPlus< T > img; - - private final FinalInterval interval; - - private Cursor< T > cursor; - - private final double[] x; - - private final double[] y; - - private boolean hasNext; - - private RandomAccess< T > ra; - - public MyCursor( final SpotRoi roi, final RealLocalizable center, final ImgPlus< T > img ) - { - this.roi = roi; - this.center = center; - this.img = img; - x = roi.toPolygonX( img.averageScale( 0 ), 0, center.getDoublePosition( 0 ), 1. ); - y = roi.toPolygonY( img.averageScale( 1 ), 0, center.getDoublePosition( 1 ), 1. ); - final long minX = ( long ) Math.floor( Util.min( x ) ); - final long maxX = ( long ) Math.ceil( Util.max( x ) ); - final long minY = ( long ) Math.floor( Util.min( y ) ); - final long maxY = ( long ) Math.ceil( Util.max( y ) ); - interval = Intervals.createMinMax( minX, minY, maxX, maxY ); - reset(); - } - - @Override - public T get() - { - return ra.get(); - } - - @Override - public void fwd() - { - ra.setPosition( cursor ); - fetch(); - } - - private void fetch() - { - while ( cursor.hasNext() ) - { - cursor.fwd(); - if ( isInside( cursor, x, y ) ) - { - hasNext = cursor.hasNext(); - return; - } - } - hasNext = false; - } - - private static final boolean isInside( final Localizable localizable, final double[] x, final double[] y ) - { - // Taken from Imglib2-roi GeomMaths. No edge case. - final double xl = localizable.getDoublePosition( 0 ); - final double yl = localizable.getDoublePosition( 1 ); - - int i; - int j; - boolean inside = false; - for ( i = 0, j = x.length - 1; i < x.length; j = i++ ) - { - final double xj = x[ j ]; - final double yj = y[ j ]; - - final double xi = x[ i ]; - final double yi = y[ i ]; - - if ( ( yi > yl ) != ( yj > yl ) && ( xl < ( xj - xi ) * ( yl - yi ) / ( yj - yi ) + xi ) ) - inside = !inside; - } - return inside; - } - - @Override - public void reset() - { - final IntervalView< T > view = Views.interval( img, interval ); - cursor = view.localizingCursor(); - ra = Views.extendMirrorSingle( img ).randomAccess(); - fetch(); - } - - @Override - public double getDoublePosition( final int d ) - { - return ra.getDoublePosition( d ); - } - - @Override - public int numDimensions() - { - return 2; - } - - @Override - public void jumpFwd( final long steps ) - { - for ( int i = 0; i < steps; i++ ) - fwd(); - } - - @Override - public boolean hasNext() - { - return hasNext; - } - - @Override - public T next() - { - fwd(); - return get(); - } - - @Override - public long getLongPosition( final int d ) - { - return ra.getLongPosition( d ); - } - - @Override - public Cursor< T > copy() - { - return new MyCursor<>( roi, center, img ); - } - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 87031943a..af1cc0e7d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -21,7 +21,7 @@ * @author Jean-Yves Tinevez * */ -public class PaintSpotMesh extends TrackMatePainter +public class PaintSpotMesh extends TrackMatePainter< SpotMesh > { private final Path2D.Double polygon; @@ -37,11 +37,9 @@ public PaintSpotMesh( final ImagePlus imp, final double[] calibration, final Dis } @Override - public int paint( final Graphics2D g2d, final Spot spot ) + public int paint( final Graphics2D g2d, final SpotMesh spot ) { - final SpotMesh sm = spot.getMesh(); - - if ( !intersect( sm.boundingBox, spot ) ) + if ( !intersect( spot.boundingBox, spot ) ) return -1; // Z plane does not cross bounding box. @@ -52,7 +50,7 @@ public int paint( final Graphics2D g2d, final Spot spot ) final double z = spot.getFeature( Spot.POSITION_Z ); final int zSlice = imp.getSlice() - 1; final double dz = zSlice * calibration[ 2 ]; - if ( sm.boundingBox.realMin( 2 ) + z > dz || sm.boundingBox.realMax( 2 ) + z < dz ) + if ( spot.boundingBox.realMin( 2 ) + z > dz || spot.boundingBox.realMax( 2 ) + z < dz ) { paintOutOfFocus( g2d, xs, ys ); return -1; @@ -60,7 +58,7 @@ public int paint( final Graphics2D g2d, final Spot spot ) // Convert to AWT shape. Only work in non-pathological cases, and // because contours are sorted by decreasing area. - final Slice slice = sm.getZSlice( zSlice, calibration[ 0 ], calibration[ 2 ] ); + final Slice slice = spot.getZSlice( zSlice, calibration[ 0 ], calibration[ 2 ] ); if ( slice == null ) { paintOutOfFocus( g2d, xs, ys ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java index 4a091e91d..37d6d617b 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java @@ -5,7 +5,6 @@ import java.awt.geom.Path2D; import java.util.function.DoubleUnaryOperator; -import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import gnu.trove.list.TDoubleList; @@ -19,7 +18,7 @@ * @author Jean-Yves Tinevez * */ -public class PaintSpotRoi extends TrackMatePainter +public class PaintSpotRoi extends TrackMatePainter< SpotRoi > { private final java.awt.geom.Path2D.Double polygon; @@ -42,9 +41,9 @@ public PaintSpotRoi( final ImagePlus imp, final double[] calibration, final Disp * next to the painted contour. */ @Override - public int paint( final Graphics2D g2d, final Spot spot ) + public int paint( final Graphics2D g2d, final SpotRoi spot ) { - if ( !intersect( boundingBox( spot.getRoi() ), spot ) ) + if ( !intersect( boundingBox( spot ), spot ) ) return -1; final double maxTextPos = toPolygon( spot, polygon, this::toScreenX, this::toScreenY ); @@ -114,21 +113,20 @@ static final double max( final TDoubleList l ) * screen coordinates. * @return the max X position in screen units of this shape. */ - private static final double toPolygon( final Spot spot, final Path2D polygon, final DoubleUnaryOperator toScreenX, final DoubleUnaryOperator toScreenY ) + private static final double toPolygon( final SpotRoi roi, final Path2D polygon, final DoubleUnaryOperator toScreenX, final DoubleUnaryOperator toScreenY ) { - final SpotRoi roi = spot.getRoi(); double maxTextPos = Double.NEGATIVE_INFINITY; polygon.reset(); - final double x0 = toScreenX.applyAsDouble( roi.x[ 0 ] + spot.getDoublePosition( 0 ) ); - final double y0 = toScreenY.applyAsDouble( roi.y[ 0 ] + spot.getDoublePosition( 1 ) ); + final double x0 = toScreenX.applyAsDouble( roi.x[ 0 ] + roi.getDoublePosition( 0 ) ); + final double y0 = toScreenY.applyAsDouble( roi.y[ 0 ] + roi.getDoublePosition( 1 ) ); polygon.moveTo( x0, y0 ); if ( x0 > maxTextPos ) maxTextPos = x0; for ( int i = 1; i < roi.x.length; i++ ) { - final double xi = toScreenX.applyAsDouble( roi.x[ i ] + spot.getDoublePosition( 0 ) ); - final double yi = toScreenY.applyAsDouble( roi.y[ i ] + spot.getDoublePosition( 1 ) ); + final double xi = toScreenX.applyAsDouble( roi.x[ i ] + roi.getDoublePosition( 0 ) ); + final double yi = toScreenY.applyAsDouble( roi.y[ i ] + roi.getDoublePosition( 1 ) ); polygon.lineTo( xi, yi ); if ( xi > maxTextPos ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java index 77b32e3b7..38b5e5bff 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java @@ -3,6 +3,7 @@ import java.awt.Graphics2D; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; import net.imglib2.RealInterval; @@ -14,7 +15,7 @@ * @author Jean-Yves Tinevez * */ -public class PaintSpotSphere extends TrackMatePainter +public class PaintSpotSphere extends TrackMatePainter< SpotBase > { public PaintSpotSphere( final ImagePlus imp, final double[] calibration, final DisplaySettings displaySettings ) @@ -23,7 +24,7 @@ public PaintSpotSphere( final ImagePlus imp, final double[] calibration, final D } @Override - public int paint( final Graphics2D g2d, final Spot spot ) + public int paint( final Graphics2D g2d, final SpotBase spot ) { if ( !intersect( boundingBox( spot ), spot ) ) return -1; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java index da8e0c468..a97769a00 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -263,7 +263,9 @@ protected void drawSpot( final Graphics2D g2d, final Spot spot, final double zsl final double ys = ( yp - ycorner ) * magnification; // Get a painter adequate for the spot and config we have. + @SuppressWarnings( "rawtypes" ) final TrackMatePainter painter = getPainter( spot ); + @SuppressWarnings( "unchecked" ) final int textPos = painter.paint( g2d, spot ); if ( textPos >= 0 && displaySettings.isSpotShowName() ) @@ -273,18 +275,18 @@ protected void drawSpot( final Graphics2D g2d, final Spot spot, final double zsl } } - private TrackMatePainter getPainter( final Spot spot ) + private TrackMatePainter< ? extends Spot > getPainter( final Spot spot ) { - final SpotRoi roi = spot.getRoi(); - final SpotMesh mesh = spot.getMesh(); - - if ( !displaySettings.isSpotDisplayedAsRoi() || ( mesh == null && roi == null ) ) + if ( !displaySettings.isSpotDisplayedAsRoi() ) return paintSpotSphere; - if ( roi != null ) + if ( spot instanceof SpotRoi ) return paintSpotRoi; - return paintSpotMesh; + if ( spot instanceof SpotMesh ) + return paintSpotMesh; + + return paintSpotSphere; } private static final void drawString( diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java index 3d96f1ef1..55f0273ae 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java @@ -9,7 +9,7 @@ import net.imglib2.RealInterval; import net.imglib2.RealLocalizable; -public abstract class TrackMatePainter +public abstract class TrackMatePainter< T extends Spot > { protected final double[] calibration; @@ -25,7 +25,7 @@ public TrackMatePainter( final ImagePlus imp, final double[] calibration, final this.displaySettings = displaySettings; } - public abstract int paint( final Graphics2D g2d, final Spot spot ); + public abstract int paint( final Graphics2D g2d, final T spot ); /** * Returns true if the specified bounding-box, shifted by the diff --git a/src/test/java/fiji/plugin/trackmate/ModelTest.java b/src/test/java/fiji/plugin/trackmate/ModelTest.java index b7ee8b6bd..dd2a85f4a 100644 --- a/src/test/java/fiji/plugin/trackmate/ModelTest.java +++ b/src/test/java/fiji/plugin/trackmate/ModelTest.java @@ -42,17 +42,17 @@ public class ModelTest { public void testTrackVisibility() { final Model model = new Model(); // Build track 1 with 5 spots - final Spot s1 = new Spot( 0d, 0d, 0d, 1d, -1d, "S1" ); - final Spot s2 = new Spot( 0d, 0d, 0d, 1d, -1d, "S2" ); - final Spot s3 = new Spot( 0d, 0d, 0d, 1d, -1d, "S3" ); - final Spot s4 = new Spot( 0d, 0d, 0d, 1d, -1d, "S4" ); - final Spot s5 = new Spot( 0d, 0d, 0d, 1d, -1d, "S5" ); + final Spot s1 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S1" ); + final Spot s2 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S2" ); + final Spot s3 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S3" ); + final Spot s4 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S4" ); + final Spot s5 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S5" ); // Build track 2 with 2 spots - final Spot s6 = new Spot( 0d, 0d, 0d, 1d, -1d, "S6" ); - final Spot s7 = new Spot( 0d, 0d, 0d, 1d, -1d, "S7" ); + final Spot s6 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S6" ); + final Spot s7 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S7" ); // Build track 3 with 2 spots - final Spot s8 = new Spot( 0d, 0d, 0d, 1d, -1d, "S8" ); - final Spot s9 = new Spot( 0d, 0d, 0d, 1d, -1d, "S9" ); + final Spot s8 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S8" ); + final Spot s9 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S9" ); model.beginUpdate(); try { @@ -161,11 +161,11 @@ public void testTrackNumber() { assertEquals(0, model.getTrackModel().nTracks(false)); // Build track with 5 spots - final Spot s1 = new Spot( 0d, 0d, 0d, 1d, -1d, "S1" ); - final Spot s2 = new Spot( 0d, 0d, 0d, 1d, -1d, "S2" ); - final Spot s3 = new Spot( 0d, 0d, 0d, 1d, -1d, "S3" ); - final Spot s4 = new Spot( 0d, 0d, 0d, 1d, -1d, "S4" ); - final Spot s5 = new Spot( 0d, 0d, 0d, 1d, -1d, "S5" ); + final Spot s1 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S1" ); + final Spot s2 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S2" ); + final Spot s3 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S3" ); + final Spot s4 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S4" ); + final Spot s5 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S5" ); model.beginUpdate(); try { model.addSpotTo(s1, 0); @@ -242,11 +242,11 @@ public void modelChanged(final ModelChangeEvent event) { model.addModelChangeListener(eventLogger); - final Spot s1 = new Spot( 0d, 0d, 0d, 1d, -1d, "S1" ); - final Spot s2 = new Spot( 0d, 0d, 0d, 1d, -1d, "S2" ); - final Spot s3 = new Spot( 0d, 0d, 0d, 1d, -1d, "S3" ); - final Spot s4 = new Spot( 0d, 0d, 0d, 1d, -1d, "S4" ); - final Spot s5 = new Spot( 0d, 0d, 0d, 1d, -1d, "S5" ); + final Spot s1 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S1" ); + final Spot s2 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S2" ); + final Spot s3 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S3" ); + final Spot s4 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S4" ); + final Spot s5 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S5" ); // System.out.println("Create the graph in one update:"); model.beginUpdate(); @@ -381,7 +381,7 @@ public void testRemovingWholeTracksAtOnce() { Spot previous = null; Spot spot = null; for (int j = 0; j < DEPTH; j++) { - spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpotTo(spot, j); if (i == 0) { trackSpots.add(spot); @@ -444,11 +444,11 @@ public void exampleManipulation() { // Add an event listener now model.addModelChangeListener(new EventLogger()); - final Spot s1 = new Spot( 0d, 0d, 0d, 1d, -1d, "S1" ); - final Spot s2 = new Spot( 0d, 0d, 0d, 1d, -1d, "S2" ); - final Spot s3 = new Spot( 0d, 0d, 0d, 1d, -1d, "S3" ); - final Spot s4 = new Spot( 0d, 0d, 0d, 1d, -1d, "S4" ); - final Spot s5 = new Spot( 0d, 0d, 0d, 1d, -1d, "S5" ); + final Spot s1 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S1" ); + final Spot s2 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S2" ); + final Spot s3 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S3" ); + final Spot s4 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S4" ); + final Spot s5 = new SpotBase( 0d, 0d, 0d, 1d, -1d, "S5" ); System.out.println("Create the graph in one update:"); model.beginUpdate(); diff --git a/src/test/java/fiji/plugin/trackmate/SpotCollectionTest.java b/src/test/java/fiji/plugin/trackmate/SpotCollectionTest.java index 0e7fce526..4acdb0aff 100644 --- a/src/test/java/fiji/plugin/trackmate/SpotCollectionTest.java +++ b/src/test/java/fiji/plugin/trackmate/SpotCollectionTest.java @@ -70,7 +70,7 @@ public void setUp() throws Exception final HashSet< Spot > spots = new HashSet<>( 100 ); for ( int j = 0; j < N_SPOTS; j++ ) { - final Spot spot = new Spot( j, j, j, 1d, -1d ); + final Spot spot = new SpotBase( j, j, j, 1d, -1d ); spot.putFeature( Spot.POSITION_T, Double.valueOf( i ) ); spot.putFeature( Spot.QUALITY, Double.valueOf( j ) ); spot.putFeature( Spot.RADIUS, Double.valueOf( j / 2 ) ); @@ -101,7 +101,7 @@ public void testAdd() } // Add a spot to target frame final int targetFrame = 1 + 2 * new Random().nextInt( 50 ); - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); sc.add( spot, targetFrame ); // Test for ( final Integer frame : frames ) @@ -221,7 +221,7 @@ public void testGetClosestSpot() final FeatureFilter filter = new FeatureFilter( Spot.QUALITY, 20d, false ); sc.filter( filter ); - final Spot location = new Spot( 50.1, 50.1, 50.1, 1d, -1d ); + final Spot location = new SpotBase( 50.1, 50.1, 50.1, 1d, -1d ); for ( final Integer frame : frames ) { // Closest non-visible spot should be the one with QUALITY = 50 @@ -240,8 +240,8 @@ public void testGetSpotAt() final FeatureFilter filter = new FeatureFilter( Spot.QUALITY, 20d, false ); sc.filter( filter ); - final Spot location1 = new Spot( 50.1, 50.1, 50.1, 1d, -1d ); - final Spot location2 = new Spot( 10.1, 10.1, 10.1, 1d, -1d ); + final Spot location1 = new SpotBase( 50.1, 50.1, 50.1, 1d, -1d ); + final Spot location2 = new SpotBase( 10.1, 10.1, 10.1, 1d, -1d ); for ( final Integer frame : frames ) { // The closest non-visible spot should be the one with QUALITY = 50 @@ -391,7 +391,7 @@ public void testPut() final HashSet< Spot > spots = new HashSet<>( N_SPOTS_TO_ADD ); for ( int i = 0; i < N_SPOTS_TO_ADD; i++ ) { - spots.add( new Spot( -1d, -1d, -1d, 1d, -1d ) ); + spots.add( new SpotBase( -1d, -1d, -1d, 1d, -1d ) ); } // Add it to a new frame int targetFrame = 1000; @@ -435,7 +435,7 @@ public void testFirstKey() final HashSet< Spot > spots = new HashSet<>( N_SPOTS_TO_ADD ); for ( int i = 0; i < N_SPOTS_TO_ADD; i++ ) { - spots.add( new Spot( -1d, -1d, -1d, 1d, -1d ) ); + spots.add( new SpotBase( -1d, -1d, -1d, 1d, -1d ) ); } // Add it to a new frame final int targetFrame = -1; @@ -456,7 +456,7 @@ public void testLastKey() final HashSet< Spot > spots = new HashSet<>( N_SPOTS_TO_ADD ); for ( int i = 0; i < N_SPOTS_TO_ADD; i++ ) { - spots.add( new Spot( -1d, -1d, -1d, 1d, -1d ) ); + spots.add( new SpotBase( -1d, -1d, -1d, 1d, -1d ) ); } // Add it to a new frame final int targetFrame = 1000; diff --git a/src/test/java/fiji/plugin/trackmate/TrackModelTest.java b/src/test/java/fiji/plugin/trackmate/TrackModelTest.java index 5731fb74b..52a1e1740 100644 --- a/src/test/java/fiji/plugin/trackmate/TrackModelTest.java +++ b/src/test/java/fiji/plugin/trackmate/TrackModelTest.java @@ -49,7 +49,7 @@ public void testBuildingTracks() Spot previous = null; for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpot( spot ); if ( null != previous ) { @@ -83,7 +83,7 @@ public void testConnectingTracks() Spot spot = null; for ( int j = 0; j < DEPTH; j++ ) { - spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpot( spot ); if ( null != previous ) { @@ -125,7 +125,7 @@ public void testBreakingTracksBySpots() { for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpot( spot ); if ( null != previous ) { @@ -158,14 +158,14 @@ public void testBreakingTracksByEdges() // Build 1 long track final TrackModel model = new TrackModel(); final List< DefaultWeightedEdge > trackBreaks = new ArrayList<>(); - Spot previous = new Spot( 0d, 0d, 0d, 1d, -1d ); + Spot previous = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpot( previous ); for ( int i = 0; i < N_TRACKS; i++ ) { DefaultWeightedEdge edge = null; for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpot( spot ); edge = model.addEdge( previous, spot, 1 ); previous = spot; @@ -198,7 +198,7 @@ public void testVisibility() Spot previous = null; for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpot( spot ); if ( null != previous ) { @@ -245,7 +245,7 @@ public void testVisibilityMerge() Spot previous = null; for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpot( spot ); if ( null != previous ) { diff --git a/src/test/java/fiji/plugin/trackmate/action/CloseGapsByLinearInterpolationActionTest.java b/src/test/java/fiji/plugin/trackmate/action/CloseGapsByLinearInterpolationActionTest.java index 795e3cd27..cd223f3f8 100644 --- a/src/test/java/fiji/plugin/trackmate/action/CloseGapsByLinearInterpolationActionTest.java +++ b/src/test/java/fiji/plugin/trackmate/action/CloseGapsByLinearInterpolationActionTest.java @@ -29,6 +29,7 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.TrackModel; import fiji.plugin.trackmate.action.closegaps.CloseGapsByLinearInterpolation; @@ -180,8 +181,7 @@ private void checkPositions( final GraphIterator< Spot, DefaultWeightedEdge > sp private Spot createSpot( final double x, final double y, final double z ) { - final Spot newSpot = new Spot( x, y, z, 1.0, 1.0 ); - + final Spot newSpot = new SpotBase( x, y, z, 1.0, 1.0 ); newSpot.getFeatures().put( Spot.POSITION_T, 1.0 ); return newSpot; } diff --git a/src/test/java/fiji/plugin/trackmate/detection/HessianDetectorTestDrive1.java b/src/test/java/fiji/plugin/trackmate/detection/HessianDetectorTestDrive1.java index 4a954868c..f546832c1 100644 --- a/src/test/java/fiji/plugin/trackmate/detection/HessianDetectorTestDrive1.java +++ b/src/test/java/fiji/plugin/trackmate/detection/HessianDetectorTestDrive1.java @@ -55,7 +55,6 @@ public static < T extends RealType< T > & NativeType< T > > void main( final Str final ImagePlus imp = IJ.openImage( "samples/TSabateCell.tif" ); imp.show(); - @SuppressWarnings( "unchecked" ) final ImgPlus< T > input = TMUtils.rawWraps( imp ); final double[] calibration = TMUtils.getSpatialCalibration( imp ); final double radiusXY = 0.6 / 2.; // um; diff --git a/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTargetAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTargetAnalyzerTest.java index b41f66811..3775ae727 100644 --- a/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTargetAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTargetAnalyzerTest.java @@ -23,11 +23,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.ModelChangeEvent; -import fiji.plugin.trackmate.ModelChangeListener; -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.features.edges.EdgeTargetAnalyzer; import java.util.Collection; import java.util.HashMap; @@ -37,6 +32,13 @@ import org.junit.Before; import org.junit.Test; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.ModelChangeListener; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.features.edges.EdgeTargetAnalyzer; + public class EdgeTargetAnalyzerTest { @@ -73,7 +75,7 @@ public void setUp() for ( int j = 0; j <= DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpotTo( spot, j ); if ( null != previous ) { diff --git a/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTimeAndLocationAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTimeAndLocationAnalyzerTest.java index 500ae7464..a1bfe32c3 100644 --- a/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTimeAndLocationAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTimeAndLocationAnalyzerTest.java @@ -23,12 +23,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import fiji.plugin.trackmate.Dimension; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.ModelChangeEvent; -import fiji.plugin.trackmate.ModelChangeListener; -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.features.edges.EdgeTimeLocationAnalyzer; import java.util.Collection; import java.util.HashMap; @@ -39,6 +33,14 @@ import org.junit.Before; import org.junit.Test; +import fiji.plugin.trackmate.Dimension; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.ModelChangeListener; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.features.edges.EdgeTimeLocationAnalyzer; + public class EdgeTimeAndLocationAnalyzerTest { @@ -75,7 +77,7 @@ public void setUp() for ( int j = 0; j <= DEPTH; j++ ) { - final Spot spot = new Spot( i + j, i + j, i + j, 1d, -1d ); + final Spot spot = new SpotBase( i + j, i + j, i + j, 1d, -1d ); spot.putFeature( Spot.POSITION_T, Double.valueOf( j ) ); model.addSpotTo( spot, j ); if ( null != previous ) diff --git a/src/test/java/fiji/plugin/trackmate/features/edge/EdgeVelocityAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/edge/EdgeVelocityAnalyzerTest.java index 4b70968b2..0d313108a 100644 --- a/src/test/java/fiji/plugin/trackmate/features/edge/EdgeVelocityAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/edge/EdgeVelocityAnalyzerTest.java @@ -23,11 +23,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.ModelChangeEvent; -import fiji.plugin.trackmate.ModelChangeListener; -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.features.edges.EdgeSpeedAnalyzer; import java.util.Collection; import java.util.HashMap; @@ -37,6 +32,13 @@ import org.junit.Before; import org.junit.Test; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.ModelChangeListener; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.features.edges.EdgeSpeedAnalyzer; + public class EdgeVelocityAnalyzerTest { @@ -75,10 +77,9 @@ public void setUp() for ( int j = 0; j <= DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); - spot.putFeature( posFeats[ i % 3 ], Double.valueOf( i + j ) ); // rotate - // displacement - // dimension + final Spot spot = new SpotBase( 0., 0., 0., 1., -1. ); + spot.putFeature( posFeats[ i % 3 ], Double.valueOf( i + j ) ); + // rotate displacement dimension spot.putFeature( Spot.POSITION_T, Double.valueOf( 2 * j ) ); model.addSpotTo( spot, j ); if ( null != previous ) diff --git a/src/test/java/fiji/plugin/trackmate/features/spot/SpotIntensityAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/spot/SpotIntensityAnalyzerTest.java index 9556f6380..f92194793 100644 --- a/src/test/java/fiji/plugin/trackmate/features/spot/SpotIntensityAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/spot/SpotIntensityAnalyzerTest.java @@ -27,10 +27,11 @@ import org.junit.Test; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.util.SpotNeighborhood; +import fiji.plugin.trackmate.SpotBase; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.axis.AxisType; +import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.img.Img; import net.imglib2.img.array.ArrayImgs; @@ -75,7 +76,7 @@ public void setUp() throws Exception } - spot = new Spot( CENTER[ 0 ], CENTER[ 1 ], CENTER[ 2 ], RADIUS, -1d, "1" ); + spot = new SpotBase( CENTER[ 0 ], CENTER[ 1 ], CENTER[ 2 ], RADIUS, -1d, "1" ); } @Test @@ -97,16 +98,12 @@ public static void main( final String[] args ) throws Exception final SpotIntensityAnalyzerTest test = new SpotIntensityAnalyzerTest(); test.setUp(); - final Spot tmpSpot = new Spot( CENTER[ 0 ], CENTER[ 1 ], CENTER[ 2 ], RADIUS, -1d ); - final SpotNeighborhood< UnsignedShortType > disc = new SpotNeighborhood<>( tmpSpot, test.img2D ); + final Spot tmpSpot = new SpotBase( CENTER[ 0 ], CENTER[ 1 ], CENTER[ 2 ], RADIUS, -1d ); + final IterableInterval< UnsignedShortType > disc = tmpSpot.iterable( test.img2D ); for ( final UnsignedShortType pixel : disc ) - { pixel.set( 1500 ); - } ij.ImageJ.main( args ); net.imglib2.img.display.imagej.ImageJFunctions.show( test.img2D ); - } - } diff --git a/src/test/java/fiji/plugin/trackmate/features/track/TrackBranchingAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/track/TrackBranchingAnalyzerTest.java index 86d2f6cea..d55ffa590 100644 --- a/src/test/java/fiji/plugin/trackmate/features/track/TrackBranchingAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/track/TrackBranchingAnalyzerTest.java @@ -37,6 +37,7 @@ import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.ModelChangeListener; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; public class TrackBranchingAnalyzerTest { @@ -74,7 +75,7 @@ public void setUp() Spot previous = null; for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpotTo( spot, j ); if ( null != previous ) { @@ -93,7 +94,7 @@ public void setUp() { continue; } - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpotTo( spot, j ); if ( null != previous ) { @@ -109,7 +110,7 @@ public void setUp() split = null; // Store the spot at the branch split for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); if ( j == DEPTH / 2 ) { split = spot; @@ -129,7 +130,7 @@ public void setUp() previous = split; for ( int j = DEPTH / 2 + 1; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpotTo( spot, j ); model.addEdge( previous, spot, 1 ); previous = spot; @@ -143,7 +144,7 @@ public void setUp() Spot merge = null; for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); if ( j == DEPTH / 2 ) { merge = spot; @@ -158,7 +159,7 @@ public void setUp() previous = null; for ( int j = 0; j < DEPTH / 2; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpotTo( spot, j ); if ( null != previous ) { @@ -233,8 +234,8 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { - final Spot spot1 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 0 ); - final Spot spot2 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 1 ); + final Spot spot1 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 0 ); + final Spot spot2 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 1 ); model.addEdge( spot1, spot2, 1 ); } @@ -269,7 +270,7 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { - newSpot = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), firstFrame + 1 ); + newSpot = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), firstFrame + 1 ); model.addEdge( lFirstSpot, newSpot, 1 ); } finally diff --git a/src/test/java/fiji/plugin/trackmate/features/track/TrackDurationAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/track/TrackDurationAnalyzerTest.java index 07ed099fe..0669806d0 100644 --- a/src/test/java/fiji/plugin/trackmate/features/track/TrackDurationAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/track/TrackDurationAnalyzerTest.java @@ -24,10 +24,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.ModelChangeEvent; -import fiji.plugin.trackmate.ModelChangeListener; -import fiji.plugin.trackmate.Spot; import java.util.Collection; import java.util.HashMap; @@ -39,6 +35,12 @@ import org.junit.Before; import org.junit.Test; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.ModelChangeListener; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; + public class TrackDurationAnalyzerTest { @@ -86,7 +88,7 @@ public void setUp() final HashSet< Spot > track = new HashSet<>(); for ( int j = start; j <= stop; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); spot.putFeature( Spot.POSITION_T, Double.valueOf( j ) ); model.addSpotTo( spot, j ); track.add( spot ); @@ -161,9 +163,9 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { - final Spot spot1 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 0 ); + final Spot spot1 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 0 ); spot1.putFeature( Spot.POSITION_T, 0d ); - final Spot spot2 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 1 ); + final Spot spot2 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 1 ); spot2.putFeature( Spot.POSITION_T, 1d ); model.addEdge( spot1, spot2, 1 ); @@ -201,7 +203,7 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { - newSpot = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), firstFrame + 1 ); + newSpot = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), firstFrame + 1 ); newSpot.putFeature( Spot.POSITION_T, Double.valueOf( firstFrame + 1 ) ); model.addEdge( firstSpot, newSpot, 1 ); } diff --git a/src/test/java/fiji/plugin/trackmate/features/track/TrackIndexAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/track/TrackIndexAnalyzerTest.java index 096b887a1..42147997c 100644 --- a/src/test/java/fiji/plugin/trackmate/features/track/TrackIndexAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/track/TrackIndexAnalyzerTest.java @@ -39,6 +39,7 @@ import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.ModelChangeListener; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; /** * @author Jean-Yves Tinevez @@ -65,7 +66,7 @@ public void setUp() Spot previous = null; for ( int j = 0; j < DEPTH; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); model.addSpotTo( spot, j ); if ( null != previous ) { @@ -151,7 +152,7 @@ public void modelChanged( final ModelChangeEvent event ) try { final Spot targetSpot = model.getSpots().iterator( 0, true ).next(); - final Spot newSpot = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 1 ); + final Spot newSpot = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 1 ); model.addEdge( targetSpot, newSpot, 1 ); } finally diff --git a/src/test/java/fiji/plugin/trackmate/features/track/TrackLocationAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/track/TrackLocationAnalyzerTest.java index 4111c4696..a8867811d 100644 --- a/src/test/java/fiji/plugin/trackmate/features/track/TrackLocationAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/track/TrackLocationAnalyzerTest.java @@ -24,10 +24,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.ModelChangeEvent; -import fiji.plugin.trackmate.ModelChangeListener; -import fiji.plugin.trackmate.Spot; import java.util.Collection; import java.util.HashMap; @@ -38,6 +34,12 @@ import org.junit.Before; import org.junit.Test; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.ModelChangeListener; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; + public class TrackLocationAnalyzerTest { @@ -73,7 +75,7 @@ public void setUp() for ( int j = 0; j <= DEPTH; j++ ) { // We use deterministic locations - final Spot spot = new Spot( j + i, j + i, j + i, 1d, -1d ); + final Spot spot = new SpotBase( j + i, j + i, j + i, 1d, -1d ); model.addSpotTo( spot, j ); track.add( spot ); if ( null != previous ) @@ -144,9 +146,9 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { - final Spot spot1 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 0 ); + final Spot spot1 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 0 ); spot1.putFeature( Spot.POSITION_T, 0d ); - final Spot spot2 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 1 ); + final Spot spot2 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 1 ); spot2.putFeature( Spot.POSITION_T, 1d ); model.addEdge( spot1, spot2, 1 ); diff --git a/src/test/java/fiji/plugin/trackmate/features/track/TrackSpeedStatisticsAnalyzerTest.java b/src/test/java/fiji/plugin/trackmate/features/track/TrackSpeedStatisticsAnalyzerTest.java index 10b04a7a3..3d23a5ac6 100644 --- a/src/test/java/fiji/plugin/trackmate/features/track/TrackSpeedStatisticsAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/track/TrackSpeedStatisticsAnalyzerTest.java @@ -38,6 +38,7 @@ import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.ModelChangeListener; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; public class TrackSpeedStatisticsAnalyzerTest { @@ -71,7 +72,7 @@ public void setUp() for ( int j = 0; j <= DEPTH; j++ ) { // We use deterministic locations - final Spot spot = new Spot( j * i, i, i, 1d, -1d ); + final Spot spot = new SpotBase( j * i, i, i, 1d, -1d ); spot.putFeature( Spot.POSITION_T, Double.valueOf( j ) ); model.addSpotTo( spot, j ); track.add( spot ); @@ -127,7 +128,7 @@ public final void testProcess2() for ( int j = 0; j <= DEPTH; j++ ) { // We use deterministic locations - final Spot spot = new Spot( j * j, 0d, 0d, 1d, -1d ); + final Spot spot = new SpotBase( j * j, 0d, 0d, 1d, -1d ); spot.putFeature( Spot.POSITION_T, Double.valueOf( j ) ); model2.addSpotTo( spot, j ); track.add( spot ); @@ -197,9 +198,9 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { - final Spot spot1 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 0 ); + final Spot spot1 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 0 ); spot1.putFeature( Spot.POSITION_T, 0d ); - final Spot spot2 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d ), 1 ); + final Spot spot2 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d ), 1 ); spot2.putFeature( Spot.POSITION_T, 1d ); model.addEdge( spot1, spot2, 1 ); diff --git a/src/test/java/fiji/plugin/trackmate/graph/ConvexBranchDecompositionDebug.java b/src/test/java/fiji/plugin/trackmate/graph/ConvexBranchDecompositionDebug.java index 5f0b8cea0..5e481754e 100644 --- a/src/test/java/fiji/plugin/trackmate/graph/ConvexBranchDecompositionDebug.java +++ b/src/test/java/fiji/plugin/trackmate/graph/ConvexBranchDecompositionDebug.java @@ -24,6 +24,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.TrackModel; import fiji.plugin.trackmate.graph.ConvexBranchesDecomposition.TrackBranchDecomposition; @@ -35,17 +36,17 @@ public class ConvexBranchDecompositionDebug public static void main( final String[] args ) { - final Spot sa0 = new Spot( 0, 0, 0, 1, -1, "SA_0" ); - final Spot sa1 = new Spot( 0, 0, 0, 1, -1, "SA_1" ); - final Spot sa3 = new Spot( 0, 0, 0, 1, -1, "SA_3" ); - final Spot sa4 = new Spot( 0, 0, 0, 1, -1, "SA_4" ); + final Spot sa0 = new SpotBase( 0, 0, 0, 1, -1, "SA_0" ); + final Spot sa1 = new SpotBase( 0, 0, 0, 1, -1, "SA_1" ); + final Spot sa3 = new SpotBase( 0, 0, 0, 1, -1, "SA_3" ); + final Spot sa4 = new SpotBase( 0, 0, 0, 1, -1, "SA_4" ); - final Spot sb0 = new Spot( 0, 0, 0, 1, -1, "SB_0" ); - final Spot sb1 = new Spot( 0, 0, 0, 1, -1, "SB_1" ); - final Spot sb3 = new Spot( 0, 0, 0, 1, -1, "SB_3" ); - final Spot sb4 = new Spot( 0, 0, 0, 1, -1, "SB_4" ); + final Spot sb0 = new SpotBase( 0, 0, 0, 1, -1, "SB_0" ); + final Spot sb1 = new SpotBase( 0, 0, 0, 1, -1, "SB_1" ); + final Spot sb3 = new SpotBase( 0, 0, 0, 1, -1, "SB_3" ); + final Spot sb4 = new SpotBase( 0, 0, 0, 1, -1, "SB_4" ); - final Spot nexus = new Spot( 0, 0, 0, 1, -1, "NEXUS" ); + final Spot nexus = new SpotBase( 0, 0, 0, 1, -1, "NEXUS" ); final SpotCollection spots = new SpotCollection(); spots.add( sa0, 0 ); diff --git a/src/test/java/fiji/plugin/trackmate/graph/SortedDepthFirstIteratorTest.java b/src/test/java/fiji/plugin/trackmate/graph/SortedDepthFirstIteratorTest.java index 86c6a1d33..dead02c46 100644 --- a/src/test/java/fiji/plugin/trackmate/graph/SortedDepthFirstIteratorTest.java +++ b/src/test/java/fiji/plugin/trackmate/graph/SortedDepthFirstIteratorTest.java @@ -22,8 +22,6 @@ package fiji.plugin.trackmate.graph; import static org.junit.Assert.assertArrayEquals; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.Spot; import java.util.Arrays; import java.util.Comparator; @@ -33,6 +31,10 @@ import org.junit.BeforeClass; import org.junit.Test; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; + public class SortedDepthFirstIteratorTest { @@ -78,7 +80,7 @@ public int compare( final Spot o1, final Spot o2 ) { // Root - root = new Spot( 0d, 0d, 0d, 1d, -1d, "Root" ); + root = new SpotBase( 0d, 0d, 0d, 1d, -1d, "Root" ); model.addSpotTo( root, 0 ); // First level @@ -88,7 +90,7 @@ public int compare( final Spot o1, final Spot o2 ) { names[ i ] = "A"; // randomString( 5 ); - final Spot spotChild = new Spot( 0d, 0d, 0d, 1d, -1d, names[ i ] ); + final Spot spotChild = new SpotBase( 0d, 0d, 0d, 1d, -1d, names[ i ] ); model.addSpotTo( spotChild, 1 ); model.addEdge( root, spotChild, -1 ); spots[ 0 ][ i ] = spotChild; @@ -96,7 +98,7 @@ public int compare( final Spot o1, final Spot o2 ) spots[ 0 ][ i ] = spotChild; for ( int j = 1; j < spots.length; j++ ) { - final Spot spot = new Spot( 0d, 0d, 0d, 1d, -1d, " " + j + "_" + randomString( 3 ) ); + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d, " " + j + "_" + randomString( 3 ) ); spots[ j ][ i ] = spot; model.addSpotTo( spot, j + 1 ); model.addEdge( spots[ j - 1 ][ i ], spots[ j ][ i ], -1 ); diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java b/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java index a6c55615f..e309122b3 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java @@ -30,6 +30,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.graph.GraphUtils; import fiji.plugin.trackmate.graph.TimeDirectedNeighborIndex; @@ -103,22 +104,22 @@ public static final Model getExampleModel() // Create spots - final Spot root = new Spot( 3d, 0d, 0d, 1d, -1d, "Zygote" ); + final Spot root = new SpotBase( 3d, 0d, 0d, 1d, -1d, "Zygote" ); - final Spot AB = new Spot( 0d, 1d, 0d, 1d, -1d, "AB" ); - final Spot P1 = new Spot( 3d, 1d, 0d, 1d, -1d, "P1" ); + final Spot AB = new SpotBase( 0d, 1d, 0d, 1d, -1d, "AB" ); + final Spot P1 = new SpotBase( 3d, 1d, 0d, 1d, -1d, "P1" ); - final Spot P2 = new Spot( 4d, 2d, 0d, 1d, -1d, "P2" ); - final Spot EMS = new Spot( 2d, 2d, 0d, 1d, -1d, "EMS" ); + final Spot P2 = new SpotBase( 4d, 2d, 0d, 1d, -1d, "P2" ); + final Spot EMS = new SpotBase( 2d, 2d, 0d, 1d, -1d, "EMS" ); - final Spot P3 = new Spot( 5d, 3d, 0d, 1d, -1d, "P3" ); - final Spot C = new Spot( 3d, 3d, 0d, 1d, -1d, "C" ); - final Spot E = new Spot( 1d, 3d, 0d, 1d, -1d, "E" ); - final Spot MS = new Spot( 2d, 3d, 0d, 1d, -1d, "MS" ); - final Spot AB3 = new Spot( 0d, 3d, 0d, 1d, -1d, "AB" ); + final Spot P3 = new SpotBase( 5d, 3d, 0d, 1d, -1d, "P3" ); + final Spot C = new SpotBase( 3d, 3d, 0d, 1d, -1d, "C" ); + final Spot E = new SpotBase( 1d, 3d, 0d, 1d, -1d, "E" ); + final Spot MS = new SpotBase( 2d, 3d, 0d, 1d, -1d, "MS" ); + final Spot AB3 = new SpotBase( 0d, 3d, 0d, 1d, -1d, "AB" ); - final Spot D = new Spot( 4d, 4d, 0d, 1d, -1d, "D" ); - final Spot P4 = new Spot( 5d, 4d, 0d, 1d, -1d, "P4" ); + final Spot D = new SpotBase( 4d, 4d, 0d, 1d, -1d, "D" ); + final Spot P4 = new SpotBase( 5d, 4d, 0d, 1d, -1d, "P4" ); // Add them to the graph @@ -194,9 +195,9 @@ public static final Model getComplicatedExample() try { // new spots - final Spot Q1 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d, "Q1" ), 0 ); - final Spot Q2 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d, "Q2" ), 1 ); - final Spot Q3 = model.addSpotTo( new Spot( 0d, 0d, 0d, 1d, -1d, "Q3" ), 2 ); + final Spot Q1 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d, "Q1" ), 0 ); + final Spot Q2 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d, "Q2" ), 1 ); + final Spot Q3 = model.addSpotTo( new SpotBase( 0d, 0d, 0d, 1d, -1d, "Q3" ), 2 ); // new links model.addEdge( Q1, Q2, -1 ); model.addEdge( Q2, Q3, -1 ); diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/SpotFeatureGrapherExample.java b/src/test/java/fiji/plugin/trackmate/interactivetests/SpotFeatureGrapherExample.java index 3acc2b682..0de7fe53d 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/SpotFeatureGrapherExample.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/SpotFeatureGrapherExample.java @@ -35,6 +35,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.SpotFeatureGrapher; @@ -96,7 +97,7 @@ private static Model getSpiralModel() final double x = 100d + 100 * i / 100. * Math.cos( i / 100. * 5 * 2 * Math.PI ); final double y = 100d + 100 * i / 100. * Math.sin( i / 100. * 5 * 2 * Math.PI ); final double z = 0d; - final Spot spot = new Spot( x, y, z, 2d, -1d ); + final Spot spot = new SpotBase( x, y, z, 2d, -1d ); spot.putFeature( Spot.POSITION_T, Double.valueOf( i ) ); spots.add( spot ); diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/SpotNeighborhoodTest.java b/src/test/java/fiji/plugin/trackmate/interactivetests/SpotNeighborhoodTest.java index c26273c44..4df98de72 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/SpotNeighborhoodTest.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/SpotNeighborhoodTest.java @@ -22,15 +22,17 @@ package fiji.plugin.trackmate.interactivetests; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.util.SpotNeighborhood; -import fiji.plugin.trackmate.util.SpotNeighborhoodCursor; +import fiji.plugin.trackmate.SpotBase; import ij.ImageJ; import net.imagej.ImgPlus; +import net.imglib2.Cursor; +import net.imglib2.IterableInterval; import net.imglib2.img.array.ArrayImg; import net.imglib2.img.array.ArrayImgs; import net.imglib2.img.basictypeaccess.array.ShortArray; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.numeric.integer.UnsignedShortType; +import net.imglib2.util.Util; public class SpotNeighborhoodTest { @@ -42,12 +44,13 @@ public static void main( final String[] args ) // 3D final ArrayImg< UnsignedShortType, ShortArray > image = ArrayImgs.unsignedShorts( 100, 100, 100 ); final ImgPlus< UnsignedShortType > img = new ImgPlus<>( image ); - final Spot spot = new Spot( 50d, 50d, 50d, 30d, -1d ); - final SpotNeighborhood< UnsignedShortType > neighborhood = new SpotNeighborhood<>( spot, img ); - final SpotNeighborhoodCursor< UnsignedShortType > cursor = neighborhood.cursor(); + final Spot spot = new SpotBase( 50d, 50d, 50d, 30d, -1d ); + final IterableInterval< UnsignedShortType > neighborhood = spot.iterable( img ); + final Cursor< UnsignedShortType > cursor = neighborhood.cursor(); while ( cursor.hasNext() ) { - cursor.next().set( ( int ) cursor.getDistanceSquared() ); + final double d = Util.distance( spot, cursor ); + cursor.next().set( ( int ) ( d * d ) ); } System.out.println( "Finished" ); ImageJFunctions.wrap( img, "3D" ).show(); @@ -55,12 +58,13 @@ public static void main( final String[] args ) // 2D final ArrayImg< UnsignedShortType, ShortArray > image2 = ArrayImgs.unsignedShorts( 100, 100 ); final ImgPlus< UnsignedShortType > img2 = new ImgPlus<>( image2 ); - final Spot spot2 = new Spot( 50d, 50d, 0d, 30d, -1d ); - final SpotNeighborhood< UnsignedShortType > neighborhood2 = new SpotNeighborhood<>( spot2, img2 ); - final SpotNeighborhoodCursor< UnsignedShortType > cursor2 = neighborhood2.cursor(); + final Spot spot2 = new SpotBase( 50d, 50d, 0d, 30d, -1d ); + final IterableInterval< UnsignedShortType > neighborhood2 = spot2.iterable( img2 ); + final Cursor< UnsignedShortType > cursor2 = neighborhood2.cursor(); while ( cursor2.hasNext() ) { - cursor2.next().set( ( int ) cursor2.getDistanceSquared() ); + final double d = Util.distance( spot2, cursor2 ); + cursor2.next().set( ( int ) ( d * d ) ); } System.out.println( "Finished" ); ImageJFunctions.wrap( img2, "3D" ).show(); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java index 6c2aacffe..d23ba545d 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java @@ -5,6 +5,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.util.TMUtils; @@ -49,7 +50,7 @@ public static void main( final String[] args ) imp.setZ( ( int ) Math.round( z / calibration[ 2 ] ) + 1 ); - final Slice contours = ZSlicer.slice( spot.getMesh().mesh, z, calibration[ 2 ] ); + final Slice contours = ZSlicer.slice( ( ( SpotMesh ) spot ).mesh, z, calibration[ 2 ] ); System.out.println( "Found " + contours.size() + " contours." ); for ( final Contour contour : contours ) System.out.println( contour ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java index d3e4dc6ef..8ce097ad2 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java @@ -42,7 +42,7 @@ public static void main( final String[] args ) for ( final Spot spot : spots ) { model.getSpots().add( spot, 0 ); - System.out.println( spot.getMesh() ); + System.out.println( spot ); } final SelectionModel selectionModel = new SelectionModel( model ); @@ -65,7 +65,7 @@ public static void main2( final String[] args ) final long[] min = new long[] { 2, 2, 2 }; final long[] max = new long[] { 20, 20, 20 }; final Mesh mesh = Demo3DMesh.debugMesh( min, max ); - final Spot spot = SpotMesh.createSpot( mesh, 1. ); + final Spot spot = new SpotMesh( mesh, 1. ); final Model model = new Model(); model.beginUpdate(); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index efb1a286c..84b9912f9 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -234,7 +234,6 @@ static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTes final ImagePlus imp = IJ.openImage( filePath ); // First channel is the mask. - @SuppressWarnings( "unchecked" ) final ImgPlus< T > img = TMUtils.rawWraps( imp ); final ImgPlus< T > c1 = ImgPlusViews.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 0 ); @@ -249,7 +248,6 @@ static < T extends RealType< T > & NumericType< T > > ImgPlus< BitType > loadTes { final String filePath = "samples/mesh/Cube.tif"; final ImagePlus imp = IJ.openImage( filePath ); - @SuppressWarnings( "unchecked" ) final ImgPlus< T > img = TMUtils.rawWraps( imp ); final RandomAccessibleInterval< BitType > mask = RealTypeConverters.convert( img, new BitType() ); return new ImgPlus<>( ImgView.wrap( mask ), img ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java index baba9a008..52a32b51c 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java @@ -14,8 +14,8 @@ public static void main( final String[] args ) { ImageJ.main( args ); - final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; -// final String filePath = "samples/mesh/CElegansMask3D.tif"; +// final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; + final String filePath = "samples/Celegans-5pc-17timepoints.tif"; final ImagePlus imp = IJ.openImage( filePath ); imp.show(); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java index d2530dada..077c9f4e8 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java @@ -4,11 +4,11 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; -import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImageJ; @@ -57,12 +57,12 @@ public static ImagePlus makeImg() final double r1 = imp.getWidth() / 4.; final double r2 = imp.getWidth() / 8.; final double r3 = imp.getWidth() / 16.; - final Spot s1 = new Spot( center, r1, 1. ); - final Spot s2 = new Spot( center, r2, 1. ); - final Spot s3 = new Spot( center, r3, 1. ); - SpotUtil.iterable( s1, img ).forEach( p -> p.setReal( 250. ) ); - SpotUtil.iterable( s2, img ).forEach( p -> p.setZero() ); - SpotUtil.iterable( s3, img ).forEach( p -> p.setReal( 250. ) ); + final Spot s1 = new SpotBase( center, r1, 1. ); + final Spot s2 = new SpotBase( center, r2, 1. ); + final Spot s3 = new SpotBase( center, r3, 1. ); + s1.iterable( img ).forEach( p -> p.setReal( 250. ) ); + s2.iterable( img ).forEach( p -> p.setZero() ); + s3.iterable( img ).forEach( p -> p.setReal( 250. ) ); return imp; } } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index ee6a75370..f3f02d2d9 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -12,7 +12,6 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.util.mesh.SpotMeshCursor; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.IJ; import ij.ImageJ; @@ -79,7 +78,7 @@ public static < T extends RealType< T > > void main( final String[] args ) { System.out.println( spot ); final ImgPlus< T > img = TMUtils.rawWraps( out ); - final Cursor< T > cursor = new SpotMeshCursor< T >( img.randomAccess(), spot.getMesh(), cal ); + final Cursor< T > cursor = spot.iterable( img, cal ).localizingCursor(); final RandomAccess< T > ra = img.randomAccess(); while ( cursor.hasNext() ) { diff --git a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java index bee4247c0..b247da01e 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java @@ -48,9 +48,11 @@ public static void main( final String[] args ) final int t = spot.getFeature( Spot.FRAME ).intValue(); final int id = spot.ID(); final String savePath = String.format( "%s/mesh_t%2d_id_%04d.stl", meshDir, t, id ); - final SpotMesh mesh = spot.getMesh(); - if ( mesh != null ) + if ( spot instanceof SpotMesh ) + { + final SpotMesh mesh = ( SpotMesh ) spot; io.save( mesh.mesh, savePath ); + } } System.out.println( "Export done." ); } diff --git a/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest.java b/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest.java index d07627e35..057f0e950 100644 --- a/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest.java +++ b/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest.java @@ -32,6 +32,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -237,7 +238,7 @@ private SpotCollection createParallelLines() { for ( int k = 0; k < y.length; k++ ) { - final Spot spot = new Spot( x[ k ] + ran.nextGaussian() * WIDTH / 100, y[ k ] + ran.nextGaussian() * WIDTH / 100, 0, 2, k, "T_" + k + "_S_" + t ); + final Spot spot = new SpotBase( x[ k ] + ran.nextGaussian() * WIDTH / 100, y[ k ] + ran.nextGaussian() * WIDTH / 100, 0, 2, k, "T_" + k + "_S_" + t ); spots.add( spot, t ); x[ k ] += vx0[ k ]; @@ -274,7 +275,13 @@ private SpotCollection createSpots() { for ( int k = 0; k < y.length; k++ ) { - final Spot spot = new Spot( x[ k ] + ran.nextGaussian() * WIDTH / 200, y[ k ] + ran.nextGaussian() * WIDTH / 200, 0, 2, k, "T_" + k + "_S_" + t ); + final Spot spot = new SpotBase( + x[ k ] + ran.nextGaussian() * WIDTH / 200, + y[ k ] + ran.nextGaussian() * WIDTH / 200, + 0, + 2, + k, + "T_" + k + "_S_" + t ); spots.add( spot, t ); x[ k ] += vx0[ k ]; diff --git a/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest3.java b/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest3.java index ae55af4bd..48b807e5a 100755 --- a/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest3.java +++ b/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest3.java @@ -27,6 +27,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -115,7 +116,13 @@ private SpotCollection createSingleLine() double y = y0; for ( int t = 0; t < NFRAMES; t++ ) { - final Spot spot = new Spot( x + ran.nextGaussian() * sigma, y + ran.nextGaussian() * sigma, 0, 2, 1, "S_" + t ); + final Spot spot = new SpotBase( + x + ran.nextGaussian() * sigma, + y + ran.nextGaussian() * sigma, + 0, + 2, + 1, + "S_" + t ); spots.add( spot, t ); x += vx0; diff --git a/src/test/java/fiji/plugin/trackmate/util/SpotRoiIterableTest.java b/src/test/java/fiji/plugin/trackmate/util/SpotRoiIterableTest.java index 74b27db3d..1fcd102f3 100644 --- a/src/test/java/fiji/plugin/trackmate/util/SpotRoiIterableTest.java +++ b/src/test/java/fiji/plugin/trackmate/util/SpotRoiIterableTest.java @@ -61,7 +61,7 @@ public void testIterationPolygon() 84, 85 }; final TIntArrayList vals = new TIntArrayList(); - final IterableInterval< UnsignedByteType > iterable = SpotUtil.iterable( spot, new ImgPlus<>( img ) ); + final IterableInterval< UnsignedByteType > iterable = spot.iterable( new ImgPlus<>( img ) ); final Cursor< UnsignedByteType > cursor = iterable.cursor(); while ( cursor.hasNext() ) { @@ -85,7 +85,7 @@ public static void main( final String[] args ) final double[] yp = new double[] { 1.5, 5, 8.8, 5 }; final Spot spot = SpotRoi.createSpot( xp, yp, 1. ); - final IterableInterval< UnsignedByteType > iterable = SpotUtil.iterable( spot, new ImgPlus<>( img ) ); + final IterableInterval< UnsignedByteType > iterable = spot.iterable( new ImgPlus<>( img ) ); final Cursor< UnsignedByteType > cursor = iterable.cursor(); while ( cursor.hasNext() ) { From 0edbc1e2a9c0f14aff587b1c2acf28a58de7045b Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 22:04:14 +0200 Subject: [PATCH 131/263] Fix some javadoc errors. --- src/main/java/fiji/plugin/trackmate/SpotBase.java | 4 ++-- .../trackmate/visualization/hyperstack/PaintSpotRoi.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotBase.java b/src/main/java/fiji/plugin/trackmate/SpotBase.java index 3cdd632dc..384e853b7 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotBase.java +++ b/src/main/java/fiji/plugin/trackmate/SpotBase.java @@ -36,7 +36,7 @@ import net.imglib2.view.Views; /** - * A {@link RealLocalizable} implementation of {@link SpotI}, used in TrackMate + * A {@link RealLocalizable} implementation of {@link Spot}, used in TrackMate * to represent a detection. This concrete implementation has the simplest * shape: a spot is a sphere of fixed radius. *

@@ -172,7 +172,7 @@ public SpotBase( final RealLocalizable location, final double radius, final doub * Creates a new spot, taking its location, its radius, its quality value * and its name from the specified spot. * - * @param spot + * @param oldSpot * the spot to read from. */ public SpotBase( final Spot oldSpot ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java index 37d6d617b..e33a07220 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java @@ -35,8 +35,8 @@ public PaintSpotRoi( final ImagePlus imp, final double[] calibration, final Disp * * @param g2d * the graphics object, configured to paint the spot with. - * @param roi - * the spot roi. + * @param spot + * the spot to paint. * @return the text position X indent in pixels to use to paint a string * next to the painted contour. */ From d048129eb1b5a8bc84a7df90d3e76bd511633873 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 22:10:22 +0200 Subject: [PATCH 132/263] Implement RealInterval methods in SpotRoi and SpotMesh. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 12 ++++++++++++ src/main/java/fiji/plugin/trackmate/SpotRoi.java | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 16ad4f42c..10aaa9b39 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -116,6 +116,18 @@ public SpotMesh( final int ID, final BufferMesh mesh ) this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); } + @Override + public double realMax( final int d ) + { + return getDoublePosition( d ) + boundingBox.realMax( d ); + } + + @Override + public double realMin( final int d ) + { + return getDoublePosition( d ) + boundingBox.realMin( d ); + } + @Override public < T extends RealType< T > > IterableInterval< T > iterable( final RandomAccessible< T > ra, final double[] calibration ) { diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index df5b856f2..e830abcaa 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -33,6 +33,7 @@ import net.imglib2.roi.geom.real.WritablePolygon2D; import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.RealType; +import net.imglib2.util.Util; public class SpotRoi extends SpotBase { @@ -88,6 +89,20 @@ public SpotRoi copy() return new SpotRoi( xc, yc, zc, r, quality, getName(), x.clone(), y.clone() ); } + @Override + public double realMin( final int d ) + { + final double[] arr = ( d == 0 ) ? x : y; + return getDoublePosition( d ) + Util.min( arr ); + } + + @Override + public double realMax( final int d ) + { + final double[] arr = ( d == 0 ) ? x : y; + return getDoublePosition( d ) + Util.max( arr ); + } + /** * Returns a new int array containing the X pixel coordinates * to which to paint this polygon. From c217b230e8fd3cf4aecc8a87921f09379fbc46d2 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 22:16:52 +0200 Subject: [PATCH 133/263] Remove unused interfaces. --- .../trackmate/graph/OutputFunction.java | 35 ----------------- .../trackmate/graph/StringFormater.java | 39 ------------------- 2 files changed, 74 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/graph/OutputFunction.java delete mode 100644 src/main/java/fiji/plugin/trackmate/graph/StringFormater.java diff --git a/src/main/java/fiji/plugin/trackmate/graph/OutputFunction.java b/src/main/java/fiji/plugin/trackmate/graph/OutputFunction.java deleted file mode 100644 index e6f89c6c6..000000000 --- a/src/main/java/fiji/plugin/trackmate/graph/OutputFunction.java +++ /dev/null @@ -1,35 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 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.graph; - -/** - * Interface for functions that return a new object, computed from two input - * arguments. - * - * @author Jean-Yves Tinevez - */ -public interface OutputFunction< E > -{ - - public E compute( E input1, E input2 ); - -} diff --git a/src/main/java/fiji/plugin/trackmate/graph/StringFormater.java b/src/main/java/fiji/plugin/trackmate/graph/StringFormater.java deleted file mode 100644 index cef4b94bf..000000000 --- a/src/main/java/fiji/plugin/trackmate/graph/StringFormater.java +++ /dev/null @@ -1,39 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 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.graph; - -/** - * Interface for function that can build a human-readable string representation - * of an object - * - * @author JeanYves - * - */ -public interface StringFormater< V > -{ - - /** - * Convert the given instance to a string representation. - */ - public String toString( V instance ); - -} From fba374774d3981aef939e2c9754b7c5a9a98b30c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 11 May 2023 16:51:59 +0200 Subject: [PATCH 134/263] Fix spot meshes not abiding to ROI origin. --- .../fiji/plugin/trackmate/detection/MaskUtils.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index af7d5377f..636a644a4 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -660,7 +660,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot while ( iterator.hasNext() ) { final LabelRegion< Integer > region = iterator.next(); - final Spot spot = regionToSpotMesh( region, simplify, calibration, qualityImage ); + final Spot spot = regionToSpotMesh( region, simplify, calibration, qualityImage, interval.minAsDoubleArray() ); if ( spot == null ) continue; @@ -1295,6 +1295,8 @@ private static void scale( final Vertices vertices, final double[] scale, final * null, the quality of the spot will be the max * value of this image inside the mesh. If null, the * quality will be the mesh volume. + * @param minInterval + * the origin in image coordinates of the ROI used for detection. * * @return a new spot. */ @@ -1302,7 +1304,8 @@ private static < S extends RealType< S > > Spot regionToSpotMesh( final RandomAccessibleInterval< BoolType > region, final boolean simplify, final double[] calibration, - final RandomAccessibleInterval< S > qualityImage ) + final RandomAccessibleInterval< S > qualityImage, + final double[] minInterval ) { // To mesh. final IntervalView< BoolType > box = Views.zeroMin( region ); @@ -1341,8 +1344,13 @@ else if ( nTriangles < 1_000_000 ) if ( SpotMesh.volume( mesh ) < volumeThreshold ) return null; + // Translate back to interval coords. + // Scale to physical coords. - final double[] origin = region.minAsDoubleArray(); + final double[] originRegion = region.minAsDoubleArray(); + final double[] origin = new double[3]; + for ( int d = 0; d < 3; d++ ) + origin[ d ] = originRegion[ d ] + minInterval[ d ]; scale( simplified.vertices(), calibration, origin ); // Make spot with default quality. From 075bbd74daa46c7215b2b0ddf1a4ea58d559337e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 11 May 2023 17:05:07 +0200 Subject: [PATCH 135/263] Abide to Z ROI settings in the preview. --- src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java b/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java index 3bf2102d9..8ef5e66a5 100644 --- a/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java +++ b/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java @@ -162,6 +162,8 @@ protected Pair< Model, Double > runPreviewDetection( final Settings lSettings = new Settings( settings.imp ); lSettings.tstart = frame; lSettings.tend = frame; + lSettings.zstart = settings.zstart; + lSettings.zend = settings.zend; settings.setRoi( settings.imp.getRoi() ); lSettings.detectorFactory = detectorFactory; From 5447f63bd3b992f4d44d7ef8ea63512ad75a12cc Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 12 May 2023 18:30:34 +0200 Subject: [PATCH 136/263] Rework the SpotRoi class. - Make fields private, replace by public methods. - Review iteration over coordinates. - More sensible convenience methods. --- .../java/fiji/plugin/trackmate/SpotRoi.java | 216 ++++++++++-------- .../trackmate/action/IJRoiExporter.java | 7 +- .../trackmate/features/spot/ConvexHull2D.java | 6 +- .../spot/Spot2DFitEllipseAnalyzer.java | 35 +-- .../features/spot/Spot2DShapeAnalyzer.java | 19 +- .../fiji/plugin/trackmate/io/TmXmlWriter.java | 6 +- .../tracking/overlap/OverlapTracker.java | 14 +- .../hyperstack/PaintSpotRoi.java | 36 +-- .../hyperstack/TrackMatePainter.java | 24 ++ 9 files changed, 188 insertions(+), 175 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index e830abcaa..e18ee746b 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -39,10 +39,10 @@ public class SpotRoi extends SpotBase { /** Polygon points X coordinates, in physical units, centered (0,0). */ - public final double[] x; + private final double[] x; /** Polygon points Y coordinates, in physical units, centered (0,0). */ - public final double[] y; + private final double[] y; public SpotRoi( final double xc, @@ -73,7 +73,7 @@ public SpotRoi( final double[] x, final double[] y ) { - super( ID ); + super( ID ); this.x = x; this.y = y; } @@ -89,6 +89,63 @@ public SpotRoi copy() return new SpotRoi( xc, yc, zc, r, quality, getName(), x.clone(), y.clone() ); } + /** + * Returns the X coordinates of the ith vertex of the polygon, in physical + * coordinates. + * + * @param i + * the index of the vertex. + * @return the vertex X position. + */ + public double x( final int i ) + { + return x[ i ] + getDoublePosition( 0 ); + } + + /** + * Returns the Y coordinates of the ith vertex of the polygon, in physical + * coordinates. + * + * @param i + * the index of the vertex. + * @return the vertex Y position. + */ + public double y( final int i ) + { + return y[ i ] + getDoublePosition( 1 ); + } + + /** + * Returns the X coordinates of the ith vertex of the polygon, relative + * to the spot center, in physical coordinates. + * + * @param i + * the index of the vertex. + * @return the vertex X position. + */ + public double xr( final int i ) + { + return x[ i ]; + } + + /** + * Returns the Y coordinates of the ith vertex of the polygon, relative + * to the spot center, in physical coordinates. + * + * @param i + * the index of the vertex. + * @return the vertex Y position. + */ + public double yr( final int i ) + { + return y[ i ]; + } + + public int nPoints() + { + return x.length; + } + @Override public double realMin( final int d ) { @@ -104,102 +161,77 @@ public double realMax( final int d ) } /** - * Returns a new int array containing the X pixel coordinates - * to which to paint this polygon. - * - * @param calibration - * the pixel size in X, to convert physical coordinates to pixel - * coordinates. - * @param xcorner - * the top-left X corner of the view in the image to paint. - * @param magnification - * the magnification of the view. - * @return a new int array. - */ - public double[] toPolygonX( final double calibration, final double xcorner, final double spotXCenter, final double magnification ) - { - final double[] xp = new double[ x.length ]; - for ( int i = 0; i < xp.length; i++ ) - { - final double xc = ( spotXCenter + x[ i ] ) / calibration; - xp[ i ] = ( xc - xcorner ) * magnification; - } - return xp; - } - - /** - * Returns a new int array containing the Y pixel coordinates - * to which to paint this polygon. - * - * @param calibration - * the pixel size in Y, to convert physical coordinates to pixel - * coordinates. - * @param ycorner - * the top-left Y corner of the view in the image to paint. - * @param magnification - * the magnification of the view. - * @return a new int array. + * Convenience method that returns the X and Y coordinates of the polygon on + * this spot, possibly shifted and scale by a specified amount. Such that: + * + *

+	 * xout = x * sx + cx
+	 * yout = y * sy + cy
+	 * 
+ * + * @param cx + * the shift in X to apply after scaling coordinates. + * @param cy + * the shift in Y to apply after scaling coordinates. + * @param sx + * the scale to apply in X. + * @param sy + * the scale to apply in Y. + * @param xout + * a list in which to write resulting X coordinates. Reset by + * this call. + * @param yout + * a list in which to write resulting Y coordinates. Reset by + * this call. */ - public double[] toPolygonY( final double calibration, final double ycorner, final double spotYCenter, final double magnification ) + public void toArray( final double cx, final double cy, final double sx, final double sy, final TDoubleArrayList xout, final TDoubleArrayList yout ) { - final double[] yp = new double[ y.length ]; - for ( int i = 0; i < yp.length; i++ ) + xout.resetQuick(); + yout.resetQuick(); + for ( int i = 0; i < x.length; i++ ) { - final double yc = ( spotYCenter + y[ i ] ) / calibration; - yp[ i ] = ( yc - ycorner ) * magnification; + xout.add( x( i ) + sx + cx ); + yout.add( y( i ) + sx + cy ); } - return yp; } /** - * Writes the X AND Y pixel coordinates of the contour of the ROI inside a - * double list, cleared first when this method is called. Similar to - * {@link #toPolygonX(double, double, double, double)} and - * {@link #toPolygonY(double, double, double, double)} but allocation-free. - * - * @param calibration - * the pixel sizes, to convert physical coordinates to pixel - * coordinates. - * @param xcorner - * the top-left X corner of the view in the image to paint. - * @param magnification - * the magnification of the view. + * Convenience method that returns the X and Y coordinates of the polygon on + * this spot, possibly shifted and scale by a specified amount. Such that: + * + *
+	 * xout = x * sx + cx
+	 * yout = y * sy + cy
+	 * 
+ * * @param cx - * the list in which to write the contour X coordinates. First - * reset when called. + * the shift in X to apply after scaling coordinates. * @param cy - * the list in which to write the contour Y coordinates. First - * reset when called. + * the shift in Y to apply after scaling coordinates. + * @param sx + * the scale to apply in X. + * @param sy + * the scale to apply in Y. + * @return a new 2D double array, with the array of X values as the first + * element, and the array of Y values as a second element. */ - public void toPolygon( - final double calibration[], - final double xcorner, - final double ycorner, - final double spotXCenter, - final double spotYCenter, - final double magnification, - final TDoubleArrayList cx, - final TDoubleArrayList cy ) + public double[][] toArray( final double cx, final double cy, final double sx, final double sy ) { - cx.resetQuick(); - cy.resetQuick(); + final double[] xout = new double[ x.length ]; + final double[] yout = new double[ x.length ]; for ( int i = 0; i < x.length; i++ ) { - final double xc = ( spotXCenter + x[ i ] ) / calibration[ 0 ]; - final double xp = ( xc - xcorner ) * magnification; - cx.add( xp ); - final double yc = ( spotYCenter + y[ i ] ) / calibration[ 1 ]; - final double yp = ( yc - ycorner ) * magnification; - cy.add( yp ); + xout[ i ] = x( i ) * sx + cx; + yout[ i ] = y( i ) * sy + cy; } + return new double[][] { xout, yout }; } - + @Override public < T extends RealType< T > > IterableInterval< T > iterable( final RandomAccessible< T > ra, final double[] calibration ) { - final double[] xp = toPolygonX( calibration[ 0 ], 0, this.getDoublePosition( 0 ), 1. ); - final double[] yp = toPolygonY( calibration[ 1 ], 0, this.getDoublePosition( 1 ), 1. ); - final WritablePolygon2D polygon = GeomMasks.closedPolygon2D( xp, yp ); + final double[][] out = toArray( 0., 0., 1 / calibration[ 0 ], 1 / calibration[ 1 ] ); + final WritablePolygon2D polygon = GeomMasks.closedPolygon2D( out[ 0 ], out[ 1 ] ); final IterableRegion< BoolType > region = Masks.toIterableRegion( polygon ); return Regions.sample( region, ra ); } @@ -259,16 +291,14 @@ private static final double[] centroid( final double[] x, final double[] y ) double ax = 0.0; double ay = 0.0; final int n = x.length; - for ( int i = 0; i < n - 1; i++ ) + int i; + int j; + for ( i = 0, j = n - 1; i < n; j = i++ ) { - final double w = x[ i ] * y[ i + 1 ] - x[ i + 1 ] * y[ i ]; - ax += ( x[ i ] + x[ i + 1 ] ) * w; - ay += ( y[ i ] + y[ i + 1 ] ) * w; + final double w = x[ j ] * y[ i ] - x[ i ] * y[ j ]; + ax += ( x[ j ] + x[ i ] ) * w; + ay += ( y[ j ] + y[ i ] ) * w; } - - final double w0 = x[ n - 1 ] * y[ 0 ] - x[ 0 ] * y[ n - 1 ]; - ax += ( x[ n - 1 ] + x[ 0 ] ) * w0; - ay += ( y[ n - 1 ] + y[ 0 ] ) * w0; return new double[] { ax / 6. / area, ay / 6. / area }; } @@ -276,9 +306,11 @@ private static final double signedArea( final double[] x, final double[] y ) { final int n = x.length; double a = 0.0; - for ( int i = 0; i < n - 1; i++ ) - a += x[ i ] * y[ i + 1 ] - x[ i + 1 ] * y[ i ]; + int i; + int j; + for ( i = 0, j = n - 1; i < n; j = i++ ) + a += x[ j ] * y[ i ] - x[ i ] * y[ j ]; - return ( a + x[ n - 1 ] * y[ 0 ] - x[ 0 ] * y[ n - 1 ] ) / 2.0; + return a / 2.; } } diff --git a/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java index 0e7c0ee13..9d2925711 100644 --- a/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java @@ -90,10 +90,9 @@ public void export( final Spot spot ) if ( spot instanceof SpotRoi ) { final SpotRoi sroi = ( SpotRoi ) spot; - final double[] xs = sroi.toPolygonX( dx, 0., spot.getDoublePosition( 0 ), 1. ); - final double[] ys = sroi.toPolygonY( dy, 0., spot.getDoublePosition( 1 ), 1. ); - final float[] xp = toFloat( xs ); - final float[] yp = toFloat( ys ); + final double[][] out = sroi.toArray( 0., 0., 1 / dx, 1 / dy ); + final float[] xp = toFloat( out[ 0 ] ); + final float[] yp = toFloat( out[ 1 ] ); roi = new PolygonRoi( xp, yp, PolygonRoi.POLYGON ); } else diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java b/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java index c8f26a65a..92bdef088 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/ConvexHull2D.java @@ -122,9 +122,9 @@ public int compareTo( final Point other ) public static SpotRoi convexHull( final SpotRoi roi ) { - final List< Point > points = new ArrayList<>( roi.x.length ); - for ( int i = 0; i < roi.x.length; i++ ) - points.add( new Point( roi.x[ i ], roi.y[ i ] ) ); + final List< Point > points = new ArrayList<>( roi.nPoints() ); + for ( int i = 0; i < roi.nPoints(); i++ ) + points.add( new Point( roi.xr( i ), roi.yr( i ) ) ); final List< Point > hull = makeHull( points ); final double[] xhull = new double[ hull.size() ]; diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java index 331a34d88..76d2d80b2 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java @@ -27,7 +27,6 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotRoi; import net.imglib2.type.numeric.RealType; -import net.imglib2.util.Util; public class Spot2DFitEllipseAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > { @@ -54,10 +53,10 @@ public void process( final Spot spot ) if ( spot instanceof SpotRoi ) { final SpotRoi roi = ( SpotRoi ) spot; - final double[] Q = fitEllipse( roi.x, roi.y ); + final double[] Q = fitEllipse( roi ); final double[] A = quadraticToCartesian( Q ); - x0 = A[ 0 ]; - y0 = A[ 1 ]; + x0 = A[ 0 ] - roi.getDoublePosition( 0 ); + y0 = A[ 1 ] - roi.getDoublePosition( 1 ); major = A[ 2 ]; minor = A[ 3 ]; theta = A[ 4 ]; @@ -76,11 +75,6 @@ public void process( final Spot spot ) } else { - /* - * TODO: deal with 3D case with a mesh. Fit an ellipsoid, with extra - * parameters that are left blank for 2d? Put it in another case? - */ - x0 = Double.NaN; y0 = Double.NaN; major = Double.NaN; @@ -117,17 +111,17 @@ public void process( final Spot spot ) * script * @author Michael Doube */ - private static double[] fitEllipse( final double[] x, final double[] y ) + private static double[] fitEllipse( final SpotRoi roi ) { - final int nPoints = x.length; - final double[] centroid = getCentroid( x, y ); - final double xC = centroid[ 0 ]; - final double yC = centroid[ 1 ]; + final double xC = roi.getDoublePosition( 0 ); + final double yC = roi.getDoublePosition( 1 ); + + final int nPoints = roi.nPoints(); final double[][] d1 = new double[ nPoints ][ 3 ]; for ( int i = 0; i < nPoints; i++ ) { - final double xixC = x[ i ] - xC; - final double yiyC = y[ i ] - yC; + final double xixC = roi.xr( i ); + final double yiyC = roi.yr( i ); d1[ i ][ 0 ] = xixC * xixC; d1[ i ][ 1 ] = xixC * yiyC; d1[ i ][ 2 ] = yiyC * yiyC; @@ -136,8 +130,8 @@ private static double[] fitEllipse( final double[] x, final double[] y ) final double[][] d2 = new double[ nPoints ][ 3 ]; for ( int i = 0; i < nPoints; i++ ) { - d2[ i ][ 0 ] = x[ i ] - xC; - d2[ i ][ 1 ] = y[ i ] - yC; + d2[ i ][ 0 ] = roi.xr( i ); + d2[ i ][ 1 ] = roi.yr( i ); d2[ i ][ 2 ] = 1; } final Matrix D2 = new Matrix( d2 ); @@ -187,11 +181,6 @@ private static double[] fitEllipse( final double[] x, final double[] y ) return A.getColumnPackedCopy(); } - private static double[] getCentroid( final double[] x, final double[] y ) - { - return new double[] { Util.average( x ), Util.average( y ) }; - } - /** * Convert to cartesian coordnates for the ellipse. Return [ x0 y0 a b theta * ]. We always have a > b. theta in radians measure the angle of the diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java index 4b318a62a..7d66eeb6c 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DShapeAnalyzer.java @@ -80,24 +80,19 @@ public void process( final Spot spot ) private static final double getLength( final SpotRoi roi ) { - final double[] x = roi.x; - final double[] y = roi.y; - final int npoints = x.length; - if ( npoints < 2 ) + final int nPoints = roi.nPoints(); + if ( nPoints < 2 ) return 0; double length = 0; - for ( int i = 0; i < npoints - 1; i++ ) + int i; + int j; + for ( i = 0, j = nPoints - 1; i < nPoints; j = i++ ) { - final double dx = x[ i + 1 ] - x[ i ]; - final double dy = y[ i + 1 ] - y[ i ]; + final double dx = roi.x( i ) - roi.x( j ); + final double dy = roi.y( i ) - roi.y( j ); length += Math.sqrt( dx * dx + dy * dy ); } - - final double dx0 = x[ 0 ] - x[ npoints - 1 ]; - final double dy0 = y[ 0 ] - y[ npoints - 1 ]; - length += Math.sqrt( dx0 * dx0 + dy0 * dy0 ); - return length; } } diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index 0ba5d513b..c2639c99e 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -739,14 +739,14 @@ private final Element marshalSpot( final Spot spot, final FeatureModel fm ) if ( spot instanceof SpotRoi ) { final SpotRoi roi = ( SpotRoi ) spot; - final int nPoints = roi.x.length; + final int nPoints = roi.nPoints(); attributes.add( new Attribute( ROI_N_POINTS_ATTRIBUTE_NAME, Integer.toString( nPoints ) ) ); final StringBuilder str = new StringBuilder(); for ( int i = 0; i < nPoints; i++ ) { - str.append( Double.toString( roi.x[ i ] ) ); + str.append( Double.toString( roi.xr( i ) ) ); str.append( ' ' ); - str.append( Double.toString( roi.y[ i ] ) ); + str.append( Double.toString( roi.yr( i ) ) ); str.append( ' ' ); } spotElement.setText( str.toString() ); diff --git a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java index 79e78e18c..3279e80da 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java @@ -24,7 +24,6 @@ import static fiji.plugin.trackmate.tracking.overlap.OverlapTrackerFactory.BASE_ERROR_MESSAGE; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -302,9 +301,8 @@ private static SimplePolygon2D toPolygon( final Spot spot, final double scale ) if ( spot instanceof SpotRoi ) { final SpotRoi roi = ( SpotRoi ) spot; - final double[] xcoords = roi.toPolygonX( 1., 0., xc, 1. ); - final double[] ycoords = roi.toPolygonY( 1., 0., yc, 1. ); - poly = new SimplePolygon2D( xcoords, ycoords ); + final double[][] out = roi.toArray( 0., 0., 1., 1. ); + poly = new SimplePolygon2D( out[ 0 ], out[ 1 ] ); } else { @@ -321,10 +319,10 @@ private static Rectangle2D toBoundingBox( final Spot spot, final double scale ) if ( spot instanceof SpotRoi ) { final SpotRoi roi = ( SpotRoi ) spot; - final double minX = Arrays.stream( roi.x ).min().getAsDouble() * scale; - final double maxX = Arrays.stream( roi.x ).max().getAsDouble() * scale; - final double minY = Arrays.stream( roi.y ).min().getAsDouble() * scale; - final double maxY = Arrays.stream( roi.y ).max().getAsDouble() * scale; + final double minX = roi.realMin( 0 ) * scale; + final double maxX = roi.realMax( 0 ) * scale; + final double minY = roi.realMin( 1 ) * scale; + final double maxY = roi.realMax( 1 ) * scale; return new Rectangle2D( xc + minX, yc + minY, maxX - minX, maxY - minY ); } else diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java index e33a07220..0698db2cf 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java @@ -9,8 +9,6 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import gnu.trove.list.TDoubleList; import ij.ImagePlus; -import net.imglib2.RealInterval; -import net.imglib2.util.Intervals; /** * Utility class to paint the {@link SpotRoi} component of spots. @@ -43,7 +41,7 @@ public PaintSpotRoi( final ImagePlus imp, final double[] calibration, final Disp @Override public int paint( final Graphics2D g2d, final SpotRoi spot ) { - if ( !intersect( boundingBox( spot ), spot ) ) + if ( !intersect( spot ) ) return -1; final double maxTextPos = toPolygon( spot, polygon, this::toScreenX, this::toScreenY ); @@ -63,28 +61,6 @@ public int paint( final Graphics2D g2d, final SpotRoi spot ) return textPos; } - private static final RealInterval boundingBox( final SpotRoi roi ) - { - double minX = roi.x[ 0 ]; - double maxX = roi.x[ 0 ]; - double minY = roi.y[ 0 ]; - double maxY = roi.y[ 0 ]; - for ( int i = 0; i < roi.x.length; i++ ) - { - final double x = roi.x[ i ]; - if ( x > maxX ) - maxX = x; - if ( x < minX ) - minX = x; - final double y = roi.y[ i ]; - if ( y > maxY ) - maxY = y; - if ( y < minY ) - minY = y; - } - return Intervals.createMinMaxReal( minX, minY, maxX, maxY ); - } - static final double max( final TDoubleList l ) { double max = Double.NEGATIVE_INFINITY; @@ -117,16 +93,16 @@ private static final double toPolygon( final SpotRoi roi, final Path2D polygon, { double maxTextPos = Double.NEGATIVE_INFINITY; polygon.reset(); - final double x0 = toScreenX.applyAsDouble( roi.x[ 0 ] + roi.getDoublePosition( 0 ) ); - final double y0 = toScreenY.applyAsDouble( roi.y[ 0 ] + roi.getDoublePosition( 1 ) ); + final double x0 = toScreenX.applyAsDouble( roi.x( 0 ) ); + final double y0 = toScreenY.applyAsDouble( roi.y( 0 ) ); polygon.moveTo( x0, y0 ); if ( x0 > maxTextPos ) maxTextPos = x0; - for ( int i = 1; i < roi.x.length; i++ ) + for ( int i = 1; i < roi.nPoints(); i++ ) { - final double xi = toScreenX.applyAsDouble( roi.x[ i ] + roi.getDoublePosition( 0 ) ); - final double yi = toScreenY.applyAsDouble( roi.y[ i ] + roi.getDoublePosition( 1 ) ); + final double xi = toScreenX.applyAsDouble( roi.x( i ) ); + final double yi = toScreenY.applyAsDouble( roi.y( i ) ); polygon.lineTo( xi, yi ); if ( xi > maxTextPos ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java index 55f0273ae..35e77fdbc 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java @@ -52,7 +52,31 @@ protected boolean intersect( final RealInterval boundingBox, final RealLocalizab if ( toScreenY( boundingBox.realMax( 1 ) + center.getDoublePosition( 1 ) ) < 0 ) return false; return true; + } + + /** + * Returns true if the specified bounding-box intersects with + * the display window. + * + * @param boundingBox + * the bounding box, in physical coordinates. + * @return + */ + protected boolean intersect( final RealInterval boundingBox ) + { + final ImageCanvas canvas = imp.getCanvas(); + if ( canvas == null ) + return false; + if ( toScreenX( boundingBox.realMin( 0 ) ) > canvas.getWidth() ) + return false; + if ( toScreenX( boundingBox.realMax( 0 ) ) < 0 ) + return false; + if ( toScreenY( boundingBox.realMin( 1 ) ) > canvas.getHeight() ) + return false; + if ( toScreenY( boundingBox.realMax( 1 ) ) < 0 ) + return false; + return true; } /** From 77c36038c7d017264d38ae659f55cdd1188e49a9 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 12 May 2023 18:43:16 +0200 Subject: [PATCH 137/263] After tracking, set spot color by track index, if spot coloring is default. --- .../gui/wizard/TrackMateWizardSequence.java | 2 +- .../descriptors/ExecuteTrackingDescriptor.java | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index 9ab663389..aae5465a8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -145,7 +145,7 @@ public TrackMateWizardSequence( final TrackMate trackmate, final SelectionModel initFilterDescriptor = new InitFilterDescriptor( trackmate, initialFilter ); spotFilterDescriptor = new SpotFilterDescriptor( trackmate, spotFilters, featureSelector ); chooseTrackerDescriptor = new ChooseTrackerDescriptor( new TrackerProvider(), trackmate ); - executeTrackingDescriptor = new ExecuteTrackingDescriptor( trackmate, logPanel ); + executeTrackingDescriptor = new ExecuteTrackingDescriptor( trackmate, logPanel, displaySettings ); trackFilterDescriptor = new TrackFilterDescriptor( trackmate, trackFilters, featureSelector ); configureViewsDescriptor = new ConfigureViewsDescriptor( displaySettings, diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ExecuteTrackingDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ExecuteTrackingDescriptor.java index 8d74b3481..f40d12c7e 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ExecuteTrackingDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ExecuteTrackingDescriptor.java @@ -28,7 +28,11 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.TrackModel; +import fiji.plugin.trackmate.features.FeatureUtils; +import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; import fiji.plugin.trackmate.gui.components.LogPanel; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; public class ExecuteTrackingDescriptor extends WizardPanelDescriptor @@ -38,11 +42,14 @@ public class ExecuteTrackingDescriptor extends WizardPanelDescriptor private final TrackMate trackmate; - public ExecuteTrackingDescriptor( final TrackMate trackmate, final LogPanel logPanel ) + private final DisplaySettings displaySettings; + + public ExecuteTrackingDescriptor( final TrackMate trackmate, final LogPanel logPanel, final DisplaySettings displaySettings ) { super( KEY ); this.trackmate = trackmate; this.targetPanel = logPanel; + this.displaySettings = displaySettings; } @Override @@ -66,6 +73,11 @@ public Runnable getForwardRunnable() logger.log( String.format( " - avg size: %.1f spots.\n", stats.getAverage() ) ); logger.log( String.format( " - min size: %d spots.\n", stats.getMin() ) ); logger.log( String.format( " - max size: %d spots.\n", stats.getMax() ) ); + + // Possibly tweak display settings: color spots by track id. + if ( displaySettings.getSpotColorByType() == TrackMateObject.DEFAULT ) + if ( displaySettings.getSpotColorByFeature().equals( FeatureUtils.USE_UNIFORM_COLOR_KEY ) ) + displaySettings.setSpotColorBy( TrackMateObject.TRACKS, TrackIndexAnalyzer.TRACK_INDEX ); }; } From 4e2769ba3fb14f17b698f117a197d8ec2ae8be29 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 12 May 2023 19:11:51 +0200 Subject: [PATCH 138/263] Rework the SpotMesh class. Simply make the mesh field private, accessible via a public method. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 14 +++++++++++++- .../features/spot/Spot3DFitEllipsoidAnalyzer.java | 2 +- .../features/spot/Spot3DShapeAnalyzer.java | 4 ++-- .../java/fiji/plugin/trackmate/io/TmXmlWriter.java | 2 +- .../fiji/plugin/trackmate/mesh/DebugZSlicer.java | 2 +- .../plugin/trackmate/mesh/ExportMeshForDemo.java | 2 +- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 10aaa9b39..367092ff6 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -30,7 +30,7 @@ public class SpotMesh extends SpotBase * (0, 0, 0) and the true position of its vertices is obtained by adding the * spot center. */ - public final Mesh mesh; + private final Mesh mesh; private Map< Integer, Slice > sliceMap; @@ -116,6 +116,18 @@ public SpotMesh( final int ID, final BufferMesh mesh ) this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); } + /** + * Exposes the mesh object stores in this spot. The coordinates of the + * vertices are relative to the spot center. That is: the coordinates are + * centered on (0,0,0). + * + * @return the mesh. + */ + public Mesh getMesh() + { + return mesh; + } + @Override public double realMax( final int d ) { diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java index 2080e55dc..ba363a07a 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java @@ -60,7 +60,7 @@ public void process( final Spot spot ) if ( spot instanceof SpotMesh ) { final SpotMesh sm = ( SpotMesh ) spot; - final EllipsoidFit fit = EllipsoidFitter.fit( sm.mesh ); + final EllipsoidFit fit = EllipsoidFitter.fit( sm.getMesh() ); x0 = fit.center.getDoublePosition( 0 ); y0 = fit.center.getDoublePosition( 1 ); z0 = fit.center.getDoublePosition( 2 ); diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java index 8ff80b8a1..64aa88096 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java @@ -58,12 +58,12 @@ public void process( final Spot spot ) if ( spot instanceof SpotMesh ) { final SpotMesh sm = ( SpotMesh ) spot; - final Mesh ch = convexHull.calculate( sm.mesh ); + final Mesh ch = convexHull.calculate( sm.getMesh() ); volume = sm.volume(); final double volumeCH = Meshes.volume( ch ); solidity = volume / volumeCH; - sa = surfaceArea.calculate( sm.mesh ).get(); + sa = surfaceArea.calculate( sm.getMesh() ).get(); final double saCH = surfaceArea.calculate( ch ).get(); convexity = sa / saCH; diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index c2639c99e..4a7f42c28 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -789,7 +789,7 @@ protected void writeSpotMeshes( final Iterable< Spot > spots ) { // Save mesh in true coordinates. final SpotMesh sm = ( SpotMesh ) spot; - final Mesh mesh = sm.mesh; + final Mesh mesh = sm.getMesh(); final Mesh translated = TranslateMesh.translate( mesh, spot ); final byte[] bs = PLY_MESH_IO.writeBinary( translated ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java index d23ba545d..94456b577 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java @@ -50,7 +50,7 @@ public static void main( final String[] args ) imp.setZ( ( int ) Math.round( z / calibration[ 2 ] ) + 1 ); - final Slice contours = ZSlicer.slice( ( ( SpotMesh ) spot ).mesh, z, calibration[ 2 ] ); + final Slice contours = ZSlicer.slice( ( ( SpotMesh ) spot ).getMesh(), z, calibration[ 2 ] ); System.out.println( "Found " + contours.size() + " contours." ); for ( final Contour contour : contours ) System.out.println( contour ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java index b247da01e..44b560382 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java @@ -51,7 +51,7 @@ public static void main( final String[] args ) if ( spot instanceof SpotMesh ) { final SpotMesh mesh = ( SpotMesh ) spot; - io.save( mesh.mesh, savePath ); + io.save( mesh.getMesh(), savePath ); } } System.out.println( "Export done." ); From 1155b5ec44fbc9284a2de2c5d27d85b551314e10 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 12 May 2023 20:09:10 +0200 Subject: [PATCH 139/263] WIP: An action to export all the meshes in a model to a PLY file series. So that they can be opened as a time series in ParaView. --- .../trackmate/action/MeshSeriesExporter.java | 177 ++++++++++++++++++ .../fiji/plugin/trackmate/io/IOUtils.java | 14 ++ 2 files changed, 191 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java diff --git a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java new file mode 100644 index 000000000..c870465fb --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java @@ -0,0 +1,177 @@ +/*- + * #%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.action; + +import static fiji.plugin.trackmate.gui.Icons.ORANGE_ASTERISK_ICON; + +import java.awt.Frame; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableSet; + +import javax.swing.ImageIcon; + +import org.scijava.plugin.Plugin; + +import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.io.IOUtils; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.io.ply.PLYMeshIO; +import net.imagej.mesh.nio.BufferMesh; +import net.imagej.mesh.obj.transform.TranslateMesh; + +public class MeshSeriesExporter extends AbstractTMAction +{ + + public static final String NAME = "Export spot 3D meshes to a file series"; + + public static final String KEY = "MESH_SERIES_EXPORTER"; + + public static final String INFO_TEXT = "" + + "Export the 3D meshes in the spot of the current model " + + "to a PLY file series. " + + "

" + + "A folder is created with the file name, in which " + + "there will be one PLY file per time-point. " + + "The series can be easily imported in mesh visualization " + + "softwares, such as ParaView. " + + "

" + + "Only the visible spots containing 3D meshes are exported. " + + "If there are no such spots, no file is created. " + + ""; + + @Override + public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + { + logger.log( "Exporting spot 3D meshes to a file series.\n" ); + final Model model = trackmate.getModel(); + File file; + final File folder = new File( System.getProperty( "user.dir" ) ).getParentFile().getParentFile(); + try + { + String filename = trackmate.getSettings().imageFileName; + filename = filename.substring( 0, filename.indexOf( "." ) ); + file = new File( folder.getPath() + File.separator + filename + "-meshes.ply" ); + } + catch ( final NullPointerException npe ) + { + file = new File( folder.getPath() + File.separator + "TrackMateMeshes.ply" ); + } + file = IOUtils.askForFileForSaving( file, parent ); + if ( null == file ) + { + logger.log( "Aborted.\n" ); + return; + } + + exportMeshesToFileSeries( model.getSpots(), file, logger ); + } + + public static void exportMeshesToFileSeries( final SpotCollection spots, final File file, final Logger logger ) + { + String folderName = file.getAbsolutePath(); + folderName = folderName.substring( 0, folderName.indexOf( "." ) ); + final File folder = new File( folderName ); + folder.mkdirs(); + + final PLYMeshIO io = new PLYMeshIO(); + + final NavigableSet< Integer > frames = spots.keySet(); + for ( final Integer frame : frames ) + { + String fileName = folder.getName(); + fileName = fileName.substring( 0, fileName.lastIndexOf( '.' ) ) + frame + ".ply"; + final File targetFile = new File( folder, fileName ); + + final List< Mesh > meshes = new ArrayList<>(); + for ( final Spot spot : spots.iterable( frame, true ) ) + { + if ( spot instanceof SpotMesh ) + { + final SpotMesh sm = ( SpotMesh ) spot; + meshes.add( TranslateMesh.translate( sm.getMesh(), spot ) ); + } + } + logger.log( " - Found " + meshes.size() + " meshes in frame " + frame + "." ); + final Mesh merged = Meshes.merge( meshes ); + final BufferMesh mesh = new BufferMesh( ( int ) merged.vertices().size(), ( int ) merged.triangles().size() ); + Meshes.calculateNormals( merged, mesh ); + try + { + io.save( mesh, targetFile.getAbsolutePath() ); + } + catch ( final IOException e ) + { + logger.error( "\nProblem writing to " + targetFile + '\n' + e.getMessage() + '\n' ); + e.printStackTrace(); + continue; + } + logger.log( " Saved.\n" ); + } + logger.log( "Done. Meshes saved to folder " + folder + '\n' ); + } + + @Plugin( type = TrackMateActionFactory.class, visible = true ) + public static class Factory implements TrackMateActionFactory + { + + @Override + public String getInfoText() + { + return INFO_TEXT; + } + + @Override + public String getName() + { + return NAME; + } + + @Override + public String getKey() + { + return KEY; + } + + @Override + public ImageIcon getIcon() + { + return ORANGE_ASTERISK_ICON; + } + + @Override + public TrackMateAction create() + { + return new MeshSeriesExporter(); + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java b/src/main/java/fiji/plugin/trackmate/io/IOUtils.java index 3a5b6cab7..958bba21f 100644 --- a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java +++ b/src/main/java/fiji/plugin/trackmate/io/IOUtils.java @@ -578,4 +578,18 @@ public static void marshallMap( final Map< String, Double > map, final Element e for ( final String key : map.keySet() ) element.setAttribute( key, map.get( key ).toString() ); } + + /** + * Possibly creates the whole directories needed to save a file with the + * specified path. + * + * @param path + * the path. + * @return true if folders have actually been created. + */ + public static boolean mkdirs( final String path ) + { + final File dir = new File( path ).getParentFile(); + return dir == null ? false : dir.mkdirs(); + } } From d3cdbc6d3701be01cb0970f94ac2ac669aeae404 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 15:54:32 +0200 Subject: [PATCH 140/263] Fix loading of SpotMeshes. --- src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index b5ced3626..8b29147d5 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -964,6 +964,11 @@ private SpotCollection getSpots( final Element modelElement ) spotMesh.copyFeaturesFrom( spot ); spotMesh.setName( spot.getName() ); cache.put( id, spotMesh ); + + // And in the content. + final Set< Spot > spots = content.get( spot.getFeature( Spot.FRAME ).intValue() ); + spots.remove( spot ); + spots.add( spotMesh ); } catch ( final IOException e ) { From 0aaf3025f9e2ce31857e49c72099dfe2aca7491c Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 16:01:22 +0200 Subject: [PATCH 141/263] Properly read 3d morphology analyzers declarations. --- .../fiji/plugin/trackmate/io/TmXmlReader.java | 29 ++++++++++++++----- .../TmXmlReaderTestDrive.java | 6 ++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 8b29147d5..0956d9836 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -145,6 +145,7 @@ import fiji.plugin.trackmate.providers.DetectorProvider; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot3DMorphologyAnalyzerProvider; import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackerProvider; @@ -433,7 +434,8 @@ public Settings readSettings( final ImagePlus imp ) new SpotAnalyzerProvider( ( imp == null ) ? 1 : imp.getNChannels() ), new EdgeAnalyzerProvider(), new TrackAnalyzerProvider(), - new Spot2DMorphologyAnalyzerProvider( ( imp == null ) ? 1 : imp.getNChannels() ) ); + new Spot2DMorphologyAnalyzerProvider( ( imp == null ) ? 1 : imp.getNChannels() ), + new Spot3DMorphologyAnalyzerProvider( ( imp == null ) ? 1 : imp.getNChannels() ) ); } /** @@ -464,6 +466,10 @@ public Settings readSettings( final ImagePlus imp ) * the track analyzer provider, required to instantiates the * saved {@link TrackAnalyzer}s. If null, will skip * reading track analyzers. + * @param spot2DMorphologyAnalyzerProvider + * the spot 2D morphology provider. + * @param spot3DMorphologyAnalyzerProvider + * the spot 3D morphology provider. */ public Settings readSettings( final ImagePlus imp, @@ -472,7 +478,8 @@ public Settings readSettings( final SpotAnalyzerProvider spotAnalyzerProvider, final EdgeAnalyzerProvider edgeAnalyzerProvider, final TrackAnalyzerProvider trackAnalyzerProvider, - final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider ) + final Spot2DMorphologyAnalyzerProvider spot2DMorphologyAnalyzerProvider, + final Spot3DMorphologyAnalyzerProvider spot3DMorphologyAnalyzerProvider ) { final Element settingsElement = root.getChild( SETTINGS_ELEMENT_KEY ); if ( null == settingsElement ) @@ -519,7 +526,8 @@ public Settings readSettings( spotAnalyzerProvider, edgeAnalyzerProvider, trackAnalyzerProvider, - spotMorphologyAnalyzerProvider ); + spot2DMorphologyAnalyzerProvider, + spot3DMorphologyAnalyzerProvider ); return settings; } @@ -1373,7 +1381,8 @@ private void readAnalyzers( final SpotAnalyzerProvider spotAnalyzerProvider, final EdgeAnalyzerProvider edgeAnalyzerProvider, final TrackAnalyzerProvider trackAnalyzerProvider, - final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider ) + final Spot2DMorphologyAnalyzerProvider spot2DMorphologyAnalyzerProvider, + final Spot3DMorphologyAnalyzerProvider spot3DMorphologyAnalyzerProvider ) { final Element analyzersEl = settingsElement.getChild( ANALYZER_COLLECTION_ELEMENT_KEY ); @@ -1424,11 +1433,15 @@ private void readAnalyzers( /* * Special case: if we cannot find a matching * analyzer for a declared factory, then we will try - * to see whether it is a morphology spot analyzer, - * that are treated separately. If it is not, we - * give up. + * to see whether it is a morphology spot analyzer + * in 2D then in 3D, that are treated separately. If + * it is not, we give up. */ - spotAnalyzer = spotMorphologyAnalyzerProvider.getFactory( key ); + spotAnalyzer = spot2DMorphologyAnalyzerProvider.getFactory( key ); + if ( spotAnalyzer == null ) + { + spotAnalyzer = spot3DMorphologyAnalyzerProvider.getFactory( key ); + } } if ( null == spotAnalyzer ) diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/TmXmlReaderTestDrive.java b/src/test/java/fiji/plugin/trackmate/interactivetests/TmXmlReaderTestDrive.java index c9dff0ef1..60b115ec3 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/TmXmlReaderTestDrive.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/TmXmlReaderTestDrive.java @@ -31,8 +31,9 @@ import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.providers.DetectorProvider; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; -import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot3DMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackerProvider; import ij.ImagePlus; @@ -57,7 +58,8 @@ public static void main( final String args[] ) new SpotAnalyzerProvider( imp.getNChannels() ), new EdgeAnalyzerProvider(), new TrackAnalyzerProvider(), - new Spot2DMorphologyAnalyzerProvider( imp.getNChannels() ) ); + new Spot2DMorphologyAnalyzerProvider( imp.getNChannels() ), + new Spot3DMorphologyAnalyzerProvider( imp.getNChannels() ) ); System.out.println( settings ); System.out.println( model ); From b07d1c688f469b924bf2269a14de2d3d94d8019f Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 16:06:37 +0200 Subject: [PATCH 142/263] Put back default coloring after clearing tracks in the UI if needed. --- .../gui/wizard/TrackMateWizardSequence.java | 2 +- .../descriptors/ChooseTrackerDescriptor.java | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index aae5465a8..f7e1ca4bb 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -144,7 +144,7 @@ public TrackMateWizardSequence( final TrackMate trackmate, final SelectionModel executeDetectionDescriptor = new ExecuteDetectionDescriptor( trackmate, logPanel ); initFilterDescriptor = new InitFilterDescriptor( trackmate, initialFilter ); spotFilterDescriptor = new SpotFilterDescriptor( trackmate, spotFilters, featureSelector ); - chooseTrackerDescriptor = new ChooseTrackerDescriptor( new TrackerProvider(), trackmate ); + chooseTrackerDescriptor = new ChooseTrackerDescriptor( new TrackerProvider(), trackmate, displaySettings ); executeTrackingDescriptor = new ExecuteTrackingDescriptor( trackmate, logPanel, displaySettings ); trackFilterDescriptor = new TrackFilterDescriptor( trackmate, trackFilters, featureSelector ); configureViewsDescriptor = new ConfigureViewsDescriptor( diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ChooseTrackerDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ChooseTrackerDescriptor.java index 7e01dbe10..345a34eb4 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ChooseTrackerDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ChooseTrackerDescriptor.java @@ -24,7 +24,10 @@ import java.util.Map; import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.gui.components.ModuleChooserPanel; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; import fiji.plugin.trackmate.io.SettingsPersistence; import fiji.plugin.trackmate.providers.TrackerProvider; @@ -40,11 +43,17 @@ public class ChooseTrackerDescriptor extends WizardPanelDescriptor private final TrackerProvider trackerProvider; - public ChooseTrackerDescriptor( final TrackerProvider trackerProvider, final TrackMate trackmate ) + private final DisplaySettings displaySettings; + + public ChooseTrackerDescriptor( + final TrackerProvider trackerProvider, + final TrackMate trackmate, + final DisplaySettings displaySettings ) { super( KEY ); this.trackmate = trackmate; this.trackerProvider = trackerProvider; + this.displaySettings = displaySettings; String selectedTracker = SimpleSparseLAPTrackerFactory.THIS2_TRACKER_KEY; // default if ( null != trackmate.getSettings().trackerFactory ) @@ -106,7 +115,12 @@ public void aboutToHidePanel() @Override public Runnable getBackwardRunnable() { - // Delete tracks. - return () -> trackmate.getModel().clearTracks( true ); + // Delete tracks and put back default coloring if needed. + return () -> { + if ( displaySettings.getSpotColorByType() == TrackMateObject.TRACKS + || displaySettings.getSpotColorByType() == TrackMateObject.EDGES ) + displaySettings.setSpotColorBy( TrackMateObject.DEFAULT, FeatureUtils.USE_UNIFORM_COLOR_KEY ); + trackmate.getModel().clearTracks( true ); + }; } } From 19666356f7cf93eaa4e69edccfd2cb90ecc3c511 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 16:21:48 +0200 Subject: [PATCH 143/263] Fix meshh series exporter. --- .../java/fiji/plugin/trackmate/action/MeshSeriesExporter.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java index c870465fb..57837ec26 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java @@ -108,10 +108,8 @@ public static void exportMeshesToFileSeries( final SpotCollection spots, final F final NavigableSet< Integer > frames = spots.keySet(); for ( final Integer frame : frames ) { - String fileName = folder.getName(); - fileName = fileName.substring( 0, fileName.lastIndexOf( '.' ) ) + frame + ".ply"; + final String fileName = folder.getName() + '_' + frame + ".ply"; final File targetFile = new File( folder, fileName ); - final List< Mesh > meshes = new ArrayList<>(); for ( final Spot spot : spots.iterable( frame, true ) ) { From cd07ac34389f28c84e4d2f5e3f7a548c376af362 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 17:44:10 +0200 Subject: [PATCH 144/263] Make a utility method public. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 367092ff6..9692b30e4 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -384,7 +384,7 @@ private static final Map< Integer, Slice > buildSliceMap( return sliceMap; } - private static final RealInterval toRealInterval( final float[] bb ) + public static final RealInterval toRealInterval( final float[] bb ) { return Intervals.createMinMaxReal( bb[ 0 ], bb[ 1 ], bb[ 2 ], bb[ 3 ], bb[ 4 ], bb[ 5 ] ); } From 8d7bb4c89eeab03d7016396995d1dd12ee34d00d Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 17:52:31 +0200 Subject: [PATCH 145/263] Rework the 2D and 3D detection utils. We have now 3D meshes that we get via the marching cubes. This algorithm behaves slightly differently for grayscale + threshold and mask images, so we have to implement that in TrackMate. First: split the shape-related methods of MaskUtils in two utility classes SpotRoiUtils and SpotMeshUtils so as to avoid having a single gigantic class. With grayscale thresholded image, the marching cube algorithm can return nice, smooth, interpolated meshes, which is what we want in a biological context. So there is now in SpotMeshUtils a method that exploits this in the following manner: The grayscale marching-cube algorithm is used to create one big mesh from the source image. It is then split in connected-components to create single spot objects. However, to deal with possible holes in objects, meshes are possibly re-merged based on full inclusion of their bounding-box. For instance, a hollow sphere would be represented by two connected-components, yielding two meshes. But because the small one isincluded in the big one, they are merged in this method so that the hole is properly included in the shape representation. When dealing with masks as inputs, we want to generate a mesh that can retrieve the exact pixel content of the mask when iterated. So a separate method convert the mask into a label image, and each of its connected-component is treated separately to generate a mesh. We will use the grayscale marching-cube algorithm, using a threshold value of 0.5 on the bit-masks resulting from connected-component analysis of the label image generated from the mask. --- .../detection/LabelImageDetector.java | 25 +- .../trackmate/detection/MaskDetector.java | 64 ++ .../plugin/trackmate/detection/MaskUtils.java | 1004 ++--------------- .../trackmate/detection/SpotMeshUtils.java | 361 ++++++ .../trackmate/detection/SpotRoiUtils.java | 707 ++++++++++++ .../detection/ThresholdDetector.java | 18 +- 6 files changed, 1250 insertions(+), 929 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java create mode 100644 src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java create mode 100644 src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index d89ba879c..42594b013 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -137,11 +137,30 @@ private < R extends IntegerType< R > > void processIntegerImg( final RandomAcces final ImgLabeling< Integer, R > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); if ( input.numDimensions() == 2 ) - spots = MaskUtils.from2DLabelingWithROI( labeling, interval, calibration, simplify, null ); + { + spots = SpotRoiUtils.from2DLabelingWithROI( + labeling, + interval, + calibration, + simplify, + null ); + } else if ( input.numDimensions() == 3 ) - spots = MaskUtils.from3DLabelingWithROI( labeling, interval, calibration, simplify, null ); + { + spots = SpotMeshUtils.from3DLabelingWithROI( + labeling, + interval, + calibration, + simplify, + null ); + } else - spots = MaskUtils.fromLabeling( labeling, interval, calibration ); + { + spots = MaskUtils.fromLabeling( + labeling, + interval, + calibration ); + } } @Override diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java new file mode 100644 index 000000000..63629f7be --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java @@ -0,0 +1,64 @@ +/*- + * #%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.detection; + +import net.imglib2.Interval; +import net.imglib2.RandomAccessible; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; + +public class MaskDetector< T extends RealType< T > & NativeType< T > > extends ThresholdDetector< T > +{ + + private final static String BASE_ERROR_MESSAGE = "MaskDetector: "; + + /* + * CONSTRUCTORS + */ + + public MaskDetector( + final RandomAccessible< T > input, + final Interval interval, + final double[] calibration, + final boolean simplify ) + { + super( input, interval, calibration, Double.NaN, simplify ); + baseErrorMessage = BASE_ERROR_MESSAGE; + } + + + @Override + public boolean process() + { + final long start = System.currentTimeMillis(); + spots = MaskUtils.fromMaskWithROI( + input, + interval, + calibration, + simplify, + numThreads, + null ); + final long end = System.currentTimeMillis(); + this.processingTime = end - start; + return true; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 636a644a4..d41c20459 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -21,29 +21,15 @@ */ package fiji.plugin.trackmate.detection; -import java.awt.Polygon; import java.util.ArrayList; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.concurrent.ExecutorService; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotBase; -import fiji.plugin.trackmate.SpotMesh; -import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.util.Threads; -import ij.gui.PolygonRoi; -import ij.process.FloatPolygon; -import net.imagej.ImgPlus; -import net.imagej.axis.Axes; -import net.imagej.axis.AxisType; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.Vertices; import net.imglib2.Interval; -import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; @@ -59,9 +45,7 @@ import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegionCursor; import net.imglib2.roi.labeling.LabelRegions; -import net.imglib2.type.BooleanType; import net.imglib2.type.logic.BitType; -import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; @@ -72,12 +56,6 @@ public class MaskUtils { - /** Smoothing interval for ROIs. */ - private static final double SMOOTH_INTERVAL = 2.; - - /** Douglas-Peucker polygon simplification max distance. */ - private static final double DOUGLAS_PEUCKER_MAX_DISTANCE = 0.5; - public static final < T extends RealType< T > > double otsuThreshold( final RandomAccessibleInterval< T > img ) { // Min & max @@ -310,7 +288,7 @@ public static < R extends IntegerType< R > > List< Spot > fromLabeling( volume *= calibration[ d ]; final double radius = ( labeling.numDimensions() == 2 ) ? Math.sqrt( volume / Math.PI ) - : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); + : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); final double quality = region.size(); spots.add( new SpotBase( x, y, z, radius, quality ) ); } @@ -319,9 +297,9 @@ public static < R extends IntegerType< R > > List< Spot > fromLabeling( } /** - * Creates spots from a grayscale image, thresholded to create a mask. A - * spot is created for each connected-component of the mask, with a size - * that matches the mask size. The quality of the spots is read from another + * Creates spots by thresholding a grayscale image. A spot is created for + * each connected-component object in the thresholded input, with a size that + * matches the mask size. The quality of the spots is read from another * image, by taking the max pixel value of this image with the ROI. * * @param @@ -398,7 +376,7 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > final double radius = ( labeling.numDimensions() == 2 ) ? Math.sqrt( volume / Math.PI ) - : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); + : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); spots.add( new SpotBase( x, y, z, radius, quality ) ); } @@ -407,23 +385,23 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > /** * Creates spots with their ROIs or meshes from a 2D or 3D - * grayscale image, thresholded to create a mask. A spot is created for each - * connected-component of the mask, with a size that matches the mask size. - * The quality of the spots is read from another image, by taking the max - * pixel value of this image with the ROI. + * mask. A spot is created for each connected-component of the mask, with a + * size that matches the mask size. The quality of the spots is read from + * another image, by taking the max pixel value of this image with the ROI. * * @param * the type of the input image. Must be real, scalar. * @param * the type of the quality image. Must be real, scalar. * @param input - * the input image. Can be 2D or 3D. + * the input mask image. Can be 2D or 3D. It does not have to be + * of boolean type: every pixel with a real value strictly larger + * than 0.5 will be considered true and + * false otherwise. * @param interval * the interval in the input image to analyze. * @param calibration * the physical calibration. - * @param threshold - * the threshold to apply to the input image. * @param simplify * if true the polygon will be post-processed to be * smoother and contain less points. @@ -433,361 +411,118 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > * the image in which to read the quality value. * @return a list of spots, with ROI. */ - public static final < T extends RealType< T >, S extends RealType< S > > List< Spot > fromThresholdWithROI( - final RandomAccessible< T > input, - final Interval interval, - final double[] calibration, - final double threshold, - final boolean simplify, - final int numThreads, + public static < T extends RealType< T >, S extends RealType< S > > List< Spot > fromMaskWithROI( + final RandomAccessible< T > input, + final Interval interval, + final double[] calibration, + final boolean simplify, + final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { - // Get labeling. - final ImgLabeling< Integer, IntType > labeling = toLabeling( input, interval, threshold, numThreads ); - - // Process it. + final ImgLabeling< Integer, IntType > labeling = toLabeling( + input, + interval, + .5, + numThreads ); if ( input.numDimensions() == 2 ) - return from2DLabelingWithROI( labeling, interval, calibration, simplify, qualityImage ); + { + return SpotRoiUtils.from2DLabelingWithROI( + labeling, + interval, + calibration, + simplify, + qualityImage ); + } else if ( input.numDimensions() == 3 ) - return from3DLabelingWithROI( labeling, interval, calibration, simplify, qualityImage ); - else - throw new IllegalArgumentException( "Can only process 2D or 3D images with this method, but got " + labeling.numDimensions() + "D." ); - } - - /** - * Creates spots with ROIs from a 2D label image. The quality - * value is read from a secondary image, by taking the max value in each - * ROI. - * - * @param - * the type that backs-up the labeling. - * @param - * the type of the quality image. Must be real, scalar. - * @param labeling - * the labeling, must be zero-min and 2D.. - * @param interval - * the interval, used to reposition the spots from the zero-min - * labeling to the proper coordinates. - * @param calibration - * the physical calibration. - * @param simplify - * if true the polygon will be post-processed to be - * smoother and contain less points. - * @param qualityImage - * the image in which to read the quality value. - * @return a list of spots, with ROI. - */ - public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from2DLabelingWithROI( - final ImgLabeling< Integer, R > labeling, - final Interval interval, - final double[] calibration, - final boolean simplify, - final RandomAccessibleInterval< S > qualityImage ) - { - final Map< Integer, List< Spot > > map = from2DLabelingWithROIMap( labeling, interval, calibration, simplify, qualityImage ); - final List< Spot > spots = new ArrayList<>(); - for ( final List< Spot > s : map.values() ) - spots.addAll( s ); - - return spots; - } - - /** - * Creates spots with ROIs from a 2D label image. The quality - * value is read from a secondary image, by taking the max value in each - * ROI. - *

- * The spots are returned in a map, where the key is the integer value of - * the label they correspond to in the label image. Because one spot - * corresponds to one connected component in the label image, there might be - * several spots for a label, hence the values of the map are list of spots. - * - * @param - * the type that backs-up the labeling. - * @param - * the type of the quality image. Must be real, scalar. - * @param labeling - * the labeling, must be zero-min and 2D.. - * @param interval - * the interval, used to reposition the spots from the zero-min - * labeling to the proper coordinates. - * @param calibration - * the physical calibration. - * @param simplify - * if true the polygon will be post-processed to be - * smoother and contain less points. - * @param qualityImage - * the image in which to read the quality value. - * @return a map linking the label integer value to the list of spots, with - * ROI, it corresponds to. - */ - public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > from2DLabelingWithROIMap( - final ImgLabeling< Integer, R > labeling, - final Interval interval, - final double[] calibration, - final boolean simplify, - final RandomAccessibleInterval< S > qualityImage ) - { - if ( labeling.numDimensions() != 2 ) - throw new IllegalArgumentException( "Can only process 2D images with this method, but got " + labeling.numDimensions() + "D." ); - - final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); - - /* - * Map of label in the label image to a collection of polygons around - * this label. Because 1 polygon correspond to 1 connected component, - * there might be several polygons for a label. - */ - final Map< Integer, List< Polygon > > polygonsMap = new HashMap<>( regions.getExistingLabels().size() ); - final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); - // Parse regions to create polygons on boundaries. - while ( iterator.hasNext() ) { - final LabelRegion< Integer > region = iterator.next(); - // Analyze in zero-min region. - final List< Polygon > pp = maskToPolygons( Views.zeroMin( region ) ); - // Translate back to interval coords. - for ( final Polygon polygon : pp ) - polygon.translate( ( int ) region.min( 0 ), ( int ) region.min( 1 ) ); - - final Integer label = region.getLabel(); - polygonsMap.put( label, pp ); + return SpotMeshUtils.from3DLabelingWithROI( + labeling, + interval, + calibration, + simplify, + qualityImage ); } - - // Storage for results. - final Map< Integer, List< Spot > > output = new HashMap<>( polygonsMap.size() ); - - // Simplify them and compute a quality. - for ( final Integer label : polygonsMap.keySet() ) + else { - final List< Spot > spots = new ArrayList<>( polygonsMap.size() ); - output.put( label, spots ); - - final List< Polygon > polygons = polygonsMap.get( label ); - for ( final Polygon polygon : polygons ) - { - final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON ); - - // Create Spot ROI. - final PolygonRoi fRoi; - if ( simplify ) - fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE ); - else - fRoi = roi; - - // Don't include ROIs that have been shrunk to < 1 pixel. - if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) - continue; - - final Polygon fPolygon = fRoi.getPolygon(); - final double[] xpoly = new double[ fPolygon.npoints ]; - final double[] ypoly = new double[ fPolygon.npoints ]; - for ( int i = 0; i < fPolygon.npoints; i++ ) - { - xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 ); - ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 ); - } - - final Spot spot = SpotRoi.createSpot( xpoly, ypoly, -1. ); - - // Measure quality. - final double quality; - if ( null == qualityImage ) - { - quality = fRoi.getStatistics().area; - } - else - { - final String name = "QualityImage"; - final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y }; - final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] }; - final String[] units = new String[] { "unitX", "unitY" }; - final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units ); - final IterableInterval< S > iterable = spot.iterable( qualityImgPlus ); - double max = Double.NEGATIVE_INFINITY; - for ( final S s : iterable ) - { - final double val = s.getRealDouble(); - if ( val > max ) - max = val; - } - quality = max; - } - spot.putFeature( Spot.QUALITY, quality ); - spots.add( spot ); - } + throw new IllegalArgumentException( "Can only process 2D or 3D images with this method, but got " + input.numDimensions() + "D." ); } - return output; } /** - * Creates spots with meshes from a 3D label image. The - * quality value is read from a secondary image, by taking the max value in - * each ROI. + * Creates spots with their ROIs or meshes from a 2D or 3D by + * thresholding a grayscale image. A spot is created for each object in the + * thresholded image. The quality of the spots is read from another image, + * by taking the max pixel value of this image with the ROI. * - * @param - * the type that backs-up the labeling. + * @param + * the type of the input image. Must be real, scalar. * @param * the type of the quality image. Must be real, scalar. - * @param labeling - * the labeling, must be zero-min and 3D. + * @param input + * the input image. Can be 2D or 3D. * @param interval - * the interval, used to reposition the spots from the zero-min - * labeling to the proper coordinates. + * the interval in the input image to analyze. * @param calibration * the physical calibration. + * @param threshold + * the threshold to apply to the input image. * @param simplify - * if true the meshes will be post-processed to be + * if true the polygon will be post-processed to be * smoother and contain less points. + * @param numThreads + * how many threads to use for multithreaded computation. * @param qualityImage * the image in which to read the quality value. - * @return a list of spots, with meshes. + * @return a list of spots, with ROI. */ - public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from3DLabelingWithROI( - final ImgLabeling< Integer, R > labeling, + public static final < T extends RealType< T >, S extends RealType< S > > List< Spot > fromThresholdWithROI( + final RandomAccessible< T > input, final Interval interval, final double[] calibration, + final double threshold, final boolean simplify, + final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { - if ( labeling.numDimensions() != 3 ) - throw new IllegalArgumentException( "Can only process 3D images with this method, but got " + labeling.numDimensions() + "D." ); - - // Parse regions to create meshes on label. - final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); - final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); - final List< Spot > spots = new ArrayList<>( regions.getExistingLabels().size() ); - while ( iterator.hasNext() ) - { - final LabelRegion< Integer > region = iterator.next(); - final Spot spot = regionToSpotMesh( region, simplify, calibration, qualityImage, interval.minAsDoubleArray() ); - if ( spot == null ) - continue; - - spots.add( spot ); - } - return spots; - } - - private static final double distanceSquaredBetweenPoints( final double vx, final double vy, final double wx, final double wy ) - { - final double deltax = ( vx - wx ); - final double deltay = ( vy - wy ); - return deltax * deltax + deltay * deltay; - } - - private static final double distanceToSegmentSquared( final double px, final double py, final double vx, final double vy, final double wx, final double wy ) - { - final double l2 = distanceSquaredBetweenPoints( vx, vy, wx, wy ); - if ( l2 == 0 ) - return distanceSquaredBetweenPoints( px, py, vx, vy ); - final double t = ( ( px - vx ) * ( wx - vx ) + ( py - vy ) * ( wy - vy ) ) / l2; - if ( t < 0 ) - return distanceSquaredBetweenPoints( px, py, vx, vy ); - if ( t > 1 ) - return distanceSquaredBetweenPoints( px, py, wx, wy ); - return distanceSquaredBetweenPoints( px, py, ( vx + t * ( wx - vx ) ), ( vy + t * ( wy - vy ) ) ); - } - - private static final double perpendicularDistance( final double px, final double py, final double vx, final double vy, final double wx, final double wy ) - { - return Math.sqrt( distanceToSegmentSquared( px, py, vx, vy, wx, wy ) ); - } - - private static final void douglasPeucker( final List< double[] > list, final int s, final int e, final double epsilon, final List< double[] > resultList ) - { - // Find the point with the maximum distance - double dmax = 0; - int index = 0; - - final int start = s; - final int end = e - 1; - for ( int i = start + 1; i < end; i++ ) + if ( input.numDimensions() == 2 ) { - // Point - final double px = list.get( i )[ 0 ]; - final double py = list.get( i )[ 1 ]; - // Start - final double vx = list.get( start )[ 0 ]; - final double vy = list.get( start )[ 1 ]; - // End - final double wx = list.get( end )[ 0 ]; - final double wy = list.get( end )[ 1 ]; - final double d = perpendicularDistance( px, py, vx, vy, wx, wy ); - if ( d > dmax ) - { - index = i; - dmax = d; - } + /* + * In 2D: Threshold, make a labeling, then create contours. + */ + final ImgLabeling< Integer, IntType > labeling = toLabeling( + input, + interval, + threshold, + numThreads ); + return SpotRoiUtils.from2DLabelingWithROI( + labeling, + interval, + calibration, + simplify, + qualityImage ); } - // If max distance is greater than epsilon, recursively simplify - if ( dmax > epsilon ) + else if ( input.numDimensions() == 3 ) { - // Recursive call - douglasPeucker( list, s, index, epsilon, resultList ); - douglasPeucker( list, index, e, epsilon, resultList ); + /* + * In 3D: Directly operate on grayscale to create a big mesh, + * separate it in connected components, remerge them based on + * bounding-box before creating spots. We want to use the grayscale + * version of marching-cubes to have nice, smooth meshes. + */ + return SpotMeshUtils.from3DThresholdWithROI( + input, + interval, + calibration, + threshold, + simplify, + qualityImage ); } else { - if ( ( end - start ) > 0 ) - { - resultList.add( list.get( start ) ); - resultList.add( list.get( end ) ); - } - else - { - resultList.add( list.get( start ) ); - } + throw new IllegalArgumentException( "Can only process 2D or 3D images with this method, but got " + input.numDimensions() + "D." ); } } - /** - * Given a curve composed of line segments find a similar curve with fewer - * points. - *

- * The Ramer–Douglas–Peucker algorithm (RDP) is an algorithm for reducing - * the number of points in a curve that is approximated by a series of - * points. - *

- * - * @see Ramer–Douglas–Peucker - * Algorithm (Wikipedia) - * @author Justin Wetherell - * @param list - * List of double[] points (x,y) - * @param epsilon - * Distance dimension - * @return Similar curve with fewer points - */ - public static final List< double[] > douglasPeucker( final List< double[] > list, final double epsilon ) - { - final List< double[] > resultList = new ArrayList<>(); - douglasPeucker( list, 0, list.size(), epsilon, resultList ); - return resultList; - } - - public static final PolygonRoi simplify( final PolygonRoi roi, final double smoothInterval, final double epsilon ) - { - final FloatPolygon fPoly = roi.getInterpolatedPolygon( smoothInterval, true ); - - final List< double[] > points = new ArrayList<>( fPoly.npoints ); - for ( int i = 0; i < fPoly.npoints; i++ ) - points.add( new double[] { fPoly.xpoints[ i ], fPoly.ypoints[ i ] } ); - - final List< double[] > simplifiedPoints = douglasPeucker( points, epsilon ); - final float[] sX = new float[ simplifiedPoints.size() ]; - final float[] sY = new float[ simplifiedPoints.size() ]; - for ( int i = 0; i < sX.length; i++ ) - { - sX[ i ] = ( float ) simplifiedPoints.get( i )[ 0 ]; - sY[ i ] = ( float ) simplifiedPoints.get( i )[ 1 ]; - } - final FloatPolygon simplifiedPolygon = new FloatPolygon( sX, sY ); - final PolygonRoi fRoi = new PolygonRoi( simplifiedPolygon, PolygonRoi.POLYGON ); - return fRoi; - } - /** * Start at 1. * @@ -814,567 +549,4 @@ public boolean hasNext() } }; } - - /** - * Parse a 2D mask and return a list of polygons for the external contours - * of white objects. - *

- * Warning: cannot deal with holes, they are simply ignored. - *

- * Copied and adapted from ImageJ1 code by Wayne Rasband. - * - * @param - * the type of the mask. - * @param mask - * the mask image. - * @return a new list of polygons. - */ - private static final < T extends BooleanType< T > > List< Polygon > maskToPolygons( final RandomAccessibleInterval< T > mask ) - { - final int w = ( int ) mask.dimension( 0 ); - final int h = ( int ) mask.dimension( 1 ); - final RandomAccess< T > ra = mask.randomAccess( mask ); - - final List< Polygon > polygons = new ArrayList<>(); - boolean[] prevRow = new boolean[ w + 2 ]; - boolean[] thisRow = new boolean[ w + 2 ]; - final Outline[] outline = new Outline[ w + 1 ]; - - for ( int y = 0; y <= h; y++ ) - { - ra.setPosition( y, 1 ); - - final boolean[] b = prevRow; - prevRow = thisRow; - thisRow = b; - int xAfterLowerRightCorner = -1; - Outline oAfterLowerRightCorner = null; - - ra.setPosition( 0, 0 ); - thisRow[ 1 ] = y < h ? ra.get().get() : false; - - for ( int x = 0; x <= w; x++ ) - { - // we need to read one pixel ahead - ra.setPosition( x + 1, 0 ); - if ( y < h && x < w - 1 ) - thisRow[ x + 2 ] = ra.get().get(); - else if ( x < w - 1 ) - thisRow[ x + 2 ] = false; - - if ( thisRow[ x + 1 ] ) - { // i.e., pixel (x,y) is selected - if ( !prevRow[ x + 1 ] ) - { - // Upper edge of selected area: - // - left and right outlines are null: new outline - // - left null: append (line to left) - // - right null: prepend (line to right), or - // prepend&append (after lower right corner, two borders - // from one corner) - // - left == right: close (end of hole above) unless we - // can continue at the right - // - left != right: merge (prepend) unless we can - // continue at the right - if ( outline[ x ] == null ) - { - if ( outline[ x + 1 ] == null ) - { - outline[ x + 1 ] = outline[ x ] = new Outline(); - outline[ x ].append( x + 1, y ); - outline[ x ].append( x, y ); - } - else - { - outline[ x ] = outline[ x + 1 ]; - outline[ x + 1 ] = null; - outline[ x ].append( x, y ); - } - } - else if ( outline[ x + 1 ] == null ) - { - if ( x == xAfterLowerRightCorner ) - { - outline[ x + 1 ] = outline[ x ]; - outline[ x ] = oAfterLowerRightCorner; - outline[ x ].append( x, y ); - outline[ x + 1 ].prepend( x + 1, y ); - } - else - { - outline[ x + 1 ] = outline[ x ]; - outline[ x ] = null; - outline[ x + 1 ].prepend( x + 1, y ); - } - } - else if ( outline[ x + 1 ] == outline[ x ] ) - { - if ( x < w - 1 && y < h && x != xAfterLowerRightCorner - && !thisRow[ x + 2 ] && prevRow[ x + 2 ] ) - { // at lower right corner & next pxl deselected - outline[ x ] = null; - // outline[x+1] unchanged - outline[ x + 1 ].prepend( x + 1, y ); - xAfterLowerRightCorner = x + 1; - oAfterLowerRightCorner = outline[ x + 1 ]; - } - else - { - // MINUS (add inner hole) - // We cannot handle holes in TrackMate. -// polygons.add( outline[ x ].getPolygon() ); - outline[ x + 1 ] = null; - outline[ x ] = ( x == xAfterLowerRightCorner ) ? oAfterLowerRightCorner : null; - } - } - else - { - outline[ x ].prepend( outline[ x + 1 ] ); - for ( int x1 = 0; x1 <= w; x1++ ) - if ( x1 != x + 1 && outline[ x1 ] == outline[ x + 1 ] ) - { - outline[ x1 ] = outline[ x ]; - outline[ x + 1 ] = null; - outline[ x ] = ( x == xAfterLowerRightCorner ) ? oAfterLowerRightCorner : null; - break; - } - if ( outline[ x + 1 ] != null ) - throw new RuntimeException( "assertion failed" ); - } - } - if ( !thisRow[ x ] ) - { - // left edge - if ( outline[ x ] == null ) - throw new RuntimeException( "assertion failed" ); - outline[ x ].append( x, y + 1 ); - } - } - else - { // !thisRow[x + 1], i.e., pixel (x,y) is deselected - if ( prevRow[ x + 1 ] ) - { - // Lower edge of selected area: - // - left and right outlines are null: new outline - // - left == null: prepend - // - right == null: append, or append&prepend (after - // lower right corner, two borders from one corner) - // - right == left: close unless we can continue at the - // right - // - right != left: merge (append) unless we can - // continue at the right - if ( outline[ x ] == null ) - { - if ( outline[ x + 1 ] == null ) - { - outline[ x ] = outline[ x + 1 ] = new Outline(); - outline[ x ].append( x, y ); - outline[ x ].append( x + 1, y ); - } - else - { - outline[ x ] = outline[ x + 1 ]; - outline[ x + 1 ] = null; - outline[ x ].prepend( x, y ); - } - } - else if ( outline[ x + 1 ] == null ) - { - if ( x == xAfterLowerRightCorner ) - { - outline[ x + 1 ] = outline[ x ]; - outline[ x ] = oAfterLowerRightCorner; - outline[ x ].prepend( x, y ); - outline[ x + 1 ].append( x + 1, y ); - } - else - { - outline[ x + 1 ] = outline[ x ]; - outline[ x ] = null; - outline[ x + 1 ].append( x + 1, y ); - } - } - else if ( outline[ x + 1 ] == outline[ x ] ) - { - // System.err.println("add " + outline[x]); - if ( x < w - 1 && y < h && x != xAfterLowerRightCorner - && thisRow[ x + 2 ] && !prevRow[ x + 2 ] ) - { // at lower right corner & next pxl selected - outline[ x ] = null; - // outline[x+1] unchanged - outline[ x + 1 ].append( x + 1, y ); - xAfterLowerRightCorner = x + 1; - oAfterLowerRightCorner = outline[ x + 1 ]; - } - else - { - polygons.add( outline[ x ].getPolygon() ); - outline[ x + 1 ] = null; - outline[ x ] = x == xAfterLowerRightCorner ? oAfterLowerRightCorner : null; - } - } - else - { - if ( x < w - 1 && y < h && x != xAfterLowerRightCorner - && thisRow[ x + 2 ] && !prevRow[ x + 2 ] ) - { // at lower right corner && next pxl selected - outline[ x ].append( x + 1, y ); - outline[ x + 1 ].prepend( x + 1, y ); - xAfterLowerRightCorner = x + 1; - oAfterLowerRightCorner = outline[ x ]; - // outline[x + 1] unchanged (the one at the - // right-hand side of (x, y-1) to the top) - outline[ x ] = null; - } - else - { - outline[ x ].append( outline[ x + 1 ] ); // merge - for ( int x1 = 0; x1 <= w; x1++ ) - if ( x1 != x + 1 && outline[ x1 ] == outline[ x + 1 ] ) - { - outline[ x1 ] = outline[ x ]; - outline[ x + 1 ] = null; - outline[ x ] = ( x == xAfterLowerRightCorner ) ? oAfterLowerRightCorner : null; - break; - } - if ( outline[ x + 1 ] != null ) - throw new RuntimeException( "assertion failed" ); - } - } - } - if ( thisRow[ x ] ) - { - // right edge - if ( outline[ x ] == null ) - throw new RuntimeException( "assertion failed" ); - outline[ x ].prepend( x, y + 1 ); - } - } - } - } - return polygons; - } - - /** - * This class implements a Cartesian polygon in progress. The edges are - * supposed to be parallel to the x or y axis. It is implemented as a deque - * to be able to add points to both sides. - */ - private static class Outline - { - - private int[] x, y; - - private int first, last, reserved; - - /** - * Default extra (spare) space when enlarging arrays (similar - * performance with 6-20) - */ - private final int GROW = 10; - - public Outline() - { - reserved = GROW; - x = new int[ reserved ]; - y = new int[ reserved ]; - first = last = GROW / 2; - } - - /** - * Makes sure that enough free space is available at the beginning and - * end of the list, by enlarging the arrays if required - */ - private void needs( final int neededAtBegin, final int neededAtEnd ) - { - if ( neededAtBegin > first || neededAtEnd > reserved - last ) - { - final int extraSpace = Math.max( GROW, Math.abs( x[ last - 1 ] - x[ first ] ) ); - final int newSize = reserved + neededAtBegin + neededAtEnd + extraSpace; - final int newFirst = neededAtBegin + extraSpace / 2; - final int[] newX = new int[ newSize ]; - final int[] newY = new int[ newSize ]; - System.arraycopy( x, first, newX, newFirst, last - first ); - System.arraycopy( y, first, newY, newFirst, last - first ); - x = newX; - y = newY; - last += newFirst - first; - first = newFirst; - reserved = newSize; - } - } - - /** Adds point x, y at the end of the list */ - public void append( final int x, final int y ) - { - if ( last - first >= 2 && collinear( this.x[ last - 2 ], this.y[ last - 2 ], this.x[ last - 1 ], this.y[ last - 1 ], x, y ) ) - { - this.x[ last - 1 ] = x; // replace previous point - this.y[ last - 1 ] = y; - } - else - { - needs( 0, 1 ); // new point - this.x[ last ] = x; - this.y[ last ] = y; - last++; - } - } - - /** Adds point x, y at the beginning of the list */ - public void prepend( final int x, final int y ) - { - if ( last - first >= 2 && collinear( this.x[ first + 1 ], this.y[ first + 1 ], this.x[ first ], this.y[ first ], x, y ) ) - { - this.x[ first ] = x; // replace previous point - this.y[ first ] = y; - } - else - { - needs( 1, 0 ); // new point - first--; - this.x[ first ] = x; - this.y[ first ] = y; - } - } - - /** - * Merge with another Outline by adding it at the end. Thereafter, the - * other outline must not be used any more. - */ - public void append( final Outline o ) - { - final int size = last - first; - final int oSize = o.last - o.first; - if ( size <= o.first && oSize > reserved - last ) - { // we don't have enough space in our own array but in that of 'o' - System.arraycopy( x, first, o.x, o.first - size, size ); - System.arraycopy( y, first, o.y, o.first - size, size ); - x = o.x; - y = o.y; - first = o.first - size; - last = o.last; - reserved = o.reserved; - } - else - { // append to our own array - needs( 0, oSize ); - System.arraycopy( o.x, o.first, x, last, oSize ); - System.arraycopy( o.y, o.first, y, last, oSize ); - last += oSize; - } - } - - /** - * Merge with another Outline by adding it at the beginning. Thereafter, - * the other outline must not be used any more. - */ - public void prepend( final Outline o ) - { - final int size = last - first; - final int oSize = o.last - o.first; - if ( size <= o.reserved - o.last && oSize > first ) - { /* - * We don't have enough space in our own array but in that of - * 'o' so append our own data to that of 'o' - */ - System.arraycopy( x, first, o.x, o.last, size ); - System.arraycopy( y, first, o.y, o.last, size ); - x = o.x; - y = o.y; - first = o.first; - last = o.last + size; - reserved = o.reserved; - } - else - { // prepend to our own array - needs( oSize, 0 ); - first -= oSize; - System.arraycopy( o.x, o.first, x, first, oSize ); - System.arraycopy( o.y, o.first, y, first, oSize ); - } - } - - public Polygon getPolygon() - { - /* - * optimize out intermediate points of straight lines (created, - * e.g., by merging outlines) - */ - int i, j = first + 1; - for ( i = first + 1; i + 1 < last; j++ ) - { - if ( collinear( x[ j - 1 ], y[ j - 1 ], x[ j ], y[ j ], x[ j + 1 ], y[ j + 1 ] ) ) - { - // merge i + 1 into i - last--; - continue; - } - if ( i != j ) - { - x[ i ] = x[ j ]; - y[ i ] = y[ j ]; - } - i++; - } - // wraparound - if ( collinear( x[ j - 1 ], y[ j - 1 ], x[ j ], y[ j ], x[ first ], y[ first ] ) ) - last--; - else - { - x[ i ] = x[ j ]; - y[ i ] = y[ j ]; - } - if ( last - first > 2 && collinear( x[ last - 1 ], y[ last - 1 ], x[ first ], y[ first ], x[ first + 1 ], y[ first + 1 ] ) ) - first++; - - final int count = last - first; - final int[] xNew = new int[ count ]; - final int[] yNew = new int[ count ]; - System.arraycopy( x, first, xNew, 0, count ); - System.arraycopy( y, first, yNew, 0, count ); - return new Polygon( xNew, yNew, count ); - } - - /** Returns whether three points are on one straight line */ - public boolean collinear( final int x1, final int y1, final int x2, final int y2, final int x3, final int y3 ) - { - return ( x2 - x1 ) * ( y3 - y2 ) == ( y2 - y1 ) * ( x3 - x2 ); - } - - @Override - public String toString() - { - String res = "[first:" + first + ",last:" + last + - ",reserved:" + reserved + ":"; - if ( last > x.length ) - System.err.println( "ERROR!" ); - int nmax = 10; // don't print more coordinates than this - for ( int i = first; i < last && i < x.length; i++ ) - { - if ( last - first > nmax && i - first > nmax / 2 ) - { - i = last - nmax / 2; - res += "..."; - nmax = last - first; // dont check again - } - else - res += "(" + x[ i ] + "," + y[ i ] + ")"; - } - return res + "]"; - } - } - - private static void scale( final Vertices vertices, final double[] scale, final double[] origin ) - { - final long nv = vertices.size(); - for ( long i = 0; i < nv; i++ ) - { - final double x = ( origin[ 0 ] + vertices.x( i ) ) * scale[ 0 ]; - final double y = ( origin[ 1 ] + vertices.y( i ) ) * scale[ 1 ]; - final double z = ( origin[ 2 ] + vertices.z( i ) ) * scale[ 2 ]; - vertices.set( i, x, y, z ); - } - } - - /** - * Returns a new {@link Spot} with a {@link SpotMesh} as shape, built from - * the specified bit-mask. - * - * @param - * the type of pixels in the quality image. - * @param region - * the bit-mask to build the mesh from. - * @param simplify - * if true the mesh will be simplified. - * @param calibration - * the pixel size array, used to scale the mesh to physical - * coordinates. - * @param qualityImage - * an image from which to read the quality value. If not - * null, the quality of the spot will be the max - * value of this image inside the mesh. If null, the - * quality will be the mesh volume. - * @param minInterval - * the origin in image coordinates of the ROI used for detection. - * - * @return a new spot. - */ - private static < S extends RealType< S > > Spot regionToSpotMesh( - final RandomAccessibleInterval< BoolType > region, - final boolean simplify, - final double[] calibration, - final RandomAccessibleInterval< S > qualityImage, - final double[] minInterval ) - { - // To mesh. - final IntervalView< BoolType > box = Views.zeroMin( region ); - final Mesh mesh = Meshes.marchingCubes( box, 0.5 ); - final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, VERTEX_DUPLICATE_REMOVAL_PRECISION ); - final Mesh simplified; - if (simplify) - { - // Dont't go below a certain number of triangles. - final int nTriangles = ( int ) cleaned.triangles().size(); - if ( nTriangles < MIN_N_TRIANGLES ) - { - simplified = cleaned; - } - else - { - // Crude heuristics. - final float targetRatio; - if ( nTriangles < 2 * MIN_N_TRIANGLES ) - targetRatio = 0.5f; - else if ( nTriangles < 10_000 ) - targetRatio = 0.2f; - else if ( nTriangles < 1_000_000 ) - targetRatio = 0.1f; - else - targetRatio = 0.05f; - simplified = Meshes.simplify( cleaned, targetRatio, SIMPLIFY_AGGRESSIVENESS ); - } - } - else - { - simplified = cleaned; - } - // Remove meshes that are too small - final double volumeThreshold = MIN_MESH_PIXEL_VOLUME * calibration[ 0 ] * calibration[ 1 ] * calibration[ 2 ]; - if ( SpotMesh.volume( mesh ) < volumeThreshold ) - return null; - - // Translate back to interval coords. - - // Scale to physical coords. - final double[] originRegion = region.minAsDoubleArray(); - final double[] origin = new double[3]; - for ( int d = 0; d < 3; d++ ) - origin[ d ] = originRegion[ d ] + minInterval[ d ]; - scale( simplified.vertices(), calibration, origin ); - - // Make spot with default quality. - final SpotMesh spot = new SpotMesh( simplified, 0. ); - - // Measure quality. - final double quality; - if ( null == qualityImage ) - { - quality = SpotMesh.volume( simplified ); - } - else - { - final IterableInterval< S > iterable = spot.iterable( qualityImage, calibration ); - double max = Double.NEGATIVE_INFINITY; - for ( final S s : iterable ) - { - final double val = s.getRealDouble(); - if ( val > max ) - max = val; - } - quality = max; - } - spot.putFeature( Spot.QUALITY, Double.valueOf( quality ) ); - return spot; - } } diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java new file mode 100644 index 000000000..0ec65e3b4 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -0,0 +1,361 @@ +package fiji.plugin.trackmate.detection; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.MeshConnectedComponents; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.Vertices; +import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.Interval; +import net.imglib2.IterableInterval; +import net.imglib2.RandomAccessible; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.RealInterval; +import net.imglib2.roi.labeling.ImgLabeling; +import net.imglib2.roi.labeling.LabelRegion; +import net.imglib2.roi.labeling.LabelRegions; +import net.imglib2.type.logic.BoolType; +import net.imglib2.type.numeric.IntegerType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.util.Intervals; +import net.imglib2.view.IntervalView; +import net.imglib2.view.Views; + +/** + * Utility classes to create 3D {@link fiji.plugin.trackmate.SpotMesh}es from + * single time-point, single channel images. + * + * @author Jean-Yves Tinevez, 2023 + */ +public class SpotMeshUtils +{ + + /** Number of triangles below which not to simplify a mesh. */ + private static final int MIN_N_TRIANGLES = 100; + + /** Quadratic mesh decimation aggressiveness for simplification. */ + private static final float SIMPLIFY_AGGRESSIVENESS = 10f; + + /** Minimal volume, in pixels, below which we discard meshes. */ + private static final double MIN_MESH_PIXEL_VOLUME = 15.; + + /** + * Precision for the vertex duplicate removal step. A value of 2 means that + * the vertices with coordinates (in pixel units) equal up to the second + * decimal will be considered duplicates and merged. + */ + private static final int VERTEX_DUPLICATE_REMOVAL_PRECISION = 2; + + /** + * Creates spots with meshes from a 3D grayscale image. The + * quality value is read from a secondary image, by taking the max value in + * each object, or the volume if the quality image is null. + *

+ * The grayscale marching-cube algorithm is used to create one big mesh from + * the source image. It is then split in connected-components to create + * single spot objects. However, to deal with possible holes in objects, + * meshes are possibly re-merged based on full inclusion of their bounding + * box. For instance, a hollow sphere would be represented by two + * connected-components, yielding two meshes. But because the small one is + * included in the big one, they are merged in this method. + * + * @param + * the type of the source image. Must be real, scalar. + * @param + * the type of the quality image. Must be real, scalar. + * @param input + * the source image, must be zero-min and 3D. + * @param interval + * the interval in which to segment spots. + * @param calibration + * the physical calibration. + * @param threshold + * the threshold to apply to the input image. + * @param simplify + * if true the meshes will be post-processed to be + * smoother and contain less points. + * @param qualityImage + * the image in which to read the quality value. + * @return a list of spots, with meshes. + */ + public static < T extends RealType< T >, S extends RealType< S > > List< Spot > from3DThresholdWithROI( + final RandomAccessible< T > input, + final Interval interval, + final double[] calibration, + final double threshold, + final boolean simplify, + final RandomAccessibleInterval< S > qualityImage ) + { + if ( input.numDimensions() != 3 ) + throw new IllegalArgumentException( "Can only process 3D images with this method, but got " + input.numDimensions() + "D." ); + + // Crop. + final RandomAccessibleInterval< T > crop = Views.interval( input, interval ); + final RandomAccessibleInterval< T > in = Views.zeroMin( crop ); + + // Get big mesh. + final Mesh mc = Meshes.marchingCubes( in, threshold ); + final Mesh bigMesh = Meshes.removeDuplicateVertices( mc, VERTEX_DUPLICATE_REMOVAL_PRECISION ); + + // Split into connected components. + final List< Mesh > meshes = new ArrayList<>(); + final List< RealInterval > boundingBoxes = new ArrayList<>(); + for ( final BufferMesh m : MeshConnectedComponents.iterable( bigMesh ) ) + { + meshes.add( m ); + boundingBoxes.add( SpotMesh.toRealInterval( Meshes.boundingBox( m ) ) ); + } + + // Merge if bb is included in one another. + final List< Mesh > out = new ArrayList<>(); + MESH_I: for ( int i = 0; i < meshes.size(); i++ ) + { + final RealInterval bbi = boundingBoxes.get( i ); + final Mesh meshi = meshes.get( i ); + + // Can we put it inside another? + for ( int j = i + 1; j < meshes.size(); j++ ) + { + final RealInterval bbj = boundingBoxes.get( j ); + if ( Intervals.contains( bbj, bbi ) ) + { + final Mesh meshj = meshes.get( j ); + + // Merge the ith into the jth. + final Mesh merged = Meshes.merge( Arrays.asList( meshi, meshj ) ); + meshes.set( j, merged ); + continue MESH_I; + } + } + + // We could not, retain it for later. + out.add( meshi ); + } + + // Create spot from merged meshes. + final List< Spot > spots = new ArrayList<>( out.size() ); + final double[] origin = interval.minAsDoubleArray(); + for ( final Mesh mesh : out ) + { + final Spot spot = meshToSpotMesh( + mesh, + simplify, + calibration, + qualityImage, + origin ); + if ( spot != null ) + spots.add( spot ); + } + return spots; + } + + /** + * Creates spots with meshes from a 3D label image. The + * quality value is read from a secondary image, by taking the max value in + * each ROI. + * + * @param + * the type that backs-up the labeling. + * @param + * the type of the quality image. Must be real, scalar. + * @param labeling + * the labeling, must be zero-min and 3D. + * @param interval + * the interval, used to reposition the spots from the zero-min + * labeling to the proper coordinates. + * @param calibration + * the physical calibration. + * @param simplify + * if true the meshes will be post-processed to be + * smoother and contain less points. + * @param qualityImage + * the image in which to read the quality value. + * @return a list of spots, with meshes. + */ + public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from3DLabelingWithROI( + final ImgLabeling< Integer, R > labeling, + final Interval interval, + final double[] calibration, + final boolean simplify, + final RandomAccessibleInterval< S > qualityImage ) + { + if ( labeling.numDimensions() != 3 ) + throw new IllegalArgumentException( "Can only process 3D images with this method, but got " + labeling.numDimensions() + "D." ); + + // Parse regions to create meshes on label. + final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); + final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); + final List< Spot > spots = new ArrayList<>( regions.getExistingLabels().size() ); + while ( iterator.hasNext() ) + { + final LabelRegion< Integer > region = iterator.next(); + final Spot spot = regionToSpotMesh( + region, + simplify, + calibration, + qualityImage, + interval.minAsDoubleArray() ); + if ( spot == null ) + continue; + + spots.add( spot ); + } + return spots; + } + + /** + * Returns a new {@link Spot} with a {@link SpotMesh} as shape, built from + * the specified bit-mask. + * + * @param + * the type of pixels in the quality image. + * @param region + * the bit-mask to build the mesh from. + * @param simplify + * if true the mesh will be simplified. + * @param calibration + * the pixel size array, used to scale the mesh to physical + * coordinates. + * @param qualityImage + * an image from which to read the quality value. If not + * null, the quality of the spot will be the max + * value of this image inside the mesh. If null, the + * quality will be the mesh volume. + * @param minInterval + * the origin in image coordinates of the ROI used for detection. + * + * @return a new spot. + */ + private static < S extends RealType< S > > Spot regionToSpotMesh( + final RandomAccessibleInterval< BoolType > region, + final boolean simplify, + final double[] calibration, + final RandomAccessibleInterval< S > qualityImage, + final double[] minInterval ) + { + // To mesh. + final IntervalView< BoolType > box = Views.zeroMin( region ); + final Mesh mesh = Meshes.marchingCubes( box, 0.5 ); + final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, VERTEX_DUPLICATE_REMOVAL_PRECISION ); + // Shift coords. + final double[] origin = region.minAsDoubleArray(); + for ( int d = 0; d < 3; d++ ) + origin[ d ] += minInterval[ d ]; + // To spot. + return meshToSpotMesh( + cleaned, + simplify, + calibration, + qualityImage, + origin ); + } + + /** + * Creates a {@link SpotMesh} from a {@link Mesh}. + * + * @param + * the type of the quality image. + * @param mesh + * the mesh to create a spot from. + * @param simplify + * whether the simplify the mesh. + * @param calibration + * the pixel size array, to map pixel coords to physical coords. + * @param qualityImage + * the quality image. If not null the quality he + * quality of the spot will be the max value of this image inside + * the mesh. If null, the quality will be the mesh + * volume. + * @param origin + * the origin of the interval the mesh was created on. This is + * used to put back the mesh coordinates with respect to the + * initial source image (same referential that for the quality + * image). + * @return + */ + private static < S extends RealType< S > > Spot meshToSpotMesh( + final Mesh mesh, + final boolean simplify, + final double[] calibration, + final RandomAccessibleInterval< S > qualityImage, + final double[] origin ) + { + final Mesh simplified; + if ( simplify ) + { + // Dont't go below a certain number of triangles. + final int nTriangles = ( int ) mesh.triangles().size(); + if ( nTriangles < MIN_N_TRIANGLES ) + { + simplified = mesh; + } + else + { + // Crude heuristics. + final float targetRatio; + if ( nTriangles < 2 * MIN_N_TRIANGLES ) + targetRatio = 0.5f; + else if ( nTriangles < 10_000 ) + targetRatio = 0.2f; + else if ( nTriangles < 1_000_000 ) + targetRatio = 0.1f; + else + targetRatio = 0.05f; + simplified = Meshes.simplify( mesh, targetRatio, SIMPLIFY_AGGRESSIVENESS ); + } + } + else + { + simplified = mesh; + } + // Remove meshes that are too small + final double volumeThreshold = MIN_MESH_PIXEL_VOLUME * calibration[ 0 ] * calibration[ 1 ] * calibration[ 2 ]; + if ( SpotMesh.volume( simplified ) < volumeThreshold ) + return null; + + // Translate back to interval coords & scale to physical coords. + scale( simplified.vertices(), calibration, origin ); + + // Make spot with default quality. + final SpotMesh spot = new SpotMesh( simplified, 0. ); + + // Measure quality. + final double quality; + if ( null == qualityImage ) + { + quality = SpotMesh.volume( simplified ); + } + else + { + final IterableInterval< S > iterable = spot.iterable( qualityImage, calibration ); + double max = Double.NEGATIVE_INFINITY; + for ( final S s : iterable ) + { + final double val = s.getRealDouble(); + if ( val > max ) + max = val; + } + quality = max; + } + spot.putFeature( Spot.QUALITY, Double.valueOf( quality ) ); + return spot; + } + + private static void scale( final Vertices vertices, final double[] scale, final double[] origin ) + { + final long nv = vertices.size(); + for ( long i = 0; i < nv; i++ ) + { + final double x = ( origin[ 0 ] + vertices.x( i ) ) * scale[ 0 ]; + final double y = ( origin[ 1 ] + vertices.y( i ) ) * scale[ 1 ]; + final double z = ( origin[ 2 ] + vertices.z( i ) ) * scale[ 2 ]; + vertices.set( i, x, y, z ); + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java new file mode 100644 index 000000000..a1efc6933 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java @@ -0,0 +1,707 @@ +package fiji.plugin.trackmate.detection; + +import java.awt.Polygon; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotRoi; +import ij.ImagePlus; +import ij.gui.PolygonRoi; +import ij.measure.Measurements; +import ij.process.FloatPolygon; +import net.imglib2.Interval; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.roi.labeling.ImgLabeling; +import net.imglib2.roi.labeling.LabelRegion; +import net.imglib2.roi.labeling.LabelRegions; +import net.imglib2.type.BooleanType; +import net.imglib2.type.numeric.IntegerType; +import net.imglib2.type.numeric.NumericType; +import net.imglib2.view.Views; + +/** + * Utility classes to create 2D {@link fiji.plugin.trackmate.SpotRoi}s from + * single time-point, single channel images. + * + * @author Jean-Yves Tinevez, 2023 + */ +public class SpotRoiUtils +{ + + /** Smoothing interval for ROIs. */ + private static final double SMOOTH_INTERVAL = 2.; + + /** Douglas-Peucker polygon simplification max distance. */ + private static final double DOUGLAS_PEUCKER_MAX_DISTANCE = 0.5; + + /** + * Creates spots with ROIs from a 2D label image. The quality + * value is read from a secondary image, by taking the max value in each + * ROI. + * + * @param + * the type that backs-up the labeling. + * @param + * the type of the quality image. Must be real, scalar. + * @param labeling + * the labeling, must be zero-min and 2D.. + * @param interval + * the interval, used to reposition the spots from the zero-min + * labeling to the proper coordinates. + * @param calibration + * the physical calibration. + * @param simplify + * if true the polygon will be post-processed to be + * smoother and contain less points. + * @param qualityImage + * the image in which to read the quality value. + * @return a list of spots, with ROI. + */ + public static < R extends IntegerType< R >, S extends NumericType< S > > List< Spot > from2DLabelingWithROI( + final ImgLabeling< Integer, R > labeling, + final Interval interval, + final double[] calibration, + final boolean simplify, + final RandomAccessibleInterval< S > qualityImage ) + { + if ( labeling.numDimensions() != 2 ) + throw new IllegalArgumentException( "Can only process 2D images with this method, but got " + labeling.numDimensions() + "D." ); + + final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); + + // Parse regions to create polygons on boundaries. + final List< Polygon > polygons = new ArrayList<>( regions.getExistingLabels().size() ); + final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); + while ( iterator.hasNext() ) + { + final LabelRegion< Integer > region = iterator.next(); + // Analyze in zero-min region. + final List< Polygon > pp = maskToPolygons( Views.zeroMin( region ) ); + // Translate back to interval coords. + for ( final Polygon polygon : pp ) + polygon.translate( ( int ) region.min( 0 ), ( int ) region.min( 1 ) ); + + polygons.addAll( pp ); + } + + // Quality image. + final List< Spot > spots = new ArrayList<>( polygons.size() ); + final ImagePlus qualityImp = ( null == qualityImage ) + ? null + : ImageJFunctions.wrap( qualityImage, "QualityImage" ); + + // Simplify them and compute a quality. + for ( final Polygon polygon : polygons ) + { + final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON ); + + // Create Spot ROI. + final PolygonRoi fRoi; + if ( simplify ) + fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE ); + else + fRoi = roi; + + // Don't include ROIs that have been shrunk to < 1 pixel. + if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) + continue; + + // Measure quality. + final double quality; + if ( null == qualityImp ) + { + quality = fRoi.getStatistics().area; + } + else + { + qualityImp.setRoi( fRoi ); + quality = qualityImp.getStatistics( Measurements.MIN_MAX ).max; + } + + final Polygon fPolygon = fRoi.getPolygon(); + final double[] xpoly = new double[ fPolygon.npoints ]; + final double[] ypoly = new double[ fPolygon.npoints ]; + for ( int i = 0; i < fPolygon.npoints; i++ ) + { + xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 ); + ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 ); + } + + spots.add( SpotRoi.createSpot( xpoly, ypoly, quality ) ); + } + return spots; + } + + private static final double distanceSquaredBetweenPoints( final double vx, final double vy, final double wx, final double wy ) + { + final double deltax = ( vx - wx ); + final double deltay = ( vy - wy ); + return deltax * deltax + deltay * deltay; + } + + private static final double distanceToSegmentSquared( final double px, final double py, final double vx, final double vy, final double wx, final double wy ) + { + final double l2 = distanceSquaredBetweenPoints( vx, vy, wx, wy ); + if ( l2 == 0 ) + return distanceSquaredBetweenPoints( px, py, vx, vy ); + final double t = ( ( px - vx ) * ( wx - vx ) + ( py - vy ) * ( wy - vy ) ) / l2; + if ( t < 0 ) + return distanceSquaredBetweenPoints( px, py, vx, vy ); + if ( t > 1 ) + return distanceSquaredBetweenPoints( px, py, wx, wy ); + return distanceSquaredBetweenPoints( px, py, ( vx + t * ( wx - vx ) ), ( vy + t * ( wy - vy ) ) ); + } + + private static final double perpendicularDistance( final double px, final double py, final double vx, final double vy, final double wx, final double wy ) + { + return Math.sqrt( distanceToSegmentSquared( px, py, vx, vy, wx, wy ) ); + } + + private static final void douglasPeucker( final List< double[] > list, final int s, final int e, final double epsilon, final List< double[] > resultList ) + { + // Find the point with the maximum distance + double dmax = 0; + int index = 0; + + final int start = s; + final int end = e - 1; + for ( int i = start + 1; i < end; i++ ) + { + // Point + final double px = list.get( i )[ 0 ]; + final double py = list.get( i )[ 1 ]; + // Start + final double vx = list.get( start )[ 0 ]; + final double vy = list.get( start )[ 1 ]; + // End + final double wx = list.get( end )[ 0 ]; + final double wy = list.get( end )[ 1 ]; + final double d = perpendicularDistance( px, py, vx, vy, wx, wy ); + if ( d > dmax ) + { + index = i; + dmax = d; + } + } + // If max distance is greater than epsilon, recursively simplify + if ( dmax > epsilon ) + { + // Recursive call + douglasPeucker( list, s, index, epsilon, resultList ); + douglasPeucker( list, index, e, epsilon, resultList ); + } + else + { + if ( ( end - start ) > 0 ) + { + resultList.add( list.get( start ) ); + resultList.add( list.get( end ) ); + } + else + { + resultList.add( list.get( start ) ); + } + } + } + + /** + * Given a curve composed of line segments find a similar curve with fewer + * points. + *

+ * The Ramer–Douglas–Peucker algorithm (RDP) is an algorithm for reducing + * the number of points in a curve that is approximated by a series of + * points. + *

+ * + * @see Ramer–Douglas–Peucker + * Algorithm (Wikipedia) + * @author Justin Wetherell + * @param list + * List of double[] points (x,y) + * @param epsilon + * Distance dimension + * @return Similar curve with fewer points + */ + public static final List< double[] > douglasPeucker( final List< double[] > list, final double epsilon ) + { + final List< double[] > resultList = new ArrayList<>(); + douglasPeucker( list, 0, list.size(), epsilon, resultList ); + return resultList; + } + + public static final PolygonRoi simplify( final PolygonRoi roi, final double smoothInterval, final double epsilon ) + { + final FloatPolygon fPoly = roi.getInterpolatedPolygon( smoothInterval, true ); + + final List< double[] > points = new ArrayList<>( fPoly.npoints ); + for ( int i = 0; i < fPoly.npoints; i++ ) + points.add( new double[] { fPoly.xpoints[ i ], fPoly.ypoints[ i ] } ); + + final List< double[] > simplifiedPoints = douglasPeucker( points, epsilon ); + final float[] sX = new float[ simplifiedPoints.size() ]; + final float[] sY = new float[ simplifiedPoints.size() ]; + for ( int i = 0; i < sX.length; i++ ) + { + sX[ i ] = ( float ) simplifiedPoints.get( i )[ 0 ]; + sY[ i ] = ( float ) simplifiedPoints.get( i )[ 1 ]; + } + final FloatPolygon simplifiedPolygon = new FloatPolygon( sX, sY ); + final PolygonRoi fRoi = new PolygonRoi( simplifiedPolygon, PolygonRoi.POLYGON ); + return fRoi; + } + + /** + * Parse a 2D mask and return a list of polygons for the external contours + * of white objects. + *

+ * Warning: cannot deal with holes, they are simply ignored. + *

+ * Copied and adapted from ImageJ1 code by Wayne Rasband. + * + * @param + * the type of the mask. + * @param mask + * the mask image. + * @return a new list of polygons. + */ + private static final < T extends BooleanType< T > > List< Polygon > maskToPolygons( final RandomAccessibleInterval< T > mask ) + { + final int w = ( int ) mask.dimension( 0 ); + final int h = ( int ) mask.dimension( 1 ); + final RandomAccess< T > ra = mask.randomAccess( mask ); + + final List< Polygon > polygons = new ArrayList<>(); + boolean[] prevRow = new boolean[ w + 2 ]; + boolean[] thisRow = new boolean[ w + 2 ]; + final Outline[] outline = new Outline[ w + 1 ]; + + for ( int y = 0; y <= h; y++ ) + { + ra.setPosition( y, 1 ); + + final boolean[] b = prevRow; + prevRow = thisRow; + thisRow = b; + int xAfterLowerRightCorner = -1; + Outline oAfterLowerRightCorner = null; + + ra.setPosition( 0, 0 ); + thisRow[ 1 ] = y < h ? ra.get().get() : false; + + for ( int x = 0; x <= w; x++ ) + { + // we need to read one pixel ahead + ra.setPosition( x + 1, 0 ); + if ( y < h && x < w - 1 ) + thisRow[ x + 2 ] = ra.get().get(); + else if ( x < w - 1 ) + thisRow[ x + 2 ] = false; + + if ( thisRow[ x + 1 ] ) + { // i.e., pixel (x,y) is selected + if ( !prevRow[ x + 1 ] ) + { + // Upper edge of selected area: + // - left and right outlines are null: new outline + // - left null: append (line to left) + // - right null: prepend (line to right), or + // prepend&append (after lower right corner, two borders + // from one corner) + // - left == right: close (end of hole above) unless we + // can continue at the right + // - left != right: merge (prepend) unless we can + // continue at the right + if ( outline[ x ] == null ) + { + if ( outline[ x + 1 ] == null ) + { + outline[ x + 1 ] = outline[ x ] = new Outline(); + outline[ x ].append( x + 1, y ); + outline[ x ].append( x, y ); + } + else + { + outline[ x ] = outline[ x + 1 ]; + outline[ x + 1 ] = null; + outline[ x ].append( x, y ); + } + } + else if ( outline[ x + 1 ] == null ) + { + if ( x == xAfterLowerRightCorner ) + { + outline[ x + 1 ] = outline[ x ]; + outline[ x ] = oAfterLowerRightCorner; + outline[ x ].append( x, y ); + outline[ x + 1 ].prepend( x + 1, y ); + } + else + { + outline[ x + 1 ] = outline[ x ]; + outline[ x ] = null; + outline[ x + 1 ].prepend( x + 1, y ); + } + } + else if ( outline[ x + 1 ] == outline[ x ] ) + { + if ( x < w - 1 && y < h && x != xAfterLowerRightCorner + && !thisRow[ x + 2 ] && prevRow[ x + 2 ] ) + { // at lower right corner & next pxl deselected + outline[ x ] = null; + // outline[x+1] unchanged + outline[ x + 1 ].prepend( x + 1, y ); + xAfterLowerRightCorner = x + 1; + oAfterLowerRightCorner = outline[ x + 1 ]; + } + else + { + // MINUS (add inner hole) + // We cannot handle holes in TrackMate. +// polygons.add( outline[ x ].getPolygon() ); + outline[ x + 1 ] = null; + outline[ x ] = ( x == xAfterLowerRightCorner ) ? oAfterLowerRightCorner : null; + } + } + else + { + outline[ x ].prepend( outline[ x + 1 ] ); + for ( int x1 = 0; x1 <= w; x1++ ) + if ( x1 != x + 1 && outline[ x1 ] == outline[ x + 1 ] ) + { + outline[ x1 ] = outline[ x ]; + outline[ x + 1 ] = null; + outline[ x ] = ( x == xAfterLowerRightCorner ) ? oAfterLowerRightCorner : null; + break; + } + if ( outline[ x + 1 ] != null ) + throw new RuntimeException( "assertion failed" ); + } + } + if ( !thisRow[ x ] ) + { + // left edge + if ( outline[ x ] == null ) + throw new RuntimeException( "assertion failed" ); + outline[ x ].append( x, y + 1 ); + } + } + else + { // !thisRow[x + 1], i.e., pixel (x,y) is deselected + if ( prevRow[ x + 1 ] ) + { + // Lower edge of selected area: + // - left and right outlines are null: new outline + // - left == null: prepend + // - right == null: append, or append&prepend (after + // lower right corner, two borders from one corner) + // - right == left: close unless we can continue at the + // right + // - right != left: merge (append) unless we can + // continue at the right + if ( outline[ x ] == null ) + { + if ( outline[ x + 1 ] == null ) + { + outline[ x ] = outline[ x + 1 ] = new Outline(); + outline[ x ].append( x, y ); + outline[ x ].append( x + 1, y ); + } + else + { + outline[ x ] = outline[ x + 1 ]; + outline[ x + 1 ] = null; + outline[ x ].prepend( x, y ); + } + } + else if ( outline[ x + 1 ] == null ) + { + if ( x == xAfterLowerRightCorner ) + { + outline[ x + 1 ] = outline[ x ]; + outline[ x ] = oAfterLowerRightCorner; + outline[ x ].prepend( x, y ); + outline[ x + 1 ].append( x + 1, y ); + } + else + { + outline[ x + 1 ] = outline[ x ]; + outline[ x ] = null; + outline[ x + 1 ].append( x + 1, y ); + } + } + else if ( outline[ x + 1 ] == outline[ x ] ) + { + // System.err.println("add " + outline[x]); + if ( x < w - 1 && y < h && x != xAfterLowerRightCorner + && thisRow[ x + 2 ] && !prevRow[ x + 2 ] ) + { // at lower right corner & next pxl selected + outline[ x ] = null; + // outline[x+1] unchanged + outline[ x + 1 ].append( x + 1, y ); + xAfterLowerRightCorner = x + 1; + oAfterLowerRightCorner = outline[ x + 1 ]; + } + else + { + polygons.add( outline[ x ].getPolygon() ); + outline[ x + 1 ] = null; + outline[ x ] = x == xAfterLowerRightCorner ? oAfterLowerRightCorner : null; + } + } + else + { + if ( x < w - 1 && y < h && x != xAfterLowerRightCorner + && thisRow[ x + 2 ] && !prevRow[ x + 2 ] ) + { // at lower right corner && next pxl selected + outline[ x ].append( x + 1, y ); + outline[ x + 1 ].prepend( x + 1, y ); + xAfterLowerRightCorner = x + 1; + oAfterLowerRightCorner = outline[ x ]; + // outline[x + 1] unchanged (the one at the + // right-hand side of (x, y-1) to the top) + outline[ x ] = null; + } + else + { + outline[ x ].append( outline[ x + 1 ] ); // merge + for ( int x1 = 0; x1 <= w; x1++ ) + if ( x1 != x + 1 && outline[ x1 ] == outline[ x + 1 ] ) + { + outline[ x1 ] = outline[ x ]; + outline[ x + 1 ] = null; + outline[ x ] = ( x == xAfterLowerRightCorner ) ? oAfterLowerRightCorner : null; + break; + } + if ( outline[ x + 1 ] != null ) + throw new RuntimeException( "assertion failed" ); + } + } + } + if ( thisRow[ x ] ) + { + // right edge + if ( outline[ x ] == null ) + throw new RuntimeException( "assertion failed" ); + outline[ x ].prepend( x, y + 1 ); + } + } + } + } + return polygons; + } + + /** + * This class implements a Cartesian polygon in progress. The edges are + * supposed to be parallel to the x or y axis. It is implemented as a deque + * to be able to add points to both sides. + */ + private static class Outline + { + + private int[] x, y; + + private int first, last, reserved; + + /** + * Default extra (spare) space when enlarging arrays (similar + * performance with 6-20) + */ + private final int GROW = 10; + + public Outline() + { + reserved = GROW; + x = new int[ reserved ]; + y = new int[ reserved ]; + first = last = GROW / 2; + } + + /** + * Makes sure that enough free space is available at the beginning and + * end of the list, by enlarging the arrays if required + */ + private void needs( final int neededAtBegin, final int neededAtEnd ) + { + if ( neededAtBegin > first || neededAtEnd > reserved - last ) + { + final int extraSpace = Math.max( GROW, Math.abs( x[ last - 1 ] - x[ first ] ) ); + final int newSize = reserved + neededAtBegin + neededAtEnd + extraSpace; + final int newFirst = neededAtBegin + extraSpace / 2; + final int[] newX = new int[ newSize ]; + final int[] newY = new int[ newSize ]; + System.arraycopy( x, first, newX, newFirst, last - first ); + System.arraycopy( y, first, newY, newFirst, last - first ); + x = newX; + y = newY; + last += newFirst - first; + first = newFirst; + reserved = newSize; + } + } + + /** Adds point x, y at the end of the list */ + public void append( final int x, final int y ) + { + if ( last - first >= 2 && collinear( this.x[ last - 2 ], this.y[ last - 2 ], this.x[ last - 1 ], this.y[ last - 1 ], x, y ) ) + { + this.x[ last - 1 ] = x; // replace previous point + this.y[ last - 1 ] = y; + } + else + { + needs( 0, 1 ); // new point + this.x[ last ] = x; + this.y[ last ] = y; + last++; + } + } + + /** Adds point x, y at the beginning of the list */ + public void prepend( final int x, final int y ) + { + if ( last - first >= 2 && collinear( this.x[ first + 1 ], this.y[ first + 1 ], this.x[ first ], this.y[ first ], x, y ) ) + { + this.x[ first ] = x; // replace previous point + this.y[ first ] = y; + } + else + { + needs( 1, 0 ); // new point + first--; + this.x[ first ] = x; + this.y[ first ] = y; + } + } + + /** + * Merge with another Outline by adding it at the end. Thereafter, the + * other outline must not be used any more. + */ + public void append( final Outline o ) + { + final int size = last - first; + final int oSize = o.last - o.first; + if ( size <= o.first && oSize > reserved - last ) + { // we don't have enough space in our own array but in that of 'o' + System.arraycopy( x, first, o.x, o.first - size, size ); + System.arraycopy( y, first, o.y, o.first - size, size ); + x = o.x; + y = o.y; + first = o.first - size; + last = o.last; + reserved = o.reserved; + } + else + { // append to our own array + needs( 0, oSize ); + System.arraycopy( o.x, o.first, x, last, oSize ); + System.arraycopy( o.y, o.first, y, last, oSize ); + last += oSize; + } + } + + /** + * Merge with another Outline by adding it at the beginning. Thereafter, + * the other outline must not be used any more. + */ + public void prepend( final Outline o ) + { + final int size = last - first; + final int oSize = o.last - o.first; + if ( size <= o.reserved - o.last && oSize > first ) + { /* + * We don't have enough space in our own array but in that of + * 'o' so append our own data to that of 'o' + */ + System.arraycopy( x, first, o.x, o.last, size ); + System.arraycopy( y, first, o.y, o.last, size ); + x = o.x; + y = o.y; + first = o.first; + last = o.last + size; + reserved = o.reserved; + } + else + { // prepend to our own array + needs( oSize, 0 ); + first -= oSize; + System.arraycopy( o.x, o.first, x, first, oSize ); + System.arraycopy( o.y, o.first, y, first, oSize ); + } + } + + public Polygon getPolygon() + { + /* + * optimize out intermediate points of straight lines (created, + * e.g., by merging outlines) + */ + int i, j = first + 1; + for ( i = first + 1; i + 1 < last; j++ ) + { + if ( collinear( x[ j - 1 ], y[ j - 1 ], x[ j ], y[ j ], x[ j + 1 ], y[ j + 1 ] ) ) + { + // merge i + 1 into i + last--; + continue; + } + if ( i != j ) + { + x[ i ] = x[ j ]; + y[ i ] = y[ j ]; + } + i++; + } + // wraparound + if ( collinear( x[ j - 1 ], y[ j - 1 ], x[ j ], y[ j ], x[ first ], y[ first ] ) ) + last--; + else + { + x[ i ] = x[ j ]; + y[ i ] = y[ j ]; + } + if ( last - first > 2 && collinear( x[ last - 1 ], y[ last - 1 ], x[ first ], y[ first ], x[ first + 1 ], y[ first + 1 ] ) ) + first++; + + final int count = last - first; + final int[] xNew = new int[ count ]; + final int[] yNew = new int[ count ]; + System.arraycopy( x, first, xNew, 0, count ); + System.arraycopy( y, first, yNew, 0, count ); + return new Polygon( xNew, yNew, count ); + } + + /** Returns whether three points are on one straight line */ + public boolean collinear( final int x1, final int y1, final int x2, final int y2, final int x3, final int y3 ) + { + return ( x2 - x1 ) * ( y3 - y2 ) == ( y2 - y1 ) * ( x3 - x2 ); + } + + @Override + public String toString() + { + String res = "[first:" + first + ",last:" + last + + ",reserved:" + reserved + ":"; + if ( last > x.length ) + System.err.println( "ERROR!" ); + int nmax = 10; // don't print more coordinates than this + for ( int i = first; i < last && i < x.length; i++ ) + { + if ( last - first > nmax && i - first > nmax / 2 ) + { + i = last - nmax / 2; + res += "..."; + nmax = last - first; // dont check again + } + else + res += "(" + x[ i ] + "," + y[ i ] + ")"; + } + return res + "]"; + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java index d274ed6ee..68736fb0b 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java @@ -110,16 +110,14 @@ public boolean checkInput() public boolean process() { final long start = System.currentTimeMillis(); - if ( input.numDimensions() == 2 || input.numDimensions() == 3 ) - { - spots = MaskUtils.fromThresholdWithROI( input, interval, calibration, threshold, simplify, numThreads, null ); - } - else - { - errorMessage = baseErrorMessage + "Required a 2D or 3D input, got " + input.numDimensions() + "D."; - return false; - } - + spots = MaskUtils.fromThresholdWithROI( + input, + interval, + calibration, + threshold, + simplify, + numThreads, + null ); final long end = System.currentTimeMillis(); this.processingTime = end - start; From c78002e7de1665bd094abc25bc157a141d312e09 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 15 May 2023 18:06:47 +0200 Subject: [PATCH 146/263] Don't crash the MeshSeriesExporter if the image name is weird. --- .../fiji/plugin/trackmate/action/MeshSeriesExporter.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java index 57837ec26..abe3a263c 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java @@ -79,7 +79,10 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo try { String filename = trackmate.getSettings().imageFileName; - filename = filename.substring( 0, filename.indexOf( "." ) ); + int i = filename.indexOf( "." ); + if ( i < 0 ) + i = filename.length(); + filename = filename.substring( 0, i ); file = new File( folder.getPath() + File.separator + filename + "-meshes.ply" ); } catch ( final NullPointerException npe ) From ed99c4b89710d45605c3cf532aed77bf371b78d8 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 9 May 2023 22:33:12 +0200 Subject: [PATCH 147/263] Add ui-behaviour as a dependency. --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 564a21017..756aa3484 100644 --- a/pom.xml +++ b/pom.xml @@ -221,6 +221,10 @@ org.scijava scijava-listeners + + org.scijava + ui-behaviour + From 04a1b56053a7c34929bd8fdb10d636b9bd290519 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 9 May 2023 22:33:25 +0200 Subject: [PATCH 148/263] Two new icons. --- .../java/fiji/plugin/trackmate/gui/Icons.java | 4 ++++ .../plugin/trackmate/gui/images/bullet_green.png | Bin 0 -> 295 bytes .../fiji/plugin/trackmate/gui/images/help.png | Bin 0 -> 786 bytes 3 files changed, 4 insertions(+) create mode 100644 src/main/resources/fiji/plugin/trackmate/gui/images/bullet_green.png create mode 100644 src/main/resources/fiji/plugin/trackmate/gui/images/help.png diff --git a/src/main/java/fiji/plugin/trackmate/gui/Icons.java b/src/main/java/fiji/plugin/trackmate/gui/Icons.java index d05a1499a..6ec9b0391 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/Icons.java +++ b/src/main/java/fiji/plugin/trackmate/gui/Icons.java @@ -201,4 +201,8 @@ public class Icons public static final ImageIcon VECTOR_ICON = new ImageIcon( Icons.class.getResource( "images/vector.png" ) ); + public static final ImageIcon BULLET_GREEN_ICON = new ImageIcon( Icons.class.getResource( "images/bullet_green.png" ) ); + + public static final ImageIcon QUESTION_ICON = new ImageIcon( Icons.class.getResource( "images/help.png" ) ); + } diff --git a/src/main/resources/fiji/plugin/trackmate/gui/images/bullet_green.png b/src/main/resources/fiji/plugin/trackmate/gui/images/bullet_green.png new file mode 100644 index 0000000000000000000000000000000000000000..058ad261f520490be9d3fc2e322392fdedfd1cbd GIT binary patch literal 295 zcmV+?0oeYDP)ef43{&%10 z`rmr0`TyJtv;LcOX%laN^>UMjsi!CYUwmcZ|JfI2{-1ED=f8fLD)C;hoM$LyF$XgYMs^AIOw1Qr{*Wn)N-{9ma}x2(<~`9Go1=*>YR!KZvrBS zCd!u}@M0og%Ev@_;Z?Kk>Wwv=%h_57zmt2<_1msz_niYE=YRNPpd%02TK9oK1z z>ooPno}v^sikz_|1XHFx_L%~;ljh7i(jiay5F0x*+(9aXXFCl?AdQj5XlQ65%sEv+ ztfe?|YcjPN*@yYtE~ImQh{l|#A6Z8iu>pf43Rj52CzU_dMQm|S2xR62YjQOn+z8WH zaK=!}ggOZi{4pB7SQ=xC0n|vXP_Bkx_a)FeNd}w8U97BNbSWxa^QW-li9BZ#M1!_xE*?wzt^GcoeoL*JGLSe_+l-JT2#2tz!z&^ z_s5anq&^nBklIMwRvcoP3%qs%%Ea?1c{_*V*Xj&~uLu-2Dp1fUN4<0zMo$EH>*U83 zm_9;Vt%-bE{_J_!If!1y=c+`QVZ>0_BPy z+%^pgnv`f8H)Z%0&Tp8&u*MCIC4igNW5MeWM_DHpDNi)Zxz|9XboOnitwFq$ETN=X zj-tkCJnz**Y4k#6_Ty^B=hWo~L!47r`HoP=x&3T1)JLr2t2+#fH Date: Tue, 9 May 2023 22:34:17 +0200 Subject: [PATCH 149/263] FeatureTable, taken from mastodon, to display a list of togglable items. --- .../gui/featureselector/FeatureTable.java | 381 ++++++++++++++++++ .../gui/featureselector/package-info.java | 1 + 2 files changed, 382 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/featureselector/FeatureTable.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/featureselector/package-info.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/FeatureTable.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/FeatureTable.java new file mode 100644 index 000000000..46d8e2db9 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/FeatureTable.java @@ -0,0 +1,381 @@ +package fiji.plugin.trackmate.gui.featureselector; + +import static javax.swing.JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.KeyboardFocusManager; +import java.awt.event.ActionEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.ToIntFunction; + +import javax.swing.Action; +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JTable; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableCellRenderer; + +import org.scijava.listeners.Listeners; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Actions; + +import fiji.plugin.trackmate.gui.Icons; + +/** + * + * @param collection-of-elements type + * @param element type + */ +public class FeatureTable< C, T > +{ + public static class Tables implements ListSelectionListener + { + private final List< FeatureTable< ?, ? > > tables = new ArrayList<>(); + + public void add( final FeatureTable< ?, ? > table ) + { + tables.add( table ); + table.tables = this; + table.table.getSelectionModel().addListSelectionListener( this ); + } + + @Override + public void valueChanged( final ListSelectionEvent event ) + { + final ListSelectionModel source = ( ListSelectionModel ) event.getSource(); + for ( final FeatureTable< ?, ? > table : tables ) + { + final ListSelectionModel lsm = table.table.getSelectionModel(); + if ( lsm.equals( source ) ) + continue; + + lsm.removeListSelectionListener( this ); + lsm.clearSelection(); + lsm.addListSelectionListener( this ); + } + } + + boolean selectNextTable( final FeatureTable< ?, ? > table ) + { + final int i = tables.indexOf( table ); + if ( i < 0 || i >= tables.size() - 1 ) + return false; + + final JTable next = tables.get( i + 1 ).table; + if ( next.getRowCount() > 0 ) + { + table.clearSelectionQuiet(); + next.setRowSelectionInterval( 0, 0 ); + next.requestFocusInWindow(); + } + return true; + } + + boolean selectPreviousTable( final FeatureTable< ?, ? > table ) + { + final int i = tables.indexOf( table ); + if ( i <= 0 ) + return false; + + final JTable previous = tables.get( i - 1 ).table; + final int rows = previous.getRowCount(); + if ( rows > 0 ) + { + table.clearSelectionQuiet(); + previous.setRowSelectionInterval( rows - 1, rows - 1 ); + previous.requestFocusInWindow(); + } + return true; + } + } + + private static final ImageIcon UP_TO_DATE_ICON = Icons.BULLET_GREEN_ICON; + private static final ImageIcon NOT_UP_TO_DATE_ICON = Icons.QUESTION_ICON; + + private C elements; + private final ToIntFunction< C > size; + private final BiFunction< C, Integer, T > get; + private final Function< T, String > getName; + private final Predicate< T > isSelected; + private final BiConsumer< T, Boolean > setSelected; + private final Predicate< T > isUptodate; + + private final Listeners.List< SelectionListener< T > > selectionListeners; + + private final ListSelectionListener listSelectionListener; + + private final MyTableModel tableModel; + + private final JTable table; + + private Tables tables; + + /** + * Creates a new feature table. + * + * @param elements + * collection of elements. + * @param size + * given collection returns number of elements. + * @param get + * given collection and index returns element at index. + * @param getName + * given element returns name. + * @param isSelected + * given element returns whether it is selected. + * @param setSelected + * given element and boolean sets selected state of element. + * @param isUptodate + * given element returns whether it is up-to-date. + */ + public FeatureTable( + final C elements, + final ToIntFunction< C > size, + final BiFunction< C, Integer, T > get, + final Function< T, String > getName, + final Predicate< T > isSelected, + final BiConsumer< T, Boolean > setSelected, + final Predicate< T > isUptodate ) + { + this.elements = elements; + this.size = size; + this.get = get; + this.getName = getName; + this.isSelected = isSelected; + this.setSelected = setSelected; + this.isUptodate = isUptodate; + + selectionListeners = new Listeners.SynchronizedList<>(); + + tableModel = new MyTableModel(); + table = new JTable( tableModel ); + table.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); + table.setTableHeader( null ); + table.setFillsViewportHeight( true ); + table.setAutoResizeMode( JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS ); + table.setRowHeight( 30 ); + listSelectionListener = e -> { + if ( e.getValueIsAdjusting() ) + return; + final int row = table.getSelectedRow(); + final T selected = ( this.elements != null && row >= 0 && row < this.size.applyAsInt( this.elements ) ) + ? this.get.apply( this.elements, row ) + : null; + selectionListeners.list.forEach( l -> l.selectionChanged( selected ) ); + }; + table.getSelectionModel().addListSelectionListener( listSelectionListener ); + table.setIntercellSpacing( new Dimension( 0, 0 ) ); + table.setSurrendersFocusOnKeystroke( true ); + table.setFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null ); + table.setFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null ); + table.getColumnModel().getColumn( 0 ).setMaxWidth( 30 ); + table.getColumnModel().getColumn( 2 ).setMaxWidth( 64 ); + table.getColumnModel().getColumn( 2 ).setCellRenderer( new UpdatedCellRenderer() ); + table.setShowGrid( false ); + + final Actions actions = new Actions( table.getInputMap( WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ), table.getActionMap(), new InputTriggerConfig() ); + actions.runnableAction( this::toggleSelectedRow, "toggle selected row", "SPACE", "ENTER" ); + actions.runnableAction( this::nextRowOrTable, "select next row or table", "DOWN" ); + actions.runnableAction( this::previousRowOrTable, "select previous row or table", "UP" ); + + setElements( elements ); + } + + private void toggleSelectedRow() + { + final int row = table.getSelectedRow(); + if ( row >= 0 ) + { + final T feature = get.apply( elements, row ); + setSelected.accept( feature, !isSelected.test( feature ) ); + tableModel.fireTableCellUpdated( row, 0 ); + } + } + + private void nextRowOrTable() + { + final int row = table.getSelectedRow(); + if ( elements == null || tables == null || row != table.getRowCount() - 1 || !tables.selectNextTable( this ) ) + { + final Action action = table.getActionMap().get( "selectNextRow" ); + if ( action != null ) + action.actionPerformed( new ActionEvent( table, 0, null ) ); + } + } + + private void previousRowOrTable() + { + final int row = table.getSelectedRow(); + if ( elements == null || tables == null || row != 0 || !tables.selectPreviousTable( this ) ) + { + final Action action = table.getActionMap().get( "selectPreviousRow" ); + if ( action != null ) + action.actionPerformed( new ActionEvent( table, 0, null ) ); + } + } + + private void clearSelectionQuiet() + { + table.getSelectionModel().removeListSelectionListener( listSelectionListener ); + table.clearSelection(); + table.getSelectionModel().addListSelectionListener( listSelectionListener ); + + } + + /** + * Exposes the component in which the elements are displayed. + * + * @return the component. + */ + public JComponent getComponent() + { + return table; + } + + /** + * Sets the collection of elements to show. + * + * @param elements + * the collection of elements to show. + */ + public void setElements( final C elements ) + { + this.elements = elements; + if ( elements == null ) + selectionListeners.list.forEach( l -> l.selectionChanged( null ) ); + else + tableModel.fireTableDataChanged(); + } + + public void selectFirstRow() + { + if ( table.getRowCount() > 0 ) + table.setRowSelectionInterval( 0, 0 ); + } + + public interface SelectionListener< T > + { + void selectionChanged( T selected ); + } + + public Listeners< SelectionListener< T > > selectionListeners() + { + return selectionListeners; + } + + private class MyTableModel extends DefaultTableModel + { + + private static final long serialVersionUID = 1L; + + @Override + public int getColumnCount() + { + return 3; + } + + @Override + public int getRowCount() + { + return ( null == elements ) ? 0 : size.applyAsInt( elements ); + } + + public T get( final int index ) + { + return get.apply( elements, Integer.valueOf( index ) ); + } + + @Override + public Object getValueAt( final int rowIndex, final int columnIndex ) + { + switch ( columnIndex ) + { + case 0: + return isSelected.test( get( rowIndex ) ); + case 1: + return getName.apply( get( rowIndex ) ); + case 2: + return isUptodate.test( get( rowIndex ) ); + } + throw new IllegalArgumentException( "Cannot return value for colum index larger than " + getColumnCount() ); + } + + @Override + public Class< ? > getColumnClass( final int columnIndex ) + { + switch ( columnIndex ) + { + case 0: + return Boolean.class; + case 1: + return String.class; + case 2: + return Boolean.class; + } + throw new IllegalArgumentException( "Cannot return value for colum index larger than " + getColumnCount() ); + } + + @Override + public boolean isCellEditable( final int rowIndex, final int columnIndex ) + { + return columnIndex == 0; + } + + @Override + public void setValueAt( final Object aValue, final int rowIndex, final int columnIndex ) + { + final boolean selected = (columnIndex == 0) + ? ( boolean ) aValue + : !isSelected.test( get( rowIndex ) ); + if ( selected != isSelected.test( get( rowIndex ) ) ) + { + setSelected.accept( get( rowIndex ), ( Boolean ) aValue ); + fireTableRowsUpdated( rowIndex, rowIndex ); + for ( final SelectionListener< T > listener : selectionListeners.list ) + listener.selectionChanged( get( rowIndex ) ); + } + } + } + + private class UpdatedCellRenderer implements TableCellRenderer + { + + private final DefaultTableCellRenderer renderer; + + public UpdatedCellRenderer() + { + this.renderer = new DefaultTableCellRenderer(); + final JLabel label = ( JLabel ) renderer.getTableCellRendererComponent( null, null, false, false, 0, 0 ); + label.setHorizontalAlignment( SwingConstants.CENTER ); + } + + @Override + public Component getTableCellRendererComponent( + final JTable table, + final Object value, + final boolean isSelected, + final boolean hasFocus, + final int row, + final int column ) + { + final JLabel label = ( JLabel ) renderer.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column ); + label.setIcon( isUptodate.test( get.apply( elements, Integer.valueOf( row ) ) ) + ? UP_TO_DATE_ICON + : NOT_UP_TO_DATE_ICON ); + label.setText( "" ); + return label; + } + } +} \ No newline at end of file diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/package-info.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/package-info.java new file mode 100644 index 000000000..e20f8c4b1 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/package-info.java @@ -0,0 +1 @@ +package fiji.plugin.trackmate.gui.featureselector; From 47bfbf107f78c049a04389cf3c834bdfa5b4ee4a Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 9 May 2023 22:36:39 +0200 Subject: [PATCH 150/263] A UI tool to build a selection of feature analyzers and save it to disk. Later we will make the GUI sessions use the saved selection, so that users can choose what they want to compute or not. Might be handy for some features that are long to compute. Contrast and SNR I am looking at you. And actually you are deselected by default now. --- .../featureselector/AnalyzerSelection.java | 110 +++++++ .../featureselector/AnalyzerSelectionIO.java | 91 ++++++ .../gui/featureselector/AnalyzerSelector.java | 35 +++ .../AnalyzerSelectorPanel.java | 293 ++++++++++++++++++ 4 files changed, 529 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectionIO.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java new file mode 100644 index 000000000..c62dfa72a --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java @@ -0,0 +1,110 @@ +package fiji.plugin.trackmate.gui.featureselector; + +import java.util.Map; +import java.util.TreeMap; + +import fiji.plugin.trackmate.features.spot.SpotContrastAndSNRAnalyzerFactory; +import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.Spot3DMorphologyAnalyzerProvider; +import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; +import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; + +public class AnalyzerSelection +{ + + final Map< String, Boolean > spotAnalyzers = new TreeMap<>(); + + final Map< String, Boolean > edgeAnalyzers = new TreeMap<>(); + + final Map< String, Boolean > trackAnalyzers = new TreeMap<>(); + + private AnalyzerSelection() + {} + + public boolean isSpotAnalyzersSelected( final String key ) + { + return spotAnalyzers.getOrDefault( key, false ); + } + + public boolean isEdgeAnalyzersSelected( final String key ) + { + return spotAnalyzers.getOrDefault( key, false ); + } + + public boolean isTrackAnalyzersSelected( final String key ) + { + return spotAnalyzers.getOrDefault( key, false ); + } + + /** + * Possibly adds the analyzers that are discovered at runtime, but not + * present in the analyzer selection, with the 'selected' flag. + */ + public void mergeWithDefault() + { + final AnalyzerSelection df = defaultSelection(); + + for ( final String key : df.spotAnalyzers.keySet() ) + spotAnalyzers.putIfAbsent( key, true ); + + for ( final String key : df.edgeAnalyzers.keySet() ) + edgeAnalyzers.putIfAbsent( key, true ); + + for ( final String key : df.trackAnalyzers.keySet() ) + trackAnalyzers.putIfAbsent( key, true ); + } + + @Override + public String toString() + { + final StringBuilder str = new StringBuilder( super.toString() ); + str.append( "\nSpot analyzers:" ); + for ( final String key : spotAnalyzers.keySet() ) + str.append( String.format( "\n\t%25s \t-> %s", key, ( spotAnalyzers.get( key ).booleanValue() ? "selected" : "deselected" ) ) ); + str.append( "\nEdge analyzers:" ); + for ( final String key : edgeAnalyzers.keySet() ) + str.append( String.format( "\n\t%25s \t-> %s", key, ( edgeAnalyzers.get( key ).booleanValue() ? "selected" : "deselected" ) ) ); + str.append( "\nTrack analyzers:" ); + for ( final String key : trackAnalyzers.keySet() ) + str.append( String.format( "\n\t%25s \t-> %s", key, ( trackAnalyzers.get( key ).booleanValue() ? "selected" : "deselected" ) ) ); + + return str.toString(); + } + + public static AnalyzerSelection defaultSelection() + { + final AnalyzerSelection fs = new AnalyzerSelection(); + + for ( final String key : new SpotAnalyzerProvider( 1 ).getVisibleKeys() ) + fs.spotAnalyzers.put( key, true ); + + for ( final String key : new Spot2DMorphologyAnalyzerProvider( 1 ).getKeys() ) + fs.spotAnalyzers.put( key, true ); + + for ( final String key : new Spot3DMorphologyAnalyzerProvider( 1 ).getKeys() ) + fs.spotAnalyzers.put( key, true ); + + for ( final String key : new EdgeAnalyzerProvider().getKeys() ) + fs.edgeAnalyzers.put( key, true ); + + for ( final String key : new TrackAnalyzerProvider().getKeys() ) + fs.trackAnalyzers.put( key, true ); + + // Fine tune. + fs.spotAnalyzers.put( SpotContrastAndSNRAnalyzerFactory.KEY, false ); + + return fs; + } + + public void set( final AnalyzerSelection o ) + { + spotAnalyzers.clear(); + spotAnalyzers.putAll( o.spotAnalyzers ); + edgeAnalyzers.clear(); + edgeAnalyzers.putAll( o.edgeAnalyzers ); + trackAnalyzers.clear(); + trackAnalyzers.putAll( o.trackAnalyzers ); + mergeWithDefault(); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectionIO.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectionIO.java new file mode 100644 index 000000000..fe515f852 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectionIO.java @@ -0,0 +1,91 @@ +package fiji.plugin.trackmate.gui.featureselector; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Collectors; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class AnalyzerSelectionIO +{ + + private static File userSelectionFile = new File( new File( System.getProperty( "user.home" ), ".trackmate" ), "featureselection.json" ); + + public static AnalyzerSelection readUserDefault() + { + if ( !userSelectionFile.exists() ) + { + final AnalyzerSelection fs = AnalyzerSelection.defaultSelection(); + saveToUserDefault( fs ); + return fs; + } + + try (FileReader reader = new FileReader( userSelectionFile )) + { + final String str = Files.lines( Paths.get( userSelectionFile.getAbsolutePath() ) ) + .collect( Collectors.joining( System.lineSeparator() ) ); + + return fromJson( str ); + } + catch ( final FileNotFoundException e ) + { + System.err.println( "Could not find the user feature selection file: " + userSelectionFile + + ". Using built-in default setting." ); + e.printStackTrace(); + } + catch ( final IOException e ) + { + System.err.println( "Could not read the user feature selection file: " + userSelectionFile + + ". Using built-in default setting." ); + e.printStackTrace(); + } + return AnalyzerSelection.defaultSelection(); + } + + public static AnalyzerSelection fromJson( final String str ) + { + final AnalyzerSelection fs = ( str == null || str.isEmpty() ) ? readUserDefault() : getGson().fromJson( str, AnalyzerSelection.class ); + fs.mergeWithDefault(); + return fs; + } + + public static void saveToUserDefault( final AnalyzerSelection fs ) + { + final String str = toJson( fs ); + + if ( !userSelectionFile.exists() ) + userSelectionFile.getParentFile().mkdirs(); + + try (FileWriter writer = new FileWriter( userSelectionFile )) + { + writer.append( str ); + } + catch ( final IOException e ) + { + System.err.println( "Could not write the user default settings to " + userSelectionFile ); + e.printStackTrace(); + } + } + + public static String toJson( final AnalyzerSelection fs ) + { + return getGson().toJson( fs ); + } + + private static Gson getGson() + { + final GsonBuilder builder = new GsonBuilder(); + return builder.setPrettyPrinting().create(); + } + + public static void main( final String[] args ) + { + System.out.println( readUserDefault() ); // DEBUG + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java new file mode 100644 index 000000000..6b0294514 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java @@ -0,0 +1,35 @@ +package fiji.plugin.trackmate.gui.featureselector; + +import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; + +import javax.swing.JDialog; +import javax.swing.JFrame; + +public class AnalyzerSelector +{ + + private final JDialog dialog; + + private final AnalyzerSelectorPanel gui; + + public AnalyzerSelector() + { + dialog = new JDialog( ( JFrame ) null, "TrackMate feature analyzers selection" ); + dialog.setLocationByPlatform( true ); + dialog.setLocationRelativeTo( null ); + gui = new AnalyzerSelectorPanel( AnalyzerSelectionIO.readUserDefault() ); + dialog.getContentPane().add( gui ); + dialog.setIconImage( TRACKMATE_ICON.getImage() ); + dialog.pack(); + } + + public JDialog getDialog() + { + return dialog; + } + + public static void main( final String[] args ) + { + new AnalyzerSelector().getDialog().setVisible( true ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java new file mode 100644 index 000000000..ebacd211b --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java @@ -0,0 +1,293 @@ +package fiji.plugin.trackmate.gui.featureselector; + +import static fiji.plugin.trackmate.gui.Icons.APPLY_ICON; +import static fiji.plugin.trackmate.gui.Icons.RESET_ICON; +import static fiji.plugin.trackmate.gui.Icons.REVERT_ICON; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.ScrollPaneConstants; + +import fiji.plugin.trackmate.features.FeatureAnalyzer; +import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactoryBase; +import fiji.plugin.trackmate.providers.AbstractProvider; +import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; +import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; + +public class AnalyzerSelectorPanel extends JPanel +{ + private static final long serialVersionUID = 1L; + + private static final String SPOT_ANALYZER_KEY = "Spot analyzers"; + + private static final String EDGE_ANALYZER_KEY = "Edge analyzers"; + + private static final String TRACK_ANALYZER_KEY = "Track analyzers"; + + private static final String APPLY_TOOLTIP = "Save the current analyzer selection to the user default settings. " + + "The selection be used in all the following TrackMate sessions."; + + private static final String REVERT_TOOLTIP = "Revert the current analyzer selection to the ones saved in the " + + "user default settings file."; + + private static final String RESET_TOOLTIP = "Reset the current analyzer selection to the built-in defaults."; + + final JPanel panelConfig; + + public AnalyzerSelectorPanel( final AnalyzerSelection selection ) + { + setLayout( new BorderLayout( 0, 0 ) ); + + final JPanel panelTable = new JPanel(); + add( panelTable, BorderLayout.SOUTH ); + + final GridBagLayout gblPanelTable = new GridBagLayout(); + gblPanelTable.columnWeights = new double[] { 0.0, 1.0 }; + gblPanelTable.rowWeights = new double[] { 1.0 }; + panelTable.setLayout( gblPanelTable ); + + final JPanel panelButton = new JPanel(); + final GridBagConstraints gbcPanelButton = new GridBagConstraints(); + gbcPanelButton.gridwidth = 2; + gbcPanelButton.insets = new Insets( 10, 10, 10, 10 ); + gbcPanelButton.fill = GridBagConstraints.BOTH; + gbcPanelButton.gridx = 0; + gbcPanelButton.gridy = 0; + panelTable.add( panelButton, gbcPanelButton ); + panelButton.setLayout( new BoxLayout( panelButton, BoxLayout.X_AXIS ) ); + + final BoxLayout panelButtonLayout = new BoxLayout( panelButton, BoxLayout.LINE_AXIS ); + panelButton.setLayout( panelButtonLayout ); + final JButton btnReset = new JButton( "Reset", RESET_ICON ); + btnReset.setToolTipText( RESET_TOOLTIP ); + final JButton btnRevert = new JButton( "Revert", REVERT_ICON ); + btnRevert.setToolTipText( REVERT_TOOLTIP ); + final JButton btnApply = new JButton( "Save to user defaults", APPLY_ICON ); + btnApply.setToolTipText( APPLY_TOOLTIP ); + panelButton.add( btnReset ); + panelButton.add( Box.createHorizontalStrut( 5 ) ); + panelButton.add( btnRevert ); + panelButton.add( Box.createHorizontalGlue() ); + panelButton.add( btnApply ); + panelButton.setBorder( BorderFactory.createEmptyBorder( 10, 5, 10, 5 ) ); + + final JPanel panelTitle = new JPanel( new FlowLayout( FlowLayout.LEADING ) ); + add( panelTitle, BorderLayout.NORTH ); + + final JLabel title = new JLabel( "Configure TrackMate feature analyzers:" ); + title.setFont( getFont().deriveFont( Font.BOLD ) ); + panelTitle.add( title ); + + final JSplitPane splitPane = new JSplitPane(); + splitPane.setBorder( null ); + splitPane.setResizeWeight( 0.5 ); + add( splitPane, BorderLayout.CENTER ); + + final JPanel panelLeft = new JPanel(); + splitPane.setLeftComponent( panelLeft ); + panelLeft.setLayout( new BorderLayout( 0, 0 ) ); + + final JScrollPane scrollPaneFeatures = new JScrollPane(); + panelLeft.add( scrollPaneFeatures ); + scrollPaneFeatures.setViewportBorder( null ); + scrollPaneFeatures.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); + scrollPaneFeatures.getVerticalScrollBar().setUnitIncrement( 20 ); + + final JPanel panelFeatures = new JPanel(); + panelFeatures.setBorder( BorderFactory.createEmptyBorder( 10, 10, 10, 10 ) ); + scrollPaneFeatures.setViewportView( panelFeatures ); + final BoxLayout boxLayout = new BoxLayout( panelFeatures, BoxLayout.PAGE_AXIS ); + panelFeatures.setLayout( boxLayout ); + + final JPanel panelRight = new JPanel(); + panelRight.setPreferredSize( new Dimension( 300, 300 ) ); + splitPane.setRightComponent( panelRight ); + panelRight.setLayout( new BorderLayout( 0, 0 ) ); + + this.panelConfig = new JPanel(); + panelConfig.setLayout( new BorderLayout() ); + final JScrollPane scrollPane = new JScrollPane( panelConfig ); + scrollPane.getVerticalScrollBar().setUnitIncrement( 16 ); + panelRight.add( scrollPane, BorderLayout.CENTER ); + + // Feed the feature panel. + final FeatureTable.Tables aggregator = new FeatureTable.Tables(); + + // Aggregate all maps. + final Map< String, Map< String, Boolean > > allAnalyzers = new LinkedHashMap<>( 3 ); + allAnalyzers.put( SPOT_ANALYZER_KEY, selection.spotAnalyzers ); + allAnalyzers.put( EDGE_ANALYZER_KEY, selection.edgeAnalyzers ); + allAnalyzers.put( TRACK_ANALYZER_KEY, selection.trackAnalyzers ); + + // Providers to test presence of an analyzer and get info. + final Map< String, AbstractProvider< ? > > allProviders = new LinkedHashMap<>( 3 ); + allProviders.put( SPOT_ANALYZER_KEY, new MySpotAnalyzerProvider() ); + allProviders.put( EDGE_ANALYZER_KEY, new EdgeAnalyzerProvider() ); + allProviders.put( TRACK_ANALYZER_KEY, new TrackAnalyzerProvider() ); + + for ( final String target : allAnalyzers.keySet() ) + { + final Map< String, Boolean > analyzers = allAnalyzers.get( target ); + @SuppressWarnings( "unchecked" ) + final AbstractProvider< FeatureAnalyzer > provider = ( AbstractProvider< FeatureAnalyzer > ) allProviders.get( target ); + + final JPanel headerPanel = new JPanel(); + final BoxLayout hpLayout = new BoxLayout( headerPanel, BoxLayout.LINE_AXIS ); + headerPanel.setLayout( hpLayout ); + + final JLabel lbl = new JLabel( target ); + lbl.setFont( panelFeatures.getFont().deriveFont( Font.BOLD ) ); + lbl.setAlignmentX( Component.LEFT_ALIGNMENT ); + + headerPanel.add( lbl ); + + panelFeatures.add( headerPanel ); + headerPanel.setAlignmentX( Component.LEFT_ALIGNMENT ); + panelFeatures.add( Box.createVerticalStrut( 5 ) ); + + final List< String > featureSpecs = new ArrayList<>( analyzers.keySet() ); + + final Function< String, String > getName = k -> provider.getFactory( k ).getName(); + final Predicate< String > isSelected = k -> analyzers.getOrDefault( k, true ); + final BiConsumer< String, Boolean > setSelected = ( k, b ) -> analyzers.put( k, b ); + final Predicate< String > isAnalyzerPresent = k -> provider.getKeys().contains( k ); + + final FeatureTable< List< String >, String > featureTable = + new FeatureTable<>( + featureSpecs, + List::size, + List::get, + getName, + isSelected, + setSelected, + isAnalyzerPresent ); + + featureTable.getComponent().setAlignmentX( Component.LEFT_ALIGNMENT ); + featureTable.getComponent().setBackground( panelFeatures.getBackground() ); + panelFeatures.add( featureTable.getComponent() ); + panelFeatures.add( Box.createVerticalStrut( 10 ) ); + + aggregator.add( featureTable ); + + final FeatureTable.SelectionListener< String > sl = key -> displayConfigPanel( provider.getFactory( key ) ); + featureTable.selectionListeners().add( sl ); + } + scrollPaneFeatures.setPreferredSize( new Dimension( 300, 300 ) ); + + /* + * Listeners. + */ + + btnReset.addActionListener( e -> { + selection.set( AnalyzerSelection.defaultSelection() ); + title.setText( "Reset the current settings to the built-in defaults." ); + repaint(); + } ); + btnRevert.addActionListener( e -> { + selection.set( AnalyzerSelectionIO.readUserDefault() ); + title.setText( "Reverted the current settings to the user defaults." ); + repaint(); + } ); + btnApply.addActionListener( e -> { + AnalyzerSelectionIO.saveToUserDefault( selection ); + title.setText( "Saved the current settings to the user defaults file." ); + } ); + } + + private void displayConfigPanel( final FeatureAnalyzer factory ) + { + panelConfig.removeAll(); + if ( null == factory ) + return; + + final JPanel infoPanel = new JPanel(); + infoPanel.setLayout( new GridBagLayout() ); + final GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets( 5, 5, 5, 5 ); + c.anchor = GridBagConstraints.LINE_START; + c.fill = GridBagConstraints.BOTH; + c.weightx = 1.; + c.weighty = 1.; + + final JLabel title = new JLabel( factory.getName(), factory.getIcon(), JLabel.CENTER ); + title.setFont( getFont().deriveFont( Font.BOLD ) ); + c.gridy = 0; + infoPanel.add( title, c ); + + final JLabel infoLbl = new JLabel(); + infoLbl.setFont( getFont().deriveFont( Font.ITALIC ) ); + final String infoText = factory.getInfoText(); + infoLbl.setText( "" + ( ( infoText != null ) ? infoText : "No documentation." + "" ) ); + c.gridy++; + infoPanel.add( infoLbl, c ); + + final StringBuilder infoStr = new StringBuilder( "" ); + + infoStr.append( "Features included:

    " ); + for ( final String featureKey : factory.getFeatures() ) + { + infoStr.append( "
  • " + factory.getFeatureNames().get( featureKey ) ); + infoStr.append( "
    - Short name: " + factory.getFeatureShortNames().get( featureKey ) ); + infoStr.append( "
    - Is integer valued: " + factory.getIsIntFeature().get( featureKey ) ); + infoStr.append( "
    - Dimension: " + factory.getFeatureDimensions().get( featureKey ) ); + infoStr.append( "
    - Key: " + featureKey ); + infoStr.append( "
  • " ); + infoStr.append( "
    " ); + } + infoStr.append( "

" ); + + infoStr.append( "Details:

    " ); + infoStr.append( String.format( "
  • %25s: %s
  • ", "Key", + factory.getKey() ) ); + infoStr.append( String.format( "
  • %25s: %s
  • ", "Can use multithreading", + !factory.forbidMultithreading() ) ); + infoStr.append( String.format( "
  • %25s: %s
  • ", "Is manual", + factory.isManualFeature() ) ); + infoStr.append( "
" ); + c.gridy++; + final JLabel infoLabel = new JLabel( infoStr.toString() ); + infoLabel.setFont( getFont().deriveFont( Font.PLAIN ) ); + infoPanel.add( infoLabel, c ); + + panelConfig.add( infoPanel, BorderLayout.NORTH ); + panelConfig.revalidate(); + panelConfig.repaint(); + } + + /** + * A private provider, that return all spot providers, regardless of whether + * they act on 2D shape, 3D shape or dont use shape information. + */ + @SuppressWarnings( "rawtypes" ) + private static class MySpotAnalyzerProvider extends AbstractProvider< SpotAnalyzerFactoryBase > + { + + public MySpotAnalyzerProvider() + { + super( SpotAnalyzerFactoryBase.class ); + } + + } +} \ No newline at end of file From af4d012948cffcae614f9b1d84a8fe608058e860 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 10 May 2023 16:17:20 +0200 Subject: [PATCH 151/263] Use proper methods in the analyzer selection class. --- .../featureselector/AnalyzerSelection.java | 109 +++++++++++------- .../AnalyzerSelectorPanel.java | 39 +++---- 2 files changed, 81 insertions(+), 67 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java index c62dfa72a..4241f7c17 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java @@ -1,9 +1,21 @@ package fiji.plugin.trackmate.gui.featureselector; +import static fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject.EDGES; +import static fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject.SPOTS; +import static fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject.TRACKS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.TreeMap; +import org.apache.commons.lang3.StringUtils; + import fiji.plugin.trackmate.features.spot.SpotContrastAndSNRAnalyzerFactory; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; import fiji.plugin.trackmate.providers.Spot3DMorphologyAnalyzerProvider; @@ -13,28 +25,41 @@ public class AnalyzerSelection { - final Map< String, Boolean > spotAnalyzers = new TreeMap<>(); - - final Map< String, Boolean > edgeAnalyzers = new TreeMap<>(); + static final List< TrackMateObject > objs = Arrays.asList( new TrackMateObject[] { SPOTS, EDGES, TRACKS } ); - final Map< String, Boolean > trackAnalyzers = new TreeMap<>(); + private final Map< TrackMateObject, Map< String, Boolean > > allAnalyzers = new LinkedHashMap<>(); private AnalyzerSelection() - {} + { + allAnalyzers.put( SPOTS, new TreeMap<>() ); + allAnalyzers.put( EDGES, new TreeMap<>() ); + allAnalyzers.put( TRACKS, new TreeMap<>() ); + } - public boolean isSpotAnalyzersSelected( final String key ) + public boolean isSelected( final TrackMateObject obj, final String key ) { - return spotAnalyzers.getOrDefault( key, false ); + final Map< String, Boolean > map = allAnalyzers.get( obj ); + if ( map == null ) + return false; + return map.getOrDefault( key, false ); } - public boolean isEdgeAnalyzersSelected( final String key ) + public void setSelected( final TrackMateObject obj, final String key, final boolean selected ) { - return spotAnalyzers.getOrDefault( key, false ); + final Map< String, Boolean > map = allAnalyzers.get( obj ); + if ( map == null ) + return; + + map.put( key, selected ); } - public boolean isTrackAnalyzersSelected( final String key ) + public List< String > getKeys( final TrackMateObject obj ) { - return spotAnalyzers.getOrDefault( key, false ); + final Map< String, Boolean > map = allAnalyzers.get( obj ); + if ( map == null ) + return Collections.emptyList(); + + return new ArrayList<>( map.keySet() ); } /** @@ -44,31 +69,27 @@ public boolean isTrackAnalyzersSelected( final String key ) public void mergeWithDefault() { final AnalyzerSelection df = defaultSelection(); - - for ( final String key : df.spotAnalyzers.keySet() ) - spotAnalyzers.putIfAbsent( key, true ); - - for ( final String key : df.edgeAnalyzers.keySet() ) - edgeAnalyzers.putIfAbsent( key, true ); - - for ( final String key : df.trackAnalyzers.keySet() ) - trackAnalyzers.putIfAbsent( key, true ); + for ( final TrackMateObject obj : objs ) + { + final Map< String, Boolean > source = df.allAnalyzers.get( obj ); + final Map< String, Boolean > target = allAnalyzers.get( obj ); + for ( final String key : source.keySet() ) + target.putIfAbsent( key, true ); + } } @Override public String toString() { final StringBuilder str = new StringBuilder( super.toString() ); - str.append( "\nSpot analyzers:" ); - for ( final String key : spotAnalyzers.keySet() ) - str.append( String.format( "\n\t%25s \t-> %s", key, ( spotAnalyzers.get( key ).booleanValue() ? "selected" : "deselected" ) ) ); - str.append( "\nEdge analyzers:" ); - for ( final String key : edgeAnalyzers.keySet() ) - str.append( String.format( "\n\t%25s \t-> %s", key, ( edgeAnalyzers.get( key ).booleanValue() ? "selected" : "deselected" ) ) ); - str.append( "\nTrack analyzers:" ); - for ( final String key : trackAnalyzers.keySet() ) - str.append( String.format( "\n\t%25s \t-> %s", key, ( trackAnalyzers.get( key ).booleanValue() ? "selected" : "deselected" ) ) ); - + for ( final TrackMateObject obj : objs ) + { + str.append( "\n" + toName( obj ) + " analyzers:" ); + + final Map< String, Boolean > map = allAnalyzers.get( obj ); + for ( final String key : map.keySet() ) + str.append( String.format( "\n\t%25s \t-> %s", key, ( map.get( key ).booleanValue() ? "selected" : "deselected" ) ) ); + } return str.toString(); } @@ -77,34 +98,38 @@ public static AnalyzerSelection defaultSelection() final AnalyzerSelection fs = new AnalyzerSelection(); for ( final String key : new SpotAnalyzerProvider( 1 ).getVisibleKeys() ) - fs.spotAnalyzers.put( key, true ); + fs.setSelected( SPOTS, key, true ); for ( final String key : new Spot2DMorphologyAnalyzerProvider( 1 ).getKeys() ) - fs.spotAnalyzers.put( key, true ); + fs.setSelected( SPOTS, key, true ); for ( final String key : new Spot3DMorphologyAnalyzerProvider( 1 ).getKeys() ) - fs.spotAnalyzers.put( key, true ); + fs.setSelected( SPOTS, key, true ); for ( final String key : new EdgeAnalyzerProvider().getKeys() ) - fs.edgeAnalyzers.put( key, true ); + fs.setSelected( EDGES, key, true ); for ( final String key : new TrackAnalyzerProvider().getKeys() ) - fs.trackAnalyzers.put( key, true ); + fs.setSelected( TRACKS, key, true ); // Fine tune. - fs.spotAnalyzers.put( SpotContrastAndSNRAnalyzerFactory.KEY, false ); + fs.setSelected( SPOTS, SpotContrastAndSNRAnalyzerFactory.KEY, false ); return fs; } public void set( final AnalyzerSelection o ) { - spotAnalyzers.clear(); - spotAnalyzers.putAll( o.spotAnalyzers ); - edgeAnalyzers.clear(); - edgeAnalyzers.putAll( o.edgeAnalyzers ); - trackAnalyzers.clear(); - trackAnalyzers.putAll( o.trackAnalyzers ); + allAnalyzers.clear(); + for ( final TrackMateObject obj : objs ) + allAnalyzers.put( obj, new TreeMap<>( o.allAnalyzers.get( obj ) ) ); + mergeWithDefault(); } + + public static final String toName(final TrackMateObject obj) + { + final String str = obj.toString(); + return StringUtils.capitalize( str ).substring( 0, str.length() - 1 ); + } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java index ebacd211b..24da785b3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java @@ -3,6 +3,9 @@ import static fiji.plugin.trackmate.gui.Icons.APPLY_ICON; import static fiji.plugin.trackmate.gui.Icons.RESET_ICON; import static fiji.plugin.trackmate.gui.Icons.REVERT_ICON; +import static fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject.EDGES; +import static fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject.SPOTS; +import static fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject.TRACKS; import java.awt.BorderLayout; import java.awt.Component; @@ -12,7 +15,6 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -32,6 +34,7 @@ import fiji.plugin.trackmate.features.FeatureAnalyzer; import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactoryBase; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.providers.AbstractProvider; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; @@ -40,12 +43,6 @@ public class AnalyzerSelectorPanel extends JPanel { private static final long serialVersionUID = 1L; - private static final String SPOT_ANALYZER_KEY = "Spot analyzers"; - - private static final String EDGE_ANALYZER_KEY = "Edge analyzers"; - - private static final String TRACK_ANALYZER_KEY = "Track analyzers"; - private static final String APPLY_TOOLTIP = "Save the current analyzer selection to the user default settings. " + "The selection be used in all the following TrackMate sessions."; @@ -135,21 +132,14 @@ public AnalyzerSelectorPanel( final AnalyzerSelection selection ) // Feed the feature panel. final FeatureTable.Tables aggregator = new FeatureTable.Tables(); - // Aggregate all maps. - final Map< String, Map< String, Boolean > > allAnalyzers = new LinkedHashMap<>( 3 ); - allAnalyzers.put( SPOT_ANALYZER_KEY, selection.spotAnalyzers ); - allAnalyzers.put( EDGE_ANALYZER_KEY, selection.edgeAnalyzers ); - allAnalyzers.put( TRACK_ANALYZER_KEY, selection.trackAnalyzers ); - // Providers to test presence of an analyzer and get info. - final Map< String, AbstractProvider< ? > > allProviders = new LinkedHashMap<>( 3 ); - allProviders.put( SPOT_ANALYZER_KEY, new MySpotAnalyzerProvider() ); - allProviders.put( EDGE_ANALYZER_KEY, new EdgeAnalyzerProvider() ); - allProviders.put( TRACK_ANALYZER_KEY, new TrackAnalyzerProvider() ); + final Map< TrackMateObject, AbstractProvider< ? > > allProviders = new LinkedHashMap<>( 3 ); + allProviders.put( SPOTS, new MySpotAnalyzerProvider() ); + allProviders.put( EDGES, new EdgeAnalyzerProvider() ); + allProviders.put( TRACKS, new TrackAnalyzerProvider() ); - for ( final String target : allAnalyzers.keySet() ) + for ( final TrackMateObject target : AnalyzerSelection.objs ) { - final Map< String, Boolean > analyzers = allAnalyzers.get( target ); @SuppressWarnings( "unchecked" ) final AbstractProvider< FeatureAnalyzer > provider = ( AbstractProvider< FeatureAnalyzer > ) allProviders.get( target ); @@ -157,7 +147,7 @@ public AnalyzerSelectorPanel( final AnalyzerSelection selection ) final BoxLayout hpLayout = new BoxLayout( headerPanel, BoxLayout.LINE_AXIS ); headerPanel.setLayout( hpLayout ); - final JLabel lbl = new JLabel( target ); + final JLabel lbl = new JLabel( AnalyzerSelection.toName( target ) + " analyzers:" ); lbl.setFont( panelFeatures.getFont().deriveFont( Font.BOLD ) ); lbl.setAlignmentX( Component.LEFT_ALIGNMENT ); @@ -167,16 +157,15 @@ public AnalyzerSelectorPanel( final AnalyzerSelection selection ) headerPanel.setAlignmentX( Component.LEFT_ALIGNMENT ); panelFeatures.add( Box.createVerticalStrut( 5 ) ); - final List< String > featureSpecs = new ArrayList<>( analyzers.keySet() ); - + final List< String > analyzerKeys = selection.getKeys( target ); final Function< String, String > getName = k -> provider.getFactory( k ).getName(); - final Predicate< String > isSelected = k -> analyzers.getOrDefault( k, true ); - final BiConsumer< String, Boolean > setSelected = ( k, b ) -> analyzers.put( k, b ); + final Predicate< String > isSelected = k -> selection.isSelected( target, k ); + final BiConsumer< String, Boolean > setSelected = ( k, b ) -> selection.setSelected( target, k, b ); final Predicate< String > isAnalyzerPresent = k -> provider.getKeys().contains( k ); final FeatureTable< List< String >, String > featureTable = new FeatureTable<>( - featureSpecs, + analyzerKeys, List::size, List::get, getName, From 0f4bf9b81d7b02f7134736a0d55f0094027bd7ce Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 10 May 2023 16:44:11 +0200 Subject: [PATCH 152/263] Use the user feature analyzers selection when using the plugin. --- .../plugin/trackmate/TrackMatePlugIn.java | 11 ++++++---- .../gui/featureselector/AnalyzerSelector.java | 20 +++++++++++++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java index 868319c14..e02dbb318 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java @@ -30,6 +30,8 @@ import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.gui.featureselector.AnalyzerSelection; +import fiji.plugin.trackmate.gui.featureselector.AnalyzerSelectionIO; import fiji.plugin.trackmate.gui.wizard.TrackMateWizardSequence; import fiji.plugin.trackmate.gui.wizard.WizardSequence; import fiji.plugin.trackmate.io.SettingsPersistence; @@ -149,10 +151,11 @@ protected Model createModel( final ImagePlus imp ) protected Settings createSettings( final ImagePlus imp ) { // Persistence. - final Settings ls = SettingsPersistence.readLastUsedSettings( imp, Logger.DEFAULT_LOGGER ); - // Force adding analyzers found at runtime - ls.addAllAnalyzers(); - return ls; + final Settings settings = SettingsPersistence.readLastUsedSettings( imp, Logger.DEFAULT_LOGGER ); + // Add the analyzers configured by the user. + final AnalyzerSelection analyzerSelection = AnalyzerSelectionIO.readUserDefault(); + analyzerSelection.configure( settings ); + return settings; } /** diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java index 6b0294514..0d58105c5 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java @@ -5,7 +5,17 @@ import javax.swing.JDialog; import javax.swing.JFrame; -public class AnalyzerSelector +import org.scijava.command.Command; +import org.scijava.plugin.Plugin; + +@Plugin( type = Command.class, + label = "Configure TrackMate feature analyzers...", + iconPath = "/icons/commands/information.png", + menuPath = "Edit > Options > Configure TrackMate feature analyzers...", + description = "Shows a dialog that allows configuring what feature analyzers will be used " + + "in the next TrackMate session." ) + +public class AnalyzerSelector implements Command { private final JDialog dialog; @@ -28,8 +38,14 @@ public JDialog getDialog() return dialog; } + @Override + public void run() + { + dialog.setVisible( true ); + } + public static void main( final String[] args ) { - new AnalyzerSelector().getDialog().setVisible( true ); + new AnalyzerSelector().run(); } } From 00ebafa493125a1b6fb23eaf7ba4cede60347f1c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 10 May 2023 16:44:34 +0200 Subject: [PATCH 153/263] Make the feature analyzer selector a IJ2 command. --- .../featureselector/AnalyzerSelection.java | 93 ++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java index 4241f7c17..98da81707 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java @@ -11,10 +11,16 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.features.edges.EdgeAnalyzer; +import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactory; import fiji.plugin.trackmate.features.spot.SpotContrastAndSNRAnalyzerFactory; +import fiji.plugin.trackmate.features.track.TrackAnalyzer; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.providers.EdgeAnalyzerProvider; import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; @@ -62,6 +68,90 @@ public List< String > getKeys( final TrackMateObject obj ) return new ArrayList<>( map.keySet() ); } + public List< String > getSelectedAnalyzers( final TrackMateObject obj ) + { + final Map< String, Boolean > map = allAnalyzers.get( obj ); + if ( map == null ) + return Collections.emptyList(); + + return map.entrySet() + .stream() + .filter( e -> e.getValue() ) + .map( e -> e.getKey() ) + .collect( Collectors.toList() ); + } + + /** + * Configure the specified settings object so that it includes only all the + * analyzers in this selection. + * + * @param settings + */ + public void configure( final Settings settings ) + { + settings.clearSpotAnalyzerFactories(); + settings.clearEdgeAnalyzers(); + settings.clearTrackAnalyzers(); + + final List< String > spotAnalyzers = getSelectedAnalyzers( SPOTS ); + + // Base spot analyzers. + final SpotAnalyzerProvider spotAnalyzerProvider = new SpotAnalyzerProvider( settings.imp == null + ? 1 : settings.imp.getNChannels() ); + for ( final String key : spotAnalyzers ) + { + final SpotAnalyzerFactory< ? > factory = spotAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addSpotAnalyzerFactory( factory ); + } + + // Shall we add 2D morphology analyzers? + if ( settings.imp != null + && DetectionUtils.is2D( settings.imp ) + && settings.detectorFactory != null + && settings.detectorFactory.has2Dsegmentation() ) + { + for ( final String key : spotAnalyzers ) + { + final SpotAnalyzerFactory< ? > factory = spotAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addSpotAnalyzerFactory( factory ); + } + } + + // Shall we add 3D morphology analyzers? + if ( settings.imp != null + && !DetectionUtils.is2D( settings.imp ) + && settings.detectorFactory != null + && settings.detectorFactory.has3Dsegmentation() ) + { + for ( final String key : spotAnalyzers ) + { + final SpotAnalyzerFactory< ? > factory = spotAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addSpotAnalyzerFactory( factory ); + } + } + + // Edge analyzers. + final EdgeAnalyzerProvider edgeAnalyzerProvider = new EdgeAnalyzerProvider(); + for ( final String key : getSelectedAnalyzers( EDGES ) ) + { + final EdgeAnalyzer factory = edgeAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addEdgeAnalyzer( factory ); + } + + // Track analyzers. + final TrackAnalyzerProvider trackAnalyzerProvider = new TrackAnalyzerProvider(); + for ( final String key : getSelectedAnalyzers( TRACKS ) ) + { + final TrackAnalyzer factory = trackAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addTrackAnalyzer( factory ); + } + } + /** * Possibly adds the analyzers that are discovered at runtime, but not * present in the analyzer selection, with the 'selected' flag. @@ -127,9 +217,10 @@ public void set( final AnalyzerSelection o ) mergeWithDefault(); } - public static final String toName(final TrackMateObject obj) + public static final String toName( final TrackMateObject obj ) { final String str = obj.toString(); return StringUtils.capitalize( str ).substring( 0, str.length() - 1 ); } + } From 14d7ca60153037c1d19e538d270d2e30dfd3e96d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 10 May 2023 16:57:24 +0200 Subject: [PATCH 154/263] Also abides to user spot analyser selection after detection step. Because morphology analyzers (2D or 3D) are added only after detection step (they are added at this moment, because we need to know whether the detector can return the spot shape). --- .../descriptors/SpotFilterDescriptor.java | 76 +++++++++++++------ 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java index d4bb32c44..5f9432e1a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java @@ -22,8 +22,8 @@ package fiji.plugin.trackmate.gui.wizard.descriptors; import java.awt.Container; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import javax.swing.JLabel; @@ -41,6 +41,8 @@ import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.FilterGuiPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; +import fiji.plugin.trackmate.gui.featureselector.AnalyzerSelection; +import fiji.plugin.trackmate.gui.featureselector.AnalyzerSelectionIO; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; import fiji.plugin.trackmate.io.SettingsPersistence; import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; @@ -113,19 +115,33 @@ public void run() && trackmate.getSettings().detectorFactory.has2Dsegmentation() && DetectionUtils.is2D( trackmate.getSettings().imp ) ) { - logger.log( "\nAdding 2D morphology analyzers...\n", Logger.BLUE_COLOR ); final Settings settings = trackmate.getSettings(); final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot2DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); - @SuppressWarnings( "rawtypes" ) - final List< Spot2DMorphologyAnalyzerFactory > factories = spotMorphologyAnalyzerProvider - .getKeys() - .stream() - .map( key -> spotMorphologyAnalyzerProvider.getFactory( key ) ) - .collect( Collectors.toList() ); - factories.forEach( settings::addSpotAnalyzerFactory ); - final StringBuilder strb = new StringBuilder(); - Settings.prettyPrintFeatureAnalyzer( factories, strb ); - logger.log( strb.toString() ); + + final AnalyzerSelection analyzerSelection = AnalyzerSelectionIO.readUserDefault(); + final List< Spot2DMorphologyAnalyzerFactory< ? > > factories = new ArrayList<>(); + + for ( final String key : analyzerSelection.getSelectedAnalyzers( TrackMateObject.SPOTS ) ) + { + final Spot2DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); + if ( factory != null ) + { + settings.addSpotAnalyzerFactory( factory ); + factories.add( factory ); + } + } + + if ( factories.isEmpty() ) + { + logger.log( "\nNo 2D morphology analyzers to add.\n", Logger.BLUE_COLOR ); + } + else + { + logger.log( "\nAdding 2D morphology analyzers...\n", Logger.BLUE_COLOR ); + final StringBuilder strb = new StringBuilder(); + Settings.prettyPrintFeatureAnalyzer( factories, strb ); + logger.log( strb.toString() ); + } } // 3D. @@ -133,19 +149,33 @@ public void run() && trackmate.getSettings().detectorFactory.has3Dsegmentation() && !DetectionUtils.is2D( trackmate.getSettings().imp ) ) { - logger.log( "\nAdding 3D morphology analyzers...\n", Logger.BLUE_COLOR ); final Settings settings = trackmate.getSettings(); final Spot3DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot3DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); - @SuppressWarnings( "rawtypes" ) - final List< Spot3DMorphologyAnalyzerFactory > factories = spotMorphologyAnalyzerProvider - .getKeys() - .stream() - .map( key -> spotMorphologyAnalyzerProvider.getFactory( key ) ) - .collect( Collectors.toList() ); - factories.forEach( settings::addSpotAnalyzerFactory ); - final StringBuilder strb = new StringBuilder(); - Settings.prettyPrintFeatureAnalyzer( factories, strb ); - logger.log( strb.toString() ); + + final AnalyzerSelection analyzerSelection = AnalyzerSelectionIO.readUserDefault(); + final List< Spot3DMorphologyAnalyzerFactory< ? > > factories = new ArrayList<>(); + + for ( final String key : analyzerSelection.getSelectedAnalyzers( TrackMateObject.SPOTS ) ) + { + final Spot3DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); + if ( factory != null ) + { + settings.addSpotAnalyzerFactory( factory ); + factories.add( factory ); + } + } + + if ( factories.isEmpty() ) + { + logger.log( "\nNo 3D morphology analyzers to add.\n", Logger.BLUE_COLOR ); + } + else + { + logger.log( "\nAdding 3D morphology analyzers...\n", Logger.BLUE_COLOR ); + final StringBuilder strb = new StringBuilder(); + Settings.prettyPrintFeatureAnalyzer( factories, strb ); + logger.log( strb.toString() ); + } } /* From bde9aef7498987d31c3c2f1f779709ab24d4c711 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 18 May 2023 18:19:07 +0200 Subject: [PATCH 155/263] Minor tweak of javadoc and error reporting. --- .../plugin/trackmate/detection/SpotDetector.java | 12 +++++------- .../trackmate/detection/SpotDetectorFactory.java | 2 +- .../tracking/overlap/OverlapTrackerFactory.java | 2 ++ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetector.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetector.java index 8578ecc08..e62e562df 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetector.java @@ -30,15 +30,13 @@ import net.imglib2.type.numeric.RealType; /** - * Interface for Spot detector classes, that are able to segment spots of a - * given estimated radius within a 2D or 3D image. + * Interface for Spot detector classes, that are able to detect or segment spots + * in a single time-point 2D or 3D image. *

- * Normally, concrete implementation are not expected to be multi-threaded. - * Indeed, the {@link fiji.plugin.trackmate.TrackMate} trackmate generates one - * instance of the concrete implementation per thread, to process multiple - * frames simultaneously. + * Concrete implementation can be multithreaded. In that case TrackMate will + * possible allocate some threads to each instance of this class. * - * @author Jean-Yves Tinevez <jeanyves.tinevez@gmail.com> 2010 - 2012 + * @author Jean-Yves Tinevez, 2010 - 2012 * */ public interface SpotDetector< T extends RealType< T > & NativeType< T > > extends OutputAlgorithm< List< Spot > >, Benchmark diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java index 0800908dd..58ef2a739 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java @@ -27,7 +27,7 @@ /** * For detectors that process one time-point at a time, independently, and for - * which we can therefore propose multithreading. * + * which we can therefore propose multithreading. *

* These classes are able to configure a {@link SpotDetector} to operate on a * single time-point of the target ImgPlus. diff --git a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTrackerFactory.java b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTrackerFactory.java index 5bc05f791..5741b343b 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTrackerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTrackerFactory.java @@ -175,6 +175,8 @@ public boolean unmarshall( final Element element, final Map< String, Object > se ok = ok & readDoubleAttribute( element, settings, KEY_SCALE_FACTOR, errorHolder ); ok = ok & readDoubleAttribute( element, settings, KEY_MIN_IOU, errorHolder ); ok = ok & readStringAttribute( element, settings, KEY_IOU_CALCULATION, errorHolder ); + if ( !ok ) + errorMessage = "[" + getKey() + "] " + errorHolder.toString(); return ok; } From f8fca46241d19c28e389c5823296917173fb5962 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 18 May 2023 18:20:01 +0200 Subject: [PATCH 156/263] Convenience class to run 2D+Z segmentation algos in 3D. A SpotDetector for 3D images that work by running a spot segmentation algorithm on 2D slices, and merging results using a tracker. This yield a label image that is then converted to 3D meshes using the LabelImageDetector. This is a convenience class, made to be used in specialized SpotDetectorFactory with specific choices of detector and merging strategy. --- .../trackmate/detection/Process2DZ.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java new file mode 100644 index 000000000..b29c832db --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -0,0 +1,119 @@ +package fiji.plugin.trackmate.detection; + +import java.util.List; + +import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.action.LabelImgExporter; +import fiji.plugin.trackmate.util.TMUtils; +import ij.ImagePlus; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imglib2.algorithm.MultiThreadedBenchmarkAlgorithm; +import net.imglib2.img.display.imagej.CalibrationUtils; +import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; + +/** + * A {@link SpotDetector} for 3D images that work by running a spot segmentation + * algorithm on 2D slices, and merging results using a tracker. This yield a + * label image that is then converted to 3D meshes using the + * {@link LabelImageDetector}. + *

+ * This is a convenience class, made to be used in specialized + * {@link SpotDetectorFactory} with specific choices of detector and merging + * strategy. + * + * @author Jean-Yves Tinevez, 2023 + * + * @param + */ +public class Process2DZ< T extends RealType< T > & NativeType< T > > + extends MultiThreadedBenchmarkAlgorithm + implements SpotDetector< T > +{ + + private static final String BASE_ERROR_MESSAGE = "[Process3Das2DZ] "; + + private final ImgPlus< T > img; + + private final Settings settings; + + private final boolean simplify; + + private List< Spot > spots; + + public Process2DZ( final ImgPlus< T > img, final Settings settings, final boolean simplifyMeshes ) + { + this.img = img; + this.settings = settings; + this.simplify = simplifyMeshes; + } + + @Override + public boolean checkInput() + { + if ( img.dimensionIndex( Axes.Z ) < 0 || img.dimension( img.dimensionIndex( Axes.Z ) ) < 2 ) + { + errorMessage = BASE_ERROR_MESSAGE + "Source image is not 3D."; + return false; + } + if ( img.dimensionIndex( Axes.TIME ) > 0 && img.dimension( img.dimensionIndex( Axes.TIME ) ) > 1 ) + { + errorMessage = BASE_ERROR_MESSAGE + "Source image has more than one time-point."; + return false; + } + return true; + } + + @Override + public boolean process() + { + spots = null; + // Make the final single T 3D image, a 2D + T image final by making Z -> T + final ImagePlus imp = ImageJFunctions.wrap( img, null ); + final int nChannels = ( int ) ( img.dimensionIndex( Axes.CHANNEL ) < 0 ? 1 : img.dimension( img.dimensionIndex( Axes.CHANNEL ) ) ); + final int nSlices = 1; // We force 2D. + final int nFrames = ( int ) img.dimension( img.dimensionIndex( Axes.Z ) ); + imp.setDimensions( nChannels, nSlices, nFrames ); + CalibrationUtils.copyCalibrationToImagePlus( img, imp ); + + // Execute segmentation and tracking. + final Settings settingsFrame = settings.copyOn( imp ); + final TrackMate trackmate = new TrackMate( settingsFrame ); + trackmate.setNumThreads( numThreads ); + trackmate.getModel().setLogger( Logger.VOID_LOGGER ); + if ( !trackmate.checkInput() || !trackmate.process() ) + { + errorMessage = BASE_ERROR_MESSAGE + trackmate.getErrorMessage(); + return false; + } + + // Get 2D+T masks + final ImagePlus lblImp = LabelImgExporter.createLabelImagePlus( trackmate, false, true, false ); + + // Back to a 3D single time-point image. + lblImp.setDimensions( lblImp.getNChannels(), lblImp.getNFrames(), lblImp.getNSlices() ); + + // Convert labels to 3D meshes. + final ImgPlus< T > lblImg = TMUtils.rawWraps( lblImp ); + final LabelImageDetector< T > detector = new LabelImageDetector<>( lblImg, lblImg, TMUtils.getSpatialCalibration( lblImp ), simplify ); + if ( !detector.checkInput() || !detector.process() ) + { + errorMessage = BASE_ERROR_MESSAGE + detector.getErrorMessage(); + return false; + } + + this.spots = detector.getResult(); + return true; + } + + @Override + public List< Spot > getResult() + { + return spots; + } +} From 1c2bc75568305af05afe73c020c5ad8886cc92da Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 16:05:43 +0200 Subject: [PATCH 157/263] Fix bug in analyzer selection. When using the configure(Settings) method, the morphology analyzers were not added to the Settings object, even when selected. --- .../trackmate/gui/featureselector/AnalyzerSelection.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java index 98da81707..25c4b5d41 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java @@ -18,6 +18,8 @@ import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.features.edges.EdgeAnalyzer; +import fiji.plugin.trackmate.features.spot.Spot2DMorphologyAnalyzerFactory; +import fiji.plugin.trackmate.features.spot.Spot3DMorphologyAnalyzerFactory; import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactory; import fiji.plugin.trackmate.features.spot.SpotContrastAndSNRAnalyzerFactory; import fiji.plugin.trackmate.features.track.TrackAnalyzer; @@ -111,9 +113,10 @@ public void configure( final Settings settings ) && settings.detectorFactory != null && settings.detectorFactory.has2Dsegmentation() ) { + final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot2DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); for ( final String key : spotAnalyzers ) { - final SpotAnalyzerFactory< ? > factory = spotAnalyzerProvider.getFactory( key ); + final Spot2DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); if ( factory != null ) settings.addSpotAnalyzerFactory( factory ); } @@ -125,9 +128,10 @@ public void configure( final Settings settings ) && settings.detectorFactory != null && settings.detectorFactory.has3Dsegmentation() ) { + final Spot3DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot3DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); for ( final String key : spotAnalyzers ) { - final SpotAnalyzerFactory< ? > factory = spotAnalyzerProvider.getFactory( key ); + final Spot3DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); if ( factory != null ) settings.addSpotAnalyzerFactory( factory ); } From abc68b50e4e277a1a26a404493653f6805596cf2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 16:06:29 +0200 Subject: [PATCH 158/263] The GUI honors the user selection when computing spot features. The spot feature analyzer factories are directly read from the user selection. --- .../descriptors/SpotFilterDescriptor.java | 83 ++----------------- 1 file changed, 9 insertions(+), 74 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java index 5f9432e1a..9f86f8f58 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java @@ -22,7 +22,6 @@ package fiji.plugin.trackmate.gui.wizard.descriptors; import java.awt.Container; -import java.util.ArrayList; import java.util.List; import javax.swing.JLabel; @@ -34,10 +33,7 @@ import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.features.FeatureFilter; -import fiji.plugin.trackmate.features.spot.Spot2DMorphologyAnalyzerFactory; -import fiji.plugin.trackmate.features.spot.Spot3DMorphologyAnalyzerFactory; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.FilterGuiPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; @@ -45,8 +41,6 @@ import fiji.plugin.trackmate.gui.featureselector.AnalyzerSelectionIO; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; import fiji.plugin.trackmate.io.SettingsPersistence; -import fiji.plugin.trackmate.providers.Spot2DMorphologyAnalyzerProvider; -import fiji.plugin.trackmate.providers.Spot3DMorphologyAnalyzerProvider; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; public class SpotFilterDescriptor extends WizardPanelDescriptor @@ -107,76 +101,17 @@ public void run() logger.log( String.format( "Retained %d spots out of %d.\n", nselected, ntotal ) ); /* - * Should we add morphology feature analyzers? + * Add analyzers in the user selection and possible the + * morphology ones in 2D or 3D. */ - // 2D. - if ( trackmate.getSettings().detectorFactory != null - && trackmate.getSettings().detectorFactory.has2Dsegmentation() - && DetectionUtils.is2D( trackmate.getSettings().imp ) ) - { - final Settings settings = trackmate.getSettings(); - final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot2DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); - - final AnalyzerSelection analyzerSelection = AnalyzerSelectionIO.readUserDefault(); - final List< Spot2DMorphologyAnalyzerFactory< ? > > factories = new ArrayList<>(); - - for ( final String key : analyzerSelection.getSelectedAnalyzers( TrackMateObject.SPOTS ) ) - { - final Spot2DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); - if ( factory != null ) - { - settings.addSpotAnalyzerFactory( factory ); - factories.add( factory ); - } - } - - if ( factories.isEmpty() ) - { - logger.log( "\nNo 2D morphology analyzers to add.\n", Logger.BLUE_COLOR ); - } - else - { - logger.log( "\nAdding 2D morphology analyzers...\n", Logger.BLUE_COLOR ); - final StringBuilder strb = new StringBuilder(); - Settings.prettyPrintFeatureAnalyzer( factories, strb ); - logger.log( strb.toString() ); - } - } - - // 3D. - if ( trackmate.getSettings().detectorFactory != null - && trackmate.getSettings().detectorFactory.has3Dsegmentation() - && !DetectionUtils.is2D( trackmate.getSettings().imp ) ) - { - final Settings settings = trackmate.getSettings(); - final Spot3DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot3DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); - - final AnalyzerSelection analyzerSelection = AnalyzerSelectionIO.readUserDefault(); - final List< Spot3DMorphologyAnalyzerFactory< ? > > factories = new ArrayList<>(); - - for ( final String key : analyzerSelection.getSelectedAnalyzers( TrackMateObject.SPOTS ) ) - { - final Spot3DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); - if ( factory != null ) - { - settings.addSpotAnalyzerFactory( factory ); - factories.add( factory ); - } - } - - if ( factories.isEmpty() ) - { - logger.log( "\nNo 3D morphology analyzers to add.\n", Logger.BLUE_COLOR ); - } - else - { - logger.log( "\nAdding 3D morphology analyzers...\n", Logger.BLUE_COLOR ); - final StringBuilder strb = new StringBuilder(); - Settings.prettyPrintFeatureAnalyzer( factories, strb ); - logger.log( strb.toString() ); - } - } + final AnalyzerSelection analyzerSelection = AnalyzerSelectionIO.readUserDefault(); + final Settings settings = trackmate.getSettings(); + analyzerSelection.configure( settings ); + logger.log( "\nAdding the following spot feature analyzers...\n", Logger.BLUE_COLOR ); + final StringBuilder strb = new StringBuilder(); + Settings.prettyPrintFeatureAnalyzer( settings.getSpotAnalyzerFactories(), strb ); + logger.log( strb.toString() ); /* * Show and log to progress bar in the filter GUI panel. From 8be3b4ef7b1daf91de1273e4d6274457e619cd2f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:03:26 +0200 Subject: [PATCH 159/263] Use again the custom 2D iteration routine for SpotRoi. I was using it before and trash it in the overhaul. But this one is about 30x faster. So let's put it back, with some adapting. Noticed by @Mini-Miette --- .../java/fiji/plugin/trackmate/SpotRoi.java | 269 +++++++++++++++++- 1 file changed, 259 insertions(+), 10 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index e18ee746b..3ae2a4caa 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -22,18 +22,20 @@ package fiji.plugin.trackmate; import java.util.Arrays; +import java.util.Iterator; import gnu.trove.list.array.TDoubleArrayList; +import net.imglib2.Cursor; +import net.imglib2.FinalInterval; import net.imglib2.IterableInterval; +import net.imglib2.Localizable; +import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; -import net.imglib2.roi.IterableRegion; -import net.imglib2.roi.Masks; -import net.imglib2.roi.Regions; -import net.imglib2.roi.geom.GeomMasks; -import net.imglib2.roi.geom.real.WritablePolygon2D; -import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.RealType; +import net.imglib2.util.Intervals; import net.imglib2.util.Util; +import net.imglib2.view.IntervalView; +import net.imglib2.view.Views; public class SpotRoi extends SpotBase { @@ -230,10 +232,7 @@ public double[][] toArray( final double cx, final double cy, final double sx, fi @Override public < T extends RealType< T > > IterableInterval< T > iterable( final RandomAccessible< T > ra, final double[] calibration ) { - final double[][] out = toArray( 0., 0., 1 / calibration[ 0 ], 1 / calibration[ 1 ] ); - final WritablePolygon2D polygon = GeomMasks.closedPolygon2D( out[ 0 ], out[ 1 ] ); - final IterableRegion< BoolType > region = Masks.toIterableRegion( polygon ); - return Regions.sample( region, ra ); + return new SpotRoiIterable<>( this, ra, calibration ); } private static double radius( final double[] x, final double[] y ) @@ -313,4 +312,254 @@ private static final double signedArea( final double[] x, final double[] y ) return a / 2.; } + + /* + * ITERABLE and ITERATOR. + */ + + private static final class SpotRoiIterable< T extends RealType< T > > implements IterableInterval< T > + { + + private final FinalInterval interval; + + /** Polygon X coords in pixel units. */ + private final double[] x; + + /** Polygon Y coords in pixel units. */ + private final double[] y; + + private final RandomAccessible< T > ra; + + public SpotRoiIterable( final SpotRoi roi, final RandomAccessible< T > ra, final double[] calibration ) + { + this.ra = ra; + final double[][] xy = roi.toArray( 0., 0., 1 / calibration[ 0 ], 1 / calibration[ 1 ] ); + this.x = xy[ 0 ]; + this.y = xy[ 1 ]; + final long minX = ( long ) Math.floor( Util.min( x ) ); + final long maxX = ( long ) Math.ceil( Util.max( x ) ); + final long minY = ( long ) Math.floor( Util.min( y ) ); + final long maxY = ( long ) Math.ceil( Util.max( y ) ); + interval = Intervals.createMinMax( minX, minY, maxX, maxY ); + } + + @Override + public long size() + { + int n = 0; + final Cursor< T > cursor = cursor(); + while ( cursor.hasNext() ) + { + cursor.fwd(); + n++; + } + return n; + } + + @Override + public T firstElement() + { + return cursor().next(); + } + + @Override + public Object iterationOrder() + { + return this; + } + + @Override + public double realMin( final int d ) + { + return interval.realMin( d ); + } + + @Override + public double realMax( final int d ) + { + return interval.realMax( d ); + } + + @Override + public int numDimensions() + { + return 2; + } + + @Override + public long min( final int d ) + { + return interval.min( d ); + } + + @Override + public long max( final int d ) + { + return interval.max( d ); + } + + @Override + public Cursor< T > cursor() + { + return new MyCursor< T >( x, y, ra ); + } + + @Override + public Cursor< T > localizingCursor() + { + return cursor(); + } + + @Override + public Iterator< T > iterator() + { + return cursor(); + } + } + + /** + * Iterates inside a close polygon given by X & Y in pixel coordinates. + * + * @param + * the type of pixel in the image. + */ + private static final class MyCursor< T extends RealType< T > > implements Cursor< T > + { + + private final FinalInterval interval; + + private Cursor< T > cursor; + + private final double[] x; + + private final double[] y; + + private boolean hasNext; + + private final RandomAccessible< T > rae; + + private RandomAccess< T > ra; + + public MyCursor( final double[] x, final double[] y, final RandomAccessible< T > rae ) + { + this.x = x; + this.y = y; + this.rae = rae; + final long minX = ( long ) Math.floor( Util.min( x ) ); + final long maxX = ( long ) Math.ceil( Util.max( x ) ); + final long minY = ( long ) Math.floor( Util.min( y ) ); + final long maxY = ( long ) Math.ceil( Util.max( y ) ); + interval = Intervals.createMinMax( minX, minY, maxX, maxY ); + reset(); + } + + @Override + public T get() + { + return ra.get(); + } + + @Override + public void fwd() + { + ra.setPosition( cursor ); + fetch(); + } + + private void fetch() + { + while ( cursor.hasNext() ) + { + cursor.fwd(); + if ( isInside( cursor, x, y ) ) + { + hasNext = cursor.hasNext(); + return; + } + } + hasNext = false; + } + + private static final boolean isInside( final Localizable localizable, final double[] x, final double[] y ) + { + // Taken from Imglib2-roi GeomMaths. No edge case. + final double xl = localizable.getDoublePosition( 0 ); + final double yl = localizable.getDoublePosition( 1 ); + + int i; + int j; + boolean inside = false; + for ( i = 0, j = x.length - 1; i < x.length; j = i++ ) + { + final double xj = x[ j ]; + final double yj = y[ j ]; + + final double xi = x[ i ]; + final double yi = y[ i ]; + + if ( ( yi > yl ) != ( yj > yl ) && ( xl < ( xj - xi ) * ( yl - yi ) / ( yj - yi ) + xi ) ) + inside = !inside; + } + return inside; + } + + @Override + public void reset() + { + final IntervalView< T > view = Views.interval( rae, interval ); + cursor = view.localizingCursor(); + ra = rae.randomAccess( interval ); + fetch(); + } + + @Override + public double getDoublePosition( final int d ) + { + return ra.getDoublePosition( d ); + } + + @Override + public int numDimensions() + { + return 2; + } + + @Override + public void jumpFwd( final long steps ) + { + for ( int i = 0; i < steps; i++ ) + fwd(); + } + + @Override + public boolean hasNext() + { + return hasNext; + } + + @Override + public T next() + { + fwd(); + return get(); + } + + @Override + public long getLongPosition( final int d ) + { + return ra.getLongPosition( d ); + } + + @Override + public Cursor< T > copy() + { + return new MyCursor<>( x, y, rae ); + } + + @Override + public Cursor< T > copyCursor() + { + return copy(); + } + } } From 9bfeed95bc278a6ba0980179c8a1ef8170c7a0e4 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:03:48 +0200 Subject: [PATCH 160/263] Tweak javadoc. --- src/main/java/fiji/plugin/trackmate/Spot.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index a55d24dc3..e98ab556c 100644 --- a/src/main/java/fiji/plugin/trackmate/Spot.java +++ b/src/main/java/fiji/plugin/trackmate/Spot.java @@ -101,14 +101,15 @@ public default int compareTo( final Spot o ) * this spot. * * @param ra - * the {@link RandomAccessible} to iterate over. + * the {@link RandomAccessible} to iterate over. It's the caller + * responsibility to ensure that the {@link RandomAccessible} can + * return values over all the pixels in this spot. * @param calibration * the pixel size array, use to map pixel integer coordinates to * the spot physical coordinates. * @param * the type of pixels in the {@link RandomAccessible}. * @return an iterable. - * @return */ public < T extends RealType< T > > IterableInterval< T > iterable( RandomAccessible< T > ra, double calibration[] ); From 4986865d3b8f4050375e68a407a33721cc205404 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:16:34 +0200 Subject: [PATCH 161/263] Only propose visible analyzers in the selection to the user. --- .../trackmate/gui/featureselector/AnalyzerSelection.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java index 25c4b5d41..3da7fb96d 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java @@ -194,16 +194,16 @@ public static AnalyzerSelection defaultSelection() for ( final String key : new SpotAnalyzerProvider( 1 ).getVisibleKeys() ) fs.setSelected( SPOTS, key, true ); - for ( final String key : new Spot2DMorphologyAnalyzerProvider( 1 ).getKeys() ) + for ( final String key : new Spot2DMorphologyAnalyzerProvider( 1 ).getVisibleKeys() ) fs.setSelected( SPOTS, key, true ); - for ( final String key : new Spot3DMorphologyAnalyzerProvider( 1 ).getKeys() ) + for ( final String key : new Spot3DMorphologyAnalyzerProvider( 1 ).getVisibleKeys() ) fs.setSelected( SPOTS, key, true ); - for ( final String key : new EdgeAnalyzerProvider().getKeys() ) + for ( final String key : new EdgeAnalyzerProvider().getVisibleKeys() ) fs.setSelected( EDGES, key, true ); - for ( final String key : new TrackAnalyzerProvider().getKeys() ) + for ( final String key : new TrackAnalyzerProvider().getVisibleKeys() ) fs.setSelected( TRACKS, key, true ); // Fine tune. From d1994b82d500c00044d13c69e248c5cf5822a8ff Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:27:06 +0200 Subject: [PATCH 162/263] Make sure the user selection of analyzers are added in priority order. This is crucial, as some analyzers depend on others to have been computed. This is implemented following the priority of the analyzer factories, which is known by the provider. The provider returns a list of keys, in priority order, so we must make sure to reproduce this order with the user selection. Hence the gymnastic in the configure(Settings) method. --- .../featureselector/AnalyzerSelection.java | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java index 3da7fb96d..04d3cc6df 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java @@ -95,16 +95,19 @@ public void configure( final Settings settings ) settings.clearEdgeAnalyzers(); settings.clearTrackAnalyzers(); - final List< String > spotAnalyzers = getSelectedAnalyzers( SPOTS ); + final List< String > selectionSpotAnalyzers = getSelectedAnalyzers( SPOTS ); - // Base spot analyzers. + // Base spot analyzers, in priority order. final SpotAnalyzerProvider spotAnalyzerProvider = new SpotAnalyzerProvider( settings.imp == null ? 1 : settings.imp.getNChannels() ); - for ( final String key : spotAnalyzers ) + for ( final String key : spotAnalyzerProvider.getVisibleKeys() ) { - final SpotAnalyzerFactory< ? > factory = spotAnalyzerProvider.getFactory( key ); - if ( factory != null ) - settings.addSpotAnalyzerFactory( factory ); + if ( selectionSpotAnalyzers.contains( key ) ) + { + final SpotAnalyzerFactory< ? > factory = spotAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addSpotAnalyzerFactory( factory ); + } } // Shall we add 2D morphology analyzers? @@ -114,11 +117,14 @@ public void configure( final Settings settings ) && settings.detectorFactory.has2Dsegmentation() ) { final Spot2DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot2DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); - for ( final String key : spotAnalyzers ) + for ( final String key : spotMorphologyAnalyzerProvider.getVisibleKeys() ) { - final Spot2DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); - if ( factory != null ) - settings.addSpotAnalyzerFactory( factory ); + if ( selectionSpotAnalyzers.contains( key ) ) + { + final Spot2DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addSpotAnalyzerFactory( factory ); + } } } @@ -129,30 +135,41 @@ public void configure( final Settings settings ) && settings.detectorFactory.has3Dsegmentation() ) { final Spot3DMorphologyAnalyzerProvider spotMorphologyAnalyzerProvider = new Spot3DMorphologyAnalyzerProvider( settings.imp.getNChannels() ); - for ( final String key : spotAnalyzers ) + for ( final String key : spotMorphologyAnalyzerProvider.getVisibleKeys() ) { - final Spot3DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); - if ( factory != null ) - settings.addSpotAnalyzerFactory( factory ); + if ( selectionSpotAnalyzers.contains( key ) ) + { + final Spot3DMorphologyAnalyzerFactory< ? > factory = spotMorphologyAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addSpotAnalyzerFactory( factory ); + } } } // Edge analyzers. + final List< String > selectedEdgeAnalyzers = getSelectedAnalyzers( EDGES ); final EdgeAnalyzerProvider edgeAnalyzerProvider = new EdgeAnalyzerProvider(); - for ( final String key : getSelectedAnalyzers( EDGES ) ) + for ( final String key : edgeAnalyzerProvider.getVisibleKeys() ) { - final EdgeAnalyzer factory = edgeAnalyzerProvider.getFactory( key ); - if ( factory != null ) - settings.addEdgeAnalyzer( factory ); + if ( selectedEdgeAnalyzers.contains( key ) ) + { + final EdgeAnalyzer factory = edgeAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addEdgeAnalyzer( factory ); + } } // Track analyzers. + final List< String > selectedTrackAnalyzers = getSelectedAnalyzers( TRACKS ); final TrackAnalyzerProvider trackAnalyzerProvider = new TrackAnalyzerProvider(); - for ( final String key : getSelectedAnalyzers( TRACKS ) ) + for ( final String key : trackAnalyzerProvider.getVisibleKeys() ) { - final TrackAnalyzer factory = trackAnalyzerProvider.getFactory( key ); - if ( factory != null ) - settings.addTrackAnalyzer( factory ); + if ( selectedTrackAnalyzers.contains( key ) ) + { + final TrackAnalyzer factory = trackAnalyzerProvider.getFactory( key ); + if ( factory != null ) + settings.addTrackAnalyzer( factory ); + } } } From a3b4587b69a4bfa87827c257cee03dc935c265ce Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:53:48 +0200 Subject: [PATCH 163/263] Implement quality measurement on the 3D object. Without using the mesh actually, we use the bitmask provided. --- .../fiji/plugin/trackmate/detection/MaskUtils.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index d41c20459..ae22a2a39 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -412,11 +412,11 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > * @return a list of spots, with ROI. */ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > fromMaskWithROI( - final RandomAccessible< T > input, - final Interval interval, - final double[] calibration, - final boolean simplify, - final int numThreads, + final RandomAccessible< T > input, + final Interval interval, + final double[] calibration, + final boolean simplify, + final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { final ImgLabeling< Integer, IntType > labeling = toLabeling( From edd086c631286b9396d90aaf29776a7268b413fa Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 21:55:39 +0200 Subject: [PATCH 164/263] Spot is now an interface, with 3 derived class. Spot -> the main interface, used by default in trackers. Define basic methods to get and store feature values. SpotBase -> Plain spots, like for TrackMate v<7 SpotRoi -> spot has a polygon as a contour in 2D SpotMesh -> spot has a 3D mesh More elegant and extensible to app consuming TrackMate trackers with special objects. --- src/main/java/fiji/plugin/trackmate/SpotRoi.java | 2 +- .../plugin/trackmate/visualization/hyperstack/SpotEditTool.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index 3ae2a4caa..eb471fd6c 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -75,7 +75,7 @@ public SpotRoi( final double[] x, final double[] y ) { - super( ID ); + super( ID ); this.x = x; this.y = y; } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java index b5e2a0a73..d9f21c898 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java @@ -402,7 +402,6 @@ public void keyPressed( final KeyEvent e ) break; } } - } @Override From 592ac97e9709c348c9b4f01b1a1b5d04f64e4a0a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:38:23 +0200 Subject: [PATCH 165/263] simplify TMUtils.rawWraps --- src/main/java/fiji/plugin/trackmate/util/TMUtils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index d611a9163..786dec60f 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -55,7 +55,6 @@ import net.imglib2.img.ImagePlusAdapter; import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.type.Type; -import net.imglib2.util.Cast; import net.imglib2.util.Util; /** @@ -139,7 +138,7 @@ else if ( obj instanceof Logger ) */ public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) { - return Cast.unchecked( ImagePlusAdapter.wrapImgPlus( imp ) ); + return ImagePlusAdapter.wrapImgPlus( imp ); } /** From e684cc4fc01db92d6333040b365b37d8ef49c1b7 Mon Sep 17 00:00:00 2001 From: tpietzsch Date: Tue, 18 Apr 2023 15:54:48 -0500 Subject: [PATCH 166/263] WIP show meshes in bvv. requires 'mesh' branch of bvv --- pom.xml | 5 + .../plugin/trackmate/mesh/MeshPlayground.java | 115 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java diff --git a/pom.xml b/pom.xml index 756aa3484..f3d33fa4c 100644 --- a/pom.xml +++ b/pom.xml @@ -185,6 +185,11 @@ net.imagej imagej-ops + + sc.fiji + bigvolumeviewer + 0.2.1-SNAPSHOT + diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java new file mode 100644 index 000000000..4054afbfa --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java @@ -0,0 +1,115 @@ +package fiji.plugin.trackmate.mesh; + +import bvv.util.Bvv; +import bvv.util.BvvFunctions; +import bvv.util.BvvSource; +import fiji.plugin.trackmate.util.TMUtils; +import ij.IJ; +import ij.ImagePlus; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.io.stl.STLMeshIO; +import net.imagej.mesh.naive.NaiveDoubleMesh; +import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.RealPoint; +import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.type.numeric.ARGBType; +import net.imglib2.util.Util; +import org.joml.Matrix4f; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Actions; +import tpietzsch.example2.VolumeViewerPanel; +import tpietzsch.scene.mesh.StupidMesh; + +public class MeshPlayground +{ + public static void main( String[] args ) + { + final String filePath = "samples/mesh/CElegansMask3D.tif"; + final ImagePlus imp = IJ.openImage( filePath ); + + final ImgPlus img = TMUtils.rawWraps( imp ); + final ImgPlus c1 = ImgPlusViews.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 1 ); + final ImgPlus t1 = ImgPlusViews.hyperSlice( c1, c1.dimensionIndex( Axes.TIME ), 0 ); + final double[] cal = TMUtils.getSpatialCalibration( t1 ); + + BvvSource source = BvvFunctions.show( t1, "t1", + Bvv.options() + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .preferredSize( 512, 512 ) + .sourceTransform( cal ) ); + + source.setDisplayRangeBounds( 0, 1024 ); + source.setColor( new ARGBType( 0xaaffaa ) ); + + + final List< StupidMesh > meshes = new ArrayList<>(); + for ( int j = 1; j <= 3; ++j) + { + String fn = String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ); + meshes.add( new StupidMesh( load( fn ) ) ); + } + + final VolumeViewerPanel viewer = source.getBvvHandle().getViewerPanel(); + + final AtomicBoolean showMeshes = new AtomicBoolean( true ); + viewer.setRenderScene( ( gl, data ) -> { + if ( showMeshes.get() ) + { + final Matrix4f pvm = new Matrix4f( data.getPv() ); + final Matrix4f vm = new Matrix4f( data.getCamview() ); + meshes.forEach( mesh -> mesh.draw( gl, pvm, vm ) ); + } + } ); + + Actions actions = new Actions( new InputTriggerConfig() ); + actions.install( source.getBvvHandle().getKeybindings(), "my-new-actions" ); + actions.runnableAction( () -> { + showMeshes.set( !showMeshes.get() ); + viewer.requestRepaint(); + }, "toggle meshes", "G" ); + + viewer.requestRepaint(); + } + + + private static BufferMesh load( String fn ) + { + BufferMesh mesh = null; + try + { + NaiveDoubleMesh nmesh = new NaiveDoubleMesh(); + STLMeshIO meshIO = new STLMeshIO(); + meshIO.read( nmesh, new File( fn ) ); + mesh = calculateNormals( + nmesh +// Meshes.removeDuplicateVertices( nmesh, 5 ) + ); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + return mesh; + } + + private static BufferMesh calculateNormals( Mesh mesh ) + { + final int nvertices = ( int ) mesh.vertices().size(); + final int ntriangles = ( int ) mesh.triangles().size(); + final BufferMesh bufferMesh = new BufferMesh( nvertices, ntriangles, true ); + Meshes.calculateNormals( mesh, bufferMesh ); + return bufferMesh; + } +} From 4667f147631e5f547f0cddc7b49c269649a63adb Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 21 May 2023 18:44:28 +0200 Subject: [PATCH 167/263] Add jogl deps. --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index f3d33fa4c..be854968c 100644 --- a/pom.xml +++ b/pom.xml @@ -190,6 +190,16 @@ bigvolumeviewer 0.2.1-SNAPSHOT + + org.jogamp.jogl + jogl-all-main + 2.3.2 + + + org.jogamp.gluegen + gluegen-rt-main + 2.3.2 + From fe5692d6dd643b81d16bc4e85525818f2334726d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 21 May 2023 18:49:02 +0200 Subject: [PATCH 168/263] Minor tweak of Tobias example. --- .../plugin/trackmate/mesh/MeshPlayground.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java index 4054afbfa..f18a6d761 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java @@ -1,18 +1,21 @@ package fiji.plugin.trackmate.mesh; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.joml.Matrix4f; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Actions; + import bvv.util.Bvv; import bvv.util.BvvFunctions; import bvv.util.BvvSource; import fiji.plugin.trackmate.util.TMUtils; import ij.IJ; import ij.ImagePlus; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imagej.mesh.Mesh; @@ -20,29 +23,25 @@ import net.imagej.mesh.io.stl.STLMeshIO; import net.imagej.mesh.naive.NaiveDoubleMesh; import net.imagej.mesh.nio.BufferMesh; -import net.imglib2.RealPoint; import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.type.Type; import net.imglib2.type.numeric.ARGBType; -import net.imglib2.util.Util; -import org.joml.Matrix4f; -import org.scijava.ui.behaviour.io.InputTriggerConfig; -import org.scijava.ui.behaviour.util.Actions; import tpietzsch.example2.VolumeViewerPanel; import tpietzsch.scene.mesh.StupidMesh; public class MeshPlayground { - public static void main( String[] args ) + public static < T extends Type< T > > void main( final String[] args ) { final String filePath = "samples/mesh/CElegansMask3D.tif"; final ImagePlus imp = IJ.openImage( filePath ); - final ImgPlus img = TMUtils.rawWraps( imp ); - final ImgPlus c1 = ImgPlusViews.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 1 ); - final ImgPlus t1 = ImgPlusViews.hyperSlice( c1, c1.dimensionIndex( Axes.TIME ), 0 ); + final ImgPlus< T > img = TMUtils.rawWraps( imp ); + final ImgPlus< T > c1 = ImgPlusViews.hyperSlice( img, img.dimensionIndex( Axes.CHANNEL ), 1 ); + final ImgPlus< T > t1 = ImgPlusViews.hyperSlice( c1, c1.dimensionIndex( Axes.TIME ), 0 ); final double[] cal = TMUtils.getSpatialCalibration( t1 ); - BvvSource source = BvvFunctions.show( t1, "t1", + final BvvSource source = BvvFunctions.show( c1, "t1", Bvv.options() .maxAllowedStepInVoxels( 0 ) .renderWidth( 1024 ) @@ -57,7 +56,7 @@ public static void main( String[] args ) final List< StupidMesh > meshes = new ArrayList<>(); for ( int j = 1; j <= 3; ++j) { - String fn = String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ); + final String fn = String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ); meshes.add( new StupidMesh( load( fn ) ) ); } @@ -73,7 +72,7 @@ public static void main( String[] args ) } } ); - Actions actions = new Actions( new InputTriggerConfig() ); + final Actions actions = new Actions( new InputTriggerConfig() ); actions.install( source.getBvvHandle().getKeybindings(), "my-new-actions" ); actions.runnableAction( () -> { showMeshes.set( !showMeshes.get() ); @@ -84,13 +83,13 @@ public static void main( String[] args ) } - private static BufferMesh load( String fn ) + private static BufferMesh load( final String fn ) { BufferMesh mesh = null; try { - NaiveDoubleMesh nmesh = new NaiveDoubleMesh(); - STLMeshIO meshIO = new STLMeshIO(); + final NaiveDoubleMesh nmesh = new NaiveDoubleMesh(); + final STLMeshIO meshIO = new STLMeshIO(); meshIO.read( nmesh, new File( fn ) ); mesh = calculateNormals( nmesh @@ -104,7 +103,7 @@ private static BufferMesh load( String fn ) return mesh; } - private static BufferMesh calculateNormals( Mesh mesh ) + private static BufferMesh calculateNormals( final Mesh mesh ) { final int nvertices = ( int ) mesh.vertices().size(); final int ntriangles = ( int ) mesh.triangles().size(); From 800bf0111f04fc9ee59c70d450127f0ed4f02c7e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 May 2023 15:29:42 +0200 Subject: [PATCH 169/263] WIP: A new viewer for TrackMate based on the BVV. Adapting the demo from Tobias. This currently requires the 'mesh' branch in the bvv repo to work. Right now it can display the 3D image (possibly multi-channel) over time, along with the meshes in spots (if there is no mesh, nothing is shown). There is still so much to do but this is cool! --- .../trackmate/visualization/bvv/BVVUtils.java | 145 ++++++++++++++++++ .../visualization/bvv/TrackMateBVV.java | 139 +++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java new file mode 100644 index 000000000..25402f25f --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -0,0 +1,145 @@ +package fiji.plugin.trackmate.visualization.bvv; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import bvv.util.Bvv; +import bvv.util.BvvFunctions; +import bvv.util.BvvHandle; +import bvv.util.BvvSource; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.util.TMUtils; +import ij.CompositeImage; +import ij.ImagePlus; +import ij.process.ImageProcessor; +import ij.process.LUT; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.nio.BufferMesh; +import net.imagej.mesh.obj.transform.TranslateMesh; +import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.type.Type; +import net.imglib2.type.numeric.ARGBType; +import tpietzsch.scene.mesh.StupidMesh; + +public class BVVUtils +{ + + public static final < T extends Type< T > > BvvHandle createViewer( final ImagePlus imp ) + { + final double[] cal = TMUtils.getSpatialCalibration( imp ); + + // Convert and split by channels. + final ImgPlus< T > img = TMUtils.rawWraps( imp ); + final int cAxis = img.dimensionIndex( Axes.CHANNEL ); + final BvvHandle bvvHandle; + if ( cAxis < 0 ) + { + final BvvSource source = BvvFunctions.show( img, imp.getShortTitle(), + Bvv.options() + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .preferredSize( 512, 512 ) + .sourceTransform( cal ) ); + source.setDisplayRange( imp.getDisplayRangeMin(), imp.getDisplayRangeMax() ); + if ( imp.getLuts().length > 0 ) + { + final LUT lut = imp.getLuts()[ 0 ]; + final int rgb = lut.getColorModel().getRGB( ( int ) imp.getDisplayRangeMax() ); + source.setColor( new ARGBType( rgb ) ); + } + bvvHandle = source.getBvvHandle(); + } + else + { + BvvHandle h = null; + final long nChannels = img.dimension( cAxis ); + final String st = imp.getShortTitle(); + for ( int c = 0; c < nChannels; c++ ) + { + final ImgPlus< T > channel = ImgPlusViews.hyperSlice( img, cAxis, c ); + final BvvSource source; + if ( h == null ) + { + source = BvvFunctions.show( channel, st + "_c" + ( c + 1 ), + Bvv.options() + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .preferredSize( 512, 512 ) + .sourceTransform( cal ) ); + h = source.getBvvHandle(); + } + else + { + source = BvvFunctions.show( channel, st + "_c" + ( c + 1 ), + Bvv.options() + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .preferredSize( 512, 512 ) + .sourceTransform( cal ) + .addTo( h ) ); + + } + final int i = imp.getStackIndex( c + 1, 1, 1 ); + if ( imp instanceof CompositeImage ) + { + final CompositeImage cp = ( CompositeImage ) imp; + source.setDisplayRange( cp.getChannelLut( c + 1 ).min, cp.getChannelLut( c + 1 ).max ); + } + else + { + final ImageProcessor ip = imp.getStack().getProcessor( i ); + source.setDisplayRange( ip.getMin(), ip.getMax() ); + } + if ( imp.getLuts().length > 0 ) + { + final LUT lut = imp.getLuts()[ c ]; + final int rgb = lut.getColorModel().getRGB( ( int ) imp.getDisplayRangeMax() ); + source.setColor( new ARGBType( rgb ) ); + } + } + bvvHandle = h; + } + return bvvHandle; + } + + public static Map< Integer, Collection< StupidMesh > > createMesh( final Model model ) + { + final Map< Integer, Collection< StupidMesh > > meshMap = new HashMap<>(); + final SpotCollection spots = model.getSpots(); + for ( final Integer frame : spots.keySet() ) + { + final List< StupidMesh > meshes = new ArrayList<>(); + for ( final Spot spot : spots.iterable( frame, true ) ) + { + if ( spot instanceof SpotMesh ) + { + final SpotMesh sm = ( SpotMesh ) spot; + final Mesh mesh = TranslateMesh.translate( sm.getMesh(), spot ); + final BufferMesh bm = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); + Meshes.copy( mesh, bm ); + meshes.add( new StupidMesh( bm ) ); + } + else if ( spot instanceof SpotBase ) + { + // TODO + System.out.println( "TODO: Deal with spherical spots" ); // DEBUG + } + meshMap.put( frame, meshes ); + } + } + return meshMap; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java new file mode 100644 index 000000000..945fea58d --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -0,0 +1,139 @@ +package fiji.plugin.trackmate.visualization.bvv; + +import java.io.File; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.joml.Matrix4f; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Actions; + +import bvv.util.BvvHandle; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.io.TmXmlReader; +import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; +import ij.ImageJ; +import ij.ImagePlus; +import net.imglib2.type.Type; +import tpietzsch.example2.VolumeViewerPanel; +import tpietzsch.scene.mesh.StupidMesh; + +public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelView +{ + + private static final String KEY = "BIGVOLUMEVIEWER"; + + private final ImagePlus imp; + + private BvvHandle handle; + + private Map< Integer, Collection< StupidMesh > > meshMap; + + public TrackMateBVV( final Model model, final SelectionModel selectionModel, final ImagePlus imp, final DisplaySettings displaySettings ) + { + super( model, selectionModel, displaySettings ); + this.imp = imp; + + } + + @Override + public void render() + { + this.handle = BVVUtils.createViewer( imp ); + this.meshMap = BVVUtils.createMesh( model ); + + final VolumeViewerPanel viewer = handle.getViewerPanel(); + final AtomicBoolean showMeshes = new AtomicBoolean( true ); + viewer.setRenderScene( ( gl, data ) -> { + if ( showMeshes.get() ) + { + final Matrix4f pvm = new Matrix4f( data.getPv() ); + final Matrix4f vm = new Matrix4f( data.getCamview() ); + + final int t = data.getTimepoint(); + final Collection< StupidMesh > meshes = meshMap.get( t ); + if ( meshes == null ) + return; + meshes.forEach( mesh -> mesh.draw( gl, pvm, vm ) ); + } + } ); + + final Actions actions = new Actions( new InputTriggerConfig() ); + actions.install( handle.getKeybindings(), "my-new-actions" ); + actions.runnableAction( () -> { + showMeshes.set( !showMeshes.get() ); + viewer.requestRepaint(); + }, "toggle meshes", "G" ); + + } + + @Override + public void refresh() + { + handle.getViewerPanel().requestRepaint(); + } + + @Override + public void clear() + { + // TODO Auto-generated method stub + + } + + @Override + public void centerViewOn( final Spot spot ) + { + // TODO Auto-generated method stub + + } + + @Override + public String getKey() + { + return KEY; + } + + @Override + public void modelChanged( final ModelChangeEvent event ) + { + // TODO Auto-generated method stub + + } + + public static < T extends Type< T > > void main( final String[] args ) + { +// final String filePath = "samples/mesh/CElegansMask3D.tif"; + final String filePath = "samples/CElegans3D-smoothed-mask-orig.xml"; + + ImageJ.main( args ); + final TmXmlReader reader = new TmXmlReader( new File( filePath ) ); + if ( !reader.isReadingOk() ) + { + System.err.println( reader.getErrorMessage() ); + return; + } + final ImagePlus imp = reader.readImage(); + imp.show(); + + final Model model = reader.getModel(); + final SelectionModel selectionModel = new SelectionModel( model ); + final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); + + try + { + final TrackMateBVV< T > tbvv = new TrackMateBVV<>( model, selectionModel, imp, ds ); + tbvv.render(); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + + } +} From 4f8a301d3142e21fd8273da8de6ac6488aae3c58 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 May 2023 15:04:17 +0200 Subject: [PATCH 170/263] Copy of Tobias 'StupidMesh'. Modified so that the color of individual meshes can be changed at runtime. We also add a white halo so that object contours are visible over the black background. --- .../visualization/bvv/StupidMesh.java | 147 ++++++++++++++++++ .../trackmate/visualization/bvv/mesh.fp | 41 +++++ .../trackmate/visualization/bvv/mesh.vp | 16 ++ 3 files changed, 204 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java new file mode 100644 index 000000000..cdb81374c --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -0,0 +1,147 @@ +/*- + * #%L + * Volume rendering of bdv datasets + * %% + * Copyright (C) 2018 - 2021 Tobias Pietzsch + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package fiji.plugin.trackmate.visualization.bvv; + +import static com.jogamp.opengl.GL.GL_FLOAT; +import static com.jogamp.opengl.GL.GL_TRIANGLES; +import static com.jogamp.opengl.GL.GL_UNSIGNED_INT; + +import java.awt.Color; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import org.joml.Matrix3f; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; + +import com.jogamp.opengl.GL; +import com.jogamp.opengl.GL3; + +import net.imagej.mesh.nio.BufferMesh; +import tpietzsch.backend.jogl.JoglGpuContext; +import tpietzsch.shadergen.DefaultShader; +import tpietzsch.shadergen.Shader; +import tpietzsch.shadergen.generate.Segment; +import tpietzsch.shadergen.generate.SegmentTemplate; + +public class StupidMesh +{ + private final Shader prog; + + private int vao; + + private final BufferMesh mesh; + + public StupidMesh( final BufferMesh mesh ) + { + this.mesh = mesh; + + final Segment meshVp = new SegmentTemplate( StupidMesh.class, "mesh.vp" ).instantiate(); + final Segment meshFp = new SegmentTemplate( StupidMesh.class, "mesh.fp" ).instantiate(); + prog = new DefaultShader( meshVp.getCode(), meshFp.getCode() ); + } + + private boolean initialized; + + private Color color = Color.WHITE; + + private final float[] carr = new float[ 4 ]; + + private void init( final GL3 gl ) + { + initialized = true; + + final int[] tmp = new int[ 3 ]; + gl.glGenBuffers( 3, tmp, 0 ); + final int meshPosVbo = tmp[ 0 ]; + final int meshNormalVbo = tmp[ 1 ]; + final int meshEbo = tmp[ 2 ]; + + final FloatBuffer vertices = mesh.vertices().verts(); + vertices.rewind(); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, meshPosVbo ); + gl.glBufferData( GL.GL_ARRAY_BUFFER, vertices.limit() * Float.BYTES, vertices, GL.GL_STATIC_DRAW ); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, 0 ); + + final FloatBuffer normals = mesh.vertices().normals(); + normals.rewind(); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, meshNormalVbo ); + gl.glBufferData( GL.GL_ARRAY_BUFFER, normals.limit() * Float.BYTES, normals, GL.GL_STATIC_DRAW ); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, 0 ); + + final IntBuffer indices = mesh.triangles().indices(); + indices.rewind(); + gl.glBindBuffer( GL.GL_ELEMENT_ARRAY_BUFFER, meshEbo ); + gl.glBufferData( GL.GL_ELEMENT_ARRAY_BUFFER, indices.limit() * Integer.BYTES, indices, GL.GL_STATIC_DRAW ); + gl.glBindBuffer( GL.GL_ELEMENT_ARRAY_BUFFER, 0 ); + + + + gl.glGenVertexArrays( 1, tmp, 0 ); + vao = tmp[ 0 ]; + gl.glBindVertexArray( vao ); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, meshPosVbo ); + gl.glVertexAttribPointer( 0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0 ); + gl.glEnableVertexAttribArray( 0 ); + gl.glBindBuffer( GL.GL_ARRAY_BUFFER, meshNormalVbo ); + gl.glVertexAttribPointer( 1, 3, GL_FLOAT, false, 3 * Float.BYTES, 0 ); + gl.glEnableVertexAttribArray( 1 ); + gl.glBindBuffer( GL.GL_ELEMENT_ARRAY_BUFFER, meshEbo ); + gl.glBindVertexArray( 0 ); + } + + public void setColor( final Color color ) + { + this.color = color; + } + + public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm ) + { + if ( !initialized ) + init( gl ); + + final JoglGpuContext context = JoglGpuContext.get( gl ); + final Matrix4f itvm = vm.invert( new Matrix4f() ).transpose(); + + prog.getUniformMatrix4f( "pvm" ).set( pvm ); + prog.getUniformMatrix4f( "vm" ).set( vm ); + prog.getUniformMatrix3f( "itvm" ).set( itvm.get3x3( new Matrix3f() ) ); + color.getComponents( carr ); + prog.getUniform4f( "ObjectColor" ).set( carr[ 0 ], carr[ 1 ], carr[ 2 ], carr[ 3 ] ); + prog.setUniforms( context ); + prog.use( context ); + + gl.glBindVertexArray( vao ); +// gl.glEnable( GL.GL_CULL_FACE ); +// gl.glCullFace( GL.GL_BACK ); +// gl.glFrontFace( GL.GL_CCW ); + gl.glDrawElements( GL_TRIANGLES, ( int ) mesh.triangles().size() * 3, GL_UNSIGNED_INT, 0 ); + gl.glBindVertexArray( 0 ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp new file mode 100644 index 000000000..818d9946b --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp @@ -0,0 +1,41 @@ +out vec4 fragColor; + +in vec3 Normal; +in vec3 FragPos; + +uniform vec4 ObjectColor; + +const vec3 lightColor1 = 0.5 * vec3(0.9, 0.9, 1); +const vec3 lightDir1 = normalize(vec3(0, -0.2, -1)); + +const vec3 lightColor2 = 0.5 * vec3(0.1, 0.1, 1); +const vec3 lightDir2 = normalize(vec3(1, 1, 0.5)); + +const vec3 ambient = vec3(0.7, 0.7, 0.7); + +const float specularStrength = 5; + +vec3 phong(vec3 norm, vec3 viewDir, vec3 lightDir, vec3 lightColor, float shininess, float specularStrength) +{ + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = diff * lightColor; + + vec3 reflectDir = reflect(-lightDir, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); + vec3 specular = specularStrength * spec * lightColor; + + return diffuse + specular; +} + +void main() +{ +// fragColor = vec4(ObjectColor, 1); + vec3 norm = normalize(Normal); + vec3 viewDir = normalize(-FragPos); + + vec3 l1 = phong( norm, viewDir, lightDir1, lightColor1, 32, 0.1 ); + vec3 l2 = phong( norm, viewDir, lightDir2, lightColor2, 32, 0.5 ); + + float it = dot(norm, viewDir); + fragColor = vec4( it * (ambient + l1 + l2), 1) * ObjectColor + (1-it) * vec4(1,1,1,1); +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp new file mode 100644 index 000000000..e86810409 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp @@ -0,0 +1,16 @@ +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; + +out vec3 FragPos; +out vec3 Normal; + +uniform mat4 pvm; +uniform mat4 vm; +uniform mat3 itvm; + +void main() +{ + gl_Position = pvm * vec4( aPos, 1.0 ); + FragPos = vec3(vm * vec4(aPos, 1.0)); + Normal = itvm * aNormal; +} From 30008d490e567f4cda035fca9b572bea2c5e6d23 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 May 2023 15:05:50 +0200 Subject: [PATCH 171/263] The BVV view abides to the spot color settings. Can be changed live using TrackMate display settings panels. --- .../trackmate/visualization/bvv/BVVUtils.java | 52 ++++-------- .../visualization/bvv/TrackMateBVV.java | 81 +++++++++++++------ 2 files changed, 70 insertions(+), 63 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java index 25402f25f..86bcd3162 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -1,19 +1,10 @@ package fiji.plugin.trackmate.visualization.bvv; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import bvv.util.Bvv; import bvv.util.BvvFunctions; import bvv.util.BvvHandle; import bvv.util.BvvSource; -import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotBase; -import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.util.TMUtils; import ij.CompositeImage; @@ -29,11 +20,24 @@ import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.type.Type; import net.imglib2.type.numeric.ARGBType; -import tpietzsch.scene.mesh.StupidMesh; public class BVVUtils { + public static final StupidMesh createMesh( final Spot spot ) + { + if ( spot instanceof SpotMesh ) + { + final SpotMesh sm = ( SpotMesh ) spot; + final Mesh mesh = TranslateMesh.translate( sm.getMesh(), spot ); + final BufferMesh bm = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); + Meshes.copy( mesh, bm ); + return new StupidMesh( bm ); + } + System.out.println( "TODO: Deal with spherical spots" ); // DEBUG + return null; + } + public static final < T extends Type< T > > BvvHandle createViewer( final ImagePlus imp ) { final double[] cal = TMUtils.getSpatialCalibration( imp ); @@ -114,32 +118,4 @@ public static final < T extends Type< T > > BvvHandle createViewer( final ImageP } return bvvHandle; } - - public static Map< Integer, Collection< StupidMesh > > createMesh( final Model model ) - { - final Map< Integer, Collection< StupidMesh > > meshMap = new HashMap<>(); - final SpotCollection spots = model.getSpots(); - for ( final Integer frame : spots.keySet() ) - { - final List< StupidMesh > meshes = new ArrayList<>(); - for ( final Spot spot : spots.iterable( frame, true ) ) - { - if ( spot instanceof SpotMesh ) - { - final SpotMesh sm = ( SpotMesh ) spot; - final Mesh mesh = TranslateMesh.translate( sm.getMesh(), spot ); - final BufferMesh bm = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); - Meshes.copy( mesh, bm ); - meshes.add( new StupidMesh( bm ) ); - } - else if ( spot instanceof SpotBase ) - { - // TODO - System.out.println( "TODO: Deal with spherical spots" ); // DEBUG - } - meshMap.put( frame, meshes ); - } - } - return meshMap; - } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index 945fea58d..1e9c8c780 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -1,28 +1,39 @@ package fiji.plugin.trackmate.visualization.bvv; +import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; + +import java.awt.Color; import java.io.File; -import java.util.Collection; +import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Map.Entry; + +import javax.swing.JFrame; import org.joml.Matrix4f; -import org.scijava.ui.behaviour.io.InputTriggerConfig; -import org.scijava.ui.behaviour.util.Actions; import bvv.util.BvvHandle; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.features.FeatureUtils; +import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.gui.wizard.TrackMateWizardSequence; +import fiji.plugin.trackmate.gui.wizard.WizardSequence; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; +import fiji.plugin.trackmate.visualization.FeatureColorGenerator; +import fiji.plugin.trackmate.visualization.TrackMateModelView; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImageJ; import ij.ImagePlus; import net.imglib2.type.Type; import tpietzsch.example2.VolumeViewerPanel; -import tpietzsch.scene.mesh.StupidMesh; public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelView { @@ -33,50 +44,42 @@ public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelV private BvvHandle handle; - private Map< Integer, Collection< StupidMesh > > meshMap; + private final Map< Spot, StupidMesh > meshMap; public TrackMateBVV( final Model model, final SelectionModel selectionModel, final ImagePlus imp, final DisplaySettings displaySettings ) { super( model, selectionModel, displaySettings ); this.imp = imp; - + this.meshMap = new HashMap<>(); + final Iterable< Spot > it = model.getSpots().iterable( true ); + it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ) ); + updateColor(); + displaySettings.listeners().add( this::updateColor ); } @Override public void render() { this.handle = BVVUtils.createViewer( imp ); - this.meshMap = BVVUtils.createMesh( model ); - final VolumeViewerPanel viewer = handle.getViewerPanel(); - final AtomicBoolean showMeshes = new AtomicBoolean( true ); viewer.setRenderScene( ( gl, data ) -> { - if ( showMeshes.get() ) + if ( displaySettings.isSpotVisible() ) { final Matrix4f pvm = new Matrix4f( data.getPv() ); final Matrix4f vm = new Matrix4f( data.getCamview() ); final int t = data.getTimepoint(); - final Collection< StupidMesh > meshes = meshMap.get( t ); - if ( meshes == null ) - return; - meshes.forEach( mesh -> mesh.draw( gl, pvm, vm ) ); + final Iterable< Spot > it = model.getSpots().iterable( t, true ); + it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm ) ); } } ); - - final Actions actions = new Actions( new InputTriggerConfig() ); - actions.install( handle.getKeybindings(), "my-new-actions" ); - actions.runnableAction( () -> { - showMeshes.set( !showMeshes.get() ); - viewer.requestRepaint(); - }, "toggle meshes", "G" ); - } @Override public void refresh() { - handle.getViewerPanel().requestRepaint(); + if ( handle != null ) + handle.getViewerPanel().requestRepaint(); } @Override @@ -106,6 +109,21 @@ public void modelChanged( final ModelChangeEvent event ) } + private void updateColor() + { + final FeatureColorGenerator< Spot > spotColorGenerator = FeatureUtils.createSpotColorGenerator( model, displaySettings ); + for ( final Entry< Spot, StupidMesh > entry : meshMap.entrySet() ) + { + final StupidMesh sm = entry.getValue(); + if ( sm == null ) + continue; + + final Color color = spotColorGenerator.color( entry.getKey() ); + sm.setColor( color ); + } + refresh(); + } + public static < T extends Type< T > > void main( final String[] args ) { // final String filePath = "samples/mesh/CElegansMask3D.tif"; @@ -119,11 +137,25 @@ public static < T extends Type< T > > void main( final String[] args ) return; } final ImagePlus imp = reader.readImage(); + final Settings settings = reader.readSettings( imp ); imp.show(); final Model model = reader.getModel(); final SelectionModel selectionModel = new SelectionModel( model ); final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); + final TrackMate trackmate = new TrackMate( model, settings ); + + // Main view + final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, imp, ds ); + displayer.render(); + + // Wizard. + final WizardSequence sequence = new TrackMateWizardSequence( trackmate, selectionModel, ds ); + sequence.setCurrent( "ConfigureViews" ); + final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); + frame.setIconImage( TRACKMATE_ICON.getImage() ); + GuiUtils.positionWindow( frame, settings.imp.getWindow() ); + frame.setVisible( true ); try { @@ -134,6 +166,5 @@ public static < T extends Type< T > > void main( final String[] args ) { e.printStackTrace(); } - } } From c3c52133b14e89843a9f2b40e1f675f56f92037e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 May 2023 18:12:28 +0200 Subject: [PATCH 172/263] Icosahedron spheres. Based on https://github.com/caosdoar/spheres --- .../visualization/bvv/Icosahedron.java | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java new file mode 100644 index 000000000..a825b3705 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java @@ -0,0 +1,154 @@ +package fiji.plugin.trackmate.visualization.bvv; + +import fiji.plugin.trackmate.Spot; +import net.imagej.mesh.Mesh; +import net.imagej.mesh.Meshes; +import net.imagej.mesh.Triangle; +import net.imagej.mesh.naive.NaiveDoubleMesh; +import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.RealLocalizable; + +/** + * Icosahedron spheres. + *

+ * Based on https://github.com/caosdoar/spheres + * + * @author Jean-Yves Tinevez + */ +public class Icosahedron +{ + + public static final Mesh core() + { + final NaiveDoubleMesh mesh = new NaiveDoubleMesh(); + + // Vertices + final double t = ( 1. + Math.sqrt( 5. ) ) / 2.; + final double[][] vs = new double[][] { + { -1.0, t, 0.0 }, + { 1.0, t, 0.0 }, + { -1.0, -t, 0.0 }, + { 1.0, -t, 0.0 }, + { 0.0, -1.0, t }, + { 0.0, 1.0, t }, + { 0.0, -1.0, -t }, + { 0.0, 1.0, -t }, + { t, 0.0, -1.0 }, + { t, 0.0, 1.0 }, + { -t, 0.0, -1.0 }, + { -t, 0.0, 1.0 } + }; + final double[] tmp = new double[ 3 ]; + for ( final double[] v : vs ) + { + normalize( v, tmp ); + mesh.vertices().add( tmp[ 0 ], tmp[ 1 ], tmp[ 2 ] ); + } + + // Faces + mesh.triangles().add( 0, 11, 5 ); + mesh.triangles().add( 0, 5, 1 ); + mesh.triangles().add( 0, 1, 7 ); + mesh.triangles().add( 0, 7, 10 ); + mesh.triangles().add( 0, 10, 11 ); + mesh.triangles().add( 1, 5, 9 ); + mesh.triangles().add( 5, 11, 4 ); + mesh.triangles().add( 11, 10, 2 ); + mesh.triangles().add( 10, 7, 6 ); + mesh.triangles().add( 7, 1, 8 ); + mesh.triangles().add( 3, 9, 4 ); + mesh.triangles().add( 3, 4, 2 ); + mesh.triangles().add( 3, 2, 6 ); + mesh.triangles().add( 3, 6, 8 ); + mesh.triangles().add( 3, 8, 9 ); + mesh.triangles().add( 4, 9, 5 ); + mesh.triangles().add( 2, 4, 11 ); + mesh.triangles().add( 6, 2, 10 ); + mesh.triangles().add( 8, 6, 7 ); + mesh.triangles().add( 9, 8, 1 ); + return mesh; + } + + public static final BufferMesh refine( final Mesh core ) + { + final int nVerticesOut = ( int ) ( 6 * core.triangles().size() ); + final int nTrianglesOut = ( int ) ( 4 * core.triangles().size() ); + final BufferMesh out = new BufferMesh( nVerticesOut, nTrianglesOut ); + + final double[] tmpIn = new double[ 3 ]; + final double[] tmpOut = new double[ 3 ]; + for ( final Triangle t : core.triangles() ) + { + final long v0 = out.vertices().add( t.v0x(), t.v0y(), t.v0z() ); + final long v1 = out.vertices().add( t.v1x(), t.v1y(), t.v1z() ); + final long v2 = out.vertices().add( t.v2x(), t.v2y(), t.v2z() ); + + tmpIn[ 0 ] = 0.5 * ( t.v0xf() + t.v1xf() ); + tmpIn[ 1 ] = 0.5 * ( t.v0yf() + t.v1yf() ); + tmpIn[ 2 ] = 0.5 * ( t.v0zf() + t.v1zf() ); + normalize( tmpIn, tmpOut ); + final long v3 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); + + tmpIn[ 0 ] = 0.5 * ( t.v2xf() + t.v1xf() ); + tmpIn[ 1 ] = 0.5 * ( t.v2yf() + t.v1yf() ); + tmpIn[ 2 ] = 0.5 * ( t.v2zf() + t.v1zf() ); + normalize( tmpIn, tmpOut ); + final long v4 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); + + tmpIn[ 0 ] = 0.5 * ( t.v0xf() + t.v2xf() ); + tmpIn[ 1 ] = 0.5 * ( t.v0yf() + t.v2yf() ); + tmpIn[ 2 ] = 0.5 * ( t.v0zf() + t.v2zf() ); + normalize( tmpIn, tmpOut ); + final long v5 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); + + out.triangles().add( v0, v3, v5 ); + out.triangles().add( v3, v1, v4 ); + out.triangles().add( v5, v4, v2 ); + out.triangles().add( v3, v4, v5 ); + } + + return out; + } + + public static BufferMesh sphere( final Spot spot ) + { + return sphere( spot, spot.getFeature( Spot.RADIUS ) ); + } + + public static BufferMesh sphere( final RealLocalizable center, final double radius ) + { + return sphere( center, radius, 3 ); + } + + public static BufferMesh sphere( final RealLocalizable center, final double radius, final int nSubdivisions ) + { + Mesh mesh = core(); + for ( int i = 0; i < nSubdivisions; i++ ) + mesh = refine( mesh ); + + scale( mesh, center, radius ); + final BufferMesh out = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); + Meshes.calculateNormals( mesh, out ); + return out; + } + + private static void scale( final Mesh mesh, final RealLocalizable center, final double radius ) + { + final long nV = mesh.vertices().size(); + for ( int i = 0; i < nV; i++ ) + { + final double x = mesh.vertices().x( i ) * radius + center.getDoublePosition( 0 ); + final double y = mesh.vertices().y( i ) * radius + center.getDoublePosition( 1 ); + final double z = mesh.vertices().z( i ) * radius + center.getDoublePosition( 2 ); + mesh.vertices().set( i, x, y, z ); + } + } + + private static void normalize( final double[] v, final double[] tmp ) + { + final double l = Math.sqrt( v[ 0 ] * v[ 0 ] + v[ 1 ] * v[ 1 ] + v[ 2 ] * v[ 2 ] ); + tmp[ 0 ] = v[ 0 ] / l; + tmp[ 1 ] = v[ 1 ] / l; + tmp[ 2 ] = v[ 2 ] / l; + } +} From 6a36f1207693a29e8ea096e80e0aa310dcbcc7c2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 May 2023 18:12:53 +0200 Subject: [PATCH 173/263] Show spots without meshes as icosahedron spheres. --- .../java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java index 86bcd3162..11b0cd245 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -34,8 +34,7 @@ public static final StupidMesh createMesh( final Spot spot ) Meshes.copy( mesh, bm ); return new StupidMesh( bm ); } - System.out.println( "TODO: Deal with spherical spots" ); // DEBUG - return null; + return new StupidMesh( Icosahedron.sphere( spot ) ); } public static final < T extends Type< T > > BvvHandle createViewer( final ImagePlus imp ) From a5a5dd5d7c034d9aca8026d20d8994cfc16ee8b3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 24 May 2023 13:34:40 +0200 Subject: [PATCH 174/263] Don't crash when loading faulty meshes. --- src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 0956d9836..1f696dee8 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -978,7 +978,7 @@ private SpotCollection getSpots( final Element modelElement ) spots.remove( spot ); spots.add( spotMesh ); } - catch ( final IOException e ) + catch ( final Exception e ) { ok = false; logger.error( "Problem reading mesh for spot " + id + ":\n" From 5311e4a729697108617c365b002aa2220a64deca Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 24 May 2023 13:35:44 +0200 Subject: [PATCH 175/263] Fix OverlapTracker not working if the first frame was devoid of visible spots. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Noticed by Laura Xénard @Mini-Miette Fix #260 --- .../plugin/trackmate/tracking/overlap/OverlapTracker.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java index 3279e80da..6feca9b02 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java @@ -203,7 +203,11 @@ public boolean process() final Map< Spot, Polygon2D > targetGeometries = createGeometry( spots.iterable( targetFrame, true ), method, enlargeFactor ); if ( sourceGeometries.isEmpty() || targetGeometries.isEmpty() ) + { + sourceGeometries = targetGeometries; + logger.setProgress( ( double ) progress++ / spots.keySet().size() ); continue; + } final ExecutorService executors = Threads.newFixedThreadPool( numThreads ); final List< Future< IoULink > > futures = new ArrayList<>(); From ed2b1afd0209b71024771fa9ba954721710757ae Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 2 Jun 2023 15:51:15 +0200 Subject: [PATCH 176/263] Put a test drive in a try/catch block so that we can get exceptions. One bug of my Eclipse installation results in not having exceptions shown in the console. --- .../visualization/bvv/TrackMateBVV.java | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index 1e9c8c780..26a996f24 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -22,7 +22,6 @@ import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; import fiji.plugin.trackmate.gui.wizard.TrackMateWizardSequence; import fiji.plugin.trackmate.gui.wizard.WizardSequence; import fiji.plugin.trackmate.io.TmXmlReader; @@ -126,39 +125,40 @@ private void updateColor() public static < T extends Type< T > > void main( final String[] args ) { + try + { // final String filePath = "samples/mesh/CElegansMask3D.tif"; - final String filePath = "samples/CElegans3D-smoothed-mask-orig.xml"; + final String filePath = "samples/CElegans3D-smoothed-mask-orig.xml"; +// final String filePath = "../TrackMate-StarDist/samples/CTC-Fluo-N3DH-SIM-multiC.xml"; - ImageJ.main( args ); - final TmXmlReader reader = new TmXmlReader( new File( filePath ) ); - if ( !reader.isReadingOk() ) - { - System.err.println( reader.getErrorMessage() ); - return; - } - final ImagePlus imp = reader.readImage(); - final Settings settings = reader.readSettings( imp ); - imp.show(); - - final Model model = reader.getModel(); - final SelectionModel selectionModel = new SelectionModel( model ); - final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); - final TrackMate trackmate = new TrackMate( model, settings ); - - // Main view - final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, imp, ds ); - displayer.render(); - - // Wizard. - final WizardSequence sequence = new TrackMateWizardSequence( trackmate, selectionModel, ds ); - sequence.setCurrent( "ConfigureViews" ); - final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); - frame.setIconImage( TRACKMATE_ICON.getImage() ); - GuiUtils.positionWindow( frame, settings.imp.getWindow() ); - frame.setVisible( true ); + ImageJ.main( args ); + final TmXmlReader reader = new TmXmlReader( new File( filePath ) ); + if ( !reader.isReadingOk() ) + { + System.err.println( reader.getErrorMessage() ); + return; + } + final ImagePlus imp = reader.readImage(); + final Settings settings = reader.readSettings( imp ); + imp.show(); + + final Model model = reader.getModel(); + final SelectionModel selectionModel = new SelectionModel( model ); + final DisplaySettings ds = reader.getDisplaySettings(); + final TrackMate trackmate = new TrackMate( model, settings ); + + // Main view + final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, imp, ds ); + displayer.render(); + + // Wizard. + final WizardSequence sequence = new TrackMateWizardSequence( trackmate, selectionModel, ds ); + sequence.setCurrent( "ConfigureViews" ); + final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); + frame.setIconImage( TRACKMATE_ICON.getImage() ); + GuiUtils.positionWindow( frame, settings.imp.getWindow() ); + frame.setVisible( true ); - try - { final TrackMateBVV< T > tbvv = new TrackMateBVV<>( model, selectionModel, imp, ds ); tbvv.render(); } From 584494c1a89b81105fd2cbeffed405efad30000a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 30 May 2023 17:01:25 +0200 Subject: [PATCH 177/263] Minor tweak of the demo. --- .../java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java index 52a32b51c..39e1add05 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java @@ -14,8 +14,8 @@ public static void main( final String[] args ) { ImageJ.main( args ); -// final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; - final String filePath = "samples/Celegans-5pc-17timepoints.tif"; + final String filePath = "samples/CElegans3D-smoothed-mask-orig.tif"; +// final String filePath = "samples/Celegans-5pc-17timepoints.tif"; final ImagePlus imp = IJ.openImage( filePath ); imp.show(); From 03f26352bbc35af0cc64cdb0be412a7d88671e67 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 2 Jun 2023 12:08:29 +0200 Subject: [PATCH 178/263] Reflect model edits in the TrackMate-BVV. --- .../visualization/bvv/TrackMateBVV.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index 26a996f24..4f773db3c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -104,8 +104,26 @@ public String getKey() @Override public void modelChanged( final ModelChangeEvent event ) { - // TODO Auto-generated method stub - + switch ( event.getEventID() ) + { + case ModelChangeEvent.SPOTS_FILTERED: + case ModelChangeEvent.SPOTS_COMPUTED: + case ModelChangeEvent.TRACKS_VISIBILITY_CHANGED: + case ModelChangeEvent.TRACKS_COMPUTED: + refresh(); + break; + case ModelChangeEvent.MODEL_MODIFIED: + { + for ( final Spot spot : event.getSpots() ) + { + final StupidMesh mesh = BVVUtils.createMesh( spot ); + meshMap.put( spot, mesh ); + } + updateColor(); + refresh(); + break; + } + } } private void updateColor() From cef0048ff44ed48325703010b94ea3fa05143fc7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 2 Jun 2023 16:32:25 +0200 Subject: [PATCH 179/263] TrackMate BVV can focus view on selected spot. --- .../visualization/bvv/TrackMateBVV.java | 46 ++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index 4f773db3c..aabd16436 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -12,6 +12,7 @@ import org.joml.Matrix4f; +import bdv.viewer.animate.TranslationAnimator; import bvv.util.BvvHandle; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; @@ -31,6 +32,8 @@ import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImageJ; import ij.ImagePlus; +import net.imglib2.RealLocalizable; +import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.Type; import tpietzsch.example2.VolumeViewerPanel; @@ -91,8 +94,49 @@ public void clear() @Override public void centerViewOn( final Spot spot ) { - // TODO Auto-generated method stub + if ( handle == null ) + return; + + final VolumeViewerPanel panel = handle.getViewerPanel(); + panel.setTimepoint( spot.getFeature( Spot.FRAME ).intValue() ); + + final AffineTransform3D c = panel.state().getViewerTransform(); + final double[] translation = getTranslation( c, spot, panel.getWidth(), panel.getHeight() ); + if ( translation != null ) + { + final TranslationAnimator animator = new TranslationAnimator( c, translation, 300 ); + animator.setTime( System.currentTimeMillis() ); + panel.setTransformAnimator( animator ); + } + } + + /** + * Returns a translation vector that will put the specified position at the + * center of the panel when used with a TranslationAnimator. + * + * @param t + * the viewer panel current view transform. + * @param target + * the position to focus on. + * @param width + * the width of the panel. + * @param height + * the height of the panel. + * @return a new double[] array with 3 elements containing the + * translation to use. + */ + private static final double[] getTranslation( final AffineTransform3D t, final RealLocalizable target, final int width, final int height ) + { + final double[] pos = new double[ 3 ]; + final double[] vPos = new double[ 3 ]; + target.localize( pos ); + t.apply( pos, vPos ); + + final double dx = width / 2 - vPos[ 0 ] + t.get( 0, 3 ); + final double dy = height / 2 - vPos[ 1 ] + t.get( 1, 3 ); + final double dz = -vPos[ 2 ] + t.get( 2, 3 ); + return new double[] { dx, dy, dz }; } @Override From 39be9433bedec053ca325e0910b84e7066b6b7a5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 2 Jun 2023 16:50:38 +0200 Subject: [PATCH 180/263] Basic support of selection highlighting in the BVV. --- .../visualization/bvv/StupidMesh.java | 18 ++++++++++++++++-- .../visualization/bvv/TrackMateBVV.java | 4 +++- .../plugin/trackmate/visualization/bvv/mesh.fp | 11 +++++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java index cdb81374c..de7aedae9 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -43,6 +43,7 @@ import com.jogamp.opengl.GL; import com.jogamp.opengl.GL3; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import net.imagej.mesh.nio.BufferMesh; import tpietzsch.backend.jogl.JoglGpuContext; import tpietzsch.shadergen.DefaultShader; @@ -69,10 +70,14 @@ public StupidMesh( final BufferMesh mesh ) private boolean initialized; - private Color color = Color.WHITE; + private Color color = DisplaySettings.defaultStyle().getSpotUniformColor(); private final float[] carr = new float[ 4 ]; + private Color selectionColor = DisplaySettings.defaultStyle().getHighlightColor(); + + private final float[] scarr = new float[ 4 ]; + private void init( final GL3 gl ) { initialized = true; @@ -121,7 +126,13 @@ public void setColor( final Color color ) this.color = color; } - public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm ) + public void setSelectionColor( final Color selectionColor ) + { + this.selectionColor = selectionColor; + + } + + public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm, final boolean isSelected ) { if ( !initialized ) init( gl ); @@ -134,6 +145,9 @@ public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm ) prog.getUniformMatrix3f( "itvm" ).set( itvm.get3x3( new Matrix3f() ) ); color.getComponents( carr ); prog.getUniform4f( "ObjectColor" ).set( carr[ 0 ], carr[ 1 ], carr[ 2 ], carr[ 3 ] ); + prog.getUniform1f( "IsSelected" ).set( isSelected ? 1f : 0f ); + selectionColor.getComponents( scarr ); + prog.getUniform4f( "SelectionColor" ).set( scarr[ 0 ], scarr[ 1 ], scarr[ 2 ], scarr[ 3 ] ); prog.setUniforms( context ); prog.use( context ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index aabd16436..0b2c8c3b8 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -57,6 +57,7 @@ public TrackMateBVV( final Model model, final SelectionModel selectionModel, fin it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ) ); updateColor(); displaySettings.listeners().add( this::updateColor ); + selectionModel.addSelectionChangeListener( e -> refresh() ); } @Override @@ -72,7 +73,7 @@ public void render() final int t = data.getTimepoint(); final Iterable< Spot > it = model.getSpots().iterable( t, true ); - it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm ) ); + it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm, selectionModel.getSpotSelection().contains( s ) ) ); } } ); } @@ -181,6 +182,7 @@ private void updateColor() final Color color = spotColorGenerator.color( entry.getKey() ); sm.setColor( color ); + sm.setSelectionColor( displaySettings.getHighlightColor() ); } refresh(); } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp index 818d9946b..9eff156d2 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp @@ -4,6 +4,8 @@ in vec3 Normal; in vec3 FragPos; uniform vec4 ObjectColor; +uniform float IsSelected; +uniform vec4 SelectionColor; const vec3 lightColor1 = 0.5 * vec3(0.9, 0.9, 1); const vec3 lightDir1 = normalize(vec3(0, -0.2, -1)); @@ -35,7 +37,12 @@ void main() vec3 l1 = phong( norm, viewDir, lightDir1, lightColor1, 32, 0.1 ); vec3 l2 = phong( norm, viewDir, lightDir2, lightColor2, 32, 0.5 ); + + if (IsSelected > 0.5) { + fragColor = vec4( (ambient + l1 + l2), 1) * SelectionColor; + } else { + float it = dot(norm, viewDir); + fragColor = vec4( it * (ambient + l1 + l2), 1) * ObjectColor + (1-it) * vec4(1,1,1,1); + } - float it = dot(norm, viewDir); - fragColor = vec4( it * (ambient + l1 + l2), 1) * ObjectColor + (1-it) * vec4(1,1,1,1); } From bba11ef6dee1ccca7510aff1252ea60d42bc0821 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 3 Jun 2023 16:06:41 +0200 Subject: [PATCH 181/263] Add a button to launch a 3D view on the GUI. --- .../java/fiji/plugin/trackmate/gui/Icons.java | 5 +- .../gui/components/ConfigureViewsPanel.java | 10 +++- .../gui/wizard/TrackMateWizardSequence.java | 43 ++++++++++++++++-- .../descriptors/ConfigureViewsDescriptor.java | 10 ++-- .../gui/images/TrackMateBVV-logo-16x16.png | Bin 0 -> 3527 bytes 5 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 src/main/resources/fiji/plugin/trackmate/gui/images/TrackMateBVV-logo-16x16.png diff --git a/src/main/java/fiji/plugin/trackmate/gui/Icons.java b/src/main/java/fiji/plugin/trackmate/gui/Icons.java index 6ec9b0391..bdd0e5725 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/Icons.java +++ b/src/main/java/fiji/plugin/trackmate/gui/Icons.java @@ -8,12 +8,12 @@ * 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 * . @@ -205,4 +205,5 @@ public class Icons public static final ImageIcon QUESTION_ICON = new ImageIcon( Icons.class.getResource( "images/help.png" ) ); + public static final ImageIcon BVV_ICON = new ImageIcon( Icons.class.getResource( "images/TrackMateBVV-logo-16x16.png" ) ); } diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java index 85ddbf7c2..da15d129a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -8,12 +8,12 @@ * 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 * . @@ -80,6 +80,7 @@ public ConfigureViewsPanel( final DisplaySettings ds, final FeatureDisplaySelector featureSelector, final String spaceUnits, + final Action launchBVVAction, final Action launchTrackSchemeAction, final Action showTrackTablesAction, final Action showSpotTableAction, @@ -355,6 +356,11 @@ public ConfigureViewsPanel( final JPanel panelButtons = new JPanel(); panelButtons.setLayout( new WrapLayout() ); + // BVV button. + final JButton btnShowBVV = new JButton( launchBVVAction ); + panelButtons.add( btnShowBVV ); + btnShowBVV.setFont( FONT ); + // TrackScheme button. final JButton btnShowTrackScheme = new JButton( launchTrackSchemeAction ); panelButtons.add( btnShowTrackScheme ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index f7e1ca4bb..0cc52ab79 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -8,12 +8,12 @@ * 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 * . @@ -21,6 +21,7 @@ */ package fiji.plugin.trackmate.gui.wizard; +import static fiji.plugin.trackmate.gui.Icons.BVV_ICON; import static fiji.plugin.trackmate.gui.Icons.SPOT_TABLE_ICON; import static fiji.plugin.trackmate.gui.Icons.TRACK_SCHEME_ICON_16x16; import static fiji.plugin.trackmate.gui.Icons.TRACK_TABLES_ICON; @@ -42,6 +43,7 @@ import fiji.plugin.trackmate.action.AbstractTMAction; import fiji.plugin.trackmate.action.ExportAllSpotsStatsAction; import fiji.plugin.trackmate.action.ExportStatsTablesAction; +import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.detection.ManualDetectorFactory; import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase; import fiji.plugin.trackmate.features.FeatureFilter; @@ -73,8 +75,10 @@ import fiji.plugin.trackmate.tracking.SpotTrackerFactory; import fiji.plugin.trackmate.tracking.manual.ManualTrackerFactory; import fiji.plugin.trackmate.util.Threads; +import fiji.plugin.trackmate.visualization.bvv.TrackMateBVV; import fiji.plugin.trackmate.visualization.trackscheme.SpotImageUpdater; import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; +import ij.ImagePlus; public class TrackMateWizardSequence implements WizardSequence { @@ -150,6 +154,7 @@ public TrackMateWizardSequence( final TrackMate trackmate, final SelectionModel configureViewsDescriptor = new ConfigureViewsDescriptor( displaySettings, featureSelector, + new LaunchBVVAction(), new LaunchTrackSchemeAction(), new ShowTrackTablesAction(), new ShowSpotTableAction(), @@ -439,7 +444,7 @@ private SpotTrackerDescriptor getTrackerConfigDescriptor() previous.put( configDescriptor, chooseTrackerDescriptor ); previous.put( executeTrackingDescriptor, configDescriptor ); previous.put( trackFilterDescriptor, configDescriptor ); - + return configDescriptor; } @@ -452,6 +457,38 @@ private SpotTrackerDescriptor getTrackerConfigDescriptor() private static final String TRACKSCHEME_BUTTON_TOOLTIP = "Launch a new instance of TrackScheme."; + private static final String BVV_BUTTON_TOOLTIP = "Launch a new 3D viewer."; + + private class LaunchBVVAction extends AbstractAction + { + private static final long serialVersionUID = 1L; + + private LaunchBVVAction() + { + super( "3D view", BVV_ICON ); + putValue( SHORT_DESCRIPTION, BVV_BUTTON_TOOLTIP ); + final ImagePlus imp = trackmate.getSettings().imp; + final boolean enabled = ( imp != null ) && !DetectionUtils.is2D( imp ); + setEnabled( enabled ); + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + new Thread( "Launching BVV thread" ) + { + @Override + public void run() + { + final Model model = trackmate.getModel(); + final ImagePlus imp = trackmate.getSettings().imp; + if ( imp != null ) + new TrackMateBVV<>( model, selectionModel, imp, displaySettings ).render(); + } + }.start(); + } + } + private class LaunchTrackSchemeAction extends AbstractAction { private static final long serialVersionUID = 1L; diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java index 7856dd874..aea1164a7 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java @@ -8,12 +8,12 @@ * 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 * . @@ -36,6 +36,7 @@ public class ConfigureViewsDescriptor extends WizardPanelDescriptor public ConfigureViewsDescriptor( final DisplaySettings ds, final FeatureDisplaySelector featureSelector, + final Action launchBVVAction, final Action launchTrackSchemeAction, final Action showTrackTablesAction, final Action showSpotTableAction, @@ -44,9 +45,10 @@ public ConfigureViewsDescriptor( { super( KEY ); this.targetPanel = new ConfigureViewsPanel( - ds, - featureSelector, + ds, + featureSelector, spaceUnits, + launchBVVAction, launchTrackSchemeAction, showTrackTablesAction, showSpotTableAction, diff --git a/src/main/resources/fiji/plugin/trackmate/gui/images/TrackMateBVV-logo-16x16.png b/src/main/resources/fiji/plugin/trackmate/gui/images/TrackMateBVV-logo-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..2e5b7d5fa7681294b59f060b81aa2d79f07c2155 GIT binary patch literal 3527 zcmV;&4LI_NP)StO&>uS)ve<0AYj>5AR{$W90N^4L=L-RlQUJ&DC0@ZjPh;=*jPLSYvv5M~MFBAl0-BNIsH z15C~g000{K(ZT*WKal6<?_01!^k@7iDG<<3=fuAC~28EsPoqkpK{9G%|Vj005J}`Hw&=0RYXHq~ibpyyzHQsFW8>#s~laM4*8xut5h5 z!4#~(4xGUqyucR%VFpA%3?#rj5JCpzfE)^;7?wd9RKPme1hudO8lVxH;SjXJF*pt9 z;1XPc>u?taU>Kgl7`%oF1VP9M6Ja4bh!J9r*dopd7nzO(B4J20l7OTj>4+3jBE`sZ zqynizYLQ(?Bl0bB6giDtK>Co|$RIL`{EECsF_eL_Q3KQhbwIhO9~z3rpmWi5G!I>X zmZEFX8nhlgfVQHi(M#xcbO3#dj$?q)F%D*o*1Pf{>6$SWH+$s3q(pv=X`qR|$iJF~TPzlc-O$C3+J1 z#CT#lv5;6stS0Uu9wDA3UMCI{Uz12A4#|?_P6{CkNG+sOq(0IRX`DyT~9-sA|ffUF>wk++Z!kWZ5P$;0Hg6gtI-;!FvmBvPc55=u2?Kjj3apE5$3psG>L zsh-pbs)#zDT1jo7c2F-(3)vyY4>O^>2$gY-Gd%Qm(Z8e zYv>2*=jns=cMJ`N4THx>VkjAF8G9M07`GWOnM|ey)0dgZR4~^v8<}UA514ONSSt1^ zd=-((5|uiYR+WC0=c-gyb5%dpd8!Lkt5pxHURHgkMpd&=fR^vEcAI*_=wwAG2sV%zY%w@v@XU~7=xdm1xY6*0;iwVIXu6TaXrs|dqbIl~ z?uTdNHFy_3W~^@g_pF#!K2~{F^;XxcN!DEJEbDF7 zS8PxlSDOr*I-AS3sI8l=#CDr)-xT5$k15hA^;2%zG3@;83hbKf2JJcaVfH2VZT8O{ z%p4LO);n}Nd~$Sk%yw*Wyz8XlG{dRHsl(}4XB%gsbDi@w7p6;)%MzD%mlsoQr;4X; zpL)xc%+^yMd)ZNTI#eJ*$O)i@o$z8)e??LqN_gLa_%;TM>o2SC_ zkmoO6c3xRt`@J4dvz#WL)-Y|z+r(Soy~}%GIzByR`p)SCKE^%*pL(B%zNWq+-#xw~ ze%5}Oeh2)X`#bu}{g3#+;d$~F@lFL`0l@*~0lk45fwKc^10MvL1f>Tx1&sx}1}_Xg z6+#RN4Ot&@lW)Km@*DYMGu&q^n$Z=?2%QyL8~QNJCQKgI5srq>2;UHXZ>IT7>CCnW zh~P(Th`1kV8JQRPeH1AwGO8}>QM6NZadh`A)~w`N`)9q5@sFvDxjWlxwsLl7tZHmh zY-8-3xPZ8-xPf?w_(k!T5_A(J3GIpG#Ms0=iQ{tu=WLoYoaCBRmULsT<=mpV7v|~C z%bs^USv6UZd^m-e5|^?+<%1wXP%juy<)>~<9TW0|n}ttBzM_qyQL(qUN<5P0omQ3h zINdvaL;7fjPeygdGYL;pD|wL_lDQ-EO;$wK-mK5raoH_7l$?~Dqf!lNmb5F^Ft;eT zPi8AClMUo~=55LwlZVRpxOiFd;3B_8yA~shQx|tGF!j;$toK>JuS&gYLDkTP@C~gS@r~shUu{a>bfJ1` z^^VQ7&C1OKHDNXFTgC{M|V%fo{xK_dk6MK@9S!GZ*1JJzrV5xZBjOk z9!NTH<(q(S+MDf~ceQX@Dh|Ry<-sT4rhI$jQ0Sq~!`#Eo-%($2E^vo}is5J@NVEf|KK?WT&2;PCq@=ncR8z zO#GQ^T~S@VXG71PKNocFOt)Y6$@AXlk6rM*aP%VgV%sIRORYVwJx6|U{ozQjTW{-S z_si{9Jg#)~P3t?+@6&(!YQWWV*Z9{iU7vZq@5byKw{9lg9JnRA_4s!7?H6|n?o8ZW zdXIRo{Jz@#>IeD{>VLHUv1Pz*;P_y`V9&!@5AO~Mho1hF|I>%z(nrik)gwkDjgOrl z9~%uCz4Bzvli{bbrxVZ0epdf^>vOB;-~HnIOV3#R*zgPai_gEVd8zYq@2jb=I>#f& zAH2?aJ@Kaet^hU3wtkGpvBNVk)^ z)vvFFescaRjLQW80J^^pZjaps_u9e@T1<_M7wR{clKG@oS5y!0X~~zxA~X&FkrXs@@7w^q%KMkAU?WwPU+rW-nhY*v~gyZv0A&>I?&bY_L7 z$&^!)hXCOHzY8oBy>C@grl1AyQ&qKNWi^9yKHnC`?IEi5TKet8xu|Bfi@HGKw z90>qAlE9!m{eAvHo5LQc2t{>qX8K)=LEmOE>W!USyS7`*CfB}g!80kLHrE8GlKMql z$kPFj+k8BVWrKEtcSkqkElh%BsVVN+u$SxUtokXp$(mci!)D| z%9$xMmr+r;kj{RwQ=~r7#qH66T(TM}62rS0+nSCiJ>HQfK&zeoR?DqD7k@3BtJyln zLP1V1cQS|5`5akDWmg!=WD?6xXqSiqu@!q-Gmw)_MY=S62|z~xUtGCo)5L>Lzn6@! zE0ZtBKacnKbsgy8xa}whWVDc*csM^hq*QB=h($LXP!tWAg#wB1IoED?T%DSUzrZpA zJA5c~iqV-I(QxLg#@equ3M~002ovPDHLkV1fll Btlt0t literal 0 HcmV?d00001 From ccf59da5b1ddab8c10bb6888aa1ceef37a2ea04b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 6 Jun 2023 15:33:18 +0200 Subject: [PATCH 182/263] Tweak error messages. --- .../java/fiji/plugin/trackmate/detection/Process2DZ.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index b29c832db..c7d1af498 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -36,7 +36,7 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > implements SpotDetector< T > { - private static final String BASE_ERROR_MESSAGE = "[Process3Das2DZ] "; + private static final String BASE_ERROR_MESSAGE = "[Process2DZ] "; private final ImgPlus< T > img; @@ -58,12 +58,12 @@ public boolean checkInput() { if ( img.dimensionIndex( Axes.Z ) < 0 || img.dimension( img.dimensionIndex( Axes.Z ) ) < 2 ) { - errorMessage = BASE_ERROR_MESSAGE + "Source image is not 3D."; + errorMessage = BASE_ERROR_MESSAGE + "Source image is not 3D.\n"; return false; } if ( img.dimensionIndex( Axes.TIME ) > 0 && img.dimension( img.dimensionIndex( Axes.TIME ) ) > 1 ) { - errorMessage = BASE_ERROR_MESSAGE + "Source image has more than one time-point."; + errorMessage = BASE_ERROR_MESSAGE + "Source image has more than one time-point.\n"; return false; } return true; From 6f71df7a3b6a7c852e4b58b7d12bbfb584ca7a2c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 6 Jun 2023 16:55:18 +0200 Subject: [PATCH 183/263] WIP: rework the 2d + z processor. --- .../trackmate/detection/Process2DZ.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index c7d1af498..3ead28ef7 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -1,16 +1,21 @@ package fiji.plugin.trackmate.detection; +import java.util.ArrayList; import java.util.List; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.action.LabelImgExporter; import fiji.plugin.trackmate.util.TMUtils; import ij.ImagePlus; import net.imagej.ImgPlus; import net.imagej.axis.Axes; +import net.imagej.mesh.alg.TaubinSmoothing; +import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.Interval; import net.imglib2.algorithm.MultiThreadedBenchmarkAlgorithm; import net.imglib2.img.display.imagej.CalibrationUtils; import net.imglib2.img.display.imagej.ImageJFunctions; @@ -46,9 +51,15 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > private List< Spot > spots; - public Process2DZ( final ImgPlus< T > img, final Settings settings, final boolean simplifyMeshes ) + private final Interval interval; + + private final double[] calibration; + + public Process2DZ( final ImgPlus< T > img, final Interval interval, final double[] calibration, final Settings settings, final boolean simplifyMeshes ) { this.img = img; + this.interval = interval; + this.calibration = calibration; this.settings = settings; this.simplify = simplifyMeshes; } @@ -99,15 +110,26 @@ public boolean process() lblImp.setDimensions( lblImp.getNChannels(), lblImp.getNFrames(), lblImp.getNSlices() ); // Convert labels to 3D meshes. + final double[] calibration = TMUtils.getSpatialCalibration( lblImp ); final ImgPlus< T > lblImg = TMUtils.rawWraps( lblImp ); - final LabelImageDetector< T > detector = new LabelImageDetector<>( lblImg, lblImg, TMUtils.getSpatialCalibration( lblImp ), simplify ); + final LabelImageDetector< T > detector = new LabelImageDetector<>( lblImg, lblImg, calibration, simplify ); if ( !detector.checkInput() || !detector.process() ) { errorMessage = BASE_ERROR_MESSAGE + detector.getErrorMessage(); return false; } - this.spots = detector.getResult(); + final List< Spot > results = detector.getResult(); + spots = new ArrayList<>( results.size() ); + for ( final Spot spot : results ) + { + if ( !spot.getClass().isAssignableFrom( SpotMesh.class ) ) + continue; + + final SpotMesh sm = ( SpotMesh ) spot; + final BufferMesh out = TaubinSmoothing.smooth( sm.getMesh() ); + spots.add( new SpotMesh( out, spot.getFeature( Spot.QUALITY ) ) ); + } return true; } From 3c21ae79f41d9bbe907b20cbfb3080c0913b9df1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 7 Jun 2023 18:36:39 +0200 Subject: [PATCH 184/263] Fix a very serious and a very stupid mistake with the move() methods of Spot. --- src/main/java/fiji/plugin/trackmate/Spot.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index e98ab556c..0ecd99497 100644 --- a/src/main/java/fiji/plugin/trackmate/Spot.java +++ b/src/main/java/fiji/plugin/trackmate/Spot.java @@ -422,34 +422,34 @@ default int numDimensions() @Override public default void move( final float distance, final int d ) { - putFeature( POSITION_FEATURES[d], getFeature( POSITION_FEATURES[d] + distance ) ); + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] ) + distance ); } @Override public default void move( final double distance, final int d ) { - putFeature( POSITION_FEATURES[d], getFeature( POSITION_FEATURES[d] + distance ) ); + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] ) + distance ); } @Override public default void move( final RealLocalizable distance ) { for ( int d = 0; d < 3; d++ ) - putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] + distance ) ); + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] ) + distance.getDoublePosition( d ) ); } @Override public default void move( final float[] distance ) { for ( int d = 0; d < 3; d++ ) - putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] + distance[ d ] ) ); + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] ) + distance[ d ] ); } @Override public default void move( final double[] distance ) { for ( int d = 0; d < 3; d++ ) - putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] + distance[ d ] ) ); + putFeature( POSITION_FEATURES[ d ], getFeature( POSITION_FEATURES[ d ] ) + distance[ d ] ); } @Override From 1e7b4730b8700c0809e8494d317c5c91627431db Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 7 Jun 2023 18:36:52 +0200 Subject: [PATCH 185/263] Make the meshToSpot method public. --- .../java/fiji/plugin/trackmate/detection/SpotMeshUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 0ec65e3b4..92c9f8c8b 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -279,7 +279,7 @@ private static < S extends RealType< S > > Spot regionToSpotMesh( * image). * @return */ - private static < S extends RealType< S > > Spot meshToSpotMesh( + public static < S extends RealType< S > > Spot meshToSpotMesh( final Mesh mesh, final boolean simplify, final double[] calibration, From 098be7a2f9ab6b71062a60e350c3780a934e536a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 7 Jun 2023 18:37:39 +0200 Subject: [PATCH 186/263] WIP: on Process2DZ, assign quality, work well with ROIs. Does not work yet, if the zmin is not 0. --- .../trackmate/detection/Process2DZ.java | 126 ++++++++++++++---- 1 file changed, 98 insertions(+), 28 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index 3ead28ef7..72741b574 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -8,19 +8,23 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.TrackModel; import fiji.plugin.trackmate.action.LabelImgExporter; import fiji.plugin.trackmate.util.TMUtils; import ij.ImagePlus; import net.imagej.ImgPlus; -import net.imagej.axis.Axes; import net.imagej.mesh.alg.TaubinSmoothing; import net.imagej.mesh.nio.BufferMesh; +import net.imagej.mesh.obj.transform.TranslateMesh; import net.imglib2.Interval; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessible; import net.imglib2.algorithm.MultiThreadedBenchmarkAlgorithm; -import net.imglib2.img.display.imagej.CalibrationUtils; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; +import net.imglib2.view.IntervalView; +import net.imglib2.view.Views; /** * A {@link SpotDetector} for 3D images that work by running a spot segmentation @@ -43,7 +47,11 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > private static final String BASE_ERROR_MESSAGE = "[Process2DZ] "; - private final ImgPlus< T > img; + private final RandomAccessible< T > img; + + private final Interval interval; + + private final double[] calibration; private final Settings settings; @@ -51,11 +59,30 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > private List< Spot > spots; - private final Interval interval; - - private final double[] calibration; - - public Process2DZ( final ImgPlus< T > img, final Interval interval, final double[] calibration, final Settings settings, final boolean simplifyMeshes ) + /** + * Creates a new {@link Process2DZ} detector. + * + * @param img + * the input data. Must be 3D and the 3 dimensions must be X, Y + * and Z. + * @param interval + * the interval in the input data to process. Must have the same + * number of dimensions that the input data. + * @param calibration + * the pixel size array. + * @param settings + * a TrackMate settings object, configured to operate on the + * (cropped) input data as if it was a 2D+T image. + * @param simplifyMeshes + * whether or not to smooth and simplify meshes resulting from + * merging the 2D contours. + */ + public Process2DZ( + final RandomAccessible< T > img, + final Interval interval, + final double[] calibration, + final Settings settings, + final boolean simplifyMeshes ) { this.img = img; this.interval = interval; @@ -67,16 +94,11 @@ public Process2DZ( final ImgPlus< T > img, final Interval interval, final double @Override public boolean checkInput() { - if ( img.dimensionIndex( Axes.Z ) < 0 || img.dimension( img.dimensionIndex( Axes.Z ) ) < 2 ) + if ( img.numDimensions() != 3 ) { errorMessage = BASE_ERROR_MESSAGE + "Source image is not 3D.\n"; return false; } - if ( img.dimensionIndex( Axes.TIME ) > 0 && img.dimension( img.dimensionIndex( Axes.TIME ) ) > 1 ) - { - errorMessage = BASE_ERROR_MESSAGE + "Source image has more than one time-point.\n"; - return false; - } return true; } @@ -84,13 +106,16 @@ public boolean checkInput() public boolean process() { spots = null; - // Make the final single T 3D image, a 2D + T image final by making Z -> T - final ImagePlus imp = ImageJFunctions.wrap( img, null ); - final int nChannels = ( int ) ( img.dimensionIndex( Axes.CHANNEL ) < 0 ? 1 : img.dimension( img.dimensionIndex( Axes.CHANNEL ) ) ); - final int nSlices = 1; // We force 2D. - final int nFrames = ( int ) img.dimension( img.dimensionIndex( Axes.Z ) ); - imp.setDimensions( nChannels, nSlices, nFrames ); - CalibrationUtils.copyCalibrationToImagePlus( img, imp ); + + // Make the final single T 3D image, a 2D + T image final by making Z->T + final IntervalView< T > cropped = Views.interval( img, interval ); + final ImagePlus imp = ImageJFunctions.wrap( cropped, null ); + final int nFrames = ( int ) interval.dimension( 2 ); + final int nChannels = ( interval.numDimensions() > 3 ) ? ( int ) interval.dimension( 3 ) : 1; + imp.setDimensions( nChannels, 1, nFrames ); + imp.getCalibration().pixelWidth = calibration[ 0 ]; + imp.getCalibration().pixelHeight = calibration[ 1 ]; + imp.getCalibration().pixelDepth = calibration[ 2 ]; // Execute segmentation and tracking. final Settings settingsFrame = settings.copyOn( imp ); @@ -108,9 +133,8 @@ public boolean process() // Back to a 3D single time-point image. lblImp.setDimensions( lblImp.getNChannels(), lblImp.getNFrames(), lblImp.getNSlices() ); - + // Convert labels to 3D meshes. - final double[] calibration = TMUtils.getSpatialCalibration( lblImp ); final ImgPlus< T > lblImg = TMUtils.rawWraps( lblImp ); final LabelImageDetector< T > detector = new LabelImageDetector<>( lblImg, lblImg, calibration, simplify ); if ( !detector.checkInput() || !detector.process() ) @@ -118,17 +142,63 @@ public boolean process() errorMessage = BASE_ERROR_MESSAGE + detector.getErrorMessage(); return false; } - + final List< Spot > results = detector.getResult(); spots = new ArrayList<>( results.size() ); + + final RandomAccess< T > ra = lblImg.randomAccess(); + final TrackModel tm = trackmate.getModel().getTrackModel(); + for ( final Spot spot : results ) { if ( !spot.getClass().isAssignableFrom( SpotMesh.class ) ) continue; - - final SpotMesh sm = ( SpotMesh ) spot; - final BufferMesh out = TaubinSmoothing.smooth( sm.getMesh() ); - spots.add( new SpotMesh( out, spot.getFeature( Spot.QUALITY ) ) ); + + /* + * Smooth spot? + */ + + final Spot newSpot; + if ( simplify ) + { + + final SpotMesh sm = ( SpotMesh ) spot; + final BufferMesh out = TaubinSmoothing.smooth( TranslateMesh.translate( sm.getMesh(), sm ) ); + newSpot = SpotMeshUtils.meshToSpotMesh( out, simplify, new double[] { 1., 1., 1. }, null, new double[] { 0., 0., 0. } ); + if ( newSpot == null ) + continue; + } + else + { + newSpot = spot; + } + + /* + * Try to get quality from the tracks resulting from the 2D+T image. + */ + + // Position RA where the spot is. + for ( int d = 0; d < 3; d++ ) + ra.setPosition( Math.round( spot.getDoublePosition( d ) / calibration[ d ] ), d ); + + // Read track ID from label value. + final int trackID = ( int ) ra.get().getRealDouble() - 1; + + // Average quality from the corresponding track. + /* + * FIXME: Does not work if zmin in the interval is not 0. Probably + * because the spots do not 'touch' the right label. + */ + final double avgQuality = tm.trackSpots( trackID ).stream().mapToDouble( s -> s.getFeature( Spot.QUALITY ).doubleValue() ).average().getAsDouble(); + + // Pass quality to new spot. + newSpot.putFeature( Spot.QUALITY, Double.valueOf( avgQuality ) ); + + // Shift them by interval min. + for ( int d = 0; d < 3; d++ ) + newSpot.move( interval.min( d ) * calibration[ d ], d ); + + spots.add( newSpot ); } return true; } From 6b20fe8fe74e7e89a0a9ca6373656782376bb9db Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 8 Jun 2023 18:45:38 +0200 Subject: [PATCH 187/263] Disable GUI when launching BVV. --- .../gui/wizard/TrackMateWizardSequence.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index 0cc52ab79..511cda3ef 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -26,6 +26,7 @@ import static fiji.plugin.trackmate.gui.Icons.TRACK_SCHEME_ICON_16x16; import static fiji.plugin.trackmate.gui.Icons.TRACK_TABLES_ICON; +import java.awt.Component; import java.awt.event.ActionEvent; import java.util.Arrays; import java.util.HashMap; @@ -33,6 +34,9 @@ import java.util.Map; import javax.swing.AbstractAction; +import javax.swing.JLabel; +import javax.swing.JRootPane; +import javax.swing.SwingUtilities; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; @@ -74,6 +78,7 @@ import fiji.plugin.trackmate.tracking.SpotImageTrackerFactory; import fiji.plugin.trackmate.tracking.SpotTrackerFactory; import fiji.plugin.trackmate.tracking.manual.ManualTrackerFactory; +import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; import fiji.plugin.trackmate.util.Threads; import fiji.plugin.trackmate.visualization.bvv.TrackMateBVV; import fiji.plugin.trackmate.visualization.trackscheme.SpotImageUpdater; @@ -480,10 +485,21 @@ public void actionPerformed( final ActionEvent e ) @Override public void run() { - final Model model = trackmate.getModel(); - final ImagePlus imp = trackmate.getSettings().imp; - if ( imp != null ) - new TrackMateBVV<>( model, selectionModel, imp, displaySettings ).render(); + final Component c = ( Component ) e.getSource(); + final JRootPane parent = SwingUtilities.getRootPane( c ); + final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( parent, new Class[] { JLabel.class } ); + enabler.disable(); + try + { + final Model model = trackmate.getModel(); + final ImagePlus imp = trackmate.getSettings().imp; + if ( imp != null ) + new TrackMateBVV<>( model, selectionModel, imp, displaySettings ).render(); + } + finally + { + enabler.reenable(); + } } }.start(); } From 2a695e7b0d54c1238550b9bdb19380d63277bf56 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:39:13 +0200 Subject: [PATCH 188/263] Simplify StupidMesh. Remove unneeded fields. --- .../visualization/bvv/StupidMesh.java | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java index de7aedae9..9d45d7bff 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -55,29 +55,26 @@ public class StupidMesh { private final Shader prog; + private final BufferMesh mesh; + + private boolean initialized; + private int vao; - private final BufferMesh mesh; + private final float[] carr = new float[ 4 ]; + + private final float[] scarr = new float[ 4 ]; public StupidMesh( final BufferMesh mesh ) { this.mesh = mesh; - final Segment meshVp = new SegmentTemplate( StupidMesh.class, "mesh.vp" ).instantiate(); final Segment meshFp = new SegmentTemplate( StupidMesh.class, "mesh.fp" ).instantiate(); prog = new DefaultShader( meshVp.getCode(), meshFp.getCode() ); + DisplaySettings.defaultStyle().getSpotUniformColor().getColorComponents( carr ); + DisplaySettings.defaultStyle().getHighlightColor().getColorComponents( scarr ); } - private boolean initialized; - - private Color color = DisplaySettings.defaultStyle().getSpotUniformColor(); - - private final float[] carr = new float[ 4 ]; - - private Color selectionColor = DisplaySettings.defaultStyle().getHighlightColor(); - - private final float[] scarr = new float[ 4 ]; - private void init( final GL3 gl ) { initialized = true; @@ -106,8 +103,6 @@ private void init( final GL3 gl ) gl.glBufferData( GL.GL_ELEMENT_ARRAY_BUFFER, indices.limit() * Integer.BYTES, indices, GL.GL_STATIC_DRAW ); gl.glBindBuffer( GL.GL_ELEMENT_ARRAY_BUFFER, 0 ); - - gl.glGenVertexArrays( 1, tmp, 0 ); vao = tmp[ 0 ]; gl.glBindVertexArray( vao ); @@ -123,13 +118,12 @@ private void init( final GL3 gl ) public void setColor( final Color color ) { - this.color = color; + color.getComponents( carr ); } public void setSelectionColor( final Color selectionColor ) { - this.selectionColor = selectionColor; - + selectionColor.getComponents( scarr ); } public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm, final boolean isSelected ) @@ -143,10 +137,8 @@ public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm, final b prog.getUniformMatrix4f( "pvm" ).set( pvm ); prog.getUniformMatrix4f( "vm" ).set( vm ); prog.getUniformMatrix3f( "itvm" ).set( itvm.get3x3( new Matrix3f() ) ); - color.getComponents( carr ); prog.getUniform4f( "ObjectColor" ).set( carr[ 0 ], carr[ 1 ], carr[ 2 ], carr[ 3 ] ); prog.getUniform1f( "IsSelected" ).set( isSelected ? 1f : 0f ); - selectionColor.getComponents( scarr ); prog.getUniform4f( "SelectionColor" ).set( scarr[ 0 ], scarr[ 1 ], scarr[ 2 ], scarr[ 3 ] ); prog.setUniforms( context ); prog.use( context ); From a0ecff558fe58101a05920cd2c4ba903b23c0abf Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:39:29 +0200 Subject: [PATCH 189/263] Exposes the BvvHandler of a TrackMate BVV view. --- .../trackmate/visualization/bvv/TrackMateBVV.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index 0b2c8c3b8..c892662b7 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -60,6 +60,17 @@ public TrackMateBVV( final Model model, final SelectionModel selectionModel, fin selectionModel.addSelectionChangeListener( e -> refresh() ); } + /** + * Returns the {@link BvvHandle} that contains this view. Returns + * null if this view has not been rendered yet. + * + * @return the BVV handle, or null. + */ + public BvvHandle getBvvHandle() + { + return handle; + } + @Override public void render() { From 3bf50b1ab2138d2341eb82834799ac3dc96b227d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:39:48 +0200 Subject: [PATCH 190/263] Set frame title of TrackMate BVV views. --- .../java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java index 11b0cd245..ba1b34aa0 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -53,6 +53,7 @@ public static final < T extends Type< T > > BvvHandle createViewer( final ImageP .renderWidth( 1024 ) .renderHeight( 1024 ) .preferredSize( 512, 512 ) + .frameTitle( "3D view " + imp.getShortTitle() ) .sourceTransform( cal ) ); source.setDisplayRange( imp.getDisplayRangeMin(), imp.getDisplayRangeMax() ); if ( imp.getLuts().length > 0 ) @@ -80,6 +81,7 @@ public static final < T extends Type< T > > BvvHandle createViewer( final ImageP .renderWidth( 1024 ) .renderHeight( 1024 ) .preferredSize( 512, 512 ) + .frameTitle( "3D view " + imp.getShortTitle() ) .sourceTransform( cal ) ); h = source.getBvvHandle(); } From 0bbb6e624abe66127231d4ebb991ebc7b0c28d46 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:43:37 +0200 Subject: [PATCH 191/263] Position the BVV window more or less next to the GUI. --- .../trackmate/gui/wizard/TrackMateWizardSequence.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index 511cda3ef..e7ca0e42f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -38,6 +38,7 @@ import javax.swing.JRootPane; import javax.swing.SwingUtilities; +import bvv.util.BvvHandle; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; @@ -52,6 +53,7 @@ import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase; import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.features.ModelFeatureUpdater; +import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.components.ConfigurationPanel; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.LogPanel; @@ -494,7 +496,12 @@ public void run() final Model model = trackmate.getModel(); final ImagePlus imp = trackmate.getSettings().imp; if ( imp != null ) - new TrackMateBVV<>( model, selectionModel, imp, displaySettings ).render(); + { + final TrackMateBVV< ? > tbvv = new TrackMateBVV<>( model, selectionModel, imp, displaySettings ); + tbvv.render(); + final BvvHandle bvvHandle = tbvv.getBvvHandle(); + GuiUtils.positionWindow( SwingUtilities.getWindowAncestor( bvvHandle.getViewerPanel() ), c ); + } } finally { From 70b968af189b9ebb51812a256ad240b2868f1b03 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:44:06 +0200 Subject: [PATCH 192/263] Better return signature for mesh to spot mesh method. --- .../fiji/plugin/trackmate/detection/SpotMeshUtils.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 92c9f8c8b..dc8683583 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -119,6 +119,10 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > final RealInterval bbi = boundingBoxes.get( i ); final Mesh meshi = meshes.get( i ); + /* + * FIXME revise this. Improper and incorrect. + */ + // Can we put it inside another? for ( int j = i + 1; j < meshes.size(); j++ ) { @@ -143,7 +147,7 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > final double[] origin = interval.minAsDoubleArray(); for ( final Mesh mesh : out ) { - final Spot spot = meshToSpotMesh( + final SpotMesh spot = meshToSpotMesh( mesh, simplify, calibration, @@ -279,7 +283,7 @@ private static < S extends RealType< S > > Spot regionToSpotMesh( * image). * @return */ - public static < S extends RealType< S > > Spot meshToSpotMesh( + public static < S extends RealType< S > > SpotMesh meshToSpotMesh( final Mesh mesh, final boolean simplify, final double[] calibration, From 27eee1dede86441b134db510f23a74d11391bd6e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:44:25 +0200 Subject: [PATCH 193/263] Make sure we have a SpotMesh built with all normals computed. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 9692b30e4..2904cfecf 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -53,12 +53,17 @@ public SpotMesh( * @param mesh */ public SpotMesh( - final Mesh mesh, + final Mesh m, final double quality, final String name ) { // Dummy coordinates and radius. super( 0., 0., 0., 0., quality, name ); + + // Compute triangles and vertices normals. + final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() ); + Meshes.calculateNormals( m, mesh ); + this.mesh = mesh; final RealPoint center = Meshes.center( mesh ); From 708c9d961f7bf6a23c7192c91baba0a236849bb8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:44:44 +0200 Subject: [PATCH 194/263] Tweak the Process2DZ detector. --- .../trackmate/detection/Process2DZ.java | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index 72741b574..bf2b67d25 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Settings; @@ -63,8 +64,8 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > * Creates a new {@link Process2DZ} detector. * * @param img - * the input data. Must be 3D and the 3 dimensions must be X, Y - * and Z. + * the input data. Must be 3D (plus possible channels) and the 3 + * dimensions must be X, Y and Z. * @param interval * the interval in the input data to process. Must have the same * number of dimensions that the input data. @@ -107,6 +108,11 @@ public boolean process() { spots = null; + /* + * Segment and track as a 2D+T image with the specified detector and + * settings. + */ + // Make the final single T 3D image, a 2D + T image final by making Z->T final IntervalView< T > cropped = Views.interval( img, interval ); final ImagePlus imp = ImageJFunctions.wrap( cropped, null ); @@ -131,6 +137,11 @@ public boolean process() // Get 2D+T masks final ImagePlus lblImp = LabelImgExporter.createLabelImagePlus( trackmate, false, true, false ); + /* + * Exposes tracked labels as a 3D image and segment them again with + * label image detector. + */ + // Back to a 3D single time-point image. lblImp.setDimensions( lblImp.getNChannels(), lblImp.getNFrames(), lblImp.getNSlices() ); @@ -146,32 +157,30 @@ public boolean process() final List< Spot > results = detector.getResult(); spots = new ArrayList<>( results.size() ); + // To read the label value (=trackID) later. final RandomAccess< T > ra = lblImg.randomAccess(); final TrackModel tm = trackmate.getModel().getTrackModel(); for ( final Spot spot : results ) { - if ( !spot.getClass().isAssignableFrom( SpotMesh.class ) ) - continue; /* * Smooth spot? */ final Spot newSpot; - if ( simplify ) + if ( !simplify || !spot.getClass().isAssignableFrom( SpotMesh.class ) ) + { + newSpot = spot; + } + else { - final SpotMesh sm = ( SpotMesh ) spot; final BufferMesh out = TaubinSmoothing.smooth( TranslateMesh.translate( sm.getMesh(), sm ) ); newSpot = SpotMeshUtils.meshToSpotMesh( out, simplify, new double[] { 1., 1., 1. }, null, new double[] { 0., 0., 0. } ); if ( newSpot == null ) continue; } - else - { - newSpot = spot; - } /* * Try to get quality from the tracks resulting from the 2D+T image. @@ -185,11 +194,20 @@ public boolean process() final int trackID = ( int ) ra.get().getRealDouble() - 1; // Average quality from the corresponding track. - /* - * FIXME: Does not work if zmin in the interval is not 0. Probably - * because the spots do not 'touch' the right label. - */ - final double avgQuality = tm.trackSpots( trackID ).stream().mapToDouble( s -> s.getFeature( Spot.QUALITY ).doubleValue() ).average().getAsDouble(); + final Set< Spot > trackSpots = tm.trackSpots( trackID ); + final double avgQuality; + if ( trackSpots != null ) + { + avgQuality = trackSpots.stream() + .mapToDouble( s -> s.getFeature( Spot.QUALITY ).doubleValue() ) + .average() + .getAsDouble(); + } + else + { + // default if something goes wrong. + avgQuality = spot.getFeature( Spot.QUALITY ); + } // Pass quality to new spot. newSpot.putFeature( Spot.QUALITY, Double.valueOf( avgQuality ) ); From 470612099d3e515ac5307f90305e953d4b0076d0 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Jul 2023 18:10:14 +0200 Subject: [PATCH 195/263] Update to non-SNAPSHOT version of bigvolumeviewer. This version is deployed on maven.imagej.net. Tobias made a special branch for TrackMate, and the difference I could identify is the presence of a method that returns the camera view. See the diff in TrackMateBVV. --- pom.xml | 2 +- .../gui/wizard/TrackMateWizardSequence.java | 2 +- .../trackmate/visualization/bvv/BVVUtils.java | 8 ++++---- .../trackmate/visualization/bvv/StupidMesh.java | 10 +++++----- .../visualization/bvv/TrackMateBVV.java | 8 +++++--- .../plugin/trackmate/mesh/MeshPlayground.java | 16 +++++++++------- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/pom.xml b/pom.xml index be854968c..63521db59 100644 --- a/pom.xml +++ b/pom.xml @@ -188,7 +188,7 @@ sc.fiji bigvolumeviewer - 0.2.1-SNAPSHOT + 0.3.1 org.jogamp.jogl diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index e7ca0e42f..bc41be9d8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -38,7 +38,7 @@ import javax.swing.JRootPane; import javax.swing.SwingUtilities; -import bvv.util.BvvHandle; +import bvv.vistools.BvvHandle; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java index ba1b34aa0..61d6c848a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -1,9 +1,9 @@ package fiji.plugin.trackmate.visualization.bvv; -import bvv.util.Bvv; -import bvv.util.BvvFunctions; -import bvv.util.BvvHandle; -import bvv.util.BvvSource; +import bvv.vistools.Bvv; +import bvv.vistools.BvvFunctions; +import bvv.vistools.BvvHandle; +import bvv.vistools.BvvSource; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.util.TMUtils; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java index 9d45d7bff..5c6a232cc 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -43,13 +43,13 @@ import com.jogamp.opengl.GL; import com.jogamp.opengl.GL3; +import bvv.core.backend.jogl.JoglGpuContext; +import bvv.core.shadergen.DefaultShader; +import bvv.core.shadergen.Shader; +import bvv.core.shadergen.generate.Segment; +import bvv.core.shadergen.generate.SegmentTemplate; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import net.imagej.mesh.nio.BufferMesh; -import tpietzsch.backend.jogl.JoglGpuContext; -import tpietzsch.shadergen.DefaultShader; -import tpietzsch.shadergen.Shader; -import tpietzsch.shadergen.generate.Segment; -import tpietzsch.shadergen.generate.SegmentTemplate; public class StupidMesh { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index c892662b7..307c1a6f5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -13,7 +13,9 @@ import org.joml.Matrix4f; import bdv.viewer.animate.TranslationAnimator; -import bvv.util.BvvHandle; +import bvv.core.VolumeViewerPanel; +import bvv.core.util.MatrixMath; +import bvv.vistools.BvvHandle; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.SelectionModel; @@ -35,7 +37,6 @@ import net.imglib2.RealLocalizable; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.Type; -import tpietzsch.example2.VolumeViewerPanel; public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelView { @@ -80,7 +81,8 @@ public void render() if ( displaySettings.isSpotVisible() ) { final Matrix4f pvm = new Matrix4f( data.getPv() ); - final Matrix4f vm = new Matrix4f( data.getCamview() ); + Matrix4f view = MatrixMath.affine( data.getRenderTransformWorldToScreen(), new Matrix4f() ); + Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view ); final int t = data.getTimepoint(); final Iterable< Spot > it = model.getSpots().iterable( t, true ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java index f18a6d761..7e3f4e7dc 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java @@ -10,10 +10,13 @@ import org.scijava.ui.behaviour.io.InputTriggerConfig; import org.scijava.ui.behaviour.util.Actions; -import bvv.util.Bvv; -import bvv.util.BvvFunctions; -import bvv.util.BvvSource; +import bvv.core.VolumeViewerPanel; +import bvv.core.util.MatrixMath; +import bvv.vistools.Bvv; +import bvv.vistools.BvvFunctions; +import bvv.vistools.BvvSource; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.bvv.StupidMesh; import ij.IJ; import ij.ImagePlus; import net.imagej.ImgPlus; @@ -26,8 +29,6 @@ import net.imglib2.img.display.imagej.ImgPlusViews; import net.imglib2.type.Type; import net.imglib2.type.numeric.ARGBType; -import tpietzsch.example2.VolumeViewerPanel; -import tpietzsch.scene.mesh.StupidMesh; public class MeshPlayground { @@ -67,8 +68,9 @@ public static < T extends Type< T > > void main( final String[] args ) if ( showMeshes.get() ) { final Matrix4f pvm = new Matrix4f( data.getPv() ); - final Matrix4f vm = new Matrix4f( data.getCamview() ); - meshes.forEach( mesh -> mesh.draw( gl, pvm, vm ) ); + Matrix4f view = MatrixMath.affine( data.getRenderTransformWorldToScreen(), new Matrix4f() ); + Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view ); + meshes.forEach( mesh -> mesh.draw( gl, pvm, vm, false ) ); } } ); From 00b889d674f923816e941bba0b606370910290e7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Jul 2023 18:34:28 +0200 Subject: [PATCH 196/263] WIP: Trying to implement transparency of meshes. Right now we don't "see through" the meshes. They simply get darker. --- .../trackmate/visualization/bvv/StupidMesh.java | 12 +++++++----- .../trackmate/visualization/bvv/TrackMateBVV.java | 5 +++-- .../fiji/plugin/trackmate/visualization/bvv/mesh.fp | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java index 5c6a232cc..89a85bd65 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -116,14 +116,16 @@ private void init( final GL3 gl ) gl.glBindVertexArray( 0 ); } - public void setColor( final Color color ) + public void setColor( final Color color, float alpha ) { color.getComponents( carr ); + carr[ 3 ] = alpha; } - public void setSelectionColor( final Color selectionColor ) + public void setSelectionColor( final Color selectionColor, float alpha ) { selectionColor.getComponents( scarr ); + scarr[ 3 ] = alpha; } public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm, final boolean isSelected ) @@ -144,9 +146,9 @@ public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm, final b prog.use( context ); gl.glBindVertexArray( vao ); -// gl.glEnable( GL.GL_CULL_FACE ); -// gl.glCullFace( GL.GL_BACK ); -// gl.glFrontFace( GL.GL_CCW ); + gl.glEnable( GL.GL_CULL_FACE ); + gl.glCullFace( GL.GL_BACK ); + gl.glFrontFace( GL.GL_CCW ); gl.glDrawElements( GL_TRIANGLES, ( int ) mesh.triangles().size() * 3, GL_UNSIGNED_INT, 0 ); gl.glBindVertexArray( 0 ); } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index 307c1a6f5..82020e27f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -194,8 +194,9 @@ private void updateColor() continue; final Color color = spotColorGenerator.color( entry.getKey() ); - sm.setColor( color ); - sm.setSelectionColor( displaySettings.getHighlightColor() ); + float alpha = ( float ) displaySettings.getSpotTransparencyAlpha(); + sm.setColor( color, alpha ); + sm.setSelectionColor( displaySettings.getHighlightColor(), alpha ); } refresh(); } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp index 9eff156d2..fb732ddd4 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp @@ -39,10 +39,10 @@ void main() vec3 l2 = phong( norm, viewDir, lightDir2, lightColor2, 32, 0.5 ); if (IsSelected > 0.5) { - fragColor = vec4( (ambient + l1 + l2), 1) * SelectionColor; + fragColor = vec4( (ambient + l1 + l2), SelectionColor[3]) * SelectionColor; } else { float it = dot(norm, viewDir); - fragColor = vec4( it * (ambient + l1 + l2), 1) * ObjectColor + (1-it) * vec4(1,1,1,1); + fragColor = vec4( it * (ambient + l1 + l2), 1) * ObjectColor + (1-it) * vec4(1,1,1,ObjectColor[3]); } } From 829de7e99fb774c7e7b319a496786d1e69a410a7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:40:50 +0200 Subject: [PATCH 197/263] Fix cast error ?! How did it get there unnoticed? --- src/main/java/fiji/plugin/trackmate/util/TMUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index 786dec60f..6c0f31a4e 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -136,9 +136,10 @@ else if ( obj instanceof Logger ) * Wraps an IJ {@link ImagePlus} in an imglib2 {@link ImgPlus}, abinding to * a returned type. */ + @SuppressWarnings( "unchecked" ) public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) { - return ImagePlusAdapter.wrapImgPlus( imp ); + return ( ImgPlus< T > ) ImagePlusAdapter.wrapImgPlus( imp ); } /** From 19042faea3a73d135f891dbbe91769896412feb1 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 21:55:39 +0200 Subject: [PATCH 198/263] Spot is now an interface, with 3 derived class. Spot -> the main interface, used by default in trackers. Define basic methods to get and store feature values. SpotBase -> Plain spots, like for TrackMate v<7 SpotRoi -> spot has a polygon as a contour in 2D SpotMesh -> spot has a 3D mesh More elegant and extensible to app consuming TrackMate trackers with special objects. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 2 +- .../trackmate/visualization/hyperstack/ModelEditActions.java | 3 ++- .../java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 2904cfecf..b43a8426d 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -50,7 +50,7 @@ public SpotMesh( * * @param quality * @param name - * @param mesh + * @param m */ public SpotMesh( final Mesh m, diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java index 50173291c..2d7731c2f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java @@ -38,6 +38,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotShape; import fiji.plugin.trackmate.detection.semiauto.SemiAutoTracker; import fiji.plugin.trackmate.util.ModelTools; @@ -97,7 +98,7 @@ private Spot makeSpot( Point mouseLocation ) SwingUtilities.convertPointFromScreen( mouseLocation, canvas ); } final double[] calibration = TMUtils.getSpatialCalibration( imp ); - return new Spot( + return new SpotBase( ( -0.5 + canvas.offScreenXD( mouseLocation.x ) ) * calibration[ 0 ], ( -0.5 + canvas.offScreenYD( mouseLocation.y ) ) * calibration[ 1 ], ( imp.getSlice() - 1 ) * calibration[ 2 ], diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java index 39e1add05..52a32b51c 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java @@ -14,8 +14,8 @@ public static void main( final String[] args ) { ImageJ.main( args ); - final String filePath = "samples/CElegans3D-smoothed-mask-orig.tif"; -// final String filePath = "samples/Celegans-5pc-17timepoints.tif"; +// final String filePath = "samples/CElegans3D-smoothed-mask-orig-t7.tif"; + final String filePath = "samples/Celegans-5pc-17timepoints.tif"; final ImagePlus imp = IJ.openImage( filePath ); imp.show(); From 31468ecdde147e8f8ce7d5595730d4c403659549 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 17:52:31 +0200 Subject: [PATCH 199/263] Rework the 2D and 3D detection utils. We have now 3D meshes that we get via the marching cubes. This algorithm behaves slightly differently for grayscale + threshold and mask images, so we have to implement that in TrackMate. First: split the shape-related methods of MaskUtils in two utility classes SpotRoiUtils and SpotMeshUtils so as to avoid having a single gigantic class. With grayscale thresholded image, the marching cube algorithm can return nice, smooth, interpolated meshes, which is what we want in a biological context. So there is now in SpotMeshUtils a method that exploits this in the following manner: The grayscale marching-cube algorithm is used to create one big mesh from the source image. It is then split in connected-components to create single spot objects. However, to deal with possible holes in objects, meshes are possibly re-merged based on full inclusion of their bounding-box. For instance, a hollow sphere would be represented by two connected-components, yielding two meshes. But because the small one isincluded in the big one, they are merged in this method so that the hole is properly included in the shape representation. When dealing with masks as inputs, we want to generate a mesh that can retrieve the exact pixel content of the mask when iterated. So a separate method convert the mask into a label image, and each of its connected-component is treated separately to generate a mesh. We will use the grayscale marching-cube algorithm, using a threshold value of 0.5 on the bit-masks resulting from connected-component analysis of the label image generated from the mask. --- .../fiji/plugin/trackmate/detection/MaskUtils.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index ae22a2a39..d41c20459 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -412,11 +412,11 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > * @return a list of spots, with ROI. */ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > fromMaskWithROI( - final RandomAccessible< T > input, - final Interval interval, - final double[] calibration, - final boolean simplify, - final int numThreads, + final RandomAccessible< T > input, + final Interval interval, + final double[] calibration, + final boolean simplify, + final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { final ImgLabeling< Integer, IntType > labeling = toLabeling( From a2c4252774e87565b09372dac5c2fce8f253c9db Mon Sep 17 00:00:00 2001 From: tpietzsch Date: Tue, 18 Apr 2023 15:54:48 -0500 Subject: [PATCH 200/263] WIP show meshes in bvv. requires 'mesh' branch of bvv --- .../java/fiji/plugin/trackmate/mesh/MeshPlayground.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java index 7e3f4e7dc..d0a800c14 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java @@ -55,7 +55,7 @@ public static < T extends Type< T > > void main( final String[] args ) final List< StupidMesh > meshes = new ArrayList<>(); - for ( int j = 1; j <= 3; ++j) + for ( int j = 1; j <= 3; ++j ) { final String fn = String.format( "samples/mesh/CElegansMask3D_%02d.stl", j ); meshes.add( new StupidMesh( load( fn ) ) ); @@ -68,8 +68,8 @@ public static < T extends Type< T > > void main( final String[] args ) if ( showMeshes.get() ) { final Matrix4f pvm = new Matrix4f( data.getPv() ); - Matrix4f view = MatrixMath.affine( data.getRenderTransformWorldToScreen(), new Matrix4f() ); - Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view ); + final Matrix4f view = MatrixMath.affine( data.getRenderTransformWorldToScreen(), new Matrix4f() ); + final Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view ); meshes.forEach( mesh -> mesh.draw( gl, pvm, vm, false ) ); } } ); From 3a985a9ee37f31dfc4549e212b0110831620d3bd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 1 Sep 2023 10:44:23 +0200 Subject: [PATCH 201/263] Depend on imglib2-mesh preview, not on net-imagej-mesh. Still a WIP: several utility methods in TrackMate about meshes are now in the imglib2-mesh dep. --- pom.xml | 15 +- .../java/fiji/plugin/trackmate/SpotMesh.java | 19 +- .../trackmate/action/MeshSeriesExporter.java | 14 +- .../trackmate/detection/Process2DZ.java | 6 +- .../trackmate/detection/SpotMeshUtils.java | 10 +- .../spot/Spot3DFitEllipsoidAnalyzer.java | 4 +- .../features/spot/Spot3DShapeAnalyzer.java | 21 +- .../fiji/plugin/trackmate/io/TmXmlReader.java | 10 +- .../fiji/plugin/trackmate/io/TmXmlWriter.java | 10 +- .../trackmate/util/mesh/EllipsoidFitter.java | 310 ------------------ .../trackmate/util/mesh/SpotMeshCursor.java | 2 +- .../trackmate/visualization/bvv/BVVUtils.java | 8 +- .../visualization/bvv/Icosahedron.java | 10 +- .../visualization/bvv/StupidMesh.java | 6 +- .../hyperstack/PaintSpotMesh.java | 4 +- .../plugin/trackmate/mesh/DebugZSlicer.java | 6 +- .../plugin/trackmate/mesh/DefaultMesh.java | 2 +- .../plugin/trackmate/mesh/Demo3DMesh.java | 30 +- .../trackmate/mesh/ExportMeshForDemo.java | 5 +- .../plugin/trackmate/mesh/MeshPlayground.java | 12 +- .../trackmate/mesh/TestEllipsoidFit.java | 8 +- 21 files changed, 92 insertions(+), 420 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java diff --git a/pom.xml b/pom.xml index 63521db59..8ef186bfd 100644 --- a/pom.xml +++ b/pom.xml @@ -172,14 +172,9 @@ imagej-common - net.imagej - imagej-mesh - 0.8.2-SNAPSHOT - - - net.imagej - imagej-mesh-io - 0.1.3-SNAPSHOT + net.imglib2 + imglib2-mesh + 1.0.0-SNAPSHOT net.imagej @@ -242,6 +237,10 @@ + + com.google.guava + guava + com.github.vlsi.mxgraph jgraphx diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index b43a8426d..a469de7c3 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -6,19 +6,18 @@ import java.util.stream.Collectors; import fiji.plugin.trackmate.util.mesh.SpotMeshIterable; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.Triangles; -import net.imagej.mesh.Vertices; -import net.imagej.mesh.alg.zslicer.RamerDouglasPeucker; -import net.imagej.mesh.alg.zslicer.Slice; -import net.imagej.mesh.alg.zslicer.ZSlicer; -import net.imagej.mesh.nio.BufferMesh; import net.imglib2.IterableInterval; import net.imglib2.RandomAccessible; import net.imglib2.RealInterval; import net.imglib2.RealLocalizable; import net.imglib2.RealPoint; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.alg.zslicer.RamerDouglasPeucker; +import net.imglib2.mesh.alg.zslicer.Slice; +import net.imglib2.mesh.alg.zslicer.ZSlicer; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.Vertices; +import net.imglib2.mesh.obj.nio.BufferMesh; import net.imglib2.type.numeric.RealType; import net.imglib2.util.Intervals; @@ -207,7 +206,7 @@ public static double volume( final Mesh mesh ) { final Vertices vertices = mesh.vertices(); - final Triangles triangles = mesh.triangles(); + final net.imglib2.mesh.obj.Triangles triangles = mesh.triangles(); final long nTriangles = triangles.size(); double sum = 0.; for ( long t = 0; t < nTriangles; t++ ) @@ -311,7 +310,7 @@ public String toString() str.append( String.format( "\n%5d: %7.2f %7.2f %7.2f", i, vertices.x( i ), vertices.y( i ), vertices.z( i ) ) ); - final Triangles triangles = mesh.triangles(); + final net.imglib2.mesh.obj.Triangles triangles = mesh.triangles(); final long nTriangles = triangles.size(); str.append( "\nF (" + nTriangles + "):" ); for ( long i = 0; i < nTriangles; i++ ) diff --git a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java index abe3a263c..054b723b2 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java @@ -43,11 +43,11 @@ import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.io.IOUtils; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.io.ply.PLYMeshIO; -import net.imagej.mesh.nio.BufferMesh; -import net.imagej.mesh.obj.transform.TranslateMesh; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.io.ply.PLYMeshIO; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.nio.BufferMesh; +import net.imglib2.mesh.obj.transform.TranslateMesh; public class MeshSeriesExporter extends AbstractTMAction { @@ -106,8 +106,6 @@ public static void exportMeshesToFileSeries( final SpotCollection spots, final F final File folder = new File( folderName ); folder.mkdirs(); - final PLYMeshIO io = new PLYMeshIO(); - final NavigableSet< Integer > frames = spots.keySet(); for ( final Integer frame : frames ) { @@ -128,7 +126,7 @@ public static void exportMeshesToFileSeries( final SpotCollection spots, final F Meshes.calculateNormals( merged, mesh ); try { - io.save( mesh, targetFile.getAbsolutePath() ); + PLYMeshIO.save( mesh, targetFile.getAbsolutePath() ); } catch ( final IOException e ) { diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index bf2b67d25..62a313bd7 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -14,14 +14,14 @@ import fiji.plugin.trackmate.util.TMUtils; import ij.ImagePlus; import net.imagej.ImgPlus; -import net.imagej.mesh.alg.TaubinSmoothing; -import net.imagej.mesh.nio.BufferMesh; -import net.imagej.mesh.obj.transform.TranslateMesh; import net.imglib2.Interval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.algorithm.MultiThreadedBenchmarkAlgorithm; import net.imglib2.img.display.imagej.ImageJFunctions; +import net.imglib2.mesh.alg.TaubinSmoothing; +import net.imglib2.mesh.obj.nio.BufferMesh; +import net.imglib2.mesh.obj.transform.TranslateMesh; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; import net.imglib2.view.IntervalView; diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index dc8683583..7e9d4825e 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -7,16 +7,16 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.MeshConnectedComponents; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.Vertices; -import net.imagej.mesh.nio.BufferMesh; import net.imglib2.Interval; import net.imglib2.IterableInterval; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealInterval; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.alg.MeshConnectedComponents; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.Vertices; +import net.imglib2.mesh.obj.nio.BufferMesh; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java index ba363a07a..e30e36007 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java @@ -22,9 +22,9 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; -import fiji.plugin.trackmate.util.mesh.EllipsoidFitter; -import fiji.plugin.trackmate.util.mesh.EllipsoidFitter.EllipsoidFit; import net.imglib2.RealLocalizable; +import net.imglib2.mesh.alg.EllipsoidFitter; +import net.imglib2.mesh.alg.EllipsoidFitter.EllipsoidFit; import net.imglib2.type.numeric.RealType; public class Spot3DFitEllipsoidAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java index 64aa88096..363dfd9e2 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java @@ -23,10 +23,9 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.ops.geom.geom3d.DefaultConvexHull3D; -import net.imagej.ops.geom.geom3d.DefaultSurfaceArea; +import net.imglib2.mesh.MeshShapeDescriptors; +import net.imglib2.mesh.alg.hull.ConvexHull; +import net.imglib2.mesh.obj.naive.NaiveDoubleMesh; import net.imglib2.type.numeric.RealType; public class Spot3DShapeAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > @@ -34,15 +33,9 @@ public class Spot3DShapeAnalyzer< T extends RealType< T > > extends AbstractSpot private final boolean is3D; - private final DefaultConvexHull3D convexHull; - - private final DefaultSurfaceArea surfaceArea; - public Spot3DShapeAnalyzer( final boolean is3D ) { this.is3D = is3D; - this.convexHull = new DefaultConvexHull3D(); - this.surfaceArea = new DefaultSurfaceArea(); } @Override @@ -58,13 +51,13 @@ public void process( final Spot spot ) if ( spot instanceof SpotMesh ) { final SpotMesh sm = ( SpotMesh ) spot; - final Mesh ch = convexHull.calculate( sm.getMesh() ); + final NaiveDoubleMesh ch = ConvexHull.calculate( sm.getMesh() ); volume = sm.volume(); - final double volumeCH = Meshes.volume( ch ); + final double volumeCH = MeshShapeDescriptors.volume( ch ); solidity = volume / volumeCH; - sa = surfaceArea.calculate( sm.getMesh() ).get(); - final double saCH = surfaceArea.calculate( ch ).get(); + sa = MeshShapeDescriptors.surfaceArea( sm.getMesh() ); + final double saCH = MeshShapeDescriptors.surfaceArea( ch ); convexity = sa / saCH; final double sphereArea = Math.pow( Math.PI, 1. / 3. ) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 1f696dee8..243c6f936 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -92,7 +92,6 @@ import static fiji.plugin.trackmate.io.TmXmlKeys.TRACK_ID_ELEMENT_KEY; import static fiji.plugin.trackmate.io.TmXmlKeys.TRACK_NAME_ATTRIBUTE_NAME; import static fiji.plugin.trackmate.io.TmXmlWriter.MESH_FILE_EXTENSION; -import static fiji.plugin.trackmate.io.TmXmlWriter.PLY_MESH_IO; import static fiji.plugin.trackmate.tracking.TrackerKeys.XML_ATTRIBUTE_TRACKER_NAME; import java.io.File; @@ -156,9 +155,10 @@ import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; import ij.IJ; import ij.ImagePlus; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.Meshes; -import net.imagej.mesh.nio.BufferMesh; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.io.ply.PLYMeshIO; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.nio.BufferMesh; public class TmXmlReader { @@ -962,7 +962,7 @@ private SpotCollection getSpots( final Element modelElement ) // Deserialize mesh. try { - final Mesh m = PLY_MESH_IO.open( zipFile.getInputStream( entry ) ); + final Mesh m = PLYMeshIO.open( zipFile.getInputStream( entry ) ); final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() ); Meshes.calculateNormals( m, mesh ); diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index 4a7f42c28..af9340672 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -133,15 +133,13 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; import gnu.trove.map.hash.TIntIntHashMap; import gnu.trove.procedure.TIntIntProcedure; -import net.imagej.mesh.Mesh; -import net.imagej.mesh.io.ply.PLYMeshIO; -import net.imagej.mesh.obj.transform.TranslateMesh; +import net.imglib2.mesh.io.ply.PLYMeshIO; +import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.obj.transform.TranslateMesh; public class TmXmlWriter { - static final PLYMeshIO PLY_MESH_IO = new PLYMeshIO(); - static final String MESH_FILE_EXTENSION = ".meshes"; /** Zip compression level (0-9) */ @@ -791,7 +789,7 @@ protected void writeSpotMeshes( final Iterable< Spot > spots ) final SpotMesh sm = ( SpotMesh ) spot; final Mesh mesh = sm.getMesh(); final Mesh translated = TranslateMesh.translate( mesh, spot ); - final byte[] bs = PLY_MESH_IO.writeBinary( translated ); + final byte[] bs = PLYMeshIO.writeBinary( translated ); final String entryName = spot.ID() + ".ply"; zos.putNextEntry( new ZipEntry( entryName ) ); diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java b/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java deleted file mode 100644 index 6f0deb46d..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java +++ /dev/null @@ -1,310 +0,0 @@ -package fiji.plugin.trackmate.util.mesh; - -import org.apache.commons.math3.linear.Array2DRowRealMatrix; -import org.apache.commons.math3.linear.ArrayRealVector; -import org.apache.commons.math3.linear.DecompositionSolver; -import org.apache.commons.math3.linear.EigenDecomposition; -import org.apache.commons.math3.linear.MatrixUtils; -import org.apache.commons.math3.linear.RealMatrix; -import org.apache.commons.math3.linear.RealVector; -import org.apache.commons.math3.linear.SingularValueDecomposition; - -import net.imagej.mesh.Mesh; -import net.imagej.ops.geom.geom3d.DefaultConvexHull3D; -import net.imglib2.RealLocalizable; -import net.imglib2.RealPoint; -import net.imglib2.util.Util; - -/** - * Fit an ellipsoid to the convex-Hull of a 3D mesh. - *

- * Adapted from Yury Petrov's Ellipsoid - * Fit MATLAB function and KalebKE ellipsoidfit. - * - * @author Jean-Yves Tinevez - */ -public class EllipsoidFitter -{ - - /** - * The results of fitting an ellpsoid to a mesh or a collection of points. - */ - public static final class EllipsoidFit - { - /** The ellipsoid center. */ - public final RealLocalizable center; - - /** The eigenvector of the smallest axis of the ellipsoid. */ - public final RealLocalizable ev1; - - /** The eigenvector of the middle axis of the ellipsoid. */ - public final RealLocalizable ev2; - - /** The eigenvector of the largest axis of the ellipsoid. */ - public final RealLocalizable ev3; - - /** The radius of the smallest axis of the ellipsoid. */ - public final double r1; - - /** The radius of the middle axis of the ellipsoid. */ - public final double r2; - - /** The radius of the largest axis of the ellipsoid. */ - public final double r3; - - private EllipsoidFit( final RealLocalizable center, - final RealLocalizable ev1, - final RealLocalizable ev2, - final RealLocalizable ev3, - final double r1, - final double r2, - final double r3 ) - { - this.center = center; - this.ev1 = ev1; - this.ev2 = ev2; - this.ev3 = ev3; - this.r1 = r1; - this.r2 = r2; - this.r3 = r3; - } - - @Override - public String toString() - { - final StringBuilder str = new StringBuilder( super.toString() ); - str.append( "\n - center: " + Util.printCoordinates( center ) ); - str.append( String.format( "\n - axis 1: radius = %.2f, vector = %s", r1, ev1 ) ); - str.append( String.format( "\n - axis 2: radius = %.2f, vector = %s", r2, ev2 ) ); - str.append( String.format( "\n - axis 3: radius = %.2f, vector = %s", r3, ev3 ) ); - return str.toString(); - } - } - - private static final DefaultConvexHull3D cHull = new DefaultConvexHull3D(); - - /** - * Fit an ellipsoid to the convex-Hull of a 3D mesh. - * - * @param mesh - * the mesh to fit. - * @return the fit results. - */ - public static final EllipsoidFit fit( final Mesh mesh ) - { - final Mesh ch = cHull.calculate( mesh ); - return fitOnConvexHull( ch ); - } - - /** - * Fit an ellipsoid to a 3D mesh, assuming it is the convex-Hull. - * - * @param mesh - * the convex-Hull of the mesh to fit. - * @return the fit results. - */ - public static final EllipsoidFit fitOnConvexHull( final Mesh mesh ) - { - return fit( mesh.vertices(), ( int ) mesh.vertices().size() ); - } - - /** - * Fit an ellipsoid to a collection of 3D points. - * - * @param points - * an iterable over the points to fit. - * @param nPoints - * the number of points to include in the fit. The fit will - * consider at most the first nPoints of the iterable, or all the - * points in the iterable, whatever comes first. - * @return the fit results. - */ - public static EllipsoidFit fit( final Iterable< ? extends RealLocalizable > points, final int nPoints ) - { - final RealVector V = solve( points, nPoints ); - - // To algebraix form. - final RealMatrix A = toAlgebraicForm( V ); - - // Find the center of the ellipsoid. - final RealVector C = findCenter( A ); - - // Translate the algebraic form of the ellipsoid to the center. - final RealMatrix R = translateToCenter( C, A ); - - // Ellipsoid eigenvectors and eigenvalues. - final EllipsoidFit fit = getFit( R, C ); - return fit; - } - - private static EllipsoidFit getFit( final RealMatrix R, final RealVector C ) - { - final RealMatrix subr = R.getSubMatrix( 0, 2, 0, 2 ); - - // subr[i][j] = subr[i][j] / -r[3][3]). - final double divr = -R.getEntry( 3, 3 ); - for ( int i = 0; i < subr.getRowDimension(); i++ ) - for ( int j = 0; j < subr.getRowDimension(); j++ ) - subr.setEntry( i, j, subr.getEntry( i, j ) / divr ); - - // Get the eigenvalues and eigenvectors. - final EigenDecomposition ed = new EigenDecomposition( subr ); - final double[] eigenvalues = ed.getRealEigenvalues(); - final RealVector e1 = ed.getEigenvector( 0 ); - final RealVector e2 = ed.getEigenvector( 1 ); - final RealVector e3 = ed.getEigenvector( 2 ); - - // Semi-axis length (radius). - final RealVector SAL = new ArrayRealVector( eigenvalues.length ); - for ( int i = 0; i < eigenvalues.length; i++ ) - SAL.setEntry( i, Math.sqrt( 1. / eigenvalues[ i ] ) ); - - // Put everything in a fit object. - final RealPoint center = new RealPoint( C.getEntry( 0 ), C.getEntry( 1 ), C.getEntry( 2 ) ); - final RealPoint ev1 = new RealPoint( e1.getEntry( 0 ), e1.getEntry( 1 ), e1.getEntry( 2 ) ); - final RealPoint ev2 = new RealPoint( e2.getEntry( 0 ), e2.getEntry( 1 ), e2.getEntry( 2 ) ); - final RealPoint ev3 = new RealPoint( e3.getEntry( 0 ), e3.getEntry( 1 ), e3.getEntry( 2 ) ); - return new EllipsoidFit( center, ev1, ev2, ev3, SAL.getEntry( 0 ), SAL.getEntry( 1 ), SAL.getEntry( 2 ) ); - } - - /** - * Translate the algebraic form of the ellipsoid to the center. - * - * @param C - * the center of the ellipsoid. - * @param A - * the ellipsoid matrix. - * @return the center translated form of the algebraic ellipsoid. - */ - private static final RealMatrix translateToCenter( final RealVector C, final RealMatrix A ) - { - final RealMatrix T = MatrixUtils.createRealIdentityMatrix( 4 ); - final RealMatrix centerMatrix = new Array2DRowRealMatrix( 1, 3 ); - centerMatrix.setRowVector( 0, C ); - T.setSubMatrix( centerMatrix.getData(), 3, 0 ); - final RealMatrix R = T.multiply( A ).multiply( T.transpose() ); - return R; - } - - /** - * Find the center of the ellipsoid. - * - * @param a - * the algebraic from of the polynomial. - * @return a vector containing the center of the ellipsoid. - */ - private static final RealVector findCenter( final RealMatrix A ) - { - final RealMatrix subA = A.getSubMatrix( 0, 2, 0, 2 ); - - for ( int q = 0; q < subA.getRowDimension(); q++ ) - for ( int s = 0; s < subA.getColumnDimension(); s++ ) - subA.multiplyEntry( q, s, -1.0 ); - - final RealVector subV = A.getRowVector( 3 ).getSubVector( 0, 3 ); - - final DecompositionSolver solver = new SingularValueDecomposition( subA ).getSolver(); - final RealMatrix subAi = solver.getInverse(); - return subAi.operate( subV ); - } - - /** - * Solve for Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + - * 2Iz = 1. - * - * @param points - * an iterable over 3D points. - * @param nPoints - * the number of points in the iterable. - * @return - */ - private static final RealVector solve( final Iterable< ? extends RealLocalizable > points, final int nPoints ) - { - final RealMatrix M0 = new Array2DRowRealMatrix( nPoints, 9 ); - int i = 0; - for ( final RealLocalizable point : points ) - { - final double x = point.getDoublePosition( 0 ); - final double y = point.getDoublePosition( 1 ); - final double z = point.getDoublePosition( 2 ); - - final double xx = x * x; - final double yy = y * y; - final double zz = z * z; - - final double xy = 2. * x * y; - final double xz = 2. * x * z; - final double yz = 2. * y * z; - - M0.setEntry( i, 0, xx ); - M0.setEntry( i, 1, yy ); - M0.setEntry( i, 2, zz ); - M0.setEntry( i, 3, xy ); - M0.setEntry( i, 4, xz ); - M0.setEntry( i, 5, yz ); - M0.setEntry( i, 6, 2. * x ); - M0.setEntry( i, 7, 2. * y ); - M0.setEntry( i, 8, 2. * z ); - - i++; - if ( i >= nPoints ) - break; - } - final RealMatrix M; - if ( i == nPoints ) - M = M0; - else - M = M0.getSubMatrix( 0, i, 0, 9 ); - - final RealMatrix M2 = M.transpose().multiply( M ); - - final RealVector O = new ArrayRealVector( nPoints ); - O.mapAddToSelf( 1 ); - - final RealVector MO = M.transpose().operate( O ); - - final DecompositionSolver solver = new SingularValueDecomposition( M2 ).getSolver(); - final RealMatrix I = solver.getInverse(); - - final RealVector V = I.operate( MO ); - return V; - } - - /** - * Reshape the fit result vector in the shape of an algebraic matrix. - * - *

-	 * A = 		[ Ax2 	2Dxy 	2Exz 	2Gx ] 
-	 * 		[ 2Dxy 	By2 	2Fyz 	2Hy ] 
-	 * 		[ 2Exz 	2Fyz 	Cz2 	2Iz ] 
-	 * 		[ 2Gx 	2Hy 	2Iz 	-1 ] ]
-	 * 
-	 * 
-	 * @param V the fit result.
-	 * @return a new 4x4 real matrix.
-	 */
-	private static final RealMatrix toAlgebraicForm( final RealVector V )
-	{
-		final RealMatrix A = new Array2DRowRealMatrix( 4, 4 );
-
-		A.setEntry( 0, 0, V.getEntry( 0 ) );
-		A.setEntry( 0, 1, V.getEntry( 3 ) );
-		A.setEntry( 0, 2, V.getEntry( 4 ) );
-		A.setEntry( 0, 3, V.getEntry( 6 ) );
-		A.setEntry( 1, 0, V.getEntry( 3 ) );
-		A.setEntry( 1, 1, V.getEntry( 1 ) );
-		A.setEntry( 1, 2, V.getEntry( 5 ) );
-		A.setEntry( 1, 3, V.getEntry( 7 ) );
-		A.setEntry( 2, 0, V.getEntry( 4 ) );
-		A.setEntry( 2, 1, V.getEntry( 5 ) );
-		A.setEntry( 2, 2, V.getEntry( 2 ) );
-		A.setEntry( 2, 3, V.getEntry( 8 ) );
-		A.setEntry( 3, 0, V.getEntry( 6 ) );
-		A.setEntry( 3, 1, V.getEntry( 7 ) );
-		A.setEntry( 3, 2, V.getEntry( 8 ) );
-		A.setEntry( 3, 3, -1 );
-		return A;
-	}
-}
diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java
index 0c88e45ed..2b99e4567 100644
--- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java
+++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java
@@ -2,9 +2,9 @@
 
 import fiji.plugin.trackmate.SpotMesh;
 import gnu.trove.list.array.TDoubleArrayList;
-import net.imagej.mesh.alg.zslicer.Slice;
 import net.imglib2.Cursor;
 import net.imglib2.RandomAccess;
+import net.imglib2.mesh.alg.zslicer.Slice;
 
 /**
  * A {@link Cursor} that iterates over the pixels inside a mesh.
diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java
index 61d6c848a..d3ff277bd 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java
@@ -13,11 +13,11 @@
 import ij.process.LUT;
 import net.imagej.ImgPlus;
 import net.imagej.axis.Axes;
-import net.imagej.mesh.Mesh;
-import net.imagej.mesh.Meshes;
-import net.imagej.mesh.nio.BufferMesh;
-import net.imagej.mesh.obj.transform.TranslateMesh;
 import net.imglib2.img.display.imagej.ImgPlusViews;
+import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.obj.Mesh;
+import net.imglib2.mesh.obj.nio.BufferMesh;
+import net.imglib2.mesh.obj.transform.TranslateMesh;
 import net.imglib2.type.Type;
 import net.imglib2.type.numeric.ARGBType;
 
diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java
index a825b3705..d77d407d1 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java
@@ -1,12 +1,12 @@
 package fiji.plugin.trackmate.visualization.bvv;
 
 import fiji.plugin.trackmate.Spot;
-import net.imagej.mesh.Mesh;
-import net.imagej.mesh.Meshes;
-import net.imagej.mesh.Triangle;
-import net.imagej.mesh.naive.NaiveDoubleMesh;
-import net.imagej.mesh.nio.BufferMesh;
 import net.imglib2.RealLocalizable;
+import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.obj.Mesh;
+import net.imglib2.mesh.obj.Triangle;
+import net.imglib2.mesh.obj.naive.NaiveDoubleMesh;
+import net.imglib2.mesh.obj.nio.BufferMesh;
 
 /**
  * Icosahedron spheres.
diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java
index 89a85bd65..46da5742e 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java
@@ -49,7 +49,7 @@
 import bvv.core.shadergen.generate.Segment;
 import bvv.core.shadergen.generate.SegmentTemplate;
 import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings;
-import net.imagej.mesh.nio.BufferMesh;
+import net.imglib2.mesh.obj.nio.BufferMesh;
 
 public class StupidMesh
 {
@@ -116,13 +116,13 @@ private void init( final GL3 gl )
 		gl.glBindVertexArray( 0 );
 	}
 
-	public void setColor( final Color color, float alpha )
+	public void setColor( final Color color, final float alpha )
 	{
 		color.getComponents( carr );
 		carr[ 3 ] = alpha;
 	}
 
-	public void setSelectionColor( final Color selectionColor, float alpha )
+	public void setSelectionColor( final Color selectionColor, final float alpha )
 	{
 		selectionColor.getComponents( scarr );
 		scarr[ 3 ] = alpha;
diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java
index af1cc0e7d..158260da4 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java
@@ -11,9 +11,9 @@
 import fiji.plugin.trackmate.SpotMesh;
 import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings;
 import ij.ImagePlus;
-import net.imagej.mesh.alg.zslicer.Contour;
-import net.imagej.mesh.alg.zslicer.Slice;
 import net.imglib2.RealLocalizable;
+import net.imglib2.mesh.alg.zslicer.Contour;
+import net.imglib2.mesh.alg.zslicer.Slice;
 
 /**
  * Utility class to paint the {@link SpotMesh} component of spots.
diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java
index 94456b577..9d28c8e35 100644
--- a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java
+++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java
@@ -13,9 +13,9 @@
 import ij.CompositeImage;
 import ij.ImageJ;
 import ij.ImagePlus;
-import net.imagej.mesh.alg.zslicer.Contour;
-import net.imagej.mesh.alg.zslicer.Slice;
-import net.imagej.mesh.alg.zslicer.ZSlicer;
+import net.imglib2.mesh.alg.zslicer.Contour;
+import net.imglib2.mesh.alg.zslicer.Slice;
+import net.imglib2.mesh.alg.zslicer.ZSlicer;
 
 public class DebugZSlicer
 {
diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java
index 8ce097ad2..37e6f9961 100644
--- a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java
+++ b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java
@@ -16,8 +16,8 @@
 import ij.measure.Calibration;
 import net.imagej.ImgPlus;
 import net.imagej.axis.Axes;
-import net.imagej.mesh.Mesh;
 import net.imglib2.img.display.imagej.ImageJFunctions;
+import net.imglib2.mesh.obj.Mesh;
 import net.imglib2.type.logic.BitType;
 
 public class DefaultMesh
diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java
index 84b9912f9..766d92e4b 100644
--- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java
+++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java
@@ -14,21 +14,20 @@
 import ij.gui.PolygonRoi;
 import net.imagej.ImgPlus;
 import net.imagej.axis.Axes;
-import net.imagej.mesh.Mesh;
-import net.imagej.mesh.Meshes;
-import net.imagej.mesh.Vertices;
-import net.imagej.mesh.alg.zslicer.Contour;
-import net.imagej.mesh.alg.zslicer.Slice;
-import net.imagej.mesh.alg.zslicer.ZSlicer;
-import net.imagej.mesh.io.ply.PLYMeshIO;
-import net.imagej.mesh.io.stl.STLMeshIO;
-import net.imagej.mesh.naive.NaiveDoubleMesh;
-import net.imagej.mesh.naive.NaiveDoubleMesh.Triangles;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.converter.RealTypeConverters;
 import net.imglib2.img.ImgView;
 import net.imglib2.img.display.imagej.ImageJFunctions;
 import net.imglib2.img.display.imagej.ImgPlusViews;
+import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.alg.zslicer.Contour;
+import net.imglib2.mesh.alg.zslicer.Slice;
+import net.imglib2.mesh.alg.zslicer.ZSlicer;
+import net.imglib2.mesh.io.ply.PLYMeshIO;
+import net.imglib2.mesh.io.stl.STLMeshIO;
+import net.imglib2.mesh.obj.Mesh;
+import net.imglib2.mesh.obj.Vertices;
+import net.imglib2.mesh.obj.naive.NaiveDoubleMesh;
 import net.imglib2.roi.labeling.ImgLabeling;
 import net.imglib2.roi.labeling.LabelRegion;
 import net.imglib2.roi.labeling.LabelRegions;
@@ -112,8 +111,8 @@ public static void main( final String[] args )
 	static Mesh debugMesh( final long[] min, final long[] max )
 	{
 		final NaiveDoubleMesh mesh = new NaiveDoubleMesh();
-		final net.imagej.mesh.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices();
-		final Triangles triangles = mesh.triangles();
+		final net.imglib2.mesh.obj.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices();
+		final net.imglib2.mesh.obj.naive.NaiveDoubleMesh.Triangles triangles = mesh.triangles();
 
 		// Coords as X Y Z
 
@@ -198,11 +197,10 @@ private static void testIO( final Mesh mesh, final int j )
 		// Serialize to disk.
 		try
 		{
-			new STLMeshIO().save( mesh, String.format( "samples/mesh/io/STL_%02d.stl", j ) );
+			STLMeshIO.save( mesh, String.format( "samples/mesh/io/STL_%02d.stl", j ) );
 
-			final PLYMeshIO plyio = new PLYMeshIO();
-			plyio.save( mesh, String.format( "samples/mesh/io/PLY_%02d.ply", j ) );
-			final byte[] bs = plyio.writeAscii( mesh );
+			PLYMeshIO.save( mesh, String.format( "samples/mesh/io/PLY_%02d.ply", j ) );
+			final byte[] bs = PLYMeshIO.writeAscii( mesh );
 			final String str = new String( bs );
 			try (final FileWriter writer = new FileWriter(
 					String.format( "samples/mesh/io/PLYTEXT_%02d.txt", j ) ))
diff --git a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java
index 44b560382..890b4a553 100644
--- a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java
+++ b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java
@@ -12,7 +12,7 @@
 import fiji.plugin.trackmate.detection.ThresholdDetectorFactory;
 import ij.IJ;
 import ij.ImagePlus;
-import net.imagej.mesh.io.stl.STLMeshIO;
+import net.imglib2.mesh.io.stl.STLMeshIO;
 
 public class ExportMeshForDemo
 {
@@ -42,7 +42,6 @@ public static void main( final String[] args )
 				if ( !file.isDirectory() )
 					file.delete();
 
-			final STLMeshIO io = new STLMeshIO();
 			for ( final Spot spot : spots.iterable( true ) )
 			{
 				final int t = spot.getFeature( Spot.FRAME ).intValue();
@@ -51,7 +50,7 @@ public static void main( final String[] args )
 				if ( spot instanceof SpotMesh )
 				{
 					final SpotMesh mesh = ( SpotMesh ) spot;
-					io.save( mesh.getMesh(), savePath );
+					STLMeshIO.save( mesh.getMesh(), savePath );
 				}
 			}
 			System.out.println( "Export done." );
diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java
index d0a800c14..0c2cb85e7 100644
--- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java
+++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java
@@ -21,12 +21,11 @@
 import ij.ImagePlus;
 import net.imagej.ImgPlus;
 import net.imagej.axis.Axes;
-import net.imagej.mesh.Mesh;
-import net.imagej.mesh.Meshes;
-import net.imagej.mesh.io.stl.STLMeshIO;
-import net.imagej.mesh.naive.NaiveDoubleMesh;
-import net.imagej.mesh.nio.BufferMesh;
 import net.imglib2.img.display.imagej.ImgPlusViews;
+import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.obj.Mesh;
+import net.imglib2.mesh.obj.naive.NaiveDoubleMesh;
+import net.imglib2.mesh.obj.nio.BufferMesh;
 import net.imglib2.type.Type;
 import net.imglib2.type.numeric.ARGBType;
 
@@ -91,8 +90,7 @@ private static BufferMesh load( final String fn )
 		try
 		{
 			final NaiveDoubleMesh nmesh = new NaiveDoubleMesh();
-			final STLMeshIO meshIO = new STLMeshIO();
-			meshIO.read( nmesh, new File( fn ) );
+			net.imglib2.mesh.io.stl.STLMeshIO.read( nmesh, new File( fn ) );
 			mesh = calculateNormals(
 					nmesh
 //					Meshes.removeDuplicateVertices( nmesh, 5 )
diff --git a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java
index c5fabdd62..f5172a51c 100644
--- a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java
+++ b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java
@@ -5,10 +5,10 @@
 
 import org.junit.Test;
 
-import fiji.plugin.trackmate.util.mesh.EllipsoidFitter;
-import fiji.plugin.trackmate.util.mesh.EllipsoidFitter.EllipsoidFit;
-import net.imagej.mesh.Mesh;
-import net.imagej.mesh.naive.NaiveDoubleMesh;
+import net.imglib2.mesh.alg.EllipsoidFitter;
+import net.imglib2.mesh.alg.EllipsoidFitter.EllipsoidFit;
+import net.imglib2.mesh.obj.Mesh;
+import net.imglib2.mesh.obj.naive.NaiveDoubleMesh;
 
 public class TestEllipsoidFit
 {

From d5a96e4dfdfe45eaa85b7c0e835119f82321f20f Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Tue, 12 Sep 2023 11:21:14 +0200
Subject: [PATCH 202/263] Use the scale method of the spot interface.

We don't have SpotShape anymore.
---
 .../hyperstack/ModelEditActions.java              | 15 ++-------------
 1 file changed, 2 insertions(+), 13 deletions(-)

diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java
index 2d7731c2f..581c93291 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java
@@ -39,7 +39,6 @@
 import fiji.plugin.trackmate.SelectionModel;
 import fiji.plugin.trackmate.Spot;
 import fiji.plugin.trackmate.SpotBase;
-import fiji.plugin.trackmate.SpotShape;
 import fiji.plugin.trackmate.detection.semiauto.SemiAutoTracker;
 import fiji.plugin.trackmate.util.ModelTools;
 import fiji.plugin.trackmate.util.TMUtils;
@@ -287,24 +286,14 @@ public void changeSpotRadius( final boolean increase, final boolean fast )
 				? radius + factor * dx * COARSE_STEP
 				: radius + factor * dx * FINE_STEP;
 
-
 		if ( newRadius <= dx )
 			return;
 
 		// Store new value of radius for next spot creation.
 		previousRadius = newRadius;
 
-		final SpotShape shape = target.getShape();
-		if ( null == shape )
-		{
-			target.putFeature( Spot.RADIUS, newRadius );
-		}
-		else
-		{
-			final double alpha = newRadius / radius;
-			shape.scale( alpha );
-			target.putFeature( Spot.RADIUS, shape.radius() );
-		}
+		// Actually scale the spot.
+		target.scale( radius / newRadius );
 
 		model.beginUpdate();
 		try

From 10182e260a62113326e2d64e978f5f20e546b7fa Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Tue, 12 Sep 2023 11:50:34 +0200
Subject: [PATCH 203/263] Don't hide exceptions when launching the BVV.

---
 .../plugin/trackmate/gui/wizard/TrackMateWizardSequence.java  | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java
index bc41be9d8..2c7068988 100644
--- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java
+++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java
@@ -503,6 +503,10 @@ public void run()
 							GuiUtils.positionWindow( SwingUtilities.getWindowAncestor( bvvHandle.getViewerPanel() ), c );
 						}
 					}
+					catch ( final Exception e )
+					{
+						e.printStackTrace();
+					}
 					finally
 					{
 						enabler.reenable();

From c88122976e566b64e6211db2659f44035aae32fc Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Tue, 12 Sep 2023 11:51:10 +0200
Subject: [PATCH 204/263] Try to do without deprecated method.

Given the message, we might see an error depending on the
java JRE that runs. Using the Zulu one, this passes.
---
 .../java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java
index 2b99e4567..3f384f969 100644
--- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java
+++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java
@@ -196,7 +196,7 @@ public long getLongPosition( final int d )
 	public Cursor< T > copyCursor()
 	{
 		return new SpotMeshCursor<>(
-				ra.copyRandomAccess(),
+				ra.copy(),
 				sm.copy(),
 				cal.clone() );
 	}

From cb1027b14b061525f9140bd81aa60b1d1331ffed Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Tue, 12 Sep 2023 11:51:30 +0200
Subject: [PATCH 205/263] Remove debug main method.

---
 .../visualization/bvv/TrackMateBVV.java       | 65 +------------------
 1 file changed, 3 insertions(+), 62 deletions(-)

diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java
index 82020e27f..a7c024b5b 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java
@@ -1,15 +1,10 @@
 package fiji.plugin.trackmate.visualization.bvv;
 
-import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON;
-
 import java.awt.Color;
-import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import javax.swing.JFrame;
-
 import org.joml.Matrix4f;
 
 import bdv.viewer.animate.TranslationAnimator;
@@ -19,20 +14,11 @@
 import fiji.plugin.trackmate.Model;
 import fiji.plugin.trackmate.ModelChangeEvent;
 import fiji.plugin.trackmate.SelectionModel;
-import fiji.plugin.trackmate.Settings;
 import fiji.plugin.trackmate.Spot;
-import fiji.plugin.trackmate.TrackMate;
 import fiji.plugin.trackmate.features.FeatureUtils;
-import fiji.plugin.trackmate.gui.GuiUtils;
 import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings;
-import fiji.plugin.trackmate.gui.wizard.TrackMateWizardSequence;
-import fiji.plugin.trackmate.gui.wizard.WizardSequence;
-import fiji.plugin.trackmate.io.TmXmlReader;
 import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView;
 import fiji.plugin.trackmate.visualization.FeatureColorGenerator;
-import fiji.plugin.trackmate.visualization.TrackMateModelView;
-import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer;
-import ij.ImageJ;
 import ij.ImagePlus;
 import net.imglib2.RealLocalizable;
 import net.imglib2.realtransform.AffineTransform3D;
@@ -81,8 +67,8 @@ public void render()
 			if ( displaySettings.isSpotVisible() )
 			{
 				final Matrix4f pvm = new Matrix4f( data.getPv() );
-				Matrix4f view = MatrixMath.affine( data.getRenderTransformWorldToScreen(), new Matrix4f() );
-				Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view );
+				final Matrix4f view = MatrixMath.affine( data.getRenderTransformWorldToScreen(), new Matrix4f() );
+				final Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view );
 
 				final int t = data.getTimepoint();
 				final Iterable< Spot > it = model.getSpots().iterable( t, true );
@@ -194,55 +180,10 @@ private void updateColor()
 				continue;
 
 			final Color color = spotColorGenerator.color( entry.getKey() );
-			float alpha = ( float ) displaySettings.getSpotTransparencyAlpha();
+			final float alpha = ( float ) displaySettings.getSpotTransparencyAlpha();
 			sm.setColor( color, alpha );
 			sm.setSelectionColor( displaySettings.getHighlightColor(), alpha );
 		}
 		refresh();
 	}
-
-	public static < T extends Type< T > > void main( final String[] args )
-	{
-		try
-		{
-//		final String filePath = "samples/mesh/CElegansMask3D.tif";
-			final String filePath = "samples/CElegans3D-smoothed-mask-orig.xml";
-//			final String filePath = "../TrackMate-StarDist/samples/CTC-Fluo-N3DH-SIM-multiC.xml";
-
-			ImageJ.main( args );
-			final TmXmlReader reader = new TmXmlReader( new File( filePath ) );
-			if ( !reader.isReadingOk() )
-			{
-				System.err.println( reader.getErrorMessage() );
-				return;
-			}
-			final ImagePlus imp = reader.readImage();
-			final Settings settings = reader.readSettings( imp );
-			imp.show();
-
-			final Model model = reader.getModel();
-			final SelectionModel selectionModel = new SelectionModel( model );
-			final DisplaySettings ds = reader.getDisplaySettings();
-			final TrackMate trackmate = new TrackMate( model, settings );
-
-			// Main view
-			final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, imp, ds );
-			displayer.render();
-
-			// Wizard.
-			final WizardSequence sequence = new TrackMateWizardSequence( trackmate, selectionModel, ds );
-			sequence.setCurrent( "ConfigureViews" );
-			final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() );
-			frame.setIconImage( TRACKMATE_ICON.getImage() );
-			GuiUtils.positionWindow( frame, settings.imp.getWindow() );
-			frame.setVisible( true );
-
-			final TrackMateBVV< T > tbvv = new TrackMateBVV<>( model, selectionModel, imp, ds );
-			tbvv.render();
-		}
-		catch ( final Exception e )
-		{
-			e.printStackTrace();
-		}
-	}
 }

From fdf16dbd42cc3576fbad1cad8b032d52789eb9be Mon Sep 17 00:00:00 2001
From: Jean-Yves Tinevez 
Date: Tue, 19 Sep 2023 15:06:48 +0200
Subject: [PATCH 206/263] Use the new imglib2-mesh master branch in the imglib2
 org.

---
 .../java/fiji/plugin/trackmate/SpotMesh.java  |  61 ++-----
 .../trackmate/action/MeshSeriesExporter.java  |   8 +-
 .../trackmate/detection/Process2DZ.java       |   4 +-
 .../trackmate/detection/SpotMeshUtils.java    |  13 +-
 .../spot/Spot3DFitEllipsoidAnalyzer.java      |   4 +-
 .../features/spot/Spot3DShapeAnalyzer.java    |  10 +-
 .../fiji/plugin/trackmate/io/TmXmlReader.java |   6 +-
 .../fiji/plugin/trackmate/io/TmXmlWriter.java |   4 +-
 .../trackmate/visualization/bvv/BVVUtils.java |  11 +-
 .../visualization/bvv/Icosahedron.java        | 154 ------------------
 .../visualization/bvv/StupidMesh.java         |   4 +-
 .../plugin/trackmate/mesh/DefaultMesh.java    |   2 +-
 .../plugin/trackmate/mesh/Demo3DMesh.java     |  10 +-
 .../plugin/trackmate/mesh/MeshPlayground.java |  10 +-
 .../trackmate/mesh/TestEllipsoidFit.java      |   8 +-
 15 files changed, 59 insertions(+), 250 deletions(-)
 delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java

diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java
index a469de7c3..b68612b0c 100644
--- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java
+++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java
@@ -11,13 +11,14 @@
 import net.imglib2.RealInterval;
 import net.imglib2.RealLocalizable;
 import net.imglib2.RealPoint;
+import net.imglib2.mesh.Mesh;
+import net.imglib2.mesh.MeshStats;
 import net.imglib2.mesh.Meshes;
 import net.imglib2.mesh.alg.zslicer.RamerDouglasPeucker;
 import net.imglib2.mesh.alg.zslicer.Slice;
 import net.imglib2.mesh.alg.zslicer.ZSlicer;
-import net.imglib2.mesh.obj.Mesh;
-import net.imglib2.mesh.obj.Vertices;
-import net.imglib2.mesh.obj.nio.BufferMesh;
+import net.imglib2.mesh.impl.nio.BufferMesh;
+import net.imglib2.mesh.impl.nio.BufferMesh.Vertices;
 import net.imglib2.type.numeric.RealType;
 import net.imglib2.util.Intervals;
 
@@ -60,7 +61,7 @@ public SpotMesh(
 		super( 0., 0., 0., 0., quality, name );
 
 		// Compute triangles and vertices normals.
-		final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() );
+		final BufferMesh mesh = new BufferMesh( m.vertices().size(), m.triangles().size() );
 		Meshes.calculateNormals( m, mesh );
 
 		this.mesh = mesh;
@@ -194,47 +195,7 @@ public void resetZSliceCache()
 	 */
 	public static final double radius( final Mesh mesh )
 	{
-		return Math.pow( 3. * volume( mesh ) / ( 4 * Math.PI ), 1. / 3. );
-	}
-
-	/**
-	 * Returns the volume of the specified mesh.
-	 *
-	 * @return the volume in physical units.
-	 */
-	public static double volume( final Mesh mesh )
-	{
-
-		final Vertices vertices = mesh.vertices();
-		final net.imglib2.mesh.obj.Triangles triangles = mesh.triangles();
-		final long nTriangles = triangles.size();
-		double sum = 0.;
-		for ( long t = 0; t < nTriangles; t++ )
-		{
-			final long v1 = triangles.vertex0( t );
-			final long v2 = triangles.vertex1( t );
-			final long v3 = triangles.vertex2( t );
-
-			final double x1 = vertices.x( v1 );
-			final double y1 = vertices.y( v1 );
-			final double z1 = vertices.z( v1 );
-			final double x2 = vertices.x( v2 );
-			final double y2 = vertices.y( v2 );
-			final double z2 = vertices.z( v2 );
-			final double x3 = vertices.x( v3 );
-			final double y3 = vertices.y( v3 );
-			final double z3 = vertices.z( v3 );
-
-			final double v321 = x3 * y2 * z1;
-			final double v231 = x2 * y3 * z1;
-			final double v312 = x3 * y1 * z2;
-			final double v132 = x1 * y3 * z2;
-			final double v213 = x2 * y1 * z3;
-			final double v123 = x1 * y2 * z3;
-
-			sum += ( 1. / 6. ) * ( -v321 + v231 + v312 - v132 - v213 + v123 );
-		}
-		return Math.abs( sum );
+		return Math.pow( 3. * MeshStats.volume( mesh ) / ( 4 * Math.PI ), 1. / 3. );
 	}
 
 	public double radius()
@@ -249,13 +210,13 @@ public double radius()
 	 */
 	public double volume()
 	{
-		return volume( mesh );
+		return MeshStats.volume( mesh );
 	}
 
 	@Override
 	public void scale( final double alpha )
 	{
-		final Vertices vertices = mesh.vertices();
+		final net.imglib2.mesh.Vertices vertices = mesh.vertices();
 		final long nVertices = vertices.size();
 		for ( int v = 0; v < nVertices; v++ )
 		{
@@ -288,7 +249,7 @@ public void scale( final double alpha )
 	@Override
 	public SpotMesh copy()
 	{
-		final BufferMesh meshCopy = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() );
+		final BufferMesh meshCopy = new BufferMesh( mesh.vertices().size(), mesh.triangles().size() );
 		Meshes.copy( this.mesh, meshCopy );
 		return new SpotMesh( meshCopy, getFeature( Spot.QUALITY ), getName() );
 	}
@@ -303,14 +264,14 @@ public String toString()
 		str.append( String.format( "\n%5s: %7.2f -> %7.2f", "Y", boundingBox.realMin( 1 ), boundingBox.realMax( 1 ) ) );
 		str.append( String.format( "\n%5s: %7.2f -> %7.2f", "Z", boundingBox.realMin( 2 ), boundingBox.realMax( 2 ) ) );
 
-		final Vertices vertices = mesh.vertices();
+		final net.imglib2.mesh.Vertices vertices = mesh.vertices();
 		final long nVertices = vertices.size();
 		str.append( "\nV (" + nVertices + "):" );
 		for ( long i = 0; i < nVertices; i++ )
 			str.append( String.format( "\n%5d: %7.2f %7.2f %7.2f",
 					i, vertices.x( i ), vertices.y( i ), vertices.z( i ) ) );
 
-		final net.imglib2.mesh.obj.Triangles triangles = mesh.triangles();
+		final net.imglib2.mesh.Triangles triangles = mesh.triangles();
 		final long nTriangles = triangles.size();
 		str.append( "\nF (" + nTriangles + "):" );
 		for ( long i = 0; i < nTriangles; i++ )
diff --git a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java
index 054b723b2..51e66fb1f 100644
--- a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java
+++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java
@@ -43,11 +43,11 @@
 import fiji.plugin.trackmate.TrackMate;
 import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings;
 import fiji.plugin.trackmate.io.IOUtils;
+import net.imglib2.mesh.Mesh;
 import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.impl.nio.BufferMesh;
 import net.imglib2.mesh.io.ply.PLYMeshIO;
-import net.imglib2.mesh.obj.Mesh;
-import net.imglib2.mesh.obj.nio.BufferMesh;
-import net.imglib2.mesh.obj.transform.TranslateMesh;
+import net.imglib2.mesh.view.TranslateMesh;
 
 public class MeshSeriesExporter extends AbstractTMAction
 {
@@ -122,7 +122,7 @@ public static void exportMeshesToFileSeries( final SpotCollection spots, final F
 			}
 			logger.log( " - Found " + meshes.size() + " meshes in frame " + frame + "." );
 			final Mesh merged = Meshes.merge( meshes );
-			final BufferMesh mesh = new BufferMesh( ( int ) merged.vertices().size(), ( int ) merged.triangles().size() );
+			final BufferMesh mesh = new BufferMesh( merged.vertices().size(), merged.triangles().size() );
 			Meshes.calculateNormals( merged, mesh );
 			try
 			{
diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java
index 62a313bd7..f49ac5102 100644
--- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java
+++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java
@@ -20,8 +20,8 @@
 import net.imglib2.algorithm.MultiThreadedBenchmarkAlgorithm;
 import net.imglib2.img.display.imagej.ImageJFunctions;
 import net.imglib2.mesh.alg.TaubinSmoothing;
-import net.imglib2.mesh.obj.nio.BufferMesh;
-import net.imglib2.mesh.obj.transform.TranslateMesh;
+import net.imglib2.mesh.impl.nio.BufferMesh;
+import net.imglib2.mesh.view.TranslateMesh;
 import net.imglib2.type.NativeType;
 import net.imglib2.type.numeric.RealType;
 import net.imglib2.view.IntervalView;
diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java
index 7e9d4825e..bc0c1283c 100644
--- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java
+++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java
@@ -12,11 +12,12 @@
 import net.imglib2.RandomAccessible;
 import net.imglib2.RandomAccessibleInterval;
 import net.imglib2.RealInterval;
+import net.imglib2.mesh.Mesh;
+import net.imglib2.mesh.MeshStats;
 import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.Vertices;
 import net.imglib2.mesh.alg.MeshConnectedComponents;
-import net.imglib2.mesh.obj.Mesh;
-import net.imglib2.mesh.obj.Vertices;
-import net.imglib2.mesh.obj.nio.BufferMesh;
+import net.imglib2.mesh.impl.nio.BufferMesh;
 import net.imglib2.roi.labeling.ImgLabeling;
 import net.imglib2.roi.labeling.LabelRegion;
 import net.imglib2.roi.labeling.LabelRegions;
@@ -294,7 +295,7 @@ public static < S extends RealType< S > > SpotMesh meshToSpotMesh(
 		if ( simplify )
 		{
 			// Dont't go below a certain number of triangles.
-			final int nTriangles = ( int ) mesh.triangles().size();
+			final int nTriangles = mesh.triangles().size();
 			if ( nTriangles < MIN_N_TRIANGLES )
 			{
 				simplified = mesh;
@@ -320,7 +321,7 @@ else if ( nTriangles < 1_000_000 )
 		}
 		// Remove meshes that are too small
 		final double volumeThreshold = MIN_MESH_PIXEL_VOLUME * calibration[ 0 ] * calibration[ 1 ] * calibration[ 2 ];
-		if ( SpotMesh.volume( simplified ) < volumeThreshold )
+		if ( MeshStats.volume( simplified ) < volumeThreshold )
 			return null;
 
 		// Translate back to interval coords & scale to physical coords.
@@ -333,7 +334,7 @@ else if ( nTriangles < 1_000_000 )
 		final double quality;
 		if ( null == qualityImage )
 		{
-			quality = SpotMesh.volume( simplified );
+			quality = MeshStats.volume( simplified );
 		}
 		else
 		{
diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java
index e30e36007..0d1a95a38 100644
--- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java
+++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java
@@ -24,7 +24,7 @@
 import fiji.plugin.trackmate.SpotMesh;
 import net.imglib2.RealLocalizable;
 import net.imglib2.mesh.alg.EllipsoidFitter;
-import net.imglib2.mesh.alg.EllipsoidFitter.EllipsoidFit;
+import net.imglib2.mesh.alg.EllipsoidFitter.Ellipsoid;
 import net.imglib2.type.numeric.RealType;
 
 public class Spot3DFitEllipsoidAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T >
@@ -60,7 +60,7 @@ public void process( final Spot spot )
 			if ( spot instanceof SpotMesh )
 			{
 				final SpotMesh sm = ( SpotMesh ) spot;
-				final EllipsoidFit fit = EllipsoidFitter.fit( sm.getMesh() );
+				final Ellipsoid fit = EllipsoidFitter.fit( sm.getMesh() );
 				x0 = fit.center.getDoublePosition( 0 );
 				y0 = fit.center.getDoublePosition( 1 );
 				z0 = fit.center.getDoublePosition( 2 );
diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java
index 363dfd9e2..42b2eeaa4 100644
--- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java
+++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java
@@ -23,9 +23,9 @@
 
 import fiji.plugin.trackmate.Spot;
 import fiji.plugin.trackmate.SpotMesh;
-import net.imglib2.mesh.MeshShapeDescriptors;
+import net.imglib2.mesh.MeshStats;
 import net.imglib2.mesh.alg.hull.ConvexHull;
-import net.imglib2.mesh.obj.naive.NaiveDoubleMesh;
+import net.imglib2.mesh.impl.naive.NaiveDoubleMesh;
 import net.imglib2.type.numeric.RealType;
 
 public class Spot3DShapeAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T >
@@ -53,11 +53,11 @@ public void process( final Spot spot )
 				final SpotMesh sm = ( SpotMesh ) spot;
 				final NaiveDoubleMesh ch = ConvexHull.calculate( sm.getMesh() );
 				volume = sm.volume();
-				final double volumeCH = MeshShapeDescriptors.volume( ch );
+				final double volumeCH = MeshStats.volume( ch );
 				solidity = volume / volumeCH;
 
-				sa = MeshShapeDescriptors.surfaceArea( sm.getMesh() );
-				final double saCH = MeshShapeDescriptors.surfaceArea( ch );
+				sa = MeshStats.surfaceArea( sm.getMesh() );
+				final double saCH = MeshStats.surfaceArea( ch );
 				convexity = sa / saCH;
 
 				final double sphereArea = Math.pow( Math.PI, 1. / 3. )
diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java
index 243c6f936..cd0bfaf0b 100644
--- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java
+++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java
@@ -155,10 +155,10 @@
 import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme;
 import ij.IJ;
 import ij.ImagePlus;
+import net.imglib2.mesh.Mesh;
 import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.impl.nio.BufferMesh;
 import net.imglib2.mesh.io.ply.PLYMeshIO;
-import net.imglib2.mesh.obj.Mesh;
-import net.imglib2.mesh.obj.nio.BufferMesh;
 
 public class TmXmlReader
 {
@@ -963,7 +963,7 @@ private SpotCollection getSpots( final Element modelElement )
 						try
 						{
 							final Mesh m = PLYMeshIO.open( zipFile.getInputStream( entry ) );
-							final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() );
+							final BufferMesh mesh = new BufferMesh( m.vertices().size(), m.triangles().size() );
 							Meshes.calculateNormals( m, mesh );
 
 							// Create new spot in the mesh and replace it in the
diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java
index af9340672..e422d23e3 100644
--- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java
+++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java
@@ -133,9 +133,9 @@
 import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO;
 import gnu.trove.map.hash.TIntIntHashMap;
 import gnu.trove.procedure.TIntIntProcedure;
+import net.imglib2.mesh.Mesh;
 import net.imglib2.mesh.io.ply.PLYMeshIO;
-import net.imglib2.mesh.obj.Mesh;
-import net.imglib2.mesh.obj.transform.TranslateMesh;
+import net.imglib2.mesh.view.TranslateMesh;
 
 public class TmXmlWriter
 {
diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java
index d3ff277bd..331367a62 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java
@@ -14,10 +14,11 @@
 import net.imagej.ImgPlus;
 import net.imagej.axis.Axes;
 import net.imglib2.img.display.imagej.ImgPlusViews;
+import net.imglib2.mesh.Mesh;
 import net.imglib2.mesh.Meshes;
-import net.imglib2.mesh.obj.Mesh;
-import net.imglib2.mesh.obj.nio.BufferMesh;
-import net.imglib2.mesh.obj.transform.TranslateMesh;
+import net.imglib2.mesh.impl.nio.BufferMesh;
+import net.imglib2.mesh.util.Icosahedron;
+import net.imglib2.mesh.view.TranslateMesh;
 import net.imglib2.type.Type;
 import net.imglib2.type.numeric.ARGBType;
 
@@ -30,11 +31,11 @@ public static final StupidMesh createMesh( final Spot spot )
 		{
 			final SpotMesh sm = ( SpotMesh ) spot;
 			final Mesh mesh = TranslateMesh.translate( sm.getMesh(), spot );
-			final BufferMesh bm = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() );
+			final BufferMesh bm = new BufferMesh( mesh.vertices().size(), mesh.triangles().size() );
 			Meshes.copy( mesh, bm );
 			return new StupidMesh( bm );
 		}
-		return new StupidMesh( Icosahedron.sphere( spot ) );
+		return new StupidMesh( Icosahedron.sphere( spot, spot.getFeature( Spot.RADIUS ).doubleValue() ) );
 	}
 
 	public static final < T extends Type< T > > BvvHandle createViewer( final ImagePlus imp )
diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java
deleted file mode 100644
index d77d407d1..000000000
--- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package fiji.plugin.trackmate.visualization.bvv;
-
-import fiji.plugin.trackmate.Spot;
-import net.imglib2.RealLocalizable;
-import net.imglib2.mesh.Meshes;
-import net.imglib2.mesh.obj.Mesh;
-import net.imglib2.mesh.obj.Triangle;
-import net.imglib2.mesh.obj.naive.NaiveDoubleMesh;
-import net.imglib2.mesh.obj.nio.BufferMesh;
-
-/**
- * Icosahedron spheres.
- * 

- * Based on https://github.com/caosdoar/spheres - * - * @author Jean-Yves Tinevez - */ -public class Icosahedron -{ - - public static final Mesh core() - { - final NaiveDoubleMesh mesh = new NaiveDoubleMesh(); - - // Vertices - final double t = ( 1. + Math.sqrt( 5. ) ) / 2.; - final double[][] vs = new double[][] { - { -1.0, t, 0.0 }, - { 1.0, t, 0.0 }, - { -1.0, -t, 0.0 }, - { 1.0, -t, 0.0 }, - { 0.0, -1.0, t }, - { 0.0, 1.0, t }, - { 0.0, -1.0, -t }, - { 0.0, 1.0, -t }, - { t, 0.0, -1.0 }, - { t, 0.0, 1.0 }, - { -t, 0.0, -1.0 }, - { -t, 0.0, 1.0 } - }; - final double[] tmp = new double[ 3 ]; - for ( final double[] v : vs ) - { - normalize( v, tmp ); - mesh.vertices().add( tmp[ 0 ], tmp[ 1 ], tmp[ 2 ] ); - } - - // Faces - mesh.triangles().add( 0, 11, 5 ); - mesh.triangles().add( 0, 5, 1 ); - mesh.triangles().add( 0, 1, 7 ); - mesh.triangles().add( 0, 7, 10 ); - mesh.triangles().add( 0, 10, 11 ); - mesh.triangles().add( 1, 5, 9 ); - mesh.triangles().add( 5, 11, 4 ); - mesh.triangles().add( 11, 10, 2 ); - mesh.triangles().add( 10, 7, 6 ); - mesh.triangles().add( 7, 1, 8 ); - mesh.triangles().add( 3, 9, 4 ); - mesh.triangles().add( 3, 4, 2 ); - mesh.triangles().add( 3, 2, 6 ); - mesh.triangles().add( 3, 6, 8 ); - mesh.triangles().add( 3, 8, 9 ); - mesh.triangles().add( 4, 9, 5 ); - mesh.triangles().add( 2, 4, 11 ); - mesh.triangles().add( 6, 2, 10 ); - mesh.triangles().add( 8, 6, 7 ); - mesh.triangles().add( 9, 8, 1 ); - return mesh; - } - - public static final BufferMesh refine( final Mesh core ) - { - final int nVerticesOut = ( int ) ( 6 * core.triangles().size() ); - final int nTrianglesOut = ( int ) ( 4 * core.triangles().size() ); - final BufferMesh out = new BufferMesh( nVerticesOut, nTrianglesOut ); - - final double[] tmpIn = new double[ 3 ]; - final double[] tmpOut = new double[ 3 ]; - for ( final Triangle t : core.triangles() ) - { - final long v0 = out.vertices().add( t.v0x(), t.v0y(), t.v0z() ); - final long v1 = out.vertices().add( t.v1x(), t.v1y(), t.v1z() ); - final long v2 = out.vertices().add( t.v2x(), t.v2y(), t.v2z() ); - - tmpIn[ 0 ] = 0.5 * ( t.v0xf() + t.v1xf() ); - tmpIn[ 1 ] = 0.5 * ( t.v0yf() + t.v1yf() ); - tmpIn[ 2 ] = 0.5 * ( t.v0zf() + t.v1zf() ); - normalize( tmpIn, tmpOut ); - final long v3 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); - - tmpIn[ 0 ] = 0.5 * ( t.v2xf() + t.v1xf() ); - tmpIn[ 1 ] = 0.5 * ( t.v2yf() + t.v1yf() ); - tmpIn[ 2 ] = 0.5 * ( t.v2zf() + t.v1zf() ); - normalize( tmpIn, tmpOut ); - final long v4 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); - - tmpIn[ 0 ] = 0.5 * ( t.v0xf() + t.v2xf() ); - tmpIn[ 1 ] = 0.5 * ( t.v0yf() + t.v2yf() ); - tmpIn[ 2 ] = 0.5 * ( t.v0zf() + t.v2zf() ); - normalize( tmpIn, tmpOut ); - final long v5 = out.vertices().add( tmpOut[ 0 ], tmpOut[ 1 ], tmpOut[ 2 ] ); - - out.triangles().add( v0, v3, v5 ); - out.triangles().add( v3, v1, v4 ); - out.triangles().add( v5, v4, v2 ); - out.triangles().add( v3, v4, v5 ); - } - - return out; - } - - public static BufferMesh sphere( final Spot spot ) - { - return sphere( spot, spot.getFeature( Spot.RADIUS ) ); - } - - public static BufferMesh sphere( final RealLocalizable center, final double radius ) - { - return sphere( center, radius, 3 ); - } - - public static BufferMesh sphere( final RealLocalizable center, final double radius, final int nSubdivisions ) - { - Mesh mesh = core(); - for ( int i = 0; i < nSubdivisions; i++ ) - mesh = refine( mesh ); - - scale( mesh, center, radius ); - final BufferMesh out = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); - Meshes.calculateNormals( mesh, out ); - return out; - } - - private static void scale( final Mesh mesh, final RealLocalizable center, final double radius ) - { - final long nV = mesh.vertices().size(); - for ( int i = 0; i < nV; i++ ) - { - final double x = mesh.vertices().x( i ) * radius + center.getDoublePosition( 0 ); - final double y = mesh.vertices().y( i ) * radius + center.getDoublePosition( 1 ); - final double z = mesh.vertices().z( i ) * radius + center.getDoublePosition( 2 ); - mesh.vertices().set( i, x, y, z ); - } - } - - private static void normalize( final double[] v, final double[] tmp ) - { - final double l = Math.sqrt( v[ 0 ] * v[ 0 ] + v[ 1 ] * v[ 1 ] + v[ 2 ] * v[ 2 ] ); - tmp[ 0 ] = v[ 0 ] / l; - tmp[ 1 ] = v[ 1 ] / l; - tmp[ 2 ] = v[ 2 ] / l; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java index 46da5742e..26d705670 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -49,7 +49,7 @@ import bvv.core.shadergen.generate.Segment; import bvv.core.shadergen.generate.SegmentTemplate; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import net.imglib2.mesh.obj.nio.BufferMesh; +import net.imglib2.mesh.impl.nio.BufferMesh; public class StupidMesh { @@ -149,7 +149,7 @@ public void draw( final GL3 gl, final Matrix4fc pvm, final Matrix4fc vm, final b gl.glEnable( GL.GL_CULL_FACE ); gl.glCullFace( GL.GL_BACK ); gl.glFrontFace( GL.GL_CCW ); - gl.glDrawElements( GL_TRIANGLES, ( int ) mesh.triangles().size() * 3, GL_UNSIGNED_INT, 0 ); + gl.glDrawElements( GL_TRIANGLES, mesh.triangles().size() * 3, GL_UNSIGNED_INT, 0 ); gl.glBindVertexArray( 0 ); } } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java index 37e6f9961..cc2f76a3d 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java @@ -17,7 +17,7 @@ import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imglib2.img.display.imagej.ImageJFunctions; -import net.imglib2.mesh.obj.Mesh; +import net.imglib2.mesh.Mesh; import net.imglib2.type.logic.BitType; public class DefaultMesh diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 766d92e4b..7e7cdf5fb 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -19,15 +19,15 @@ import net.imglib2.img.ImgView; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.mesh.Mesh; import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.Vertices; import net.imglib2.mesh.alg.zslicer.Contour; import net.imglib2.mesh.alg.zslicer.Slice; import net.imglib2.mesh.alg.zslicer.ZSlicer; +import net.imglib2.mesh.impl.naive.NaiveDoubleMesh; import net.imglib2.mesh.io.ply.PLYMeshIO; import net.imglib2.mesh.io.stl.STLMeshIO; -import net.imglib2.mesh.obj.Mesh; -import net.imglib2.mesh.obj.Vertices; -import net.imglib2.mesh.obj.naive.NaiveDoubleMesh; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; @@ -111,8 +111,8 @@ public static void main( final String[] args ) static Mesh debugMesh( final long[] min, final long[] max ) { final NaiveDoubleMesh mesh = new NaiveDoubleMesh(); - final net.imglib2.mesh.obj.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices(); - final net.imglib2.mesh.obj.naive.NaiveDoubleMesh.Triangles triangles = mesh.triangles(); + final net.imglib2.mesh.impl.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices(); + final net.imglib2.mesh.impl.naive.NaiveDoubleMesh.Triangles triangles = mesh.triangles(); // Coords as X Y Z diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java index 0c2cb85e7..22b3f4d58 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java @@ -22,10 +22,10 @@ import net.imagej.ImgPlus; import net.imagej.axis.Axes; import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.mesh.Mesh; import net.imglib2.mesh.Meshes; -import net.imglib2.mesh.obj.Mesh; -import net.imglib2.mesh.obj.naive.NaiveDoubleMesh; -import net.imglib2.mesh.obj.nio.BufferMesh; +import net.imglib2.mesh.impl.naive.NaiveDoubleMesh; +import net.imglib2.mesh.impl.nio.BufferMesh; import net.imglib2.type.Type; import net.imglib2.type.numeric.ARGBType; @@ -105,8 +105,8 @@ private static BufferMesh load( final String fn ) private static BufferMesh calculateNormals( final Mesh mesh ) { - final int nvertices = ( int ) mesh.vertices().size(); - final int ntriangles = ( int ) mesh.triangles().size(); + final int nvertices = mesh.vertices().size(); + final int ntriangles = mesh.triangles().size(); final BufferMesh bufferMesh = new BufferMesh( nvertices, ntriangles, true ); Meshes.calculateNormals( mesh, bufferMesh ); return bufferMesh; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java index f5172a51c..5a5037042 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java @@ -5,10 +5,10 @@ import org.junit.Test; +import net.imglib2.mesh.Mesh; import net.imglib2.mesh.alg.EllipsoidFitter; -import net.imglib2.mesh.alg.EllipsoidFitter.EllipsoidFit; -import net.imglib2.mesh.obj.Mesh; -import net.imglib2.mesh.obj.naive.NaiveDoubleMesh; +import net.imglib2.mesh.alg.EllipsoidFitter.Ellipsoid; +import net.imglib2.mesh.impl.naive.NaiveDoubleMesh; public class TestEllipsoidFit { @@ -23,7 +23,7 @@ public void testSimpleEllipsoids() for ( double rc = 3.; rc < 10.; rc++ ) { final Mesh mesh = generateEllipsoidMesh( ra, rb, rc, -1, -1 ); - final EllipsoidFit fit = EllipsoidFitter.fit( mesh ); + final Ellipsoid fit = EllipsoidFitter.fit( mesh ); final double[] arr = new double[ 3 ]; From 80b27baf17ff723bf84c57ba1d16e58a3c4f2bfb Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 16:37:43 +0200 Subject: [PATCH 207/263] Abide to recent changes in imglib2-mesh. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 6 +++--- .../trackmate/detection/SpotMeshUtils.java | 17 ++--------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index b68612b0c..7b158452e 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -84,7 +84,7 @@ public SpotMesh( putFeature( Spot.RADIUS, r ); // Bounding box, also centered on (0,0,0) - this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); + this.boundingBox = Meshes.boundingBox( mesh ); } /** @@ -118,7 +118,7 @@ public SpotMesh( final int ID, final BufferMesh mesh ) putFeature( Spot.RADIUS, r ); // Bounding box, also centered on (0,0,0) - this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); + this.boundingBox = Meshes.boundingBox( mesh ); } /** @@ -243,7 +243,7 @@ public void scale( final double alpha ) final float za = ( float ) ( ra * Math.cos( theta ) ); vertices.setPositionf( v, xa, ya, za ); } - this.boundingBox = toRealInterval( Meshes.boundingBox( mesh ) ); + this.boundingBox = Meshes.boundingBox( mesh ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index bc0c1283c..d0e063765 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -15,7 +15,6 @@ import net.imglib2.mesh.Mesh; import net.imglib2.mesh.MeshStats; import net.imglib2.mesh.Meshes; -import net.imglib2.mesh.Vertices; import net.imglib2.mesh.alg.MeshConnectedComponents; import net.imglib2.mesh.impl.nio.BufferMesh; import net.imglib2.roi.labeling.ImgLabeling; @@ -110,7 +109,7 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > for ( final BufferMesh m : MeshConnectedComponents.iterable( bigMesh ) ) { meshes.add( m ); - boundingBoxes.add( SpotMesh.toRealInterval( Meshes.boundingBox( m ) ) ); + boundingBoxes.add( Meshes.boundingBox( m ) ); } // Merge if bb is included in one another. @@ -325,7 +324,7 @@ else if ( nTriangles < 1_000_000 ) return null; // Translate back to interval coords & scale to physical coords. - scale( simplified.vertices(), calibration, origin ); + Meshes.translateScale( simplified, calibration, origin ); // Make spot with default quality. final SpotMesh spot = new SpotMesh( simplified, 0. ); @@ -351,16 +350,4 @@ else if ( nTriangles < 1_000_000 ) spot.putFeature( Spot.QUALITY, Double.valueOf( quality ) ); return spot; } - - private static void scale( final Vertices vertices, final double[] scale, final double[] origin ) - { - final long nv = vertices.size(); - for ( long i = 0; i < nv; i++ ) - { - final double x = ( origin[ 0 ] + vertices.x( i ) ) * scale[ 0 ]; - final double y = ( origin[ 1 ] + vertices.y( i ) ) * scale[ 1 ]; - final double z = ( origin[ 2 ] + vertices.z( i ) ) * scale[ 2 ]; - vertices.set( i, x, y, z ); - } - } } From 2b5b26b8adf576cfa4d7e7849ae70d01561d9bc4 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 18:21:01 +0200 Subject: [PATCH 208/263] Fix all javadoc warnings. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 9 +- .../java/fiji/plugin/trackmate/SpotRoi.java | 3 + .../plugin/trackmate/TrackMatePlugIn.java | 15 ++- .../plugin/trackmate/action/CTCExporter.java | 2 + .../action/CaptureOverlayAction.java | 4 +- .../trackmate/action/LabelImgExporter.java | 44 +++++--- .../trackmate/action/TrackMateAction.java | 3 + .../action/closegaps/GapClosingMethod.java | 2 + .../trackmate/action/fit/SpotFitterPanel.java | 4 +- .../trackmate/detection/DetectionUtils.java | 13 ++- .../plugin/trackmate/detection/MaskUtils.java | 8 +- .../trackmate/detection/Process2DZ.java | 1 + .../detection/SpotDetectorFactory.java | 2 + .../detection/SpotDetectorFactoryBase.java | 21 ++-- .../detection/SpotGlobalDetectorFactory.java | 2 + .../trackmate/detection/SpotMeshUtils.java | 2 +- .../trackmate/features/FeatureAnalyzer.java | 10 ++ .../trackmate/features/FeatureUtils.java | 8 +- .../features/SpotFeatureCalculator.java | 7 +- .../features/TrackFeatureCalculator.java | 5 + .../features/edges/EdgeAnalyzer.java | 2 + .../spot/Spot2DFitEllipseAnalyzer.java | 9 +- .../spot/SpotAnalyzerFactoryBase.java | 1 + .../features/track/TrackAnalyzer.java | 6 +- .../plugin/trackmate/graph/GraphUtils.java | 14 ++- .../graph/SortedDepthFirstIterator.java | 12 +-- .../fiji/plugin/trackmate/gui/GuiUtils.java | 11 +- .../gui/components/CategoryJComboBox.java | 3 - .../gui/components/ConfigurationPanel.java | 10 +- .../components/FeatureDisplaySelector.java | 10 +- .../gui/components/FilterGuiPanel.java | 16 ++- .../trackmate/gui/components/FilterPanel.java | 11 +- .../gui/components/InitFilterPanel.java | 7 +- .../tracker/JPanelFeatureSelectionGui.java | 5 + .../gui/displaysettings/Colormap.java | 24 +++++ .../gui/displaysettings/SliderPanel.java | 2 + .../displaysettings/SliderPanelDouble.java | 4 +- .../featureselector/AnalyzerSelection.java | 1 + .../trackmate/gui/wizard/WizardSequence.java | 4 +- .../fiji/plugin/trackmate/io/IOUtils.java | 21 ++++ .../fiji/plugin/trackmate/io/TmXmlReader.java | 19 +++- .../fiji/plugin/trackmate/io/TmXmlWriter.java | 8 ++ .../tracking/SpotTrackerFactory.java | 12 ++- .../trackmate/tracking/jaqaman/LAPUtils.java | 37 +++++-- .../costmatrix/DefaultCostMatrixCreator.java | 3 + .../JaqamanLinkingCostMatrixCreator.java | 2 + .../JaqamanSegmentCostMatrixCreator.java | 22 ++-- .../jaqaman/costmatrix/SparseCostMatrix.java | 4 +- .../tracking/kalman/KalmanTracker.java | 9 ++ .../plugin/trackmate/util/ChartExporter.java | 2 +- .../trackmate/util/OnRequestUpdater.java | 3 + .../util/SpotNeighborhoodCursor.java | 47 ++++---- .../fiji/plugin/trackmate/util/TMUtils.java | 102 ++++++++++++++++-- .../visualization/TrackMateModelView.java | 5 + .../hyperstack/HyperStackDisplayer.java | 4 +- .../hyperstack/SpotEditTool.java | 15 ++- .../visualization/hyperstack/SpotOverlay.java | 4 - .../hyperstack/TrackMatePainter.java | 5 +- .../visualization/trackscheme/SaveAction.java | 7 ++ .../trackscheme/SpotIconGrabber.java | 1 + .../trackscheme/SpotImageUpdater.java | 2 + .../trackscheme/TrackScheme.java | 22 +++- .../TrackSchemeKeyboardHandler.java | 4 +- 63 files changed, 533 insertions(+), 144 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 7b158452e..75296dff3 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -49,8 +49,11 @@ public SpotMesh( * calculated from the mesh. * * @param quality + * the spot quality. * @param name + * the spot name. * @param m + * the mesh to create the spot from. */ public SpotMesh( final Mesh m, @@ -93,7 +96,9 @@ public SpotMesh( * otherwise. * * @param ID + * the ID to create the spot with. * @param mesh + * the mesh used to create the spot. */ public SpotMesh( final int ID, final BufferMesh mesh ) { @@ -190,7 +195,9 @@ public void resetZSliceCache() /** * Returns the radius of the equivalent sphere with the same volume that of * the specified mesh. - * + * + * @param mesh + * the mesh. * @return the radius in physical units. */ public static final double radius( final Mesh mesh ) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index eb471fd6c..6987c6228 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -67,8 +67,11 @@ public SpotRoi( * otherwise. * * @param ID + * the ID to use when creating the spot. * @param x + * the spot contour X coordinates. * @param y + * the spot contour Y coordinates. */ public SpotRoi( final int ID, diff --git a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java index e02dbb318..803c5fb81 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java @@ -111,8 +111,11 @@ else if ( imp.getType() == ImagePlus.COLOR_RGB ) * launched by this plugin. * * @param trackmate + * the TrackMate instance. * @param selectionModel + * the selection model. * @param displaySettings + * the display settings. * @return a new sequence. */ protected WizardSequence createSequence( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings ) @@ -126,7 +129,7 @@ protected WizardSequence createSequence( final TrackMate trackmate, final Select * {@link TrackMate} instance. * * @param imp - * + * the image the tracking data will be created on. * @return a new {@link Model} instance. */ protected Model createModel( final ImagePlus imp ) @@ -161,7 +164,11 @@ protected Settings createSettings( final ImagePlus imp ) /** * Hook for subclassers:
* Creates the TrackMate instance that will be controlled in the GUI. - * + * + * @param model + * the model to create the TrackMate instance with. + * @param settings + * the settings to create the TrackMate instance with. * @return a new {@link TrackMate} instance. */ protected TrackMate createTrackMate( final Model model, final Settings settings ) @@ -175,7 +182,9 @@ protected TrackMate createTrackMate( final Model model, final Settings settings model.setPhysicalUnits( spaceUnits, timeUnits ); final TrackMate trackmate = new TrackMate( model, settings ); - ObjectService objectService = TMUtils.getContext().service( ObjectService.class ); + + // Register it to the object service. + final ObjectService objectService = TMUtils.getContext().service( ObjectService.class ); if ( objectService != null ) objectService.addObject( trackmate ); diff --git a/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java b/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java index bd3f473db..e532311a2 100644 --- a/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java @@ -184,6 +184,8 @@ public static String exportAll( final String exportRootFolder, final TrackMate t * the trackmate to export. * @param logger * a logger to report progress. + * @throws IOException + * if there's any problem writing. */ public static void exportSettingsFile( final String exportRootFolder, final int saveId, final TrackMate trackmate, final Logger logger ) throws IOException { diff --git a/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java b/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java index b6560ded4..87e939a33 100644 --- a/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java @@ -120,7 +120,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo TMUtils.getSpatialCalibration( imp ) ); if ( whiteBackground ) { - ImageProcessor ip = imp2.getProcessor(); + final ImageProcessor ip = imp2.getProcessor(); ip.invertLut(); if ( imp2.getStackSize() > 1 ) imp2.getStack().setColorModel( ip.getColorModel() ); @@ -153,6 +153,8 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo * the first frame, inclusive, to capture. * @param last * the last frame, inclusive, to capture. + * @param logger + * a logger instance to echo capture progress. * @return a new ImagePlus. */ public static ImagePlus capture( final TrackMate trackmate, final int first, final int last, final Logger logger ) diff --git a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java index d801f2c01..38add12ec 100644 --- a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java @@ -165,8 +165,8 @@ public static final ImagePlus createLabelImagePlus( * of this input image, except for the number of channels, which * will be 1. * @param exportSpotsAsDots - * if true, spots will be painted as single dots - * instead their shape. + * if true, spots will be painted as single dots. If + * false they will be painted with their shape. * @param exportTracksOnly * if true, only the spots belonging to visible * tracks will be painted. If false, spots not @@ -202,8 +202,8 @@ public static final ImagePlus createLabelImagePlus( * source image, except for the number of channels, which will be * 1. * @param exportSpotsAsDots - * if true, spots will be painted as single dots - * instead their shape. + * if true, spots will be painted as single dots. If + * false they will be painted with their shape. * @param exportTracksOnly * if true, only the spots belonging to visible * tracks will be painted. If false, spots not @@ -236,8 +236,8 @@ public static final ImagePlus createLabelImagePlus( * source image, except for the number of channels, which will be * 1. * @param exportSpotsAsDots - * if true, spots will be painted as single dots - * instead their shape. + * if true, spots will be painted as single dots. If + * false they will be painted with their shape. * @param exportTracksOnly * if true, only the spots belonging to visible * tracks will be painted. If false, spots not @@ -284,9 +284,12 @@ public static final ImagePlus createLabelImagePlus( * the desired dimensions of the output image (width, height, * nZSlices, nFrames) as a 4 element long array. Spots outside * these dimensions are ignored. + * @param calibration + * the pixel size to map physical spot coordinates to pixel + * coordinates. * @param exportSpotsAsDots - * if true, spots will be painted as single dots - * instead their shape. + * if true, spots will be painted as single dots. If + * false they will be painted with their shape. * @param exportTracksOnly * if true, only the spots belonging to visible * tracks will be painted. If false, spots not @@ -318,9 +321,12 @@ public static final ImagePlus createLabelImagePlus( * the desired dimensions of the output image (width, height, * nZSlices, nFrames) as a 4 element int array. Spots outside * these dimensions are ignored. + * @param calibration + * the pixel size to map physical spot coordinates to pixel + * coordinates. * @param exportSpotsAsDots - * if true, spots will be painted as single dots - * instead their shape. + * if true, spots will be painted as single dots. If + * false they will be painted with their shape. * @param exportTracksOnly * if true, only the spots belonging to visible * tracks will be painted. If false, spots not @@ -366,9 +372,12 @@ public static final ImagePlus createLabelImagePlus( * the desired dimensions of the output image (width, height, * nZSlices, nFrames) as a 4 element long array. Spots outside * these dimensions are ignored. + * @param calibration + * the pixel size to map physical spot coordinates to pixel + * coordinates. * @param exportSpotsAsDots - * if true, spots will be painted as single dots - * instead their shape. + * if true, spots will be painted as single dots. If + * false they will be painted with their shape. * @param exportTracksOnly * if true, only the spots belonging to visible * tracks will be painted. If false, spots not @@ -400,9 +409,12 @@ public static final Img< FloatType > createLabelImg( * the desired dimensions of the output image (width, height, * nZSlices, nFrames) as a 4 element long array. Spots outside * these dimensions are ignored. + * @param calibration + * the pixel size to map physical spot coordinates to pixel + * coordinates. * @param exportSpotsAsDots - * if true, spots will be painted as single dots - * instead their shape. + * if true, spots will be painted as single dots. If + * false they will be painted with their shape. * @param exportTracksOnly * if true, only the spots belonging to visible * tracks will be painted. If false, spots not @@ -480,8 +492,8 @@ public static final Img< FloatType > createLabelImg( * nZSlices, nFrames) as a 4 element long array. Spots outside * these dimensions are ignored. * @param exportSpotsAsDots - * if true, spots will be painted as single dots - * instead of their shape. + * if true, spots will be painted as single dots. If + * false they will be painted with their shape. * @param labelIdPainting * specifies how to paint the label ID of spots. The * {@link LabelIdPainting#LABEL_IS_TRACK_ID} is not supported and diff --git a/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java b/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java index c7838ff14..d9c6dc335 100644 --- a/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java @@ -54,6 +54,9 @@ public interface TrackMateAction /** * Sets the logger that will receive logs when this action is executed. + * + * @param logger + * the logger. */ public void setLogger( Logger logger ); } diff --git a/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java b/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java index 65dce45e0..90e73da00 100644 --- a/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java +++ b/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java @@ -88,7 +88,9 @@ public default List< GapClosingParameter > getParameters() * Performs the gap closing. * * @param trackmate + * the trackmate instance to operate on. * @param logger + * a logger instance to echoes the gap-closing process. */ public void execute( TrackMate trackmate, Logger logger ); diff --git a/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterPanel.java b/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterPanel.java index c432dc5ce..38c20c166 100644 --- a/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterPanel.java @@ -200,9 +200,9 @@ public SpotFitterPanel( final List< String > fits, final List< String > docs, fi } /** - * 1-based. + * Returns the selected channel. 1-based. * - * @return + * @return the selected channel. */ public int getSelectedChannel() { diff --git a/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java b/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java index 8ccdfb210..7f66e5671 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java @@ -40,8 +40,8 @@ import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.util.MedianFilter2D; -import fiji.plugin.trackmate.util.Threads; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.util.Threads; import ij.ImagePlus; import net.imagej.ImgPlus; import net.imagej.axis.Axes; @@ -271,6 +271,8 @@ public static final Img< FloatType > createLoGKernel( final double radius, final * @return a new float Img. Careful: even if the specified interval does not * start at (0, 0), the new image will have its first pixel at * coordinates (0, 0). + * @param + * the pixel type of the input image. */ public static final < T extends RealType< T > > Img< FloatType > copyToFloatImg( final RandomAccessible< T > img, final Interval interval, final ImgFactory< FloatType > factory ) { @@ -323,7 +325,14 @@ public static final Interval squeeze( final Interval interval ) } /** - * Apply a simple 3x3 median filter to the target image. + * Applies a simple 3x3 median filter to the target image. + * + * @param + * the pixel type in the image. + * @param image + * the image to filter. + * @return the filtered image, as a new image, or null if there + * was a problem during processing. */ public static final < R extends RealType< R > & NativeType< R > > Img< R > applyMedianFilter( final RandomAccessibleInterval< R > image ) { diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index d41c20459..580adda5c 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -298,12 +298,14 @@ public static < R extends IntegerType< R > > List< Spot > fromLabeling( /** * Creates spots by thresholding a grayscale image. A spot is created for - * each connected-component object in the thresholded input, with a size that - * matches the mask size. The quality of the spots is read from another + * each connected-component object in the thresholded input, with a size + * that matches the mask size. The quality of the spots is read from another * image, by taking the max pixel value of this image with the ROI. * * @param - * the type of the input image. Must be real, scalar. + * the pixel type of the input image. Must be real, scalar. + * @param + * the pixel type of the quality image. Must be real, scalar. * @param input * the input image. * @param interval diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index f49ac5102..69a7d6ea2 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -40,6 +40,7 @@ * @author Jean-Yves Tinevez, 2023 * * @param + * the pixel type in the image processed. */ public class Process2DZ< T extends RealType< T > & NativeType< T > > extends MultiThreadedBenchmarkAlgorithm diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java index 58ef2a739..b57714cc7 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java @@ -35,6 +35,7 @@ * @author Jean-Yves Tinevez * * @param + * the pixel type in the image processed by the detector. */ public interface SpotDetectorFactory< T extends RealType< T > & NativeType< T > > extends SpotDetectorFactoryBase< T > { @@ -53,6 +54,7 @@ public interface SpotDetectorFactory< T extends RealType< T > & NativeType< T > * then the interval must be 3D). * @param frame * the frame index in the source image to operate on + * @return a new detector. */ public SpotDetector< T > getDetector( final Interval interval, int frame ); } diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java index d4ef93d61..2208b188f 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java @@ -65,11 +65,12 @@ public interface SpotDetectorFactoryBase< T extends RealType< T > & NativeType< * @see #setTarget(ImgPlus, Map) * @see #marshall(Map, Element) * @see #unmarshall(Element, Map) + * @return an error message. */ public String getErrorMessage(); /** - * Marshalls a settings map to a JDom element, ready for saving to XML. The + * Marshals a settings map to a JDom element, ready for saving to XML. The * element is updated with new attributes. *

* Only parameters specific to the specific detector factory are marshalled. @@ -77,13 +78,18 @@ public interface SpotDetectorFactoryBase< T extends RealType< T > & NativeType< * {@value DetectorKeys#XML_ATTRIBUTE_DETECTOR_NAME} that saves the target * {@link SpotDetectorFactory} key. * + * @param settings + * the settings map to marshal. + * @param element + * the element to marshal to. * @return true if marshalling was successful. If not, check * {@link #getErrorMessage()} + * */ public boolean marshall( final Map< String, Object > settings, final Element element ); /** - * Un-marshalls a JDom element to update a settings map. + * Un-marshals a JDom element to update a settings map. * * @param element * the JDom element to read from. @@ -106,6 +112,7 @@ public interface SpotDetectorFactoryBase< T extends RealType< T > & NativeType< * @param model * the current model, used to get info to display on the GUI * panel. + * @return a new configuration panel. */ public ConfigurationPanel getDetectorConfigurationPanel( final Settings settings, final Model model ); @@ -118,7 +125,7 @@ public interface SpotDetectorFactoryBase< T extends RealType< T > & NativeType< public Map< String, Object > getDefaultSettings(); /** - * Check that the given settings map is suitable for target detector. + * Checks that the given settings map is suitable for target detector. * * @param settings * the map to test. @@ -127,8 +134,8 @@ public interface SpotDetectorFactoryBase< T extends RealType< T > & NativeType< public boolean checkSettings( final Map< String, Object > settings ); /** - * Return true for the detectors that can provide a spot with a - * 2D SpotRoi when they operate on 2D images. + * Returns true for the detectors that can provide a spot with + * a 2D SpotRoi when they operate on 2D images. *

* This flag may be used by clients to exploit the fact that the spots * created with this detector will have a contour that can be used @@ -145,8 +152,8 @@ public default boolean has2Dsegmentation() } /** - * Return true for the detectors that can provide a spot with a - * 3D SpotMesh when they operate on 3D images. + * Returns true for the detectors that can provide a spot with + * a 3D SpotMesh when they operate on 3D images. *

* This flag may be used by clients to exploit the fact that the spots * created with this detector will have a 3D mesh that can be used diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java index 2793d46e9..ddc881490 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java @@ -33,6 +33,7 @@ * @author Jean-Yves Tinevez * * @param + * the pixel type in the image processed by the detector. */ public interface SpotGlobalDetectorFactory< T extends RealType< T > & NativeType< T > > extends SpotDetectorFactoryBase< T > { @@ -49,6 +50,7 @@ public interface SpotGlobalDetectorFactory< T extends RealType< T > & NativeType * (e.g. if the source image is 2D+T (3D), then the * interval must be 2D; if the source image is 3D without time, * then the interval must be 3D). + * @return a new detector. */ public SpotGlobalDetector< T > getDetector( final Interval interval ); diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index d0e063765..ec8a32583 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -281,7 +281,7 @@ private static < S extends RealType< S > > Spot regionToSpotMesh( * used to put back the mesh coordinates with respect to the * initial source image (same referential that for the quality * image). - * @return + * @return a new spot. */ public static < S extends RealType< S > > SpotMesh meshToSpotMesh( final Mesh mesh, diff --git a/src/main/java/fiji/plugin/trackmate/features/FeatureAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/FeatureAnalyzer.java index a84cf174b..8f22b8513 100644 --- a/src/main/java/fiji/plugin/trackmate/features/FeatureAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/FeatureAnalyzer.java @@ -32,21 +32,29 @@ public interface FeatureAnalyzer extends TrackMateModule /** * Returns the list of features this analyzer can compute. + * + * @return the list of features. */ public List< String > getFeatures(); /** * Returns the map of short names for any feature the analyzer can compute. + * + * @return the map of feature short names. */ public Map< String, String > getFeatureShortNames(); /** * Returns the map of names for any feature this analyzer can compute. + * + * @return the map of feature names. */ public Map< String, String > getFeatureNames(); /** * Returns the map of feature dimension this analyzer can compute. + * + * @return the map of feature dimension. */ public Map< String, Dimension > getFeatureDimensions(); @@ -54,6 +62,8 @@ public interface FeatureAnalyzer extends TrackMateModule * Returns the map that states whether the key feature is a feature that * returns integers. If true, then special treatment is applied * when saving/loading, etc. for clarity and precision. + * + * @return the map. */ public Map< String, Boolean > getIsIntFeature(); diff --git a/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java b/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java index 4c8cb71eb..639802788 100644 --- a/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java +++ b/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java @@ -158,12 +158,18 @@ public static final Map< String, String > collectFeatureKeys( final TrackMateObj } /** - * Missing or undefined values are not included. + * Collect feature values from the specified model. Missing or undefined + * values are not included. * * @param featureKey + * the key of the feature to collect values from. * @param target + * the type of object the feature is defined for. * @param model + * the model to read from. * @param visibleOnly + * if true feature values will be collected only + * from the objects marked as visible. * @return a new double[] array containing the numerical * feature values. */ diff --git a/src/main/java/fiji/plugin/trackmate/features/SpotFeatureCalculator.java b/src/main/java/fiji/plugin/trackmate/features/SpotFeatureCalculator.java index 18e109500..74977f052 100644 --- a/src/main/java/fiji/plugin/trackmate/features/SpotFeatureCalculator.java +++ b/src/main/java/fiji/plugin/trackmate/features/SpotFeatureCalculator.java @@ -40,8 +40,8 @@ import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.features.spot.SpotAnalyzer; import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactoryBase; -import fiji.plugin.trackmate.util.Threads; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.util.Threads; import net.imagej.ImgPlus; import net.imglib2.algorithm.MultiThreaded; import net.imglib2.algorithm.MultiThreadedBenchmarkAlgorithm; @@ -127,6 +127,11 @@ public boolean process() * Calculates all the spot features configured in the {@link Settings} * object, but only for the spots in the specified collection. Features are * calculated for each spot, using their location, and the raw image. + * + * @param toCompute + * the spot collection. + * @param doLogIt + * if true the computation will be logged. */ public void computeSpotFeatures( final SpotCollection toCompute, final boolean doLogIt ) { diff --git a/src/main/java/fiji/plugin/trackmate/features/TrackFeatureCalculator.java b/src/main/java/fiji/plugin/trackmate/features/TrackFeatureCalculator.java index 0b3136040..a103b427d 100644 --- a/src/main/java/fiji/plugin/trackmate/features/TrackFeatureCalculator.java +++ b/src/main/java/fiji/plugin/trackmate/features/TrackFeatureCalculator.java @@ -114,6 +114,11 @@ public boolean process() /** * Calculates all the track features configured in the {@link Settings} * object for the specified tracks. + * + * @param trackIDs + * the IDs of the track to compute the features of. + * @param doLogIt + * if true the computation will be logged. */ public void computeTrackFeatures( final Collection< Integer > trackIDs, final boolean doLogIt ) { diff --git a/src/main/java/fiji/plugin/trackmate/features/edges/EdgeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/edges/EdgeAnalyzer.java index 5c33d038f..a1a062ecc 100644 --- a/src/main/java/fiji/plugin/trackmate/features/edges/EdgeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/edges/EdgeAnalyzer.java @@ -66,6 +66,8 @@ public interface EdgeAnalyzer extends Benchmark, FeatureAnalyzer, MultiThreaded *

* Example of non-local edge feature: the local curvature of the trajectory, * which depends on the neighbor edges. + * + * @return whether this analyzer is a local analyzer. */ public boolean isLocal(); diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java index 76d2d80b2..e660ca701 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java @@ -235,9 +235,12 @@ else if ( A < 0 ) } /** - * Computes the Moore–Penrose pseudoinverse using the SVD method. - * - * Modified version of the original implementation by Kim van der Linde. + * Computes the Moore–Penrose pseudoinverse using the SVD method. Modified + * version of the original implementation by Kim van der Linde. + * + * @param x + * the matrix. + * @return the pseudo-inverse as a new matrix. */ public static Matrix pinv( final Matrix x ) { diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotAnalyzerFactoryBase.java b/src/main/java/fiji/plugin/trackmate/features/spot/SpotAnalyzerFactoryBase.java index 4b36ee8fa..80004d10e 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotAnalyzerFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/SpotAnalyzerFactoryBase.java @@ -53,6 +53,7 @@ public interface SpotAnalyzerFactoryBase< T extends RealType< T > & NativeType< * the target frame to operate on. * @param channel * the target channel to operate on. + * @return a new spot analyzer. */ public SpotAnalyzer< T > getAnalyzer( ImgPlus< T > img, int frame, int channel ); diff --git a/src/main/java/fiji/plugin/trackmate/features/track/TrackAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/track/TrackAnalyzer.java index 74cd391cc..6e207a605 100644 --- a/src/main/java/fiji/plugin/trackmate/features/track/TrackAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/track/TrackAnalyzer.java @@ -23,10 +23,10 @@ import java.util.Collection; -import net.imglib2.algorithm.Benchmark; -import net.imglib2.algorithm.MultiThreaded; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.features.FeatureAnalyzer; +import net.imglib2.algorithm.Benchmark; +import net.imglib2.algorithm.MultiThreaded; /** * Mother interface for the classes that can compute the feature of tracks. @@ -80,6 +80,8 @@ public interface TrackAnalyzer extends Benchmark, FeatureAnalyzer, MultiThreaded *

* Example of a non-local track feature: the rank of the track sorted by its * number of spots, compared to other tracks. + * + * @return whether this analyzer is a local analyzer. */ public boolean isLocal(); diff --git a/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java b/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java index 2bfbee9f9..f1ed71a80 100644 --- a/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java +++ b/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java @@ -43,6 +43,10 @@ public class GraphUtils { /** + * Pretty-prints a model. + * + * @param model + * the model. * @return a pretty-print string representation of a {@link TrackModel}, as * long it is a tree (each spot must not have more than one * predecessor). @@ -379,8 +383,14 @@ private static char[] makeChars( final int width, final char c ) } /** - * @return true only if the given model is a tree; that is: every spot has - * one or less predecessors. + * Returns the siblings of a spot. That is: all the spots that have the same + * predecessor. + * + * @param cache + * a neighbor cache. + * @param spot + * the spot to inspect. + * @return a new set made of the spot siblings. Includes the spot. */ public static final Set< Spot > getSibblings( final NeighborCache< Spot, DefaultWeightedEdge > cache, final Spot spot ) { diff --git a/src/main/java/fiji/plugin/trackmate/graph/SortedDepthFirstIterator.java b/src/main/java/fiji/plugin/trackmate/graph/SortedDepthFirstIterator.java index c176c36f6..c97cb348d 100644 --- a/src/main/java/fiji/plugin/trackmate/graph/SortedDepthFirstIterator.java +++ b/src/main/java/fiji/plugin/trackmate/graph/SortedDepthFirstIterator.java @@ -132,7 +132,9 @@ private static enum VisitColor * the graph to be iterated. * @param startVertex * the vertex iteration to be started. - * + * @param comparator + * used to compare the several children of a vertex, and + * specifies in what order they are iterated. * @throws IllegalArgumentException * if g==null or does not contain * startVertex @@ -276,7 +278,7 @@ private static < V, E > Specifics< V, E > createGraphSpecifics( final Graph< V, return new UndirectedSpecifics<>( g ); } - /** + /* * This is where we add the multiple children in proper sorted order. */ protected void addUnseenChildrenOf( final V vertex ) @@ -352,18 +354,12 @@ private boolean isConnectedComponentExhausted() } } - /** - * @param edge - */ protected void encounterVertex( final V vertex, final E edge ) { seen.put( vertex, VisitColor.WHITE ); stack.addLast( vertex ); } - /** - * @param edge - */ protected void encounterVertexAgain( final V vertex, final E edge ) { final VisitColor color = seen.get( vertex ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java b/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java index 3b59b62d8..8428a78f5 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java @@ -121,8 +121,10 @@ public static Color textColorForBackground( final Color backgroundColor ) * https://stackoverflow.com/questions/4593469/java-how-to-convert-rgb-color-to-cie-lab * * @param a + * the first color. * @param b - * @return + * the second color. + * @return the distance between the two colors. */ public static double colorDistance( final Color a, final Color b ) { @@ -179,7 +181,12 @@ public static final Color invert( final Color color ) } /** - * Positions a JFrame more or less cleverly next a {@link Component}. + * Positions a window more or less cleverly next a {@link Component}. + * + * @param gui + * the window to position. + * @param component + * the component to position the window with respect to. */ public static void positionWindow( final Window gui, final Component component ) { diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/CategoryJComboBox.java b/src/main/java/fiji/plugin/trackmate/gui/components/CategoryJComboBox.java index 5dbe8031c..dcf03a345 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/CategoryJComboBox.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/CategoryJComboBox.java @@ -270,9 +270,6 @@ public void actionPerformed( final ActionEvent e ) im.put( KeyStroke.getKeyStroke( KeyEvent.VK_KP_DOWN, 0 ), "selectNext" ); } - /** - * Demo - */ public static void main( final String[] args ) { // diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigurationPanel.java index 694daa0bf..583206e2a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigurationPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigurationPanel.java @@ -45,12 +45,18 @@ public abstract class ConfigurationPanel extends JPanel public final ActionEvent PREVIEW_BUTTON_PUSHED = new ActionEvent( this, 0, "PreviewButtonPushed" ); /** - * Echo the parameters of the given settings on this panel. + * Echoes the parameters of the given settings on this panel. + * + * @param settings + * the settings as a map. */ public abstract void setSettings( final Map< String, Object > settings ); /** - * @return a new settings map object with its values set by this panel. + * Returns a new settings map of string-object with its values set by this + * panel. + * + * @return a new map. */ public abstract Map< String, Object > getSettings(); diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/FeatureDisplaySelector.java b/src/main/java/fiji/plugin/trackmate/gui/components/FeatureDisplaySelector.java index 519168ad1..019204688 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/FeatureDisplaySelector.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/FeatureDisplaySelector.java @@ -138,9 +138,15 @@ private double[] autoMinMax( final TrackMateObject target ) } /** - * Return a {@link CategoryJComboBox} that lets a user select among all - * available features in TrackMate. + * Returns a {@link CategoryJComboBox} that lets a user select among all + * available features in TrackMate. The features are read from the model and + * settings, and the model is listened to so that the combo-box is updated + * when new features are added to the model. * + * @param model + * the model to read existing features from. + * @param settings + * the settings to read configured features from. * @return a new {@link CategoryJComboBox}. */ public static final CategoryJComboBox< TrackMateObject, String > createComboBoxSelector( final Model model, final Settings settings ) diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/FilterGuiPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/FilterGuiPanel.java index b473be9d5..89b6f7410 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/FilterGuiPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/FilterGuiPanel.java @@ -238,7 +238,9 @@ public void stateChanged( final ChangeEvent e ) } /** - * Returns the thresholds currently set by this GUI. + * Returns the filters currently set by this GUI. + * + * @return the list of filters. */ public List< FeatureFilter > getFeatureFilters() { @@ -246,10 +248,13 @@ public List< FeatureFilter > getFeatureFilters() } /** - * Add an {@link ChangeListener} to this panel. The {@link ChangeListener} + * Adds a {@link ChangeListener} to this panel. The {@link ChangeListener} * will be notified when a change happens to the thresholds displayed by * this panel, whether due to the slider being move, the auto-threshold * button being pressed, or the combo-box selection being changed. + * + * @param listener + * the listener to add. */ public void addChangeListener( final ChangeListener listener ) { @@ -257,9 +262,12 @@ public void addChangeListener( final ChangeListener listener ) } /** - * Remove a ChangeListener from this panel. + * Removes a ChangeListener from this panel. * - * @return true if the listener was in listener collection of this instance. + * @param listener + * the listener to remove. + * @return true if the listener was in listener collection of + * this instance. */ public boolean removeChangeListener( final ChangeListener listener ) { diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/FilterPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/FilterPanel.java index 0d61c975c..9b5d23047 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/FilterPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/FilterPanel.java @@ -70,8 +70,8 @@ import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.gui.GuiUtils; -import fiji.plugin.trackmate.util.Threads; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.util.Threads; import fiji.util.NumberParser; /** @@ -307,10 +307,13 @@ public FeatureFilter getFilter() } /** - * Add an {@link ChangeListener} to this panel. The {@link ChangeListener} + * Adds a {@link ChangeListener} to this panel. The {@link ChangeListener} * will be notified when a change happens to the threshold displayed by this * panel, whether due to the slider being move, the auto-threshold button * being pressed, or the combo-box selection being changed. + * + * @param listener + * the listener to add. */ public void addChangeListener( final ChangeListener listener ) { @@ -318,8 +321,10 @@ public void addChangeListener( final ChangeListener listener ) } /** - * Remove an ChangeListener. + * Removes a ChangeListener. * + * @param listener + * the listener to remove. * @return true if the listener was in listener collection of this instance. */ public boolean removeChangeListener( final ChangeListener listener ) diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/InitFilterPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/InitFilterPanel.java index 7c8e0da9b..247cc96bb 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/InitFilterPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/InitFilterPanel.java @@ -62,7 +62,10 @@ public class InitFilterPanel extends JPanel * Default constructor, initialize component. * * @param filter + * the filter to initialize the panel with. * @param valueCollector + * a function that can return the value collection of a specified + * feature. */ public InitFilterPanel( final FeatureFilter filter, final Function< String, double[] > valueCollector ) { @@ -135,7 +138,9 @@ public void refresh() } /** - * Return the feature threshold on quality set by this panel. + * Returns the feature threshold on quality set by this panel. + * + * @return the feature threshold. */ public FeatureFilter getFeatureThreshold() { diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/tracker/JPanelFeatureSelectionGui.java b/src/main/java/fiji/plugin/trackmate/gui/components/tracker/JPanelFeatureSelectionGui.java index fab2155f4..e8697378d 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/tracker/JPanelFeatureSelectionGui.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/tracker/JPanelFeatureSelectionGui.java @@ -86,6 +86,11 @@ public JPanelFeatureSelectionGui() /** * Set the features and their names that should be presented by this GUI. * The user will be allowed to choose amongst the given features. + * + * @param features + * the features to add in the GUI. + * @param featureNames + * the feature names that will be displayed. */ public void setDisplayFeatures( final Collection< String > features, final Map< String, String > featureNames ) { diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java index 1e4862069..7fd52abd9 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java @@ -160,6 +160,15 @@ public static List< Colormap > getAvailableLUTs() /** * Create a paint scale with given lower and upper bound, and a specified * default color. + * + * @param name + * the name of the colormap. + * @param lowerBound + * the lower bound. + * @param upperBound + * the upper bound. + * @param defaultColor + * a default color. */ public Colormap( final String name, final double lowerBound, final double upperBound, final Color defaultColor ) { @@ -172,6 +181,13 @@ public Colormap( final String name, final double lowerBound, final double upperB /** * Create a paint scale with a given lower and upper bound and a default * black color. + * + * @param name + * the name of the colormap. + * @param lowerBound + * the lower bound. + * @param upperBound + * the upper bound. */ public Colormap( final String name, final double lowerBound, final double upperBound ) { @@ -181,6 +197,9 @@ public Colormap( final String name, final double lowerBound, final double upperB /** * Create a paint scale with a lower bound of 0, an upper bound of 1 and a * default black color. + * + * @param name + * the colormap name. */ public Colormap( final String name ) { @@ -201,6 +220,11 @@ public String getName() * by value. If value is greater than the upper * bound or lower than the lower bound set at construction, this call will * be ignored. + * + * @param value + * the value at which to add the color. + * @param color + * the color. */ public void add( final double value, final Color color ) { diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java index 1b00641c2..0c59fa9a2 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -61,6 +61,8 @@ public class SliderPanel extends JPanel implements BoundedValue.UpdateListener * label to show next to the slider. * @param model * the value that is modified. + * @param spinnerStepSize + * the step size in the spinner to create. */ public SliderPanel( final String name, final BoundedValue model, final int spinnerStepSize ) { diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java index 9e49e5162..eb9fe13c3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -69,13 +69,15 @@ public interface RangeListener } /** - * Create a {@link SliderPanelDouble} to modify a given {@link BoundedValueDouble value}. + * Create a {@link SliderPanelDouble} to modify a given + * {@link BoundedValueDouble value}. * * @param name * label to show next to the slider. * @param model * the value that is modified. * @param spinnerStepSize + * the steps size for the spinner created. */ public SliderPanelDouble( final String name, diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java index 04d3cc6df..c24dcb10b 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java @@ -88,6 +88,7 @@ public List< String > getSelectedAnalyzers( final TrackMateObject obj ) * analyzers in this selection. * * @param settings + * the settings to configure. */ public void configure( final Settings settings ) { diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardSequence.java index ba8b9fb23..51660bf66 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardSequence.java @@ -37,7 +37,9 @@ public interface WizardSequence /** * Launches the wizard to play this sequence. - * + * + * @param title + * the title of the frame in which the wizard is displayed. * @return the {@link JFrame} in which the wizard is displayed. */ public default JFrame run( final String title ) diff --git a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java b/src/main/java/fiji/plugin/trackmate/io/IOUtils.java index 958bba21f..5a5a3cdf7 100644 --- a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java +++ b/src/main/java/fiji/plugin/trackmate/io/IOUtils.java @@ -294,6 +294,14 @@ protected JDialog createDialog( final Component lParent ) throws HeadlessExcepti * Read and return an integer attribute from a JDom {@link Element}, and * substitute a default value of 0 if the attribute is not found or of the * wrong type. + * + * @param element + * the element to read from. + * @param name + * the name of the integer attribute. + * @param logger + * error messages will be logged via this logger. + * @return the int value. */ public static final int readIntAttribute( final Element element, final String name, final Logger logger ) { @@ -474,6 +482,12 @@ public static final boolean readStringAttribute( final Element element, final Ma * are added to the specified map. If a value is found not to be a * double, an error is returned. * + * @param element + * the element to unmarshall. + * @param map + * the map the unmarshalled info will be added to. + * @param errorHolder + * error messages will be appended to this buffer. * @return true if all values were found and mapped as doubles, * false otherwise and the error holder is updated. */ @@ -545,6 +559,8 @@ public static final boolean writeDownsamplingFactor( final Map< String, Object > * the key to the parameter value in the map * @param expectedClass * the expected class for the value + * @param errorHolder + * a buffer to append possible errors to. * @return true if the parameter was found, of the right class, * and was successfully added to the element, false if * not, and updated the specified error holder. @@ -572,6 +588,11 @@ public static final boolean writeAttribute( final Map< String, Object > settings /** * Stores the given mapping in a given JDom element, using attributes in a * KEY="VALUE" fashion. + * + * @param map + * the map. + * @param element + * the element to write the map into. */ public static void marshallMap( final Map< String, Double > map, final Element element ) { diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index cd0bfaf0b..28d91135a 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -196,7 +196,10 @@ public class TmXmlReader */ /** - * Initialize this reader to read the file given in argument. + * Initializes this reader to read the file given in argument. + * + * @param file + * the file to read. */ public TmXmlReader( final File file ) { @@ -230,6 +233,8 @@ public TmXmlReader( final File file ) /** * Returns the log text saved in the file, or null if log text * was not saved. + * + * @return the log. */ public String getLog() { @@ -445,7 +450,7 @@ public Settings readSettings( final ImagePlus imp ) * file. * * @param imp - * + * the image to store in the new Settings object. * @param detectorProvider * the detector provider, required to configure the settings with * a correct SpotDetectorFactory. If @@ -470,6 +475,7 @@ public Settings readSettings( final ImagePlus imp ) * the spot 2D morphology provider. * @param spot3DMorphologyAnalyzerProvider * the spot 3D morphology provider. + * @return a new Settings object. */ public Settings readSettings( final ImagePlus imp, @@ -534,6 +540,8 @@ public Settings readSettings( /** * Returns the version string stored in the file. + * + * @return the version string stored in the file. */ public String getVersion() { @@ -1012,7 +1020,12 @@ private SpotCollection getSpots( final Element modelElement ) * into the model specified. The track collection element is expected to be * found as a child of the specified element. * - * @return true if reading tracks was successful, false otherwise. + * @param modelElement + * the element to read from. + * @param model + * the model to add to. + * @return true if reading tracks was successful, + * false otherwise. */ protected boolean readTracks( final Element modelElement, final Model model ) { diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index e422d23e3..74be85e77 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -175,6 +175,8 @@ public TmXmlWriter( final File file ) * * @param file * the xml file to write to, will be overwritten. + * @param logger + * a logger instance to log writing progress and errors. */ public TmXmlWriter( final File file, final Logger logger ) { @@ -191,6 +193,12 @@ public TmXmlWriter( final File file, final Logger logger ) /** * Writes the document to the file. Content must be appended first. * + * @throws FileNotFoundException + * if the file exists but is a directory rather than a regular + * file, does not exist but cannot be created, or cannot be + * opened for any other reason. + * @throws IOException + * if there's any problem writing. * @see #appendLog(String) * @see #appendModel(Model) * @see #appendSettings(Settings) diff --git a/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactory.java b/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactory.java index c867b045a..e06a8e5af 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactory.java @@ -56,14 +56,18 @@ public interface SpotTrackerFactory extends TrackMateModule public ConfigurationPanel getTrackerConfigurationPanel( final Model model ); /** - * Marshalls a settings map to a JDom element, ready for saving to XML. The + * Marshals a settings map to a JDom element, ready for saving to XML. The * element is updated with new attributes. *

* Only parameters specific to the concrete tracker factory are marshalled. * The element also always receive an attribute named * {@value TrackerKeys#XML_ATTRIBUTE_TRACKER_NAME} that saves the target * {@link SpotTracker} key. - * + * + * @param settings + * the settings map to marshal. + * @param element + * the element to marshal to. * @return true if marshalling was successful. If not, check * {@link #getErrorMessage()} */ @@ -109,7 +113,9 @@ public interface SpotTrackerFactory extends TrackMateModule * validity check is strict: we check that all needed parameters are here * and are of the right class, and that there is no extra unwanted * parameters. - * + * + * @param settings + * the settings map. * @return true if the settings map can be used with the target factory. If * not, check {@link #getErrorMessage()} */ 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..09a714ed3 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/LAPUtils.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/LAPUtils.java @@ -43,6 +43,7 @@ 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_MAX_FRAME_GAP; +import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_KALMAN_SEARCH_RADIUS; 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_MERGING_FEATURE_PENALTIES; @@ -74,7 +75,6 @@ import javax.swing.table.TableModel; import fiji.plugin.trackmate.Spot; -import static fiji.plugin.trackmate.tracking.TrackerKeys.KEY_KALMAN_SEARCH_RADIUS; public class LAPUtils { @@ -183,8 +183,7 @@ public static String echoFeaturePenalties( final Map< String, Double > featurePe } /** - * Compute the cost to link two spots, in the default way for the TrackMate - * trackmate. + * Computes the cost to link two spots, in the default way for TrackMate. *

* This cost is calculated as follow: *

    @@ -209,6 +208,19 @@ public static String echoFeaturePenalties( final Map< String, Double > featurePe * For instance: if 2 spots differ by twice the value in a feature which is * in the penalty map with a factor of 1, they will look as if they * were twice as far. + * + * @param s0 + * the source spot. + * @param s1 + * the target spot. + * @param distanceCutOff + * the distance cutoff. + * @param blockingValue + * the blocking value. + * @param featurePenalties + * the feature penalties, as a map of feature keys to penalty + * weight. + * @return the linking cost. */ public static final double computeLinkingCostFor( final Spot s0, final Spot s1, final double distanceCutOff, final double blockingValue, final Map< String, Double > featurePenalties ) { @@ -233,18 +245,25 @@ public static final double computeLinkingCostFor( final Spot s0, final Spot s1, } /** - * @return true if the settings map can be used with the LAP trackers. We do - * not check that all the spot features used in penalties are indeed - * found in all spots, because if such a feature is absent from one - * spot, the LAP trackers simply ignores the penalty and does not - * generate an error. + * Returns true if the settings map can be used with the LAP + * trackers. We do not check that all the spot features used in penalties + * are indeed found in all spots, because if such a feature is absent from + * one spot, the LAP trackers simply ignores the penalty and does not + * generate an error. + * * @param settings * the map to test. + * @param linking + * if true will also test for the presence of the + * frame-to-frame linking keys. If false will only + * test for the segment linking keys. * @param errorHolder * a {@link StringBuilder} that will contain an error message if * the check is not successful. + * @return true if the settings map can be used with the LAP + * trackers. */ - public static final boolean checkSettingsValidity( final Map< String, Object > settings, final StringBuilder errorHolder, boolean linking ) + public static final boolean checkSettingsValidity( final Map< String, Object > settings, final StringBuilder errorHolder, final boolean linking ) { if ( null == settings ) { diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/DefaultCostMatrixCreator.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/DefaultCostMatrixCreator.java index 13099bee9..7ff0bea19 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/DefaultCostMatrixCreator.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/DefaultCostMatrixCreator.java @@ -35,6 +35,9 @@ * @author Jean-Yves Tinevez - 2014 * * @param + * the type of sources. + * @param + * the type of targets. */ public class DefaultCostMatrixCreator< K extends Comparable< K >, J extends Comparable< J > > implements CostMatrixCreator< K, J > { diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/JaqamanLinkingCostMatrixCreator.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/JaqamanLinkingCostMatrixCreator.java index e9586b238..4292d750e 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/JaqamanLinkingCostMatrixCreator.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/JaqamanLinkingCostMatrixCreator.java @@ -35,7 +35,9 @@ * @author Jean-Yves Tinevez - 2014 * * @param + * the type of sources. * @param + * the type of targets. */ public class JaqamanLinkingCostMatrixCreator< K extends Comparable< K >, J extends Comparable< J > > implements CostMatrixCreator< K, J > { diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/JaqamanSegmentCostMatrixCreator.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/JaqamanSegmentCostMatrixCreator.java index 8a03bcdb3..cd1ebbb82 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/JaqamanSegmentCostMatrixCreator.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/JaqamanSegmentCostMatrixCreator.java @@ -37,12 +37,6 @@ import static fiji.plugin.trackmate.util.TMUtils.checkMapKeys; import static fiji.plugin.trackmate.util.TMUtils.checkParameter; -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.tracking.jaqaman.costfunction.CostFunction; -import fiji.plugin.trackmate.tracking.jaqaman.costfunction.FeaturePenaltyCostFunction; -import fiji.plugin.trackmate.tracking.jaqaman.costfunction.SquareDistCostFunction; -import fiji.plugin.trackmate.util.Threads; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -50,10 +44,16 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import net.imglib2.algorithm.MultiThreaded; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultWeightedEdge; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.tracking.jaqaman.costfunction.CostFunction; +import fiji.plugin.trackmate.tracking.jaqaman.costfunction.FeaturePenaltyCostFunction; +import fiji.plugin.trackmate.tracking.jaqaman.costfunction.SquareDistCostFunction; +import fiji.plugin.trackmate.util.Threads; +import net.imglib2.algorithm.MultiThreaded; + /** * This class generates the top-left quadrant of the LAP segment linking cost * matrix, following Jaqaman et al., 2008 Nature Methods. It can @@ -98,7 +98,13 @@ public class JaqamanSegmentCostMatrixCreator implements CostMatrixCreator< Spot, /** * Instantiates a cost matrix creator for the top-left quadrant of the * segment linking cost matrix. - * + * + * @param graph + * the graph from which connected components (segments) will be + * extracted. + * @param settings + * the settings for the cost matrix, as map containing the + * Jaqaman LAP keys. */ public JaqamanSegmentCostMatrixCreator( final Graph< Spot, DefaultWeightedEdge > graph, final Map< String, Object > settings ) { diff --git a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/SparseCostMatrix.java b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/SparseCostMatrix.java index e214eabe2..97782786d 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/SparseCostMatrix.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/costmatrix/SparseCostMatrix.java @@ -101,7 +101,7 @@ public class SparseCostMatrix * These two arrays must be arranged row by row, starting with the first * one. And in each row, the columns must be sorted in increasing order (to * facilitate index search). Also, each row must have at least one - * non-infinte cost. If not, an {@link IllegalArgumentException} is thrown. + * non-infinite cost. If not, an {@link IllegalArgumentException} is thrown. *
      *
    1. number an int[] array, with one element per * row, that contains the number of non infinite cost for a row. @@ -113,6 +113,8 @@ public class SparseCostMatrix * the column index of each cost. * @param number * the number of element for each row. + * @param nCols + * the number of columns in the cost matrix. * @throws IllegalArgumentException * if the cost and column arrays are not of the same size, if * the column array is not sorted row by row, of if one row has diff --git a/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java b/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java index 1bee98031..1cb6ce27c 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java @@ -86,11 +86,20 @@ public class KalmanTracker implements SpotTracker, Benchmark, Cancelable */ /** + * Creates a new Kalman tracker. + * * @param spots * the spots to track. * @param maxSearchRadius + * the maximal search radius to continue a track, in physical + * units. * @param maxFrameGap + * the max frame gap when detections are missing, after which a + * track will be stopped. * @param initialSearchRadius + * the initial search radius to nucleate new tracks. + * @param featurePenalties + * the feature penalties. */ public KalmanTracker( final SpotCollection spots, final double maxSearchRadius, final int maxFrameGap, final double initialSearchRadius, final Map< String, Double > featurePenalties ) { diff --git a/src/main/java/fiji/plugin/trackmate/util/ChartExporter.java b/src/main/java/fiji/plugin/trackmate/util/ChartExporter.java index 97f58371d..3da46c7be 100644 --- a/src/main/java/fiji/plugin/trackmate/util/ChartExporter.java +++ b/src/main/java/fiji/plugin/trackmate/util/ChartExporter.java @@ -61,7 +61,7 @@ public class ChartExporter * @param height * the height of the panel the chart is painted in. * @throws UnsupportedEncodingException - * @throws IOException + * If the UTF-8 encoding is not supported. */ public static void exportChartAsSVG( final File svgFile, final JFreeChart chart, final int width, final int height ) throws UnsupportedEncodingException, IOException { diff --git a/src/main/java/fiji/plugin/trackmate/util/OnRequestUpdater.java b/src/main/java/fiji/plugin/trackmate/util/OnRequestUpdater.java index cc98c862c..01cf189a9 100644 --- a/src/main/java/fiji/plugin/trackmate/util/OnRequestUpdater.java +++ b/src/main/java/fiji/plugin/trackmate/util/OnRequestUpdater.java @@ -81,6 +81,9 @@ public class OnRequestUpdater extends Thread /** * Constructor autostarts thread + * + * @param refreshable + * the refreshable to update. */ public OnRequestUpdater( final Refreshable refreshable ) { diff --git a/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhoodCursor.java b/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhoodCursor.java index 168e6da30..89185f867 100644 --- a/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhoodCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhoodCursor.java @@ -44,7 +44,7 @@ public class SpotNeighborhoodCursor< T extends RealType< T > > implements Cursor * CONSTRUCTOR */ - public SpotNeighborhoodCursor( SpotNeighborhood< T > sn ) + public SpotNeighborhoodCursor( final SpotNeighborhood< T > sn ) { this.cursor = sn.neighborhood.cursor(); this.calibration = sn.calibration; @@ -59,21 +59,24 @@ public SpotNeighborhoodCursor( SpotNeighborhood< T > sn ) */ /** - * Store the relative calibrated position with respect to the + * Stores the relative calibrated position with respect to the * neighborhood center. + * + * @param position + * an array to store to relative position in. */ - public void getRelativePosition( double[] position ) + public void getRelativePosition( final double[] position ) { cursor.localize( pos ); for ( int d = 0; d < center.length; d++ ) - { position[ d ] = calibration[ d ] * ( pos[ d ] - center[ d ] ); - } } /** - * Return the square distance measured from the center of the domain to the + * Returns the square distance measured from the center of the domain to the * current cursor position, in calibrated units. + * + * @return the square distance. */ public double getDistanceSquared() { @@ -89,32 +92,36 @@ public double getDistanceSquared() } /** - * Return the current inclination with respect to this spot center. Will be + * Returns the current inclination with respect to this spot center. Will be * in the range [0, π]. *

      * In spherical coordinates, the inclination is the angle between the Z axis * and the line OM where O is the sphere center and M is the point location. + * + * @return the inclination. */ public double getTheta() { if ( numDimensions() < 2 ) return 0; - double dx = calibration[ 2 ] * ( cursor.getDoublePosition( 2 ) - center[ 2 ] ); + final double dx = calibration[ 2 ] * ( cursor.getDoublePosition( 2 ) - center[ 2 ] ); return Math.acos( dx / Math.sqrt( getDistanceSquared() ) ); } /** - * Return the azimuth of the spherical coordinates of this cursor, with + * Returns the azimuth of the spherical coordinates of this cursor, with * respect to its center. Will be in the range ]-π, π]. *

      * In spherical coordinates, the azimuth is the angle measured in the plane * XY between the X axis and the line OH where O is the sphere center and H * is the orthogonal projection of the point M on the XY plane. + * + * @return the azimuth. */ public double getPhi() { - double dx = calibration[ 0 ] * ( cursor.getDoublePosition( 0 ) - center[ 0 ] ); - double dy = calibration[ 1 ] * ( cursor.getDoublePosition( 1 ) - center[ 1 ] ); + final double dx = calibration[ 0 ] * ( cursor.getDoublePosition( 0 ) - center[ 0 ] ); + final double dy = calibration[ 1 ] * ( cursor.getDoublePosition( 1 ) - center[ 1 ] ); return Math.atan2( dy, dx ); } @@ -123,25 +130,25 @@ public double getPhi() */ @Override - public void localize( float[] position ) + public void localize( final float[] position ) { cursor.localize( position ); } @Override - public void localize( double[] position ) + public void localize( final double[] position ) { cursor.localize( position ); } @Override - public float getFloatPosition( int d ) + public float getFloatPosition( final int d ) { return cursor.getFloatPosition( d ); } @Override - public double getDoublePosition( int d ) + public double getDoublePosition( final int d ) { return cursor.getDoublePosition( d ); } @@ -165,7 +172,7 @@ public Cursor< T > copy() } @Override - public void jumpFwd( long steps ) + public void jumpFwd( final long steps ) { cursor.jumpFwd( steps ); } @@ -201,25 +208,25 @@ public void remove() } @Override - public void localize( int[] position ) + public void localize( final int[] position ) { cursor.localize( position ); } @Override - public void localize( long[] position ) + public void localize( final long[] position ) { cursor.localize( position ); } @Override - public int getIntPosition( int d ) + public int getIntPosition( final int d ) { return cursor.getIntPosition( d ); } @Override - public long getLongPosition( int d ) + public long getLongPosition( final int d ) { return cursor.getLongPosition( d ); } diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index 6c0f31a4e..3138db8ba 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -45,6 +45,7 @@ import fiji.plugin.trackmate.Dimension; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.Spot; import ij.IJ; import ij.ImagePlus; import net.imagej.ImgPlus; @@ -72,7 +73,17 @@ public class TMUtils */ /** - * Return a new map sorted by its values. + * Returns a new map sorted by its values. + * + * @param + * the type of keys in the map. + * @param + * the type of values in the map. + * @param map + * the map. + * @param comparator + * a comparator to sort based on values. + * @return a new map, with entries sorted by values. */ public static < K, V extends Comparable< ? super V > > Map< K, V > sortByValue( final Map< K, V > map, final Comparator< V > comparator ) { @@ -96,7 +107,13 @@ public int compare( final Entry< K, V > o1, final Entry< K, V > o2 ) } /** - * Generate a string representation of a map, typically a settings map. + * Generates a string representation of a map, typically a settings map. + * + * @param map + * the map. + * @param indent + * the indent size to use. + * @return a representation of the map. */ public static final String echoMap( final Map< String, Object > map, final int indent ) { @@ -133,8 +150,14 @@ else if ( obj instanceof Logger ) } /** - * Wraps an IJ {@link ImagePlus} in an imglib2 {@link ImgPlus}, abinding to - * a returned type. + * Wraps an IJ {@link ImagePlus} in an imglib2 {@link ImgPlus}, abiding to a + * returned type. + * + * @param + * the pixel type in the returned image. + * @param imp + * the {@link ImagePlus} to wrap. + * @return a wrapped {@link ImgPlus}. */ @SuppressWarnings( "unchecked" ) public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) @@ -158,6 +181,8 @@ public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) * will be appended with an error message. * @return if all mandatory keys are found in the map, and possibly some * optional ones, but no others. + * @param + * the type of keys. */ public static final < T > boolean checkMapKeys( final Map< T, ? > map, Collection< T > mandatoryKeys, Collection< T > optionalKeys, final StringBuilder errorHolder ) { @@ -224,6 +249,16 @@ public static final boolean checkParameter( final Map< String, Object > map, fin /** * Returns the mapping in a map that is targeted by a list of keys, in the * order given in the list. + * + * @param + * the type of keys in the collection and the map. + * @param + * the type of values in the map. + * @param keys + * the collection of keys. + * @param mapping + * the mapping. + * @return a new list of values. */ public static final < J, K > List< K > getArrayFromMaping( final Collection< J > keys, final Map< J, K > mapping ) { @@ -238,9 +273,13 @@ public static final < J, K > List< K > getArrayFromMaping( final Collection< J > */ /** - * Return the xyz calibration stored in an {@link ImgPlusMetadata} in a + * Returns the xyz calibration stored in an {@link ImgPlusMetadata} in a * 3-elements double array. Calibration is ordered as X, Y, Z. If one axis * is not found, then the calibration for this axis takes the value of 1. + * + * @param img + * the image metadata object. + * @return a new double array. */ public static final double[] getSpatialCalibration( final ImgPlusMetadata img ) { @@ -272,10 +311,15 @@ public static double[] getSpatialCalibration( final ImagePlus imp ) /** * Returns an estimate of the pth percentile of the values in * the values array. Taken from commons-math. + * + * @param values + * the values. + * @param p + * the percentile. + * @return the percentile of the values. */ public static final double getPercentile( final double[] values, final double p ) { - final int size = values.length; if ( ( p > 1 ) || ( p <= 0 ) ) throw new IllegalArgumentException( "invalid quantile value: " + p ); @@ -318,6 +362,22 @@ private static final double[] getRange( final double[] data ) return new double[] { ( max - min ), min, max }; } + /** + * Stores the x, y, z coordinates of the specified spot in the first 3 + * elements of the specified double array. + * + * @param spot + * the spot. + * @param coords + * the array to write coordinates to. + */ + public static final void localize( final Spot spot, final double[] coords ) + { + coords[ 0 ] = spot.getFeature( Spot.POSITION_X ).doubleValue(); + coords[ 1 ] = spot.getFeature( Spot.POSITION_Y ).doubleValue(); + coords[ 2 ] = spot.getFeature( Spot.POSITION_Z ).doubleValue(); + } + /** * Returns the optimal bin number for a histogram of the data given in * array, using the Freedman and Diaconis rule (bin_space = 2*IQR/n^(1/3)). @@ -383,8 +443,12 @@ private static final int[] histogram( final double data[], final int nBins ) } /** - * Return a threshold for the given data, using an Otsu histogram + * Returns a threshold for the given data, using an Otsu histogram * thresholding method. + * + * @param data + * the data. + * @return the Otsu threshold. */ public static final double otsuThreshold( final double[] data ) { @@ -392,8 +456,14 @@ public static final double otsuThreshold( final double[] data ) } /** - * Return a threshold for the given data, using an Otsu histogram + * Returns a threshold for the given data, using an Otsu histogram * thresholding method with a given bin number. + * + * @param data + * the data. + * @param the + * desired number of bins in the histogram. + * @return the Otsu thresold. */ private static final double otsuThreshold( final double[] data, final int nBins ) { @@ -460,9 +530,17 @@ private static final int otsuThresholdIndex( final int[] hist, final int nPoints } /** - * Return a String unit for the given dimension. When suitable, the unit is + * Returns a String unit for the given dimension. When suitable, the unit is * taken from the settings field, which contains the spatial and time units. * Otherwise, default units are used. + * + * @param dimension + * the dimension. + * @param spaceUnits + * the space units. + * @param timeUnits + * the time units. + * @return the units for the specified dimension. */ public static final String getUnitsFor( final Dimension dimension, final String spaceUnits, final String timeUnits ) { @@ -704,7 +782,11 @@ public static final Interval getInterval( final ImgPlus< ? > img, final Settings return interval; } - /** Obtains the SciJava {@link Context} in use by ImageJ. */ + /** + * Obtains the SciJava {@link Context} in use by ImageJ. + * + * @return the context. + */ public static Context getContext() { final Context localContext = context; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java index 488e329a6..591afb6c3 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java @@ -51,11 +51,16 @@ public interface TrackMateModelView /** * Centers the view on the given spot. + * + * @param spot + * the spot to center the view on. */ public void centerViewOn( final Spot spot ); /** * Returns the model displayed in this view. + * + * @return the model. */ public Model getModel(); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java index 0400ace50..ed5c9bb48 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -77,7 +77,7 @@ public HyperStackDisplayer( final Model model, final SelectionModel selectionMod * the spots. * * @param displaySettings - * + * the display settings. * @return the spot overlay */ protected SpotOverlay createSpotOverlay( final DisplaySettings displaySettings ) @@ -90,7 +90,7 @@ protected SpotOverlay createSpotOverlay( final DisplaySettings displaySettings ) * the spots. * * @param displaySettings - * + * the display settings. * @return the track overlay */ protected TrackOverlay createTrackOverlay( final DisplaySettings displaySettings ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java index d9f21c898..58e3c3f8f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java @@ -116,8 +116,10 @@ public void imageClosed( final ImagePlus imp ) } /** - * Return the singleton instance for this tool. If it was not previously + * Returns the singleton instance for this tool. If it was not previously * instantiated, this calls instantiates it. + * + * @return the instance. */ public static SpotEditTool getInstance() { @@ -128,7 +130,11 @@ public static SpotEditTool getInstance() } /** - * Return true if the tool is currently present in ImageJ toolbar. + * Returns true if the tool is currently present in ImageJ + * toolbar. + * + * @return true if the tool is currently present in ImageJ + * toolbar. */ public static boolean isLaunched() { @@ -187,8 +193,11 @@ protected void registerTool( final ImageCanvas canvas ) } /** - * Register the given {@link HyperStackDisplayer}. If this method id not + * Registers the given {@link HyperStackDisplayer}. If this method is not * called, the tool will not respond. + * + * @param displayer + * the displayer to register. */ public void register( final HyperStackDisplayer displayer ) { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java index a97769a00..0c2c9edbd 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -237,10 +237,6 @@ public void drawOverlay( final Graphics g ) g2d.setFont( originalFont ); } - /** - * @param g2d - * @param frame - */ protected void drawExtraLayer( final Graphics2D g2d, final int frame ) {} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java index 35e77fdbc..8f8280e01 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java @@ -35,7 +35,7 @@ public TrackMatePainter( final ImagePlus imp, final double[] calibration, final * the bounding box, centered at (0,0), in physical coordinates. * @param center * the center of the bounding-box, in physical coordinates. - * @return + * @return if the specified bounding-box intersects with the display window. */ protected boolean intersect( final RealInterval boundingBox, final RealLocalizable center ) { @@ -60,7 +60,8 @@ protected boolean intersect( final RealInterval boundingBox, final RealLocalizab * * @param boundingBox * the bounding box, in physical coordinates. - * @return + * @return true if the specified bounding-box intersects with + * the display window. */ protected boolean intersect( final RealInterval boundingBox ) { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SaveAction.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SaveAction.java index 8c737051d..c15e1729c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SaveAction.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SaveAction.java @@ -81,6 +81,13 @@ public SaveAction( final TrackScheme trackScheme ) * Saves XML+PNG format. * * @param frame + * the TrackScheme frame to capture. + * @param filename + * the file to save to. + * @param bg + * the color of the background in the exported image. + * @throws IOException + * if an error happens while writing. */ protected void saveXmlPng( final TrackSchemeFrame frame, final String filename, final Color bg ) throws IOException { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotIconGrabber.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotIconGrabber.java index a9115544b..7c928259a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotIconGrabber.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotIconGrabber.java @@ -74,6 +74,7 @@ public SpotIconGrabber( final ImgPlus< T > img ) * a factor that determines the size of the thumbnail. The * thumbnail will have a size equal to the spot diameter times * this radius. + * @return a Base64 representation of the spot image. */ public String getImageString( final Spot spot, final double radiusFactor ) { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotImageUpdater.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotImageUpdater.java index 8f18b1893..f092cbb12 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotImageUpdater.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotImageUpdater.java @@ -57,6 +57,8 @@ public SpotImageUpdater( final Settings settings ) * is stored for subsequent calls of this method. So it is a good idea to * group calls to this method for spots that belong to the same frame. * + * @param spot + * the spot. * @param radiusFactor * a factor that determines the size of the thumbnail. The * thumbnail will have a size equal to the spot diameter times diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java index 4f75c9391..7ac53f325 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java @@ -198,8 +198,10 @@ public SelectionModel getSelectionModel() } /** - * @return the column index that is the first one after all the track - * columns. + * Returns the column index that is the first one after all the track + * columns. + * + * @return the column index. */ public int getUnlaidSpotColumn() { @@ -207,6 +209,10 @@ public int getUnlaidSpotColumn() } /** + * Returns the first free column for the target row. + * + * @param frame + * the row. * @return the first free column for the target row. */ public int getNextFreeColumn( final int frame ) @@ -221,6 +227,8 @@ public int getNextFreeColumn( final int frame ) /** * Returns the GUI frame controlled by this class. + * + * @return the GUI. */ public TrackSchemeFrame getGUI() { @@ -230,6 +238,8 @@ public TrackSchemeFrame getGUI() /** * Returns the {@link JGraphXAdapter} that serves as a model for the graph * displayed in this frame. + * + * @return the adapter. */ public JGraphXAdapter getGraph() { @@ -238,6 +248,8 @@ public JGraphXAdapter getGraph() /** * Returns the graph layout in charge of arranging the cells on the graph. + * + * @return the graph layout. */ public TrackSchemeGraphLayout getGraphLayout() { @@ -1281,10 +1293,10 @@ public void removeSelectedCells() public void removeSelectedLinkCells() { - List< Object > edgeCells = new ArrayList<>(); - for ( Object obj : graph.getSelectionCells() ) + final List< Object > edgeCells = new ArrayList<>(); + for ( final Object obj : graph.getSelectionCells() ) { - DefaultWeightedEdge e = graph.getEdgeFor( ( mxICell ) obj ); + final DefaultWeightedEdge e = graph.getEdgeFor( ( mxICell ) obj ); if ( e == null ) continue; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java index d927df640..7d11d151f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java @@ -99,7 +99,9 @@ protected InputMap getInputMap( final int condition ) } /** - * Return the mapping between JTree's input map and JGraph's actions. + * Returns the mapping between JTree's input map and JGraph's actions. + * + * @return the action map. */ protected ActionMap createActionMap() { From 8e7c24b8c60ff8e624dbaa15a500faf0d6a95e86 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 18:53:42 +0200 Subject: [PATCH 209/263] Fix mistake in calling the translateScale mesh method. --- .../java/fiji/plugin/trackmate/detection/SpotMeshUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index ec8a32583..ced087c43 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -324,7 +324,7 @@ else if ( nTriangles < 1_000_000 ) return null; // Translate back to interval coords & scale to physical coords. - Meshes.translateScale( simplified, calibration, origin ); + Meshes.translateScale( simplified, origin, calibration ); // Make spot with default quality. final SpotMesh spot = new SpotMesh( simplified, 0. ); From bafff8b6985fdc125bd4f8b62a563d7681b71f15 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 17 Nov 2023 16:19:06 +0100 Subject: [PATCH 210/263] Make the mesh of a SpotMesh setable. Also stop exposing the bounding-box. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 24 +++++++++++++++---- .../trackmate/util/mesh/SpotMeshCursor.java | 14 ++++++----- .../trackmate/util/mesh/SpotMeshIterable.java | 4 ++-- .../hyperstack/PaintSpotMesh.java | 6 +++-- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 75296dff3..fa93ba3da 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -30,12 +30,12 @@ public class SpotMesh extends SpotBase * (0, 0, 0) and the true position of its vertices is obtained by adding the * spot center. */ - private final Mesh mesh; + private BufferMesh mesh; private Map< Integer, Slice > sliceMap; /** The bounding-box, centered on (0,0,0) of this object. */ - public RealInterval boundingBox; + private RealInterval boundingBox; public SpotMesh( final Mesh mesh, @@ -62,19 +62,24 @@ public SpotMesh( { // Dummy coordinates and radius. super( 0., 0., 0., 0., quality, name ); + setMesh( m ); + } - // Compute triangles and vertices normals. + public void setMesh( final Mesh m ) + { + // Store a copy in a Buffer mesh. final BufferMesh mesh = new BufferMesh( m.vertices().size(), m.triangles().size() ); Meshes.calculateNormals( m, mesh ); - this.mesh = mesh; + + // Compute new true center. final RealPoint center = Meshes.center( mesh ); // Reposition the spot. setPosition( center ); // Shift mesh to (0, 0, 0). - final Vertices vertices = mesh.vertices(); + final net.imglib2.mesh.Vertices vertices = mesh.vertices(); final long nVertices = vertices.size(); for ( long i = 0; i < nVertices; i++ ) vertices.setPositionf( i, @@ -88,6 +93,14 @@ public SpotMesh( // Bounding box, also centered on (0,0,0) this.boundingBox = Meshes.boundingBox( mesh ); + + // Slice cache. + resetZSliceCache(); + } + + public RealInterval getBoundingBox() + { + return boundingBox; } /** @@ -360,4 +373,5 @@ public static final RealInterval toRealInterval( final float[] bb ) { return Intervals.createMinMaxReal( bb[ 0 ], bb[ 1 ], bb[ 2 ], bb[ 3 ], bb[ 4 ], bb[ 5 ] ); } + } diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java index 3f384f969..809d29564 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -4,6 +4,7 @@ import gnu.trove.list.array.TDoubleArrayList; import net.imglib2.Cursor; import net.imglib2.RandomAccess; +import net.imglib2.RealInterval; import net.imglib2.mesh.alg.zslicer.Slice; /** @@ -61,12 +62,13 @@ public SpotMeshCursor( final RandomAccess< T > ra, final SpotMesh sm, final doub this.ra = ra; this.sm = sm; this.cal = cal; - this.minX = ( int ) Math.floor( ( sm.boundingBox.realMin( 0 ) + sm.getDoublePosition( 0 ) ) / cal[ 0 ] ); - this.maxX = ( int ) Math.ceil( ( sm.boundingBox.realMax( 0 ) + sm.getDoublePosition( 0 ) ) / cal[ 0 ] ); - this.minY = ( int ) Math.floor( ( sm.boundingBox.realMin( 1 ) + sm.getDoublePosition( 1 ) ) / cal[ 1 ] ); - this.maxY = ( int ) Math.ceil( ( sm.boundingBox.realMax( 1 ) + sm.getDoublePosition( 1 ) ) / cal[ 1 ] ); - this.minZ = ( int ) Math.floor( ( sm.boundingBox.realMin( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); - this.maxZ = ( int ) Math.ceil( ( sm.boundingBox.realMax( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); + final RealInterval bb = sm.getBoundingBox(); + this.minX = ( int ) Math.floor( ( bb.realMin( 0 ) + sm.getDoublePosition( 0 ) ) / cal[ 0 ] ); + this.maxX = ( int ) Math.ceil( ( bb.realMax( 0 ) + sm.getDoublePosition( 0 ) ) / cal[ 0 ] ); + this.minY = ( int ) Math.floor( ( bb.realMin( 1 ) + sm.getDoublePosition( 1 ) ) / cal[ 1 ] ); + this.maxY = ( int ) Math.ceil( ( bb.realMax( 1 ) + sm.getDoublePosition( 1 ) ) / cal[ 1 ] ); + this.minZ = ( int ) Math.floor( ( bb.realMin( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); + this.maxZ = ( int ) Math.ceil( ( bb.realMax( 2 ) + sm.getDoublePosition( 2 ) ) / cal[ 2 ] ); reset(); } diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java index bce81f950..912d92e56 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java @@ -72,13 +72,13 @@ public Iterator< T > iterator() @Override public long min( final int d ) { - return Math.round( ( sm.boundingBox.realMin( d ) + sm.getFloatPosition( d ) ) / calibration[ d ] ); + return Math.round( ( sm.getBoundingBox().realMin( d ) + sm.getFloatPosition( d ) ) / calibration[ d ] ); } @Override public long max( final int d ) { - return Math.round( ( sm.boundingBox.realMax( d ) + sm.getFloatPosition( d ) ) / calibration[ d ] ); + return Math.round( ( sm.getBoundingBox().realMax( d ) + sm.getFloatPosition( d ) ) / calibration[ d ] ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index 158260da4..bdc2363b1 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -11,6 +11,7 @@ import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; +import net.imglib2.RealInterval; import net.imglib2.RealLocalizable; import net.imglib2.mesh.alg.zslicer.Contour; import net.imglib2.mesh.alg.zslicer.Slice; @@ -39,7 +40,8 @@ public PaintSpotMesh( final ImagePlus imp, final double[] calibration, final Dis @Override public int paint( final Graphics2D g2d, final SpotMesh spot ) { - if ( !intersect( spot.boundingBox, spot ) ) + final RealInterval bb = spot.getBoundingBox(); + if ( !intersect( bb, spot ) ) return -1; // Z plane does not cross bounding box. @@ -50,7 +52,7 @@ public int paint( final Graphics2D g2d, final SpotMesh spot ) final double z = spot.getFeature( Spot.POSITION_Z ); final int zSlice = imp.getSlice() - 1; final double dz = zSlice * calibration[ 2 ]; - if ( spot.boundingBox.realMin( 2 ) + z > dz || spot.boundingBox.realMax( 2 ) + z < dz ) + if ( bb.realMin( 2 ) + z > dz || bb.realMax( 2 ) + z < dz ) { paintOutOfFocus( g2d, xs, ys ); return -1; From 7e25850b4885bf4eea43925d6b9f5f49c38fdab0 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 17 Nov 2023 16:19:37 +0100 Subject: [PATCH 211/263] Return the actual BufferMesh class when we expose the mesh of a SpotMesh. TODO: use this in dependent methods. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index fa93ba3da..aa0e3ad5e 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -146,7 +146,7 @@ public SpotMesh( final int ID, final BufferMesh mesh ) * * @return the mesh. */ - public Mesh getMesh() + public BufferMesh getMesh() { return mesh; } From 9c2c176680449a298409e0f9c8318266fe87c9c2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 17 Nov 2023 16:20:06 +0100 Subject: [PATCH 212/263] Display the default element in the enum combobox of StyleElements. --- .../fiji/plugin/trackmate/gui/displaysettings/StyleElements.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java index e003344f6..aa02573f0 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java @@ -1112,6 +1112,7 @@ public static < E > JComboBox< E > linkedComboBoxEnumSelector( final EnumElement if ( e != model.getSelectedItem() ) model.setSelectedItem( e ); } ); + cb.setSelectedItem( element.getValue() ); return cb; } From 83925c21e3e3e8502dbfcbe01600101ae5d9fb0a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 17 Nov 2023 16:20:26 +0100 Subject: [PATCH 213/263] An action to smooth meshes with Tuabin smoothing. --- .../action/meshtools/MeshSmoother.java | 77 ++++++++ .../action/meshtools/MeshSmootherAction.java | 117 +++++++++++ .../action/meshtools/MeshSmootherModel.java | 68 +++++++ .../action/meshtools/MeshSmootherPanel.java | 185 ++++++++++++++++++ 4 files changed, 447 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java create mode 100644 src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java create mode 100644 src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java create mode 100644 src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java new file mode 100644 index 000000000..d43773567 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java @@ -0,0 +1,77 @@ +package fiji.plugin.trackmate.action.meshtools; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotMesh; +import net.imglib2.mesh.Mesh; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.alg.TaubinSmoothing; +import net.imglib2.mesh.alg.TaubinSmoothing.TaubinWeightType; +import net.imglib2.mesh.impl.nio.BufferMesh; + +public class MeshSmoother +{ + + private final Map< SpotMesh, BufferMesh > undoMap; + + private final Logger logger; + + public MeshSmoother( final Iterable< Spot > spots, final Logger logger ) + { + this.logger = logger; + // Store undo. + this.undoMap = new HashMap<>(); + final double[] center = new double[ 3 ]; + for ( final Spot spot : spots ) + { + if ( SpotMesh.class.isInstance( spot ) ) + { + final SpotMesh sm = ( SpotMesh ) spot; + final Mesh mesh = sm.getMesh(); + final BufferMesh meshCopy = new BufferMesh( mesh.vertices().size(), mesh.triangles().size() ); + Meshes.copy( mesh, meshCopy ); + sm.localize( center ); + Meshes.translate( meshCopy, center ); + undoMap.put( sm, meshCopy ); + } + } + } + + public void undo() + { + logger.setStatus( "Undoing mesh smoothing" ); + final Set< SpotMesh > keys = undoMap.keySet(); + final int nSpots = keys.size(); + int i = 0; + for ( final SpotMesh sm : keys ) + { + final BufferMesh old = undoMap.get( sm ); + sm.setMesh( old ); + logger.setProgress( ( double ) ( ++i ) / nSpots ); + } + logger.setStatus( "" ); + } + + public void smooth( final int nIters, final double mu, final double lambda, final TaubinWeightType weightType ) + { + logger.setStatus( "Taubin smoothing" ); + final Set< SpotMesh > keys = undoMap.keySet(); + final int nSpots = keys.size(); + int i = 0; + final double[] center = new double[ 3 ]; + for ( final SpotMesh sm : keys ) + { + final Mesh mesh = sm.getMesh(); + sm.localize( center ); + Meshes.translate( mesh, center ); + final BufferMesh smoothedMesh = TaubinSmoothing.smooth( mesh, nIters, lambda, mu, weightType ); + sm.setMesh( smoothedMesh ); + logger.setProgress( ( double ) ( ++i ) / nSpots ); + } + logger.setStatus( "" ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java new file mode 100644 index 000000000..50485b378 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java @@ -0,0 +1,117 @@ +package fiji.plugin.trackmate.action.meshtools; + +import java.awt.Frame; + +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JLabel; + +import org.scijava.plugin.Plugin; + +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.action.AbstractTMAction; +import fiji.plugin.trackmate.action.TrackMateAction; +import fiji.plugin.trackmate.action.TrackMateActionFactory; +import fiji.plugin.trackmate.gui.GuiUtils; +import fiji.plugin.trackmate.gui.Icons; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; + +public class MeshSmootherAction extends AbstractTMAction +{ + + @Override + public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + { + final MeshSmoother smoother = new MeshSmoother( trackmate.getModel().getSpots().iterable( true ), logger ); + + final MeshSmootherModel model = new MeshSmootherModel(); + final MeshSmootherPanel panel = new MeshSmootherPanel( model ); + + panel.btnRun.addActionListener( e -> { + new Thread( () -> { + final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( panel, new Class[] { JLabel.class } ); + try + { + enabler.disable(); + smoother.smooth( model.getNIters(), model.getMu(), model.getLambda(), model.getWeightType() ); + // Trigger refresh. + trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.SPOTS_COMPUTED ) ) ); + } + finally + { + enabler.reenable(); + } + }, "TrackMate mesh smoother" ).start(); + } ); + + panel.btnUndo.addActionListener( e -> { + new Thread( () -> { + final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( panel, new Class[] { JLabel.class } ); + try + { + enabler.disable(); + smoother.undo(); + // Trigger refresh. + trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.SPOTS_COMPUTED ) ) ); + } + finally + { + enabler.reenable(); + } + }, "TrackMate mesh smoother" ).start(); + } ); + + final JFrame frame = new JFrame( "Smoothing params" ); + frame.getContentPane().add( panel ); + frame.setSize( 400, 300 ); + GuiUtils.positionWindow( frame, parent ); + frame.setVisible( true ); + } + + @Plugin( type = TrackMateActionFactory.class ) + public static class Factory implements TrackMateActionFactory + { + + public static final String NAME = "Smooth 3D meshes"; + + public static final String KEY = "MESH_SMOOTHER"; + + public static final String INFO_TEXT = "" + + "Displays a tool to smooth the 3D mesh present in " + + "the data, using the Taubin smoothing algorithm."; + + @Override + public String getInfoText() + { + return INFO_TEXT; + } + + @Override + public String getKey() + { + return KEY; + } + + @Override + public TrackMateAction create() + { + return new MeshSmootherAction(); + } + + @Override + public ImageIcon getIcon() + { + return Icons.VECTOR_ICON; + } + + @Override + public String getName() + { + return NAME; + } + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java new file mode 100644 index 000000000..2c061a34a --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java @@ -0,0 +1,68 @@ +package fiji.plugin.trackmate.action.meshtools; + +import net.imglib2.mesh.alg.TaubinSmoothing.TaubinWeightType; + +public class MeshSmootherModel +{ + + private int nIters = 10; + + private double mu = 0.5; + + private double lambda = -0.53; + + private TaubinWeightType weightType = TaubinWeightType.NAIVE; + + public void setWeightType( final TaubinWeightType weightType ) + { + this.weightType = weightType; + } + + public void setMu( final double mu ) + { + this.mu = Math.min( 1., Math.max( 0, mu ) ); + } + + public void setLambda( final double lambda ) + { + this.lambda = Math.min( 0., Math.max( -1., lambda ) ); + } + + public void setNIters( final int nIters ) + { + this.nIters = Math.max( 0, nIters ); + } + + public double getMu() + { + return mu; + } + + public double getLambda() + { + return lambda; + } + + public int getNIters() + { + return nIters; + } + + public TaubinWeightType getWeightType() + { + return weightType; + } + + /** + * Ad-hoc method setting parameters for little smoothing (close to 0) or a + * lot of smoothing (close to 1). + * + * @param smoothing + * the smoothing parameter. + */ + public void setSmoothing( final double smoothing ) + { + setMu( Math.max( 0, Math.min( 0.97, smoothing ) ) ); + setLambda( -mu - 0.03 ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java new file mode 100644 index 000000000..ee3af1f29 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java @@ -0,0 +1,185 @@ +package fiji.plugin.trackmate.action.meshtools; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.Arrays; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import fiji.plugin.trackmate.gui.displaysettings.StyleElements; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements.EnumElement; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements.IntElement; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StyleElement; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StyleElementVisitor; +import net.imglib2.mesh.alg.TaubinSmoothing.TaubinWeightType; + +public class MeshSmootherPanel extends JPanel +{ + + private static final long serialVersionUID = 1L; + + final JButton btnRun; + + final JButton btnUndo; + + public MeshSmootherPanel( final MeshSmootherModel model ) + { + final BoundedDoubleElement smoothing = StyleElements.boundedDoubleElement( "Smoothing (%)", 0., 100., () -> model.getMu() * 100., v -> model.setSmoothing( v / 100. ) ); + final IntElement nIters1 = StyleElements.intElement( "N iterations", 1, 50, model::getNIters, model::setNIters ); + final BoundedDoubleElement mu = StyleElements.boundedDoubleElement( "µ", 0., 1., model::getMu, model::setMu ); + final BoundedDoubleElement lambda = StyleElements.boundedDoubleElement( "-λ", 0., 1., () -> -model.getLambda(), l -> model.setLambda( -l ) ); + final EnumElement< TaubinWeightType > weightType = StyleElements.enumElement( "weight type", TaubinWeightType.values(), model::getWeightType, model::setWeightType ); + final IntElement nIters2 = StyleElements.intElement( "N iterations", 1, 50, model::getNIters, model::setNIters ); + + final List< StyleElement > simpleElements = Arrays.asList( smoothing, nIters1 ); + final List< StyleElement > advancedElements = Arrays.asList( mu, lambda, nIters2, weightType ); + + setLayout( new BorderLayout( 0, 0 ) ); + + final JPanel buttonPanel = new JPanel(); + add( buttonPanel, BorderLayout.SOUTH ); + buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.X_AXIS ) ); + + this.btnUndo = new JButton( "Undo" ); + buttonPanel.add( btnUndo ); + + final Component horizontalGlue = Box.createHorizontalGlue(); + buttonPanel.add( horizontalGlue ); + + this.btnRun = new JButton( "Run" ); + buttonPanel.add( btnRun ); + + final JTabbedPane mainPanel = new JTabbedPane( JTabbedPane.TOP ); + add( mainPanel, BorderLayout.CENTER ); + + final JPanel panelSimple = new JPanel(); + panelSimple.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); + final MyStyleElementVisitors simplePanelVisitor = new MyStyleElementVisitors( panelSimple ); + simpleElements.forEach( el -> el.accept( simplePanelVisitor ) ); + mainPanel.addTab( "Simple", null, panelSimple, null ); + + final JPanel panelAdvanced = new JPanel(); + panelAdvanced.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); + final MyStyleElementVisitors advancedPanelVisitor = new MyStyleElementVisitors( panelAdvanced ); + advancedElements.forEach( el -> el.accept( advancedPanelVisitor ) ); + mainPanel.addTab( "Advanced", null, panelAdvanced, null ); + + mainPanel.addChangeListener( new ChangeListener() + { + + @Override + public void stateChanged( final ChangeEvent e ) + { + if ( mainPanel.getSelectedIndex() == 0 ) + { + // Simple. + model.setSmoothing( smoothing.get() ); + model.setNIters( nIters1.get() ); + model.setWeightType( TaubinWeightType.NAIVE ); + } + else + { + // Advanced. + model.setMu( mu.get() ); + model.setLambda( lambda.get() ); + model.setNIters( nIters2.get() ); + model.setWeightType( weightType.getValue() ); + } + } + } ); + } + + private static class MyStyleElementVisitors implements StyleElementVisitor + { + + private final JPanel panel; + + private final GridBagConstraints gbcs; + + public MyStyleElementVisitors( final JPanel panel ) + { + this.panel = panel; + final GridBagLayout layout = new GridBagLayout(); + layout.columnWidths = new int[] { 0, 0, 0 }; + layout.rowHeights = new int[] { 40, 40, 40, 40 }; + layout.columnWeights = new double[] { 0., 1., Double.MIN_VALUE }; + layout.rowWeights = new double[] { 0., 0., 0., 0., 1. }; + panel.setLayout( layout ); + + this.gbcs = new GridBagConstraints(); + gbcs.fill = GridBagConstraints.HORIZONTAL; + gbcs.gridx = 0; + gbcs.gridy = 0; + } + + @Override + public < E > void visit( final EnumElement< E > el ) + { + gbcs.gridx = 0; + final JLabel lbl = new JLabel( el.getLabel() ); + lbl.setHorizontalAlignment( JLabel.RIGHT ); + lbl.setFont( getFont().deriveFont( getFont().getSize2D() - 1f ) ); + panel.add( lbl, gbcs ); + gbcs.gridx++; + panel.add( StyleElements.linkedComboBoxEnumSelector( el ), gbcs ); + gbcs.gridy++; + } + + @Override + public void visit( final BoundedDoubleElement el ) + { + gbcs.gridx = 0; + final JLabel lbl = new JLabel( el.getLabel() ); + lbl.setHorizontalAlignment( JLabel.RIGHT ); + lbl.setFont( getFont().deriveFont( getFont().getSize2D() - 1f ) ); + panel.add( lbl, gbcs ); + gbcs.gridx++; + panel.add( StyleElements.linkedSliderPanel( el, 3 ), gbcs ); + gbcs.gridy++; + } + + @Override + public void visit( final IntElement el ) + { + gbcs.gridx = 0; + final JLabel lbl = new JLabel( el.getLabel() ); + lbl.setHorizontalAlignment( JLabel.RIGHT ); + lbl.setFont( getFont().deriveFont( getFont().getSize2D() - 1f ) ); + panel.add( lbl, gbcs ); + gbcs.gridx++; + panel.add( StyleElements.linkedSliderPanel( el, 3 ), gbcs ); + gbcs.gridy++; + } + + private Font getFont() + { + return panel.getFont(); + } + } + + public static void main( final String[] args ) + { + final MeshSmootherPanel panel = new MeshSmootherPanel( new MeshSmootherModel() ); + + final JFrame frame = new JFrame( "Smoothing params" ); + frame.getContentPane().add( panel ); + frame.setSize( 400, 300 ); + frame.setLocationRelativeTo( null ); + frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); + frame.setVisible( true ); + } +} From 346f5d19943387c48db864d2d89464ec05005cf7 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 19 Nov 2023 19:34:11 +0100 Subject: [PATCH 214/263] Multithread the mesh smoother. --- .../action/meshtools/MeshSmoother.java | 147 +++++++++++++----- .../action/meshtools/MeshSmootherAction.java | 9 +- 2 files changed, 118 insertions(+), 38 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java index d43773567..27e696c26 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java @@ -1,77 +1,152 @@ package fiji.plugin.trackmate.action.meshtools; -import java.util.HashMap; -import java.util.Map; +import java.util.Collection; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; -import net.imglib2.mesh.Mesh; +import fiji.plugin.trackmate.util.Threads; +import net.imglib2.algorithm.MultiThreaded; import net.imglib2.mesh.Meshes; import net.imglib2.mesh.alg.TaubinSmoothing; import net.imglib2.mesh.alg.TaubinSmoothing.TaubinWeightType; import net.imglib2.mesh.impl.nio.BufferMesh; +import net.imglib2.util.ValuePair; -public class MeshSmoother +public class MeshSmoother implements MultiThreaded { - private final Map< SpotMesh, BufferMesh > undoMap; + /** Stores initial position and mesh of the spot. */ + + private final ConcurrentHashMap< SpotMesh, ValuePair< BufferMesh, double[] > > undoMap; private final Logger logger; - public MeshSmoother( final Iterable< Spot > spots, final Logger logger ) + private int numThreads; + + + public MeshSmoother( final Logger logger ) { this.logger = logger; - // Store undo. - this.undoMap = new HashMap<>(); - final double[] center = new double[ 3 ]; - for ( final Spot spot : spots ) - { - if ( SpotMesh.class.isInstance( spot ) ) - { - final SpotMesh sm = ( SpotMesh ) spot; - final Mesh mesh = sm.getMesh(); - final BufferMesh meshCopy = new BufferMesh( mesh.vertices().size(), mesh.triangles().size() ); - Meshes.copy( mesh, meshCopy ); - sm.localize( center ); - Meshes.translate( meshCopy, center ); - undoMap.put( sm, meshCopy ); - } - } + this.undoMap = new ConcurrentHashMap<>(); + setNumThreads(); } + public void undo() { logger.setStatus( "Undoing mesh smoothing" ); final Set< SpotMesh > keys = undoMap.keySet(); final int nSpots = keys.size(); int i = 0; + logger.log( "Undoing mesh smoothing for " + nSpots + " spots.\n" ); for ( final SpotMesh sm : keys ) { - final BufferMesh old = undoMap.get( sm ); - sm.setMesh( old ); + final ValuePair< BufferMesh, double[] > old = undoMap.get( sm ); + sm.setMesh( old.getA() ); + sm.setPosition( old.getB() ); logger.setProgress( ( double ) ( ++i ) / nSpots ); } logger.setStatus( "" ); + logger.log( "Done.\n" ); } - public void smooth( final int nIters, final double mu, final double lambda, final TaubinWeightType weightType ) + public void smooth( + final Iterable< Spot > spots, + final int nIters, + final double mu, + final double lambda, + final TaubinWeightType weightType ) { + final int nSpots = count( spots ); logger.setStatus( "Taubin smoothing" ); - final Set< SpotMesh > keys = undoMap.keySet(); - final int nSpots = keys.size(); - int i = 0; - final double[] center = new double[ 3 ]; - for ( final SpotMesh sm : keys ) + logger.log( "Started Taubin smoothing over " + nSpots + " spots with parameters:\n" ); + logger.log( String.format( " - %14s: %5.2f\n", "µ", mu ) ); + logger.log( String.format( " - %14s: %5.2f\n", "λ", lambda ) ); + logger.log( String.format( " - %14s: %5d\n", "N iterations", nIters ) ); + logger.log( String.format( " - %14s: %s\n", "weights", weightType ) ); + + final AtomicInteger ai = new AtomicInteger( 0 ); + final ExecutorService executors = Threads.newFixedThreadPool( numThreads ); + for ( final Spot spot : spots ) { - final Mesh mesh = sm.getMesh(); - sm.localize( center ); - Meshes.translate( mesh, center ); - final BufferMesh smoothedMesh = TaubinSmoothing.smooth( mesh, nIters, lambda, mu, weightType ); - sm.setMesh( smoothedMesh ); - logger.setProgress( ( double ) ( ++i ) / nSpots ); + if ( SpotMesh.class.isInstance( spot ) ) + { + final SpotMesh sm = ( SpotMesh ) spot; + executors.execute( process( sm, nIters, mu, lambda, weightType, ai, nSpots ) ); + } } + logger.setStatus( "" ); + logger.log( "Done.\n" ); + } + + private static final int count( final Iterable< Spot > spots ) + { + if ( Collection.class.isInstance( spots ) ) + return ( ( Collection< ? > ) spots ).size(); + + int n = 0; + for ( @SuppressWarnings( "unused" ) + final Spot spot : spots ) + n++; + return n; + } + + private Runnable process( + final SpotMesh sm, + final int nIters, + final double mu, + final double lambda, + final TaubinWeightType weightType, + final AtomicInteger ai, + final int nSpots ) + { + return new Runnable() + { + @Override + public void run() + { + final BufferMesh mesh = sm.getMesh(); + final double[] center = new double[ 3 ]; + sm.localize( center ); + + // Store for undo. + if ( !undoMap.containsKey( sm ) ) + { + final ValuePair< BufferMesh, double[] > pair = new ValuePair<>( mesh, center ); + undoMap.put( sm, pair ); + } + + // Process. + Meshes.translate( mesh, center ); + final BufferMesh smoothedMesh = TaubinSmoothing.smooth( mesh, nIters, lambda, mu, weightType ); + sm.setMesh( smoothedMesh ); + + logger.setProgress( ( double ) ai.incrementAndGet() / nSpots ); + } + }; + } + + @Override + public void setNumThreads() + { + this.numThreads = Math.max( 1, Runtime.getRuntime().availableProcessors() / 2 ); + } + + @Override + public void setNumThreads( final int numThreads ) + { + this.numThreads = numThreads; + } + + @Override + public int getNumThreads() + { + return numThreads; } } diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java index 50485b378..01a78bf5e 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java @@ -25,7 +25,7 @@ public class MeshSmootherAction extends AbstractTMAction @Override public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) { - final MeshSmoother smoother = new MeshSmoother( trackmate.getModel().getSpots().iterable( true ), logger ); + final MeshSmoother smoother = new MeshSmoother( logger ); final MeshSmootherModel model = new MeshSmootherModel(); final MeshSmootherPanel panel = new MeshSmootherPanel( model ); @@ -36,7 +36,12 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo try { enabler.disable(); - smoother.smooth( model.getNIters(), model.getMu(), model.getLambda(), model.getWeightType() ); + smoother.smooth( + trackmate.getModel().getSpots().iterable( true ), + model.getNIters(), + model.getMu(), + model.getLambda(), + model.getWeightType() ); // Trigger refresh. trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.SPOTS_COMPUTED ) ) ); } From 536089fdd63be069dbdb0490487eef124a0210f2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 10:11:35 +0100 Subject: [PATCH 215/263] Improve smoothing action. - Actually run the tasks in multithreaded fashion (forgot to shutdown the executor service). - Notify the views that the spots have changed, including the 3D view. Takes a little delay but it's fine. --- .../action/meshtools/MeshSmoother.java | 46 +++++++++++++++---- .../action/meshtools/MeshSmootherAction.java | 20 ++++++-- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java index 27e696c26..122bf6b95 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java @@ -1,9 +1,12 @@ package fiji.plugin.trackmate.action.meshtools; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import fiji.plugin.trackmate.Logger; @@ -20,6 +23,10 @@ public class MeshSmoother implements MultiThreaded { + private static final long TIME_OUT_DELAY = 2; + + private static final TimeUnit TIME_OUT_UNITS = TimeUnit.HOURS; + /** Stores initial position and mesh of the spot. */ private final ConcurrentHashMap< SpotMesh, ValuePair< BufferMesh, double[] > > undoMap; @@ -37,25 +44,28 @@ public MeshSmoother( final Logger logger ) } - public void undo() + public List< Spot > undo() { logger.setStatus( "Undoing mesh smoothing" ); final Set< SpotMesh > keys = undoMap.keySet(); final int nSpots = keys.size(); int i = 0; logger.log( "Undoing mesh smoothing for " + nSpots + " spots.\n" ); + final List< Spot > modifiedSpots = new ArrayList<>(); for ( final SpotMesh sm : keys ) { final ValuePair< BufferMesh, double[] > old = undoMap.get( sm ); sm.setMesh( old.getA() ); sm.setPosition( old.getB() ); + modifiedSpots.add( sm ); logger.setProgress( ( double ) ( ++i ) / nSpots ); } logger.setStatus( "" ); logger.log( "Done.\n" ); + return modifiedSpots; } - public void smooth( + public List< Spot > smooth( final Iterable< Spot > spots, final int nIters, final double mu, @@ -65,24 +75,44 @@ public void smooth( final int nSpots = count( spots ); logger.setStatus( "Taubin smoothing" ); logger.log( "Started Taubin smoothing over " + nSpots + " spots with parameters:\n" ); - logger.log( String.format( " - %14s: %5.2f\n", "µ", mu ) ); - logger.log( String.format( " - %14s: %5.2f\n", "λ", lambda ) ); - logger.log( String.format( " - %14s: %5d\n", "N iterations", nIters ) ); - logger.log( String.format( " - %14s: %s\n", "weights", weightType ) ); + logger.log( String.format( " - %s: %.2f\n", "µ", mu ) ); + logger.log( String.format( " - %s: %.2f\n", "λ", lambda ) ); + logger.log( String.format( " - %s: %d\n", "N iterations", nIters ) ); + logger.log( String.format( " - %s: %s\n", "weights", weightType ) ); final AtomicInteger ai = new AtomicInteger( 0 ); final ExecutorService executors = Threads.newFixedThreadPool( numThreads ); + final List< Spot > modifiedSpots = new ArrayList<>(); for ( final Spot spot : spots ) { if ( SpotMesh.class.isInstance( spot ) ) { final SpotMesh sm = ( SpotMesh ) spot; executors.execute( process( sm, nIters, mu, lambda, weightType, ai, nSpots ) ); + modifiedSpots.add( sm ); } } - logger.setStatus( "" ); - logger.log( "Done.\n" ); + executors.shutdown(); + try + { + final boolean ok = executors.awaitTermination( TIME_OUT_DELAY, TIME_OUT_UNITS ); + if ( !ok ) + logger.error( "Timeout of " + TIME_OUT_DELAY + " " + TIME_OUT_UNITS + " reached while smoothing.\n" ); + + logger.log( "Done.\n" ); + } + catch ( final InterruptedException e ) + { + logger.error( e.getMessage() ); + e.printStackTrace(); + } + finally + { + logger.setProgress( 1 ); + logger.setStatus( "" ); + } + return modifiedSpots; } private static final int count( final Iterable< Spot > spots ) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java index 01a78bf5e..f217f2a8e 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java @@ -1,6 +1,7 @@ package fiji.plugin.trackmate.action.meshtools; import java.awt.Frame; +import java.util.Collection; import javax.swing.ImageIcon; import javax.swing.JFrame; @@ -10,6 +11,7 @@ import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.action.AbstractTMAction; import fiji.plugin.trackmate.action.TrackMateAction; @@ -36,14 +38,21 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo try { enabler.disable(); - smoother.smooth( + final Collection< Spot > modifiedSpots = smoother.smooth( trackmate.getModel().getSpots().iterable( true ), model.getNIters(), model.getMu(), model.getLambda(), model.getWeightType() ); // Trigger refresh. - trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.SPOTS_COMPUTED ) ) ); + final ModelChangeEvent event = new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ); + event.addAllSpots( modifiedSpots ); + modifiedSpots.forEach( s -> event.putSpotFlag( s, ModelChangeEvent.FLAG_SPOT_MODIFIED ) ); + trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( event ) ); + } + catch ( final Exception err ) + { + err.printStackTrace(); } finally { @@ -58,9 +67,12 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo try { enabler.disable(); - smoother.undo(); + final Collection< Spot > modifiedSpots = smoother.undo(); // Trigger refresh. - trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.SPOTS_COMPUTED ) ) ); + final ModelChangeEvent event = new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ); + event.addAllSpots( modifiedSpots ); + modifiedSpots.forEach( s -> event.putSpotFlag( s, ModelChangeEvent.FLAG_SPOT_MODIFIED ) ); + trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( event ) ); } finally { From 9a7e46a634a38b5322be393a45eb84137e9c4f5a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 10:34:54 +0100 Subject: [PATCH 216/263] Mesh smoother can run on all visible spots or only on selection. --- .../action/meshtools/MeshSmoother.java | 12 +- .../action/meshtools/MeshSmootherAction.java | 68 +----------- .../meshtools/MeshSmootherController.java | 103 ++++++++++++++++++ .../action/meshtools/MeshSmootherPanel.java | 36 +++++- 4 files changed, 143 insertions(+), 76 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java index 122bf6b95..364501287 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java @@ -65,13 +65,13 @@ public List< Spot > undo() return modifiedSpots; } - public List< Spot > smooth( - final Iterable< Spot > spots, - final int nIters, - final double mu, - final double lambda, - final TaubinWeightType weightType ) + public List< Spot > smooth( final MeshSmootherModel smootherModel, final Iterable< Spot > spots ) { + final double mu = smootherModel.getMu(); + final double lambda = smootherModel.getLambda(); + final int nIters = smootherModel.getNIters(); + final TaubinWeightType weightType = smootherModel.getWeightType(); + final int nSpots = count( spots ); logger.setStatus( "Taubin smoothing" ); logger.log( "Started Taubin smoothing over " + nSpots + " spots with parameters:\n" ); diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java index f217f2a8e..193a6dc63 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java @@ -1,25 +1,18 @@ package fiji.plugin.trackmate.action.meshtools; import java.awt.Frame; -import java.util.Collection; import javax.swing.ImageIcon; -import javax.swing.JFrame; -import javax.swing.JLabel; import org.scijava.plugin.Plugin; -import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.action.AbstractTMAction; import fiji.plugin.trackmate.action.TrackMateAction; import fiji.plugin.trackmate.action.TrackMateActionFactory; -import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; public class MeshSmootherAction extends AbstractTMAction { @@ -27,65 +20,8 @@ public class MeshSmootherAction extends AbstractTMAction @Override public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) { - final MeshSmoother smoother = new MeshSmoother( logger ); - - final MeshSmootherModel model = new MeshSmootherModel(); - final MeshSmootherPanel panel = new MeshSmootherPanel( model ); - - panel.btnRun.addActionListener( e -> { - new Thread( () -> { - final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( panel, new Class[] { JLabel.class } ); - try - { - enabler.disable(); - final Collection< Spot > modifiedSpots = smoother.smooth( - trackmate.getModel().getSpots().iterable( true ), - model.getNIters(), - model.getMu(), - model.getLambda(), - model.getWeightType() ); - // Trigger refresh. - final ModelChangeEvent event = new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ); - event.addAllSpots( modifiedSpots ); - modifiedSpots.forEach( s -> event.putSpotFlag( s, ModelChangeEvent.FLAG_SPOT_MODIFIED ) ); - trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( event ) ); - } - catch ( final Exception err ) - { - err.printStackTrace(); - } - finally - { - enabler.reenable(); - } - }, "TrackMate mesh smoother" ).start(); - } ); - - panel.btnUndo.addActionListener( e -> { - new Thread( () -> { - final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( panel, new Class[] { JLabel.class } ); - try - { - enabler.disable(); - final Collection< Spot > modifiedSpots = smoother.undo(); - // Trigger refresh. - final ModelChangeEvent event = new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ); - event.addAllSpots( modifiedSpots ); - modifiedSpots.forEach( s -> event.putSpotFlag( s, ModelChangeEvent.FLAG_SPOT_MODIFIED ) ); - trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( event ) ); - } - finally - { - enabler.reenable(); - } - }, "TrackMate mesh smoother" ).start(); - } ); - - final JFrame frame = new JFrame( "Smoothing params" ); - frame.getContentPane().add( panel ); - frame.setSize( 400, 300 ); - GuiUtils.positionWindow( frame, parent ); - frame.setVisible( true ); + final MeshSmootherController controller = new MeshSmootherController( trackmate.getModel(), selectionModel, logger ); + controller.show( parent ); } @Plugin( type = TrackMateActionFactory.class ) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java new file mode 100644 index 000000000..227775319 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java @@ -0,0 +1,103 @@ +package fiji.plugin.trackmate.action.meshtools; + +import java.awt.Component; +import java.util.Collection; + +import javax.swing.JFrame; +import javax.swing.JLabel; + +import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.gui.GuiUtils; +import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; + +public class MeshSmootherController +{ + + private final Model model; + + private final SelectionModel selectionModel; + + private final MeshSmootherPanel gui; + + private final MeshSmoother smoother; + + public MeshSmootherController( final Model model, final SelectionModel selectionModel, final Logger logger ) + { + this.model = model; + this.selectionModel = selectionModel; + final MeshSmootherModel smootherModel = new MeshSmootherModel(); + this.gui = new MeshSmootherPanel( smootherModel ); + this.smoother = new MeshSmoother( logger ); + + + gui.btnRun.addActionListener( e -> run( smootherModel ) ); + + gui.btnUndo.addActionListener( e -> undo() ); + + } + + public void show( final Component parent ) + { + final JFrame frame = new JFrame( "Smoothing params" ); + frame.getContentPane().add( gui ); + frame.setSize( 400, 300 ); + GuiUtils.positionWindow( frame, parent ); + frame.setVisible( true ); + } + + private void run( final MeshSmootherModel smootherModel ) + { + final Iterable< Spot > spots; + if ( gui.rdbtnAll.isSelected() ) + spots = model.getSpots().iterable( true ); + else + spots = selectionModel.getSpotSelection(); + + new Thread( () -> { + final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( gui, new Class[] { JLabel.class } ); + try + { + enabler.disable(); + final Collection< Spot > modifiedSpots = smoother.smooth( smootherModel, spots ); + fireEvent( modifiedSpots ); + } + catch ( final Exception err ) + { + err.printStackTrace(); + } + finally + { + enabler.reenable(); + } + }, "TrackMate mesh smoother thread" ).start(); + } + + private void undo() + { + new Thread( () -> { + final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( gui, new Class[] { JLabel.class } ); + try + { + enabler.disable(); + final Collection< Spot > modifiedSpots = smoother.undo(); + fireEvent( modifiedSpots ); + } + finally + { + enabler.reenable(); + } + }, "TrackMate mesh smoothing undoer thread" ).start(); + } + + private void fireEvent( final Collection< Spot > modifiedSpots ) + { + final ModelChangeEvent event = new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ); + event.addAllSpots( modifiedSpots ); + modifiedSpots.forEach( s -> event.putSpotFlag( s, ModelChangeEvent.FLAG_SPOT_MODIFIED ) ); + model.getModelChangeListener().forEach( l -> l.modelChanged( event ) ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java index ee3af1f29..9a6acaf0a 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java @@ -1,7 +1,6 @@ package fiji.plugin.trackmate.action.meshtools; import java.awt.BorderLayout; -import java.awt.Component; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -11,10 +10,12 @@ import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JRadioButton; import javax.swing.JTabbedPane; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -36,6 +37,10 @@ public class MeshSmootherPanel extends JPanel final JButton btnUndo; + final JRadioButton rdbtnSelection; + + final JRadioButton rdbtnAll; + public MeshSmootherPanel( final MeshSmootherModel model ) { final BoundedDoubleElement smoothing = StyleElements.boundedDoubleElement( "Smoothing (%)", 0., 100., () -> model.getMu() * 100., v -> model.setSmoothing( v / 100. ) ); @@ -50,15 +55,33 @@ public MeshSmootherPanel( final MeshSmootherModel model ) setLayout( new BorderLayout( 0, 0 ) ); + final JPanel bottomPanel = new JPanel(); + add( bottomPanel, BorderLayout.SOUTH ); + bottomPanel.setLayout( new BoxLayout( bottomPanel, BoxLayout.Y_AXIS ) ); + + final JPanel selectionPanel = new JPanel(); + bottomPanel.add( selectionPanel ); + selectionPanel.setLayout( new BoxLayout( selectionPanel, BoxLayout.X_AXIS ) ); + + final JLabel lblRunOn = new JLabel( "Run on:" ); + selectionPanel.add( lblRunOn ); + + selectionPanel.add( Box.createHorizontalGlue() ); + + rdbtnSelection = new JRadioButton( "selection only" ); + selectionPanel.add( rdbtnSelection ); + + rdbtnAll = new JRadioButton( "all visible spots" ); + selectionPanel.add( rdbtnAll ); + final JPanel buttonPanel = new JPanel(); - add( buttonPanel, BorderLayout.SOUTH ); + bottomPanel.add( buttonPanel ); buttonPanel.setLayout( new BoxLayout( buttonPanel, BoxLayout.X_AXIS ) ); this.btnUndo = new JButton( "Undo" ); buttonPanel.add( btnUndo ); - final Component horizontalGlue = Box.createHorizontalGlue(); - buttonPanel.add( horizontalGlue ); + buttonPanel.add( Box.createHorizontalGlue() ); this.btnRun = new JButton( "Run" ); buttonPanel.add( btnRun ); @@ -78,6 +101,11 @@ public MeshSmootherPanel( final MeshSmootherModel model ) advancedElements.forEach( el -> el.accept( advancedPanelVisitor ) ); mainPanel.addTab( "Advanced", null, panelAdvanced, null ); + final ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add( rdbtnAll ); + buttonGroup.add( rdbtnSelection ); + rdbtnSelection.setSelected( true ); + mainPanel.addChangeListener( new ChangeListener() { From 03411c994d26cb5f0107de383731b45a8d28121f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 10:42:25 +0100 Subject: [PATCH 217/263] Tweak the mesh smoother panel. --- .../action/meshtools/MeshSmootherController.java | 2 ++ .../action/meshtools/MeshSmootherPanel.java | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java index 227775319..485c4d224 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java @@ -12,6 +12,7 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.gui.GuiUtils; +import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; public class MeshSmootherController @@ -45,6 +46,7 @@ public void show( final Component parent ) final JFrame frame = new JFrame( "Smoothing params" ); frame.getContentPane().add( gui ); frame.setSize( 400, 300 ); + frame.setIconImage( Icons.TRACKMATE_ICON.getImage() ); GuiUtils.positionWindow( frame, parent ); frame.setVisible( true ); } diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java index 9a6acaf0a..2ac7ad763 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java @@ -20,6 +20,8 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import fiji.plugin.trackmate.gui.displaysettings.SliderPanel; +import fiji.plugin.trackmate.gui.displaysettings.SliderPanelDouble; import fiji.plugin.trackmate.gui.displaysettings.StyleElements; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.EnumElement; @@ -60,6 +62,7 @@ public MeshSmootherPanel( final MeshSmootherModel model ) bottomPanel.setLayout( new BoxLayout( bottomPanel, BoxLayout.Y_AXIS ) ); final JPanel selectionPanel = new JPanel(); + selectionPanel.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); bottomPanel.add( selectionPanel ); selectionPanel.setLayout( new BoxLayout( selectionPanel, BoxLayout.X_AXIS ) ); @@ -90,12 +93,14 @@ public MeshSmootherPanel( final MeshSmootherModel model ) add( mainPanel, BorderLayout.CENTER ); final JPanel panelSimple = new JPanel(); + panelSimple.setOpaque( false ); panelSimple.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); final MyStyleElementVisitors simplePanelVisitor = new MyStyleElementVisitors( panelSimple ); simpleElements.forEach( el -> el.accept( simplePanelVisitor ) ); mainPanel.addTab( "Simple", null, panelSimple, null ); final JPanel panelAdvanced = new JPanel(); + panelAdvanced.setOpaque( false ); panelAdvanced.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); final MyStyleElementVisitors advancedPanelVisitor = new MyStyleElementVisitors( panelAdvanced ); advancedElements.forEach( el -> el.accept( advancedPanelVisitor ) ); @@ -176,7 +181,9 @@ public void visit( final BoundedDoubleElement el ) lbl.setFont( getFont().deriveFont( getFont().getSize2D() - 1f ) ); panel.add( lbl, gbcs ); gbcs.gridx++; - panel.add( StyleElements.linkedSliderPanel( el, 3 ), gbcs ); + final SliderPanelDouble sliderPanel = StyleElements.linkedSliderPanel( el, 3 ); + sliderPanel.setOpaque( false ); + panel.add( sliderPanel, gbcs ); gbcs.gridy++; } @@ -189,7 +196,9 @@ public void visit( final IntElement el ) lbl.setFont( getFont().deriveFont( getFont().getSize2D() - 1f ) ); panel.add( lbl, gbcs ); gbcs.gridx++; - panel.add( StyleElements.linkedSliderPanel( el, 3 ), gbcs ); + final SliderPanel sliderPanel = StyleElements.linkedSliderPanel( el, 3 ); + sliderPanel.setOpaque( false ); + panel.add( sliderPanel, gbcs ); gbcs.gridy++; } From 06eeb9500f92d3f71782cffa565bdfbf2e287d87 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 16:55:31 +0100 Subject: [PATCH 218/263] Tell the user when we update meshes and features after smoothing meshes. --- .../action/meshtools/MeshSmootherController.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java index 485c4d224..33fa47c88 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java @@ -26,19 +26,19 @@ public class MeshSmootherController private final MeshSmoother smoother; + private final Logger logger; + public MeshSmootherController( final Model model, final SelectionModel selectionModel, final Logger logger ) { this.model = model; this.selectionModel = selectionModel; + this.logger = logger; final MeshSmootherModel smootherModel = new MeshSmootherModel(); this.gui = new MeshSmootherPanel( smootherModel ); this.smoother = new MeshSmoother( logger ); - gui.btnRun.addActionListener( e -> run( smootherModel ) ); - gui.btnUndo.addActionListener( e -> undo() ); - } public void show( final Component parent ) @@ -97,9 +97,11 @@ private void undo() private void fireEvent( final Collection< Spot > modifiedSpots ) { + logger.log( "Updating spot features and meshes.\n" ); final ModelChangeEvent event = new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ); event.addAllSpots( modifiedSpots ); modifiedSpots.forEach( s -> event.putSpotFlag( s, ModelChangeEvent.FLAG_SPOT_MODIFIED ) ); model.getModelChangeListener().forEach( l -> l.modelChanged( event ) ); + logger.log( "Done.\n" ); } } From 71dae840492a87c5797b116172e64bd4cf4e9ece Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 17:33:33 +0100 Subject: [PATCH 219/263] Add a toString method to MeshSmootherModel. --- .../trackmate/action/meshtools/MeshSmootherModel.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java index 2c061a34a..4256cf163 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java @@ -65,4 +65,15 @@ public void setSmoothing( final double smoothing ) setMu( Math.max( 0, Math.min( 0.97, smoothing ) ) ); setLambda( -mu - 0.03 ); } + + @Override + public String toString() + { + final StringBuilder str = new StringBuilder( super.toString() + '\n' ); + str.append( String.format( " - %s: %.2f\n", "µ", mu ) ); + str.append( String.format( " - %s: %.2f\n", "λ", lambda ) ); + str.append( String.format( " - %s: %d\n", "N iterations", nIters ) ); + str.append( String.format( " - %s: %s\n", "weights", weightType ) ); + return str.toString(); + } } From c515e9c76e8b3db620d08defd4da664dab3548d5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 17:44:31 +0100 Subject: [PATCH 220/263] Add a 'basic' setting panel to the mesh smoother. It filters with mu = 1., lambda -0, which is like applying heavy Laplace smoothing. --- .../meshtools/MeshSmootherController.java | 5 +- .../action/meshtools/MeshSmootherPanel.java | 84 +++++++++++-------- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java index 33fa47c88..e1cb645e7 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java @@ -33,11 +33,10 @@ public MeshSmootherController( final Model model, final SelectionModel selection this.model = model; this.selectionModel = selectionModel; this.logger = logger; - final MeshSmootherModel smootherModel = new MeshSmootherModel(); - this.gui = new MeshSmootherPanel( smootherModel ); + this.gui = new MeshSmootherPanel(); this.smoother = new MeshSmoother( logger ); - gui.btnRun.addActionListener( e -> run( smootherModel ) ); + gui.btnRun.addActionListener( e -> run( gui.getModel() ) ); gui.btnUndo.addActionListener( e -> undo() ); } diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java index 2ac7ad763..7fbb2c647 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java @@ -17,8 +17,6 @@ import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTabbedPane; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import fiji.plugin.trackmate.gui.displaysettings.SliderPanel; import fiji.plugin.trackmate.gui.displaysettings.SliderPanelDouble; @@ -43,17 +41,34 @@ public class MeshSmootherPanel extends JPanel final JRadioButton rdbtnAll; - public MeshSmootherPanel( final MeshSmootherModel model ) - { - final BoundedDoubleElement smoothing = StyleElements.boundedDoubleElement( "Smoothing (%)", 0., 100., () -> model.getMu() * 100., v -> model.setSmoothing( v / 100. ) ); - final IntElement nIters1 = StyleElements.intElement( "N iterations", 1, 50, model::getNIters, model::setNIters ); - final BoundedDoubleElement mu = StyleElements.boundedDoubleElement( "µ", 0., 1., model::getMu, model::setMu ); - final BoundedDoubleElement lambda = StyleElements.boundedDoubleElement( "-λ", 0., 1., () -> -model.getLambda(), l -> model.setLambda( -l ) ); - final EnumElement< TaubinWeightType > weightType = StyleElements.enumElement( "weight type", TaubinWeightType.values(), model::getWeightType, model::setWeightType ); - final IntElement nIters2 = StyleElements.intElement( "N iterations", 1, 50, model::getNIters, model::setNIters ); + private final MeshSmootherModel modelBasic; + + private final MeshSmootherModel modelSimple; + + private final MeshSmootherModel modelAdvanced; - final List< StyleElement > simpleElements = Arrays.asList( smoothing, nIters1 ); - final List< StyleElement > advancedElements = Arrays.asList( mu, lambda, nIters2, weightType ); + private final JTabbedPane mainPanel; + + public MeshSmootherPanel() + { + this.modelBasic = new MeshSmootherModel(); + modelBasic.setMu( 1. ); + modelBasic.setLambda( 0. ); + modelBasic.setWeightType( TaubinWeightType.NAIVE ); + final IntElement nItersBasic = StyleElements.intElement( "N iterations", 1, 50, modelBasic::getNIters, modelBasic::setNIters ); + final List< StyleElement > superSimpleElements = Arrays.asList( nItersBasic ); + + this.modelSimple = new MeshSmootherModel(); + final BoundedDoubleElement smoothing = StyleElements.boundedDoubleElement( "Smoothing (%)", 0., 100., () -> modelSimple.getMu() * 100., v -> modelSimple.setSmoothing( v / 100. ) ); + final IntElement nItersSimple = StyleElements.intElement( "N iterations", 1, 50, modelSimple::getNIters, modelSimple::setNIters ); + final List< StyleElement > simpleElements = Arrays.asList( smoothing, nItersSimple ); + + this.modelAdvanced = new MeshSmootherModel(); + final BoundedDoubleElement mu = StyleElements.boundedDoubleElement( "µ", 0., 1., modelAdvanced::getMu, modelAdvanced::setMu ); + final BoundedDoubleElement lambda = StyleElements.boundedDoubleElement( "-λ", 0., 1., () -> -modelAdvanced.getLambda(), l -> modelAdvanced.setLambda( -l ) ); + final EnumElement< TaubinWeightType > weightType = StyleElements.enumElement( "weight type", TaubinWeightType.values(), modelAdvanced::getWeightType, modelAdvanced::setWeightType ); + final IntElement nItersAdvanced = StyleElements.intElement( "N iterations", 1, 50, modelAdvanced::getNIters, modelAdvanced::setNIters ); + final List< StyleElement > advancedElements = Arrays.asList( mu, lambda, nItersAdvanced, weightType ); setLayout( new BorderLayout( 0, 0 ) ); @@ -89,9 +104,16 @@ public MeshSmootherPanel( final MeshSmootherModel model ) this.btnRun = new JButton( "Run" ); buttonPanel.add( btnRun ); - final JTabbedPane mainPanel = new JTabbedPane( JTabbedPane.TOP ); + this.mainPanel = new JTabbedPane( JTabbedPane.TOP ); add( mainPanel, BorderLayout.CENTER ); + final JPanel panelSuperSimple = new JPanel(); + panelSuperSimple.setOpaque( false ); + panelSuperSimple.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); + final MyStyleElementVisitors superSimplePanelVisitor = new MyStyleElementVisitors( panelSuperSimple ); + superSimpleElements.forEach( el -> el.accept( superSimplePanelVisitor ) ); + mainPanel.addTab( "Basic", null, panelSuperSimple, null ); + final JPanel panelSimple = new JPanel(); panelSimple.setOpaque( false ); panelSimple.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); @@ -110,30 +132,20 @@ public MeshSmootherPanel( final MeshSmootherModel model ) buttonGroup.add( rdbtnAll ); buttonGroup.add( rdbtnSelection ); rdbtnSelection.setSelected( true ); + } - mainPanel.addChangeListener( new ChangeListener() + public MeshSmootherModel getModel() + { + switch ( mainPanel.getSelectedIndex() ) { - - @Override - public void stateChanged( final ChangeEvent e ) - { - if ( mainPanel.getSelectedIndex() == 0 ) - { - // Simple. - model.setSmoothing( smoothing.get() ); - model.setNIters( nIters1.get() ); - model.setWeightType( TaubinWeightType.NAIVE ); - } - else - { - // Advanced. - model.setMu( mu.get() ); - model.setLambda( lambda.get() ); - model.setNIters( nIters2.get() ); - model.setWeightType( weightType.getValue() ); - } - } - } ); + case 0: + return modelBasic; + case 1: + return modelSimple; + case 2: + return modelAdvanced; + } + throw new IllegalStateException( "Cannot handle mesh smoothing settings type number " + ( mainPanel.getSelectedIndex() ) ); } private static class MyStyleElementVisitors implements StyleElementVisitor @@ -210,7 +222,7 @@ private Font getFont() public static void main( final String[] args ) { - final MeshSmootherPanel panel = new MeshSmootherPanel( new MeshSmootherModel() ); + final MeshSmootherPanel panel = new MeshSmootherPanel(); final JFrame frame = new JFrame( "Smoothing params" ); frame.getContentPane().add( panel ); From 7cf06febd5f35e6f3800ec25872baa91a7176471 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 22 Nov 2023 16:18:53 +0100 Subject: [PATCH 221/263] The mesh smoother uses the numThreads from the trackmate instance. --- .../action/meshtools/MeshSmootherAction.java | 1 + .../meshtools/MeshSmootherController.java | 21 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java index 193a6dc63..ab3000a6e 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java @@ -21,6 +21,7 @@ public class MeshSmootherAction extends AbstractTMAction public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) { final MeshSmootherController controller = new MeshSmootherController( trackmate.getModel(), selectionModel, logger ); + controller.setNumThreads( trackmate.getNumThreads() ); controller.show( parent ); } diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java index e1cb645e7..faf08827b 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java @@ -14,8 +14,9 @@ import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; +import net.imglib2.algorithm.MultiThreaded; -public class MeshSmootherController +public class MeshSmootherController implements MultiThreaded { private final Model model; @@ -103,4 +104,22 @@ private void fireEvent( final Collection< Spot > modifiedSpots ) model.getModelChangeListener().forEach( l -> l.modelChanged( event ) ); logger.log( "Done.\n" ); } + + @Override + public void setNumThreads() + { + smoother.setNumThreads(); + } + + @Override + public void setNumThreads( final int numThreads ) + { + smoother.setNumThreads( numThreads ); + } + + @Override + public int getNumThreads() + { + return smoother.getNumThreads(); + } } From bb97d01e6891b9478d9ca032267d1dd24e14eafc Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 22 Nov 2023 18:12:56 +0100 Subject: [PATCH 222/263] WIP: Smooth mask or label before generating mesh to smooth it. Incomplete: miss the 2D case. --- .../plugin/trackmate/detection/MaskUtils.java | 7 +++ .../trackmate/detection/Process2DZ.java | 13 ++++- .../trackmate/detection/SpotMeshUtils.java | 52 ++++++++++++++++--- 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 580adda5c..327f55139 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -409,6 +409,11 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > * smoother and contain less points. * @param numThreads * how many threads to use for multithreaded computation. + * @param smoothingScale + * if strictly larger than 0, the mask will be smoothed before + * creating the mesh, resulting in smoother meshes. The scale + * value sets the (Gaussian) filter radius and is specified in + * physical units. If 0 or lower than 0, no smoothing is applied. * @param qualityImage * the image in which to read the quality value. * @return a list of spots, with ROI. @@ -418,6 +423,7 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > final Interval interval, final double[] calibration, final boolean simplify, + final double smoothingScale, final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { @@ -442,6 +448,7 @@ else if ( input.numDimensions() == 3 ) interval, calibration, simplify, + smoothingScale, qualityImage ); } else diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index 69a7d6ea2..c240fb0e6 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -61,6 +61,8 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > private List< Spot > spots; + private final double smoothingScale; + /** * Creates a new {@link Process2DZ} detector. * @@ -84,13 +86,15 @@ public Process2DZ( final Interval interval, final double[] calibration, final Settings settings, - final boolean simplifyMeshes ) + final boolean simplifyMeshes, + final double smoothingScale ) { this.img = img; this.interval = interval; this.calibration = calibration; this.settings = settings; this.simplify = simplifyMeshes; + this.smoothingScale = smoothingScale; } @Override @@ -148,7 +152,12 @@ public boolean process() // Convert labels to 3D meshes. final ImgPlus< T > lblImg = TMUtils.rawWraps( lblImp ); - final LabelImageDetector< T > detector = new LabelImageDetector<>( lblImg, lblImg, calibration, simplify ); + final LabelImageDetector< T > detector = new LabelImageDetector<>( + lblImg, + lblImg, + calibration, + simplify, + smoothingScale ); if ( !detector.checkInput() || !detector.process() ) { errorMessage = BASE_ERROR_MESSAGE + detector.getErrorMessage(); diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index ced087c43..98b952d98 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -12,6 +12,8 @@ import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealInterval; +import net.imglib2.algorithm.gauss3.Gauss3; +import net.imglib2.img.Img; import net.imglib2.mesh.Mesh; import net.imglib2.mesh.MeshStats; import net.imglib2.mesh.Meshes; @@ -23,8 +25,9 @@ import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.real.FloatType; import net.imglib2.util.Intervals; -import net.imglib2.view.IntervalView; +import net.imglib2.util.Util; import net.imglib2.view.Views; /** @@ -180,6 +183,11 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > * smoother and contain less points. * @param qualityImage * the image in which to read the quality value. + * @param smoothingScale + * if strictly larger than 0, the mask will be smoothed before + * creating the mesh, resulting in smoother meshes. The scale + * value sets the (Gaussian) filter radius and is specified in + * physical units. If 0 or lower than 0, no smoothing is applied. * @return a list of spots, with meshes. */ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from3DLabelingWithROI( @@ -187,6 +195,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot final Interval interval, final double[] calibration, final boolean simplify, + final double smoothingScale, final RandomAccessibleInterval< S > qualityImage ) { if ( labeling.numDimensions() != 3 ) @@ -204,7 +213,8 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot simplify, calibration, qualityImage, - interval.minAsDoubleArray() ); + interval.minAsDoubleArray(), + smoothingScale ); if ( spot == null ) continue; @@ -233,6 +243,11 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot * quality will be the mesh volume. * @param minInterval * the origin in image coordinates of the ROI used for detection. + * @param smoothingScale + * if strictly larger than 0, the mask will be smoothed before + * creating the mesh, resulting in smoother meshes. The scale + * value sets the (Gaussian) filter radius and is specified in + * physical units. If 0 or lower than 0, no smoothing is applied. * * @return a new spot. */ @@ -241,16 +256,41 @@ private static < S extends RealType< S > > Spot regionToSpotMesh( final boolean simplify, final double[] calibration, final RandomAccessibleInterval< S > qualityImage, - final double[] minInterval ) + final double[] minInterval, + final double smoothingScale ) { + final RandomAccessibleInterval< BoolType > box = Views.zeroMin( region ); + final Mesh mesh; + + // Possibly filter. + final long[] borders; + if ( smoothingScale > 0 ) + { + final double[] sigmas = new double[ 3 ]; + for ( int d = 0; d < 3; d++ ) + sigmas[ d ] = smoothingScale / Math.sqrt( 3. ) / calibration[ d ]; + + // Increase the output size. + final int[] halfkernelsizes = Gauss3.halfkernelsizes( sigmas );; + borders = Arrays.stream( halfkernelsizes ).asLongStream().toArray(); + final Interval outputSize = Intervals.expand( box, borders ); + final Img< FloatType > img = Util.getArrayOrCellImgFactory( outputSize, new FloatType() ).create( outputSize ); + final RandomAccessibleInterval< FloatType > filtered = Views.translateInverse( img, borders ); + Gauss3.gauss( sigmas, box, filtered ); + mesh = Meshes.marchingCubes( img, 0.5 ); + } + else + { + mesh = Meshes.marchingCubes( box ); + borders = new long[] { 0, 0, 0 }; + } + // To mesh. - final IntervalView< BoolType > box = Views.zeroMin( region ); - final Mesh mesh = Meshes.marchingCubes( box, 0.5 ); final Mesh cleaned = Meshes.removeDuplicateVertices( mesh, VERTEX_DUPLICATE_REMOVAL_PRECISION ); // Shift coords. final double[] origin = region.minAsDoubleArray(); for ( int d = 0; d < 3; d++ ) - origin[ d ] += minInterval[ d ]; + origin[ d ] += minInterval[ d ] - borders[ d ]; // To spot. return meshToSpotMesh( cleaned, From 6a8ecf5723c1f579562c0ed6fe4d3b7ee5b77160 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 22 Nov 2023 18:13:22 +0100 Subject: [PATCH 223/263] WIP: Implement smoothing in threshold and mask detectors. --- .../detection/LabelImageDetector.java | 7 +- .../detection/LabelImageDetectorFactory.java | 5 +- .../trackmate/detection/MaskDetector.java | 6 +- .../detection/MaskDetectorFactory.java | 10 ++- .../detection/ThresholdDetector.java | 14 ++- .../detection/ThresholdDetectorFactory.java | 13 ++- .../gui/components/PanelSmoothContour.java | 86 +++++++++++++++++++ .../ThresholdDetectorConfigurationPanel.java | 28 ++++-- .../plugin/trackmate/mesh/DefaultMesh.java | 2 +- 9 files changed, 153 insertions(+), 18 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index 42594b013..8640f28d3 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -73,6 +73,8 @@ public class LabelImageDetector< T extends RealType< T > & NativeType< T > > imp */ protected final boolean simplify; + private final double smoothingScale; + /* * CONSTRUCTORS */ @@ -81,12 +83,14 @@ public LabelImageDetector( final RandomAccessible< T > input, final Interval interval, final double[] calibration, - final boolean simplify ) + final boolean simplify, + final double smoothingScale ) { this.input = input; this.interval = DetectionUtils.squeeze( interval ); this.calibration = calibration; this.simplify = simplify; + this.smoothingScale = smoothingScale; } @Override @@ -152,6 +156,7 @@ else if ( input.numDimensions() == 3 ) interval, calibration, simplify, + smoothingScale, null ); } else diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java index 6033257f0..bc181e5c7 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java @@ -24,6 +24,7 @@ import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_TARGET_CHANNEL; import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL; import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS; +import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_SMOOTHING_SCALE; import static fiji.plugin.trackmate.io.IOUtils.readBooleanAttribute; import static fiji.plugin.trackmate.io.IOUtils.readIntegerAttribute; import static fiji.plugin.trackmate.io.IOUtils.writeAttribute; @@ -108,6 +109,7 @@ public boolean setTarget( final ImgPlus< T > img, final Map< String, Object > se public SpotDetector< T > getDetector( final Interval interval, final int frame ) { final boolean simplifyContours = ( Boolean ) settings.get( KEY_SIMPLIFY_CONTOURS ); + final double smoothingScale = ( Double ) settings.get( KEY_SMOOTHING_SCALE ); final double[] calibration = TMUtils.getSpatialCalibration( img ); final int channel = ( Integer ) settings.get( KEY_TARGET_CHANNEL ) - 1; final RandomAccessible< T > imFrame = DetectionUtils.prepareFrameImg( img, channel, frame ); @@ -116,7 +118,8 @@ public SpotDetector< T > getDetector( final Interval interval, final int frame ) imFrame, interval, calibration, - simplifyContours ); + simplifyContours, + smoothingScale ); return detector; } diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java index 63629f7be..079e5d7de 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java @@ -39,9 +39,10 @@ public MaskDetector( final RandomAccessible< T > input, final Interval interval, final double[] calibration, - final boolean simplify ) + final boolean simplify, + final double smoothingScale ) { - super( input, interval, calibration, Double.NaN, simplify ); + super( input, interval, calibration, Double.NaN, simplify, smoothingScale ); baseErrorMessage = BASE_ERROR_MESSAGE; } @@ -55,6 +56,7 @@ public boolean process() interval, calibration, simplify, + smoothingScale, numThreads, null ); final long end = System.currentTimeMillis(); diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java index ff3d31501..23bca9571 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -96,18 +96,19 @@ public boolean has3Dsegmentation() public SpotDetector< T > getDetector( final Interval interval, final int frame ) { final boolean simplifyContours = ( Boolean ) settings.get( KEY_SIMPLIFY_CONTOURS ); + final double smoothingScale = ( Double ) settings.get( KEY_SMOOTHING_SCALE ); final double[] calibration = TMUtils.getSpatialCalibration( img ); final int channel = ( Integer ) settings.get( KEY_TARGET_CHANNEL ) - 1; final RandomAccessible< T > imFrame = DetectionUtils.prepareFrameImg( img, channel, frame ); final RandomAccessible< T > mask = mask( imFrame ); - final double intensityThreshold = 0.5; - final ThresholdDetector< T > detector = new ThresholdDetector<>( + final MaskDetector< T > detector = new MaskDetector<>( mask, interval, calibration, - intensityThreshold, - simplifyContours ); + simplifyContours, + smoothingScale ); + detector.setNumThreads( 1 ); return detector; } @@ -210,6 +211,7 @@ public Map< String, Object > getDefaultSettings() final Map< String, Object > lSettings = new HashMap<>(); lSettings.put( KEY_TARGET_CHANNEL, DEFAULT_TARGET_CHANNEL ); lSettings.put( KEY_SIMPLIFY_CONTOURS, true ); + lSettings.put( KEY_SMOOTHING_SCALE, -1. ); return lSettings; } diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java index 68736fb0b..41528b024 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java @@ -61,10 +61,16 @@ public class ThresholdDetector< T extends RealType< T > & NativeType< T > > impl protected final double threshold; + /** If true, the contours will be simplified. */ + protected final boolean simplify; + /** - * If true, the contours will be smoothed and simplified. + * If strictly larger than 0, the mask will be smoothed before creating the + * mesh, resulting in smoother meshes. The scale value sets the (Gaussian) + * filter radius and is specified in physical units. If 0 or lower than 0, + * no smoothing is applied. */ - protected final boolean simplify; + protected final double smoothingScale; /* * CONSTRUCTORS @@ -75,9 +81,11 @@ public ThresholdDetector( final Interval interval, final double[] calibration, final double threshold, - final boolean simplify ) + final boolean simplify, + final double smoothingScale ) { this.input = input; + this.smoothingScale = smoothingScale; this.interval = DetectionUtils.squeeze( interval ); this.calibration = calibration; this.threshold = threshold; diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java index 70ec9be7d..f26911e43 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -81,6 +81,14 @@ public class ThresholdDetectorFactory< T extends RealType< T > & NativeType< T > public static final String KEY_SIMPLIFY_CONTOURS = "SIMPLIFY_CONTOURS"; + /** + * If strictly larger than 0, the mask will be smoothed before creating the + * mesh, resulting in smoother meshes. The scale value sets the (Gaussian) + * filter radius and is specified in physical units. If 0 or lower than 0, + * no smoothing is applied. + */ + public static final String KEY_SMOOTHING_SCALE = "SMOOTHING_SCALE"; + public static final String KEY_INTENSITY_THRESHOLD = "INTENSITY_THRESHOLD"; /* @@ -111,6 +119,7 @@ public SpotDetector< T > getDetector( final Interval interval, final int frame ) { final double intensityThreshold = ( Double ) settings.get( KEY_INTENSITY_THRESHOLD ); final boolean simplifyContours = ( Boolean ) settings.get( KEY_SIMPLIFY_CONTOURS ); + final double smoothingScale = ( Double ) settings.get( KEY_SMOOTHING_SCALE ); final double[] calibration = TMUtils.getSpatialCalibration( img ); final int channel = ( Integer ) settings.get( KEY_TARGET_CHANNEL ) - 1; final RandomAccessible< T > imFrame = DetectionUtils.prepareFrameImg( img, channel, frame ); @@ -120,7 +129,8 @@ public SpotDetector< T > getDetector( final Interval interval, final int frame ) interval, calibration, intensityThreshold, - simplifyContours ); + simplifyContours, + smoothingScale ); detector.setNumThreads( 1 ); return detector; } @@ -225,6 +235,7 @@ public Map< String, Object > getDefaultSettings() lSettings.put( KEY_TARGET_CHANNEL, DEFAULT_TARGET_CHANNEL ); lSettings.put( KEY_INTENSITY_THRESHOLD, 0. ); lSettings.put( KEY_SIMPLIFY_CONTOURS, true ); + lSettings.put( KEY_SMOOTHING_SCALE, -1. ); return lSettings; } diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java b/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java new file mode 100644 index 000000000..b607f42ab --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java @@ -0,0 +1,86 @@ +package fiji.plugin.trackmate.gui.components; + +import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; + +import java.util.function.Consumer; +import java.util.function.DoubleSupplier; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import fiji.plugin.trackmate.gui.displaysettings.SliderPanelDouble; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; + +public class PanelSmoothContour extends JPanel +{ + + private static final long serialVersionUID = 1L; + + private double scale; + + private double previousValue; + + private final SliderPanelDouble sliderPanel; + + private final JCheckBox chckbxSmooth; + + public PanelSmoothContour( final double scale, final String units ) + { + this.scale = scale; + setLayout( new BoxLayout( this, BoxLayout.X_AXIS ) ); + + chckbxSmooth = new JCheckBox( "Smooth" ); + chckbxSmooth.setFont( SMALL_FONT ); + add( chckbxSmooth ); + add( Box.createHorizontalGlue() ); + + final DoubleSupplier getter = () -> getScale(); + final Consumer< Double > setter = v -> setScalePrivate( v ); + final BoundedDoubleElement scaleEl = StyleElements.boundedDoubleElement( "scale", 0., 20., getter, setter ); + sliderPanel = StyleElements.linkedSliderPanel( scaleEl, 2 ); + sliderPanel.setFont( SMALL_FONT ); + + add( sliderPanel ); + add( Box.createHorizontalStrut( 5 ) ); + final JLabel lblUnits = new JLabel( units ); + lblUnits.setFont( SMALL_FONT ); + + chckbxSmooth.addActionListener( e -> refresh() ); + refresh(); + } + + private void refresh() + { + final boolean selected = chckbxSmooth.isSelected(); + if ( !selected ) + { + previousValue = this.scale; + this.scale = -.1; + } + else + { + this.scale = previousValue; + } + sliderPanel.setEnabled( selected ); + } + + private void setScalePrivate( final double scale ) + { + this.scale = scale; + } + + public void setScale( final double scale ) + { + setScalePrivate( scale ); + refresh(); + } + + public double getScale() + { + return scale; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java index fc34d7386..85cec75d0 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java @@ -22,6 +22,9 @@ package fiji.plugin.trackmate.gui.components.detector; import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL; +import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD; +import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS; +import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_SMOOTHING_SCALE; 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; @@ -51,6 +54,7 @@ import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.components.ConfigurationPanel; +import fiji.plugin.trackmate.gui.components.PanelSmoothContour; import fiji.plugin.trackmate.util.DetectionPreview; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.util.Threads; @@ -87,6 +91,8 @@ public class ThresholdDetectorConfigurationPanel extends ConfigurationPanel protected final JLabel lblIntensityThreshold; + protected final PanelSmoothContour smoothingPanel; + /* * CONSTRUCTOR */ @@ -126,10 +132,10 @@ protected ThresholdDetectorConfigurationPanel( setPreferredSize( new Dimension( 300, 511 ) ); final GridBagLayout gridBagLayout = new GridBagLayout(); - gridBagLayout.rowHeights = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 47 }; + gridBagLayout.rowHeights = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 47 }; gridBagLayout.columnWidths = new int[] { 0, 0, 20 }; gridBagLayout.columnWeights = new double[] { 0.0, 1.0, 0.0 }; - gridBagLayout.rowWeights = new double[] { 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, }; + gridBagLayout.rowWeights = new double[] { 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; setLayout( gridBagLayout ); final JLabel jLabelDetectorName = new JLabel( detectorName, JLabel.CENTER ); @@ -220,6 +226,16 @@ protected ThresholdDetectorConfigurationPanel( chkboxSimplify.setText( "Simplify contours." ); chkboxSimplify.setFont( FONT ); + smoothingPanel = new PanelSmoothContour( -1., model.getSpaceUnits() ); + final GridBagConstraints gbSmoothPanel = new GridBagConstraints(); + gbSmoothPanel.anchor = GridBagConstraints.NORTHWEST; + gbSmoothPanel.insets = new Insets( 5, 5, 5, 5 ); + gbSmoothPanel.gridwidth = 3; + gbSmoothPanel.gridx = 0; + gbSmoothPanel.gridy = 6; + gbSmoothPanel.fill = GridBagConstraints.HORIZONTAL; + this.add( smoothingPanel, gbSmoothPanel ); + final DetectionPreview detectionPreview = DetectionPreview.create() .model( model ) .settings( settings ) @@ -234,7 +250,7 @@ protected ThresholdDetectorConfigurationPanel( gbcBtnPreview.insets = new Insets( 5, 5, 5, 5 ); gbcBtnPreview.gridwidth = 3; gbcBtnPreview.gridx = 0; - gbcBtnPreview.gridy = 7; + gbcBtnPreview.gridy = 8; this.add( detectionPreview.getPanel(), gbcBtnPreview ); /* @@ -292,11 +308,13 @@ public Map< String, Object > getSettings() final int targetChannel = sliderChannel.getValue(); final boolean simplify = chkboxSimplify.isSelected(); final double intensityThreshold = ( ( Number ) ftfIntensityThreshold.getValue() ).doubleValue(); + final double scale = smoothingPanel.getScale(); final HashMap< String, Object > lSettings = new HashMap<>( 3 ); lSettings.put( KEY_TARGET_CHANNEL, targetChannel ); - lSettings.put( ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD, intensityThreshold ); - lSettings.put( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, simplify ); + lSettings.put( KEY_INTENSITY_THRESHOLD, intensityThreshold ); + lSettings.put( KEY_SIMPLIFY_CONTOURS, simplify ); + lSettings.put( KEY_SMOOTHING_SCALE, scale ); return lSettings; } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java index cc2f76a3d..a7e037512 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java @@ -34,7 +34,7 @@ public static void main( final String[] args ) img.dimensionIndex( Axes.TIME ) ); final double[] calibration = new double[] { 1., 1., 1. }; - final ThresholdDetector< BitType > detector = new ThresholdDetector< BitType >( img, img, calibration, 0, false ); + final ThresholdDetector< BitType > detector = new ThresholdDetector< BitType >( img, img, calibration, 0, false, -1. ); detector.process(); final List< Spot > spots = detector.getResult(); From c6d9b633d7bfb649a78b40373b27df04296767dd Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 19:59:23 +0100 Subject: [PATCH 224/263] Optionally check for the presence of a parameter in a settings map. Does not return an error if the parameter is not there, but if it is, checks that it is of the expected class. --- .../fiji/plugin/trackmate/util/TMUtils.java | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index 3138db8ba..a3053c148 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -216,8 +216,39 @@ public static final < T > boolean checkMapKeys( final Map< T, ? > map, Collectio } /** - * Check the presence and the validity of a key in a map, and test it is of - * the desired class. + * Check the optional presence and the validity of a key in a map, and test + * it is of the desired class. If the key is not present, this method + * returns true. If it is present, it tests the value is of the + * right class. + * + * @param map + * the map to inspect. + * @param key + * the key to find. + * @param expectedClass + * the expected class of the target value . + * @param errorHolder + * will be appended with an error message. + * @return true if the key is not found in the map, or if it is found, and + * map a value of the desired class. + */ + public static final boolean checkOptionalParameter( final Map< String, Object > map, final String key, final Class< ? > expectedClass, final StringBuilder errorHolder ) + { + final Object obj = map.get( key ); + if ( null == obj ) + return true; + + if ( !expectedClass.isInstance( obj ) ) + { + errorHolder.append( "Value for parameter " + key + " is not of the right class. Expected " + expectedClass.getName() + ", got " + obj.getClass().getName() + ".\n" ); + return false; + } + return true; + } + + /** + * Check the mandatory presence and the validity of a key in a map, and test + * its value is of the desired class. * * @param map * the map to inspect. @@ -238,12 +269,7 @@ public static final boolean checkParameter( final Map< String, Object > map, fin errorHolder.append( "Parameter " + key + " could not be found in settings map, or is null.\n" ); return false; } - if ( !expectedClass.isInstance( obj ) ) - { - errorHolder.append( "Value for parameter " + key + " is not of the right class. Expected " + expectedClass.getName() + ", got " + obj.getClass().getName() + ".\n" ); - return false; - } - return true; + return checkOptionalParameter( map, key, expectedClass, errorHolder ); } /** From 3366157445547cf094c681455a9c08724f3f6710 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 19:59:57 +0100 Subject: [PATCH 225/263] Tweak the slider panels. - Mouse wheel listener. - Method to dis/enable all sub component. --- .../gui/displaysettings/SliderPanel.java | 24 +++++++++++++++++++ .../displaysettings/SliderPanelDouble.java | 24 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java index 0c59fa9a2..1a34d9b38 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -123,6 +123,22 @@ public void stateChanged( final ChangeEvent e ) add( slider, BorderLayout.CENTER ); add( spinner, BorderLayout.EAST ); + final MouseWheelListener mouseWheelListener = new MouseWheelListener() + { + + @Override + public void mouseWheelMoved( final MouseWheelEvent e ) + { + if ( !slider.isEnabled() ) + return; + final int notches = e.getWheelRotation(); + final int step = notches < 0 ? 1 : -1; + slider.setValue( slider.getValue() + step ); + } + }; + slider.addMouseWheelListener( mouseWheelListener ); + spinner.addMouseWheelListener( mouseWheelListener ); + this.model = model; model.setUpdateListener( this ); } @@ -152,6 +168,14 @@ public void setToolTipText( final String text ) slider.setToolTipText( text ); } + @Override + public void setEnabled( final boolean enabled ) + { + spinner.setEnabled( enabled ); + slider.setEnabled( enabled ); + super.setEnabled( enabled ); + } + @Override public void update() { diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java index eb9fe13c3..d2cb30060 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -153,6 +153,22 @@ public void stateChanged( final ChangeEvent e ) add( slider, BorderLayout.CENTER ); add( spinner, BorderLayout.EAST ); + final MouseWheelListener mouseWheelListener = new MouseWheelListener() + { + + @Override + public void mouseWheelMoved( final MouseWheelEvent e ) + { + if ( !slider.isEnabled() ) + return; + final int notches = e.getWheelRotation(); + final int step = notches < 0 ? 1 : -1; + slider.setValue( slider.getValue() + step ); + } + }; + slider.addMouseWheelListener( mouseWheelListener ); + spinner.addMouseWheelListener( mouseWheelListener ); + this.model = model; model.setUpdateListener( this ); } @@ -196,6 +212,14 @@ public void setToolTipText( final String text ) slider.setToolTipText( text ); } + @Override + public void setEnabled( final boolean enabled ) + { + spinner.setEnabled( enabled ); + slider.setEnabled( enabled ); + super.setEnabled( enabled ); + } + @Override public void update() { From 596c57e15cecc15286e461965795c5fe58896866 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 20:00:23 +0100 Subject: [PATCH 226/263] fix the PanelSmoothContour. --- .../gui/components/PanelSmoothContour.java | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java b/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java index b607f42ab..20dc8b9ea 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java @@ -22,12 +22,12 @@ public class PanelSmoothContour extends JPanel private double scale; - private double previousValue; - private final SliderPanelDouble sliderPanel; private final JCheckBox chckbxSmooth; + private final BoundedDoubleElement scaleEl; + public PanelSmoothContour( final double scale, final String units ) { this.scale = scale; @@ -40,7 +40,7 @@ public PanelSmoothContour( final double scale, final String units ) final DoubleSupplier getter = () -> getScale(); final Consumer< Double > setter = v -> setScalePrivate( v ); - final BoundedDoubleElement scaleEl = StyleElements.boundedDoubleElement( "scale", 0., 20., getter, setter ); + scaleEl = StyleElements.boundedDoubleElement( "scale", 0., 20., getter, setter ); sliderPanel = StyleElements.linkedSliderPanel( scaleEl, 2 ); sliderPanel.setFont( SMALL_FONT ); @@ -48,24 +48,18 @@ public PanelSmoothContour( final double scale, final String units ) add( Box.createHorizontalStrut( 5 ) ); final JLabel lblUnits = new JLabel( units ); lblUnits.setFont( SMALL_FONT ); - - chckbxSmooth.addActionListener( e -> refresh() ); - refresh(); + add( lblUnits ); + + chckbxSmooth.addActionListener( e -> sliderPanel.setEnabled( chckbxSmooth.isSelected() ) ); + setOnOff(); + if ( scale > 0. ) + scaleEl.set( scale ); } - private void refresh() + private void setOnOff() { - final boolean selected = chckbxSmooth.isSelected(); - if ( !selected ) - { - previousValue = this.scale; - this.scale = -.1; - } - else - { - this.scale = previousValue; - } - sliderPanel.setEnabled( selected ); + chckbxSmooth.setSelected( scale > 0. ); + sliderPanel.setEnabled( scale > 0. ); } private void setScalePrivate( final double scale ) @@ -76,11 +70,15 @@ private void setScalePrivate( final double scale ) public void setScale( final double scale ) { setScalePrivate( scale ); - refresh(); + setOnOff(); + scaleEl.getValue().setCurrentValue( scale ); + sliderPanel.update(); } public double getScale() { - return scale; + if ( chckbxSmooth.isSelected() ) + return scale; + return -1.; } } From 1b2748536908371880ae6a909480b27399b58f4c Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 20:04:35 +0100 Subject: [PATCH 227/263] Implement smoothing of images before segmentation. Changes in the core methods in MaskUtils, SpotMeshUtils and SpotRoiUtils: - They take a smoothingScale double parameter. - If negative, it is ignored, and the segmentation (by mask, threshold and label image) processes directly on the input image. - Otherwise, the inputs are filtered by a Gaussian filter with sigmas derived from the specified scale. In 2D and 3D. Even for the bitmasks of the label image. This yields much smoother meshes and contour, of a smoothness controlled by the scale. - This is different from the 'simplify' flag, that prunes the contour and the mesh, but still 'sticks' to the shape in the input without filtering. --- .../plugin/trackmate/detection/MaskUtils.java | 124 +++++++++++------- .../trackmate/detection/SpotMeshUtils.java | 51 +++---- .../trackmate/detection/SpotRoiUtils.java | 77 +++++++++-- .../plugin/trackmate/mesh/Demo3DMesh.java | 2 +- 4 files changed, 173 insertions(+), 81 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 327f55139..db7cedca5 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -33,6 +33,7 @@ import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.gauss3.Gauss3; import net.imglib2.algorithm.labeling.ConnectedComponents; import net.imglib2.algorithm.labeling.ConnectedComponents.StructuringElement; import net.imglib2.converter.Converter; @@ -41,14 +42,17 @@ import net.imglib2.histogram.Real1dBinMapper; import net.imglib2.img.Img; import net.imglib2.img.ImgFactory; +import net.imglib2.parallel.Parallelization; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegionCursor; import net.imglib2.roi.labeling.LabelRegions; +import net.imglib2.type.NativeType; import net.imglib2.type.logic.BitType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.type.numeric.real.FloatType; import net.imglib2.util.Util; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; @@ -169,20 +173,17 @@ public static final long getThreshold( final Histogram1d< ? > hist ) * @return a new label image. */ public static final < T extends RealType< T > > ImgLabeling< Integer, IntType > toLabeling( - final RandomAccessible< T > input, - final Interval interval, + final RandomAccessibleInterval< T > input, final double threshold, final int numThreads ) { - // Crop. - final IntervalView< T > crop = Views.interval( input, interval ); - final IntervalView< T > in = Views.zeroMin( crop ); + // To mask. final Converter< T, BitType > converter = ( a, b ) -> b.set( a.getRealDouble() > threshold ); - final RandomAccessible< BitType > bitMask = Converters.convertRAI( in, converter, new BitType() ); + final RandomAccessible< BitType > bitMask = Converters.convertRAI( input, converter, new BitType() ); // Prepare output. - final ImgFactory< IntType > factory = Util.getArrayOrCellImgFactory( in, new IntType() ); - final Img< IntType > out = factory.create( in ); + final ImgFactory< IntType > factory = Util.getArrayOrCellImgFactory( input, new IntType() ); + final Img< IntType > out = factory.create( input ); final ImgLabeling< Integer, IntType > labeling = new ImgLabeling<>( out ); // Structuring element. @@ -229,8 +230,17 @@ public static < T extends RealType< T > > List< Spot > fromThreshold( final double threshold, final int numThreads ) { + /* + * Crop. + */ + final IntervalView< T > crop = Views.interval( input, interval ); + final IntervalView< T > in = Views.zeroMin( crop ); + // Get labeling from mask. - final ImgLabeling< Integer, IntType > labeling = toLabeling( input, interval, threshold, numThreads ); + final ImgLabeling< Integer, IntType > labeling = toLabeling( + in, + threshold, + numThreads ); return fromLabeling( labeling, interval, @@ -328,8 +338,15 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > final int numThreads, final RandomAccessibleInterval< R > qualityImage ) { + // Crop. + final IntervalView< T > crop = Views.interval( input, interval ); + final IntervalView< T > in = Views.zeroMin( crop ); + // Get labeling from mask. - final ImgLabeling< Integer, IntType > labeling = toLabeling( input, interval, threshold, numThreads ); + final ImgLabeling< Integer, IntType > labeling = toLabeling( + in, + threshold, + numThreads ); // Crop of the quality image. final IntervalView< R > cropQuality = Views.interval( qualityImage, interval ); @@ -418,7 +435,7 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot > * the image in which to read the quality value. * @return a list of spots, with ROI. */ - public static < T extends RealType< T >, S extends RealType< S > > List< Spot > fromMaskWithROI( + public static < T extends RealType< T > & NativeType< T >, S extends RealType< S > > List< Spot > fromMaskWithROI( final RandomAccessible< T > input, final Interval interval, final double[] calibration, @@ -427,34 +444,16 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { - final ImgLabeling< Integer, IntType > labeling = toLabeling( + final double threshold = 0.5; + return fromThresholdWithROI( input, interval, - .5, - numThreads ); - if ( input.numDimensions() == 2 ) - { - return SpotRoiUtils.from2DLabelingWithROI( - labeling, - interval, - calibration, - simplify, - qualityImage ); - } - else if ( input.numDimensions() == 3 ) - { - return SpotMeshUtils.from3DLabelingWithROI( - labeling, - interval, - calibration, - simplify, - smoothingScale, - qualityImage ); - } - else - { - throw new IllegalArgumentException( "Can only process 2D or 3D images with this method, but got " + input.numDimensions() + "D." ); - } + calibration, + threshold, + simplify, + smoothingScale, + numThreads, + qualityImage ); } /** @@ -478,35 +477,64 @@ else if ( input.numDimensions() == 3 ) * @param simplify * if true the polygon will be post-processed to be * smoother and contain less points. + * @param smoothingScale + * if strictly larger than 0, the input will be smoothed before + * creating the contour, resulting in smoother contours. The + * scale value sets the (Gaussian) filter radius and is specified + * in physical units. If 0 or lower than 0, no smoothing is + * applied. * @param numThreads * how many threads to use for multithreaded computation. * @param qualityImage * the image in which to read the quality value. * @return a list of spots, with ROI. */ - public static final < T extends RealType< T >, S extends RealType< S > > List< Spot > fromThresholdWithROI( + @SuppressWarnings( "unchecked" ) + public static final < T extends RealType< T > & NativeType< T >, S extends RealType< S > > List< Spot > fromThresholdWithROI( final RandomAccessible< T > input, final Interval interval, final double[] calibration, final double threshold, final boolean simplify, + final double smoothingScale, final int numThreads, final RandomAccessibleInterval< S > qualityImage ) { + /* + * Crop. + */ + final IntervalView< T > crop = Views.interval( input, interval ); + final IntervalView< T > in = Views.zeroMin( crop ); + + /* + * Possibly filter. + */ + final RandomAccessibleInterval< T > filtered; + if ( smoothingScale > 0. ) + { + final double[] sigmas = new double[ in.numDimensions() ]; + for ( int d = 0; d < sigmas.length; d++ ) + sigmas[ d ] = smoothingScale / Math.sqrt( in.numDimensions() ) / calibration[ d ]; + + filtered = ( RandomAccessibleInterval< T > ) Util.getArrayOrCellImgFactory( in, new FloatType() ).create( in ); + Parallelization.runWithNumThreads( numThreads, + () -> Gauss3.gauss( sigmas, Views.extendMirrorDouble( in ), filtered ) ); + } + else + { + filtered = in; + } + if ( input.numDimensions() == 2 ) { /* * In 2D: Threshold, make a labeling, then create contours. */ - final ImgLabeling< Integer, IntType > labeling = toLabeling( - input, - interval, - threshold, - numThreads ); - return SpotRoiUtils.from2DLabelingWithROI( - labeling, - interval, + return SpotRoiUtils.from2DThresholdWithROI( + filtered, + interval.minAsDoubleArray(), calibration, + threshold, simplify, qualityImage ); } @@ -519,8 +547,8 @@ else if ( input.numDimensions() == 3 ) * version of marching-cubes to have nice, smooth meshes. */ return SpotMeshUtils.from3DThresholdWithROI( - input, - interval, + filtered, + interval.minAsDoubleArray(), calibration, threshold, simplify, diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 98b952d98..9605d29a3 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -9,7 +9,6 @@ import fiji.plugin.trackmate.SpotMesh; import net.imglib2.Interval; import net.imglib2.IterableInterval; -import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealInterval; import net.imglib2.algorithm.gauss3.Gauss3; @@ -22,6 +21,7 @@ import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; +import net.imglib2.type.NativeType; import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; @@ -74,8 +74,10 @@ public class SpotMeshUtils * the type of the quality image. Must be real, scalar. * @param input * the source image, must be zero-min and 3D. - * @param interval - * the interval in which to segment spots. + * @param origin + * the origin (min pos) of the interval the labeling was + * generated from, used to reposition the spots from the zero-min + * labeling to the proper coordinates. * @param calibration * the physical calibration. * @param threshold @@ -83,13 +85,19 @@ public class SpotMeshUtils * @param simplify * if true the meshes will be post-processed to be * smoother and contain less points. + * @param smoothingScale + * if strictly larger than 0, the input will be smoothed before + * creating the contour, resulting in smoother contours. The + * scale value sets the (Gaussian) filter radius and is specified + * in physical units. If 0 or lower than 0, no smoothing is + * applied. * @param qualityImage * the image in which to read the quality value. * @return a list of spots, with meshes. */ - public static < T extends RealType< T >, S extends RealType< S > > List< Spot > from3DThresholdWithROI( - final RandomAccessible< T > input, - final Interval interval, + public static < T extends RealType< T > & NativeType< T >, S extends RealType< S > > List< Spot > from3DThresholdWithROI( + final RandomAccessibleInterval< T > input, + final double[] origin, final double[] calibration, final double threshold, final boolean simplify, @@ -98,12 +106,8 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > if ( input.numDimensions() != 3 ) throw new IllegalArgumentException( "Can only process 3D images with this method, but got " + input.numDimensions() + "D." ); - // Crop. - final RandomAccessibleInterval< T > crop = Views.interval( input, interval ); - final RandomAccessibleInterval< T > in = Views.zeroMin( crop ); - // Get big mesh. - final Mesh mc = Meshes.marchingCubes( in, threshold ); + final Mesh mc = Meshes.marchingCubes( input, threshold ); final Mesh bigMesh = Meshes.removeDuplicateVertices( mc, VERTEX_DUPLICATE_REMOVAL_PRECISION ); // Split into connected components. @@ -147,7 +151,6 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > // Create spot from merged meshes. final List< Spot > spots = new ArrayList<>( out.size() ); - final double[] origin = interval.minAsDoubleArray(); for ( final Mesh mesh : out ) { final SpotMesh spot = meshToSpotMesh( @@ -163,9 +166,9 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > } /** - * Creates spots with meshes from a 3D label image. The - * quality value is read from a secondary image, by taking the max value in - * each ROI. + * Creates spots with meshes from a 3D label image. The labels + * are possibly smoothed before creating the mesh. The quality value is read + * from a secondary image, by taking the max value in each ROI. * * @param * the type that backs-up the labeling. @@ -179,15 +182,15 @@ public static < T extends RealType< T >, S extends RealType< S > > List< Spot > * @param calibration * the physical calibration. * @param simplify - * if true the meshes will be post-processed to be - * smoother and contain less points. - * @param qualityImage - * the image in which to read the quality value. + * if true the meshes will be post-processed to + * contain less verrtices. * @param smoothingScale * if strictly larger than 0, the mask will be smoothed before * creating the mesh, resulting in smoother meshes. The scale * value sets the (Gaussian) filter radius and is specified in * physical units. If 0 or lower than 0, no smoothing is applied. + * @param qualityImage + * the image in which to read the quality value. * @return a list of spots, with meshes. */ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from3DLabelingWithROI( @@ -212,9 +215,9 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot region, simplify, calibration, - qualityImage, + smoothingScale, interval.minAsDoubleArray(), - smoothingScale ); + qualityImage ); if ( spot == null ) continue; @@ -255,9 +258,9 @@ private static < S extends RealType< S > > Spot regionToSpotMesh( final RandomAccessibleInterval< BoolType > region, final boolean simplify, final double[] calibration, - final RandomAccessibleInterval< S > qualityImage, + final double smoothingScale, final double[] minInterval, - final double smoothingScale ) + final RandomAccessibleInterval< S > qualityImage ) { final RandomAccessibleInterval< BoolType > box = Views.zeroMin( region ); final Mesh mesh; @@ -276,7 +279,7 @@ private static < S extends RealType< S > > Spot regionToSpotMesh( final Interval outputSize = Intervals.expand( box, borders ); final Img< FloatType > img = Util.getArrayOrCellImgFactory( outputSize, new FloatType() ).create( outputSize ); final RandomAccessibleInterval< FloatType > filtered = Views.translateInverse( img, borders ); - Gauss3.gauss( sigmas, box, filtered ); + Gauss3.gauss( sigmas, Views.extendZero( box ), filtered ); mesh = Meshes.marchingCubes( img, 0.5 ); } else diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java index a1efc6933..aecee2380 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java @@ -11,16 +11,25 @@ import ij.gui.PolygonRoi; import ij.measure.Measurements; import ij.process.FloatPolygon; -import net.imglib2.Interval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.algorithm.gauss3.Gauss3; +import net.imglib2.converter.Converter; +import net.imglib2.converter.Converters; +import net.imglib2.img.Img; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; import net.imglib2.type.BooleanType; +import net.imglib2.type.NativeType; +import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.NumericType; +import net.imglib2.type.numeric.RealType; +import net.imglib2.type.numeric.integer.IntType; +import net.imglib2.type.numeric.real.FloatType; +import net.imglib2.util.Util; import net.imglib2.view.Views; /** @@ -38,6 +47,27 @@ public class SpotRoiUtils /** Douglas-Peucker polygon simplification max distance. */ private static final double DOUGLAS_PEUCKER_MAX_DISTANCE = 0.5; + public static < T extends RealType< T > & NativeType< T >, S extends NumericType< S > > List< Spot > from2DThresholdWithROI( + final RandomAccessibleInterval< T > input, + final double[] origin, + final double[] calibration, + final double threshold, + final boolean simplify, + final RandomAccessibleInterval< S > qualityImage ) + { + final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( + input, + threshold, + 1 ); + return from2DLabelingWithROI( + labeling, + origin, + calibration, + simplify, + -1., + qualityImage ); + } + /** * Creates spots with ROIs from a 2D label image. The quality * value is read from a secondary image, by taking the max value in each @@ -49,23 +79,30 @@ public class SpotRoiUtils * the type of the quality image. Must be real, scalar. * @param labeling * the labeling, must be zero-min and 2D.. - * @param interval - * the interval, used to reposition the spots from the zero-min + * @param origin + * the origin (min pos) of the interval the labeling was + * generated from, used to reposition the spots from the zero-min * labeling to the proper coordinates. * @param calibration * the physical calibration. * @param simplify * if true the polygon will be post-processed to be * smoother and contain less points. + * @param smoothingScale + * if strictly larger than 0, the mask will be smoothed before + * creating the mesh, resulting in smoother meshes. The scale + * value sets the (Gaussian) filter radius and is specified in + * physical units. If 0 or lower than 0, no smoothing is applied. * @param qualityImage * the image in which to read the quality value. * @return a list of spots, with ROI. */ public static < R extends IntegerType< R >, S extends NumericType< S > > List< Spot > from2DLabelingWithROI( final ImgLabeling< Integer, R > labeling, - final Interval interval, + final double[] origin, final double[] calibration, final boolean simplify, + final double smoothingScale, final RandomAccessibleInterval< S > qualityImage ) { if ( labeling.numDimensions() != 2 ) @@ -73,17 +110,41 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); + final double[] sigmas = new double[ 2 ]; + for ( int d = 0; d < sigmas.length; d++ ) + sigmas[ d ] = smoothingScale / Math.sqrt( 2. ) / calibration[ d ]; + + // Parse regions to create polygons on boundaries. final List< Polygon > polygons = new ArrayList<>( regions.getExistingLabels().size() ); final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); while ( iterator.hasNext() ) { final LabelRegion< Integer > region = iterator.next(); + + // Possibly smooth labels. + final RandomAccessibleInterval< BoolType > mask; + if (smoothingScale > 0.) + { + // Filter. + final Img< FloatType > filtered = Util.getArrayOrCellImgFactory( region, new FloatType() ).create( region ); + Gauss3.gauss( sigmas, region, filtered ); + + // To mask. + final double threshold = 0.5; + final Converter< FloatType, BoolType > converter = ( a, b ) -> b.set( a.getRealDouble() > threshold ); + mask = Converters.convertRAI( filtered, converter, new BoolType() ); + } + else + { + mask = region; + } + // Analyze in zero-min region. - final List< Polygon > pp = maskToPolygons( Views.zeroMin( region ) ); + final List< Polygon > pp = maskToPolygons( Views.zeroMin( mask ) ); // Translate back to interval coords. for ( final Polygon polygon : pp ) - polygon.translate( ( int ) region.min( 0 ), ( int ) region.min( 1 ) ); + polygon.translate( ( int ) mask.min( 0 ), ( int ) mask.min( 1 ) ); polygons.addAll( pp ); } @@ -127,8 +188,8 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S final double[] ypoly = new double[ fPolygon.npoints ]; for ( int i = 0; i < fPolygon.npoints; i++ ) { - xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 ); - ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 ); + xpoly[ i ] = calibration[ 0 ] * ( origin[ 0 ] + fPolygon.xpoints[ i ] - 0.5 ); + ypoly[ i ] = calibration[ 1 ] * ( origin[ 1 ] + fPolygon.ypoints[ i ] - 0.5 ); } spots.add( SpotRoi.createSpot( xpoly, ypoly, quality ) ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 7e7cdf5fb..7acfba8ee 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -52,7 +52,7 @@ public static void main( final String[] args ) final ImgPlus< BitType > mask = loadTestMask(); // Convert it to labeling. - final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( mask, mask, 0.5, 1 ); + final ImgLabeling< Integer, IntType > labeling = MaskUtils.toLabeling( mask, 0.5, 1 ); final ImagePlus out = ImageJFunctions.show( labeling.getIndexImg(), "labeling" ); out.setDimensions( mask.dimensionIndex( Axes.CHANNEL ), mask.dimensionIndex( Axes.Z ), mask.dimensionIndex( Axes.TIME ) ); From 65789e274f338b0d9e7b5e477e2d621f846404c6 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 20:06:36 +0100 Subject: [PATCH 228/263] Implement the smoothing scale in the threshold, mask and label detectors. --- .../trackmate/detection/LabelImageDetector.java | 3 ++- .../detection/LabelImageDetectorFactory.java | 10 +++++++--- .../trackmate/detection/MaskDetectorFactory.java | 11 +++++++++-- .../trackmate/detection/ThresholdDetector.java | 1 + .../detection/ThresholdDetectorFactory.java | 15 ++++++++++----- .../LabelImageDetectorConfigurationPanel.java | 8 +++----- .../detector/MaskDetectorConfigurationPanel.java | 5 +---- .../ThresholdDetectorConfigurationPanel.java | 13 ++++++++++--- 8 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index 8640f28d3..863f6b255 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -144,9 +144,10 @@ private < R extends IntegerType< R > > void processIntegerImg( final RandomAcces { spots = SpotRoiUtils.from2DLabelingWithROI( labeling, - interval, + interval.minAsDoubleArray(), calibration, simplify, + smoothingScale, null ); } else if ( input.numDimensions() == 3 ) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java index bc181e5c7..76e9dd614 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java @@ -30,6 +30,7 @@ import static fiji.plugin.trackmate.io.IOUtils.writeAttribute; import static fiji.plugin.trackmate.io.IOUtils.writeTargetChannel; import static fiji.plugin.trackmate.util.TMUtils.checkMapKeys; +import static fiji.plugin.trackmate.util.TMUtils.checkOptionalParameter; import static fiji.plugin.trackmate.util.TMUtils.checkParameter; import java.util.ArrayList; @@ -154,14 +155,16 @@ public boolean checkSettings( final Map< String, Object > lSettings ) final StringBuilder errorHolder = new StringBuilder(); ok = ok & checkParameter( lSettings, KEY_TARGET_CHANNEL, Integer.class, errorHolder ); ok = ok & checkParameter( lSettings, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder ); + ok = ok & checkOptionalParameter( lSettings, KEY_SMOOTHING_SCALE, Double.class, errorHolder ); final List< String > mandatoryKeys = new ArrayList<>(); mandatoryKeys.add( KEY_TARGET_CHANNEL ); mandatoryKeys.add( KEY_SIMPLIFY_CONTOURS ); - ok = ok & checkMapKeys( lSettings, mandatoryKeys, null, errorHolder ); + final List< String > optionalKeys = new ArrayList<>(); + optionalKeys.add( KEY_SMOOTHING_SCALE ); + ok = ok & checkMapKeys( lSettings, mandatoryKeys, optionalKeys, errorHolder ); if ( !ok ) - { errorMessage = errorHolder.toString(); - } + return ok; } @@ -218,6 +221,7 @@ public Map< String, Object > getDefaultSettings() final Map< String, Object > lSettings = new HashMap<>(); lSettings.put( KEY_TARGET_CHANNEL, DEFAULT_TARGET_CHANNEL ); lSettings.put( KEY_SIMPLIFY_CONTOURS, true ); + lSettings.put( KEY_SMOOTHING_SCALE, -1. ); return lSettings; } diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java index 23bca9571..a84ee609c 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -24,10 +24,12 @@ import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_TARGET_CHANNEL; import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL; import static fiji.plugin.trackmate.io.IOUtils.readBooleanAttribute; +import static fiji.plugin.trackmate.io.IOUtils.readDoubleAttribute; import static fiji.plugin.trackmate.io.IOUtils.readIntegerAttribute; import static fiji.plugin.trackmate.io.IOUtils.writeAttribute; import static fiji.plugin.trackmate.io.IOUtils.writeTargetChannel; import static fiji.plugin.trackmate.util.TMUtils.checkMapKeys; +import static fiji.plugin.trackmate.util.TMUtils.checkOptionalParameter; import static fiji.plugin.trackmate.util.TMUtils.checkParameter; import java.util.ArrayList; @@ -147,10 +149,13 @@ public boolean checkSettings( final Map< String, Object > lSettings ) final StringBuilder errorHolder = new StringBuilder(); ok = ok & checkParameter( lSettings, KEY_TARGET_CHANNEL, Integer.class, errorHolder ); ok = ok & checkParameter( lSettings, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder ); + ok = ok & checkOptionalParameter( lSettings, KEY_SMOOTHING_SCALE, Double.class, errorHolder ); final List< String > mandatoryKeys = new ArrayList<>(); mandatoryKeys.add( KEY_TARGET_CHANNEL ); mandatoryKeys.add( KEY_SIMPLIFY_CONTOURS ); - ok = ok & checkMapKeys( lSettings, mandatoryKeys, null, errorHolder ); + final List< String > optionalKeys = new ArrayList<>(); + optionalKeys.add( KEY_SMOOTHING_SCALE ); + ok = ok & checkMapKeys( lSettings, mandatoryKeys, optionalKeys, errorHolder ); if ( !ok ) { errorMessage = errorHolder.toString(); @@ -163,7 +168,8 @@ public boolean marshall( final Map< String, Object > lSettings, final Element el { final StringBuilder errorHolder = new StringBuilder(); final boolean ok = writeTargetChannel( lSettings, element, errorHolder ) - && writeAttribute( lSettings, element, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder ); + && writeAttribute( lSettings, element, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder ) + && writeAttribute( lSettings, element, KEY_SMOOTHING_SCALE, Double.class, errorHolder ); if ( !ok ) errorMessage = errorHolder.toString(); @@ -179,6 +185,7 @@ public boolean unmarshall( final Element element, final Map< String, Object > lS boolean ok = true; ok = ok & readIntegerAttribute( element, lSettings, KEY_TARGET_CHANNEL, errorHolder ); ok = ok & readBooleanAttribute( element, lSettings, KEY_SIMPLIFY_CONTOURS, errorHolder ); + ok = ok & readDoubleAttribute( element, lSettings, KEY_SMOOTHING_SCALE, errorHolder ); if ( !ok ) { errorMessage = errorHolder.toString(); diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java index 41528b024..50238194b 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java @@ -124,6 +124,7 @@ public boolean process() calibration, threshold, simplify, + smoothingScale, numThreads, null ); final long end = System.currentTimeMillis(); diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java index f26911e43..ce5f85cfe 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -29,6 +29,7 @@ import static fiji.plugin.trackmate.io.IOUtils.writeAttribute; import static fiji.plugin.trackmate.io.IOUtils.writeTargetChannel; import static fiji.plugin.trackmate.util.TMUtils.checkMapKeys; +import static fiji.plugin.trackmate.util.TMUtils.checkOptionalParameter; import static fiji.plugin.trackmate.util.TMUtils.checkParameter; import java.util.ArrayList; @@ -119,7 +120,7 @@ public SpotDetector< T > getDetector( final Interval interval, final int frame ) { final double intensityThreshold = ( Double ) settings.get( KEY_INTENSITY_THRESHOLD ); final boolean simplifyContours = ( Boolean ) settings.get( KEY_SIMPLIFY_CONTOURS ); - final double smoothingScale = ( Double ) settings.get( KEY_SMOOTHING_SCALE ); + final double smoothingScale = ( Double ) settings.getOrDefault( KEY_SMOOTHING_SCALE, -1. ); final double[] calibration = TMUtils.getSpatialCalibration( img ); final int channel = ( Integer ) settings.get( KEY_TARGET_CHANNEL ) - 1; final RandomAccessible< T > imFrame = DetectionUtils.prepareFrameImg( img, channel, frame ); @@ -167,15 +168,17 @@ public boolean checkSettings( final Map< String, Object > lSettings ) ok = ok & checkParameter( lSettings, KEY_TARGET_CHANNEL, Integer.class, errorHolder ); ok = ok & checkParameter( lSettings, KEY_INTENSITY_THRESHOLD, Double.class, errorHolder ); ok = ok & checkParameter( lSettings, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder ); + ok = ok & checkOptionalParameter( lSettings, KEY_SMOOTHING_SCALE, Double.class, errorHolder ); final List< String > mandatoryKeys = new ArrayList<>(); mandatoryKeys.add( KEY_TARGET_CHANNEL ); mandatoryKeys.add( KEY_INTENSITY_THRESHOLD ); mandatoryKeys.add( KEY_SIMPLIFY_CONTOURS ); - ok = ok & checkMapKeys( lSettings, mandatoryKeys, null, errorHolder ); + final List< String > optionalKeys = new ArrayList<>(); + optionalKeys.add( KEY_SMOOTHING_SCALE ); + ok = ok & checkMapKeys( lSettings, mandatoryKeys, optionalKeys, errorHolder ); if ( !ok ) - { errorMessage = errorHolder.toString(); - } + return ok; } @@ -185,7 +188,8 @@ public boolean marshall( final Map< String, Object > lSettings, final Element el final StringBuilder errorHolder = new StringBuilder(); final boolean ok = writeTargetChannel( lSettings, element, errorHolder ) && writeAttribute( lSettings, element, KEY_INTENSITY_THRESHOLD, Double.class, errorHolder ) - && writeAttribute( lSettings, element, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder ); + && writeAttribute( lSettings, element, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder ) + && writeAttribute( lSettings, element, KEY_SMOOTHING_SCALE, Double.class, errorHolder ); if ( !ok ) errorMessage = errorHolder.toString(); @@ -202,6 +206,7 @@ public boolean unmarshall( final Element element, final Map< String, Object > lS ok = ok & readIntegerAttribute( element, lSettings, KEY_TARGET_CHANNEL, errorHolder ); ok = ok & readDoubleAttribute( element, lSettings, KEY_INTENSITY_THRESHOLD, errorHolder ); ok = ok & readBooleanAttribute( element, lSettings, KEY_SIMPLIFY_CONTOURS, errorHolder ); + ok = ok & readDoubleAttribute( element, lSettings, KEY_SMOOTHING_SCALE, errorHolder ); if ( !ok ) { errorMessage = errorHolder.toString(); diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/detector/LabelImageDetectorConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/detector/LabelImageDetectorConfigurationPanel.java index 7fff2eff7..93b8a2b21 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/detector/LabelImageDetectorConfigurationPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/detector/LabelImageDetectorConfigurationPanel.java @@ -21,7 +21,7 @@ */ package fiji.plugin.trackmate.gui.components.detector; -import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL; +import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD; import java.util.Map; @@ -29,7 +29,6 @@ import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.detection.LabelImageDetectorFactory; import fiji.plugin.trackmate.detection.SpotDetectorFactory; -import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; /** * Configuration panel for spot detectors based on label images. @@ -59,15 +58,14 @@ public LabelImageDetectorConfigurationPanel( public Map< String, Object > getSettings() { final Map< String, Object > lSettings = super.getSettings(); - lSettings.remove( ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD ); + lSettings.remove( KEY_INTENSITY_THRESHOLD ); return lSettings; } @Override public void setSettings( final Map< String, Object > settings ) { - sliderChannel.setValue( ( Integer ) settings.get( KEY_TARGET_CHANNEL ) ); - chkboxSimplify.setSelected( ( Boolean ) settings.get( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS ) ); + setSettingsNonIntensity( settings ); } /** diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/detector/MaskDetectorConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/detector/MaskDetectorConfigurationPanel.java index 83090bb8b..5bb008a1d 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/detector/MaskDetectorConfigurationPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/detector/MaskDetectorConfigurationPanel.java @@ -21,8 +21,6 @@ */ package fiji.plugin.trackmate.gui.components.detector; -import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL; - import java.util.Map; import fiji.plugin.trackmate.Model; @@ -66,8 +64,7 @@ public Map< String, Object > getSettings() @Override public void setSettings( final Map< String, Object > settings ) { - sliderChannel.setValue( ( Integer ) settings.get( KEY_TARGET_CHANNEL ) ); - chkboxSimplify.setSelected( ( Boolean ) settings.get( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS ) ); + setSettingsNonIntensity( settings ); } /** diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java index 85cec75d0..23375850f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/detector/ThresholdDetectorConfigurationPanel.java @@ -318,13 +318,20 @@ public Map< String, Object > getSettings() return lSettings; } - @Override - public void setSettings( final Map< String, Object > settings ) + protected void setSettingsNonIntensity( final Map< String, Object > settings ) { sliderChannel.setValue( ( Integer ) settings.get( KEY_TARGET_CHANNEL ) ); chkboxSimplify.setSelected( ( Boolean ) settings.get( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS ) ); + final Object scaleObj = settings.get( KEY_SMOOTHING_SCALE ); + final double scale = scaleObj == null ? -1. : ( ( Number ) scaleObj ).doubleValue(); + smoothingPanel.setScale( scale ); + } - final Double intensityThreshold = Double.valueOf( ( Double ) settings.get( ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD ) ); + @Override + public void setSettings( final Map< String, Object > settings ) + { + setSettingsNonIntensity( settings ); + final Double intensityThreshold = Double.valueOf( ( Double ) settings.get( KEY_INTENSITY_THRESHOLD ) ); if ( intensityThreshold == null || intensityThreshold == 0. ) autoThreshold(); else From f79151e581a1549b00ccd2a961c4c375a8824804 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 28 Nov 2023 16:08:59 +0100 Subject: [PATCH 229/263] setFont() for the slider panels. --- .../plugin/trackmate/gui/displaysettings/SliderPanel.java | 8 ++++++++ .../trackmate/gui/displaysettings/SliderPanelDouble.java | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java index 1a34d9b38..0ca01174f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -37,6 +37,8 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import fiji.plugin.trackmate.gui.GuiUtils; + /** * A {@link JSlider} with a {@link JSpinner} next to it, both modifying the same * {@link BoundedValue value}. @@ -176,6 +178,12 @@ public void setEnabled( final boolean enabled ) super.setEnabled( enabled ); } + @Override + public void setFont( final Font font ) + { + GuiUtils.setFont( this, font ); + } + @Override public void update() { diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java index d2cb30060..0817e51b8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -39,6 +39,8 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import fiji.plugin.trackmate.gui.GuiUtils; + /** * A {@link JSlider} with a {@link JSpinner} next to it, both modifying the same * {@link BoundedValue value}. @@ -220,6 +222,12 @@ public void setEnabled( final boolean enabled ) super.setEnabled( enabled ); } + @Override + public void setFont( final Font font ) + { + GuiUtils.setFont( this, font ); + } + @Override public void update() { From 81d465032080ac8a8a6b98a45d63cd0760919cbe Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 29 Nov 2023 16:12:16 +0100 Subject: [PATCH 230/263] Fix incorrect documentation. --- .../detection/SpotGlobalDetectorFactory.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java index ddc881490..a9950892a 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java @@ -21,6 +21,7 @@ */ package fiji.plugin.trackmate.detection; +import fiji.plugin.trackmate.util.TMUtils; import net.imglib2.Interval; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; @@ -46,11 +47,13 @@ public interface SpotGlobalDetectorFactory< T extends RealType< T > & NativeType * * @param interval * the interval that determines the region in the source image to - * operate on. This must not have a dimension for time - * (e.g. if the source image is 2D+T (3D), then the - * interval must be 2D; if the source image is 3D without time, - * then the interval must be 3D). + * operate on. This must have a dimension for time, but + * not for channels (e.g. if the source image is 2D+T + * (3D), then the interval must be 3D; if the source image is 3D + * without time, then the interval must be 4D). * @return a new detector. + * @see TMUtils#getIntervalWithTime(net.imagej.ImgPlus, + * fiji.plugin.trackmate.Settings) */ public SpotGlobalDetector< T > getDetector( final Interval interval ); From c78f094037698edb35d4ca858125e669515ed6b5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Dec 2023 11:27:47 +0100 Subject: [PATCH 231/263] Fix compile error due to casting error. --- src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index db7cedca5..d05da3b40 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -489,7 +489,7 @@ public static < T extends RealType< T > & NativeType< T >, S extends RealType< S * the image in which to read the quality value. * @return a list of spots, with ROI. */ - @SuppressWarnings( "unchecked" ) + @SuppressWarnings( { "unchecked", "rawtypes" } ) public static final < T extends RealType< T > & NativeType< T >, S extends RealType< S > > List< Spot > fromThresholdWithROI( final RandomAccessible< T > input, final Interval interval, @@ -516,7 +516,7 @@ public static final < T extends RealType< T > & NativeType< T >, S extends RealT for ( int d = 0; d < sigmas.length; d++ ) sigmas[ d ] = smoothingScale / Math.sqrt( in.numDimensions() ) / calibration[ d ]; - filtered = ( RandomAccessibleInterval< T > ) Util.getArrayOrCellImgFactory( in, new FloatType() ).create( in ); + filtered = ( RandomAccessibleInterval ) Util.getArrayOrCellImgFactory( in, new FloatType() ).create( in ); Parallelization.runWithNumThreads( numThreads, () -> Gauss3.gauss( sigmas, Views.extendMirrorDouble( in ), filtered ) ); } From 949ad61404761cd31ef2359db07bba572d5134d2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Dec 2023 17:06:20 +0100 Subject: [PATCH 232/263] Move the shader files to the resources folder. So that they are properly included when compiling with maven. --- .../fiji/plugin/trackmate/visualization/bvv/mesh.fp | 0 .../fiji/plugin/trackmate/visualization/bvv/mesh.vp | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/main/{java => resources}/fiji/plugin/trackmate/visualization/bvv/mesh.fp (100%) rename src/main/{java => resources}/fiji/plugin/trackmate/visualization/bvv/mesh.vp (100%) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp b/src/main/resources/fiji/plugin/trackmate/visualization/bvv/mesh.fp similarity index 100% rename from src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.fp rename to src/main/resources/fiji/plugin/trackmate/visualization/bvv/mesh.fp diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp b/src/main/resources/fiji/plugin/trackmate/visualization/bvv/mesh.vp similarity index 100% rename from src/main/java/fiji/plugin/trackmate/visualization/bvv/mesh.vp rename to src/main/resources/fiji/plugin/trackmate/visualization/bvv/mesh.vp From ca5790b12bd38cbdd214aa04a454b007fa181c2a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 5 Feb 2024 16:46:54 +0100 Subject: [PATCH 233/263] Handle whether we have or have not channels with the Process2DZ. --- .../trackmate/detection/Process2DZ.java | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index c240fb0e6..f364529e0 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -14,9 +14,9 @@ import fiji.plugin.trackmate.util.TMUtils; import ij.ImagePlus; import net.imagej.ImgPlus; +import net.imagej.axis.Axes; import net.imglib2.Interval; import net.imglib2.RandomAccess; -import net.imglib2.RandomAccessible; import net.imglib2.algorithm.MultiThreadedBenchmarkAlgorithm; import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.mesh.alg.TaubinSmoothing; @@ -49,7 +49,7 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > private static final String BASE_ERROR_MESSAGE = "[Process2DZ] "; - private final RandomAccessible< T > img; + private final ImgPlus< T > img; private final Interval interval; @@ -67,8 +67,8 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > * Creates a new {@link Process2DZ} detector. * * @param img - * the input data. Must be 3D (plus possible channels) and the 3 - * dimensions must be X, Y and Z. + * the input data. Must be 3D or 4D (3D plus possibly channels) + * and the 3 spatial dimensions must be X, Y and Z. * @param interval * the interval in the input data to process. Must have the same * number of dimensions that the input data. @@ -76,13 +76,16 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > * the pixel size array. * @param settings * a TrackMate settings object, configured to operate on the - * (cropped) input data as if it was a 2D+T image. + * (cropped) input data as if it was a 2D(+C)+T image. * @param simplifyMeshes * whether or not to smooth and simplify meshes resulting from * merging the 2D contours. + * @param smoothingScale + * if positive, will smooth the 3D mask by a gaussian of + * specified sigma to yield smooth meshes. */ public Process2DZ( - final RandomAccessible< T > img, + final ImgPlus< T > img, final Interval interval, final double[] calibration, final Settings settings, @@ -100,9 +103,25 @@ public Process2DZ( @Override public boolean checkInput() { - if ( img.numDimensions() != 3 ) + if ( !( img.numDimensions() == 3 || img.numDimensions() == 4 ) ) { - errorMessage = BASE_ERROR_MESSAGE + "Source image is not 3D.\n"; + errorMessage = BASE_ERROR_MESSAGE + "Source image is not 3D or 4D, but " + img.numDimensions() + "D.\n"; + return false; + } + if ( img.dimensionIndex( Axes.TIME ) >= 0 ) + { + errorMessage = BASE_ERROR_MESSAGE + "Source image has a time dimension, but should not.\n"; + return false; + } + if ( img.dimensionIndex( Axes.Z ) < 0 ) + { + errorMessage = BASE_ERROR_MESSAGE + "Source image does not have a Z dimension.\n"; + return false; + } + if ( interval.numDimensions() != img.numDimensions() ) + { + errorMessage = BASE_ERROR_MESSAGE + "Provided interval does not have the same dimensionality that of the source image. " + + "Interval is " + interval.numDimensions() + "D and the image is " + img.numDimensions() + "D.\n"; return false; } return true; @@ -121,8 +140,9 @@ public boolean process() // Make the final single T 3D image, a 2D + T image final by making Z->T final IntervalView< T > cropped = Views.interval( img, interval ); final ImagePlus imp = ImageJFunctions.wrap( cropped, null ); - final int nFrames = ( int ) interval.dimension( 2 ); - final int nChannels = ( interval.numDimensions() > 3 ) ? ( int ) interval.dimension( 3 ) : 1; + final int nFrames = ( int ) interval.dimension( img.dimensionIndex( Axes.Z ) ); + final int cDim = img.dimensionIndex( Axes.CHANNEL ); + final int nChannels = cDim < 0 ? 1 : ( int ) interval.dimension( cDim ); imp.setDimensions( nChannels, 1, nFrames ); imp.getCalibration().pixelWidth = calibration[ 0 ]; imp.getCalibration().pixelHeight = calibration[ 1 ]; From ef67b22ea9ab213472157a066b9b7fffed9ddc56 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 5 Feb 2024 17:02:52 +0100 Subject: [PATCH 234/263] Fix handling of ROIs in Process2DZ with multiple channels. --- .../java/fiji/plugin/trackmate/detection/Process2DZ.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index f364529e0..7b071fc6a 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -243,8 +243,9 @@ public boolean process() newSpot.putFeature( Spot.QUALITY, Double.valueOf( avgQuality ) ); // Shift them by interval min. - for ( int d = 0; d < 3; d++ ) - newSpot.move( interval.min( d ) * calibration[ d ], d ); + newSpot.move( interval.min( img.dimensionIndex( Axes.X ) ) * calibration[ 0 ], 0 ); + newSpot.move( interval.min( img.dimensionIndex( Axes.Y ) ) * calibration[ 1 ], 1 ); + newSpot.move( interval.min( img.dimensionIndex( Axes.Z ) ) * calibration[ 2 ], 2 ); spots.add( newSpot ); } From e021eab4e51ba1d58ddc67036ad190cf72297a39 Mon Sep 17 00:00:00 2001 From: tpietzsch Date: Tue, 18 Apr 2023 15:54:48 -0500 Subject: [PATCH 235/263] WIP show meshes in bvv. requires 'mesh' branch of bvv --- src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java index 22b3f4d58..42d3bb3d4 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java @@ -42,6 +42,7 @@ public static < T extends Type< T > > void main( final String[] args ) final double[] cal = TMUtils.getSpatialCalibration( t1 ); final BvvSource source = BvvFunctions.show( c1, "t1", + Bvv.options() .maxAllowedStepInVoxels( 0 ) .renderWidth( 1024 ) @@ -52,7 +53,6 @@ public static < T extends Type< T > > void main( final String[] args ) source.setDisplayRangeBounds( 0, 1024 ); source.setColor( new ARGBType( 0xaaffaa ) ); - final List< StupidMesh > meshes = new ArrayList<>(); for ( int j = 1; j <= 3; ++j ) { @@ -83,7 +83,6 @@ public static < T extends Type< T > > void main( final String[] args ) viewer.requestRepaint(); } - private static BufferMesh load( final String fn ) { BufferMesh mesh = null; From a2f899dc211896ec12f98dcb7b66ee38d80ad443 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 1 Sep 2023 18:08:14 +0200 Subject: [PATCH 236/263] Add a button to launch LabKit in the configure views panel of the wizard. But because the other table, trackscheme and bvv buttons take too much place, we don't see it without resizing the window. --- .../trackmate/gui/components/ConfigureViewsPanel.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java index da15d129a..b604918d9 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -384,11 +384,15 @@ public ConfigureViewsPanel( btnLabKit.setText( "Launch spot editor" ); btnLabKit.setIcon( Icons.PENCIL_ICON ); btnLabKit.setToolTipText( "" - + "Launch the Labkit editor to edit spot segmentation
      " - + "on the time-point currently displayed in the main
      " + + "Launch the Labkit editor to edit spot segmentation
      " + + "on the time-point currently displayed in the main
      " + "view." + "

      " - + "Shift + click will launch the editor on all the
      " + + "If a ROI is present in the image, only the spots and the
      " + + "image in the ROI will be opened for edition in LabKit
      " + + "(this can speed up editing large images)." + + "

      " + + "Shift + click will launch the editor on all the
      " + "time-points in the movie." ); panelButtons.add( btnLabKit ); } From 1d2f2f982ee7124b18147218c86363fa11f0ab0d Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 2 Sep 2023 18:44:00 +0200 Subject: [PATCH 237/263] Rework and improve the import method of the spot editor. - We don't depend on labels anymore, but directly operate and compare the index images (before modification and after). Because the index is directly related to the spot ID, we can get a match from previous spot to novel spot in an easy manner. - The spots from the edited version are created directly from the novel index image, using something adapted from the label image detector code, so again, just one pass. We use the fact that we can provide it with a 'quality' image, and read the index of the label image 'under' the spot and write it into its quality value. This way we can retrieve the id of the matching previous spot in an easy manner. - The price to pay for not working with labels anymore is that we don't have access to the label name, but that's life. - We make only one pass over the image to collect the ids of the spots that have been modified, instead of one pass per spot. Also, this pass is multithreaded (thanks LoopBuilder). - I have also learned that I should not use weakListeners() if I am doing something with threads inside the listener. Using listeners() instead works, but I do not know why the other one does not. Probably something arcane with Java WeakReferences being collected. - As a result of all this the performance is much better than before and the 'return to TrackMate' should happen without the user noticing the process. --- .../trackmate/gui/editor/LabkitLauncher.java | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index a4025239c..9158f8b1d 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -19,14 +19,12 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; -import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; -import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import ij.ImagePlus; @@ -426,7 +424,7 @@ private final void processFrame( for ( final Spot spot : spotsThisFrame ) { final int index = spot.ID() + 1; - SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) ); + spot.iterable( lblImgPlus ).forEach( p -> p.set( index ) ); spotLabels.put( index, spot ); } } @@ -445,7 +443,7 @@ private final void processFrame( continue; final int index = spot.ID() + 1; - SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) ); + spot.iterable( lblImgPlus ).forEach( p -> p.set( index ) ); spotLabels.put( index, spot ); } } @@ -453,32 +451,14 @@ private final void processFrame( private void boundingBox( final Spot spot, final long[] min, final long[] max ) { - final SpotRoi roi = spot.getRoi(); - if ( roi == null ) - { - final double cx = spot.getDoublePosition( 0 ); - final double cy = spot.getDoublePosition( 1 ); - final double r = spot.getFeature( Spot.RADIUS ).doubleValue(); - min[ 0 ] = ( long ) Math.floor( ( cx - r ) / calibration[ 0 ] ); - min[ 1 ] = ( long ) Math.floor( ( cy - r ) / calibration[ 1 ] ); - max[ 0 ] = ( long ) Math.ceil( ( cx + r ) / calibration[ 0 ] ); - max[ 1 ] = ( long ) Math.ceil( ( cy + r ) / calibration[ 1 ] ); - } - else + final ImagePlus imp = trackmate.getSettings().imp; + final long[] maxImp = new long[] { imp.getWidth(), imp.getHeight(), imp.getNSlices() }; + + for ( int d = 0; d < min.length; d++ ) { - final double[] x = roi.toPolygonX( calibration[ 0 ], 0, spot.getDoublePosition( 0 ), 1. ); - final double[] y = roi.toPolygonY( calibration[ 1 ], 0, spot.getDoublePosition( 1 ), 1. ); - min[ 0 ] = ( long ) Math.floor( Util.min( x ) ); - min[ 1 ] = ( long ) Math.floor( Util.min( y ) ); - max[ 0 ] = ( long ) Math.ceil( Util.max( x ) ); - max[ 1 ] = ( long ) Math.ceil( Util.max( y ) ); + min[ d ] = Math.max( 0, ( long ) Math.floor( spot.realMin( d ) / calibration[ d ] ) ); + max[ d ] = Math.min( maxImp[ d ], ( long ) Math.ceil( spot.realMax( d ) / calibration[ d ] ) ); } - - min[ 0 ] = Math.max( 0, min[ 0 ] ); - min[ 1 ] = Math.max( 0, min[ 1 ] ); - final ImagePlus imp = trackmate.getSettings().imp; - max[ 0 ] = Math.min( imp.getWidth(), max[ 0 ] ); - max[ 1 ] = Math.min( imp.getHeight(), max[ 1 ] ); } public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate, final DisplaySettings ds ) From 8f1731a8bdd30464c02aac57469a172d236466fe Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 19 Sep 2023 15:18:06 +0200 Subject: [PATCH 238/263] Try to fix LegacyService error. This test fails because of the LegacyService, see the stack trace below. If I add the static { net.imagej.patcher.LegacyInjector.preinit(); } initializer block as suggested here: https://forum.image.sc/t/imagej-legacy-error/23013 then the test passes in Eclipse, but still fails in Maven. So this commit at least adds the initializer so that it works in Eclipse. Note that it still fails on Maven, and proably with deploy actions too. [INFO] Running fiji.plugin.trackmate.TrackMatePluginTest [ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.787 s <<< FAILURE! -- in fiji.plugin.trackmate.TrackMatePluginTest [ERROR] fiji.plugin.trackmate.TrackMatePluginTest.testTrackMateRegistration -- Time elapsed: 0.787 s <<< ERROR! java.lang.ExceptionInInitializerError at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:250) at org.junit.runners.BlockJUnit4ClassRunner.createTest(BlockJUnit4ClassRunner.java:260) at org.junit.runners.BlockJUnit4ClassRunner$2.runReflectiveCall(BlockJUnit4ClassRunner.java:309) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:306) at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:316) at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:240) at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:214) at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:155) at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385) at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162) at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507) at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495) Caused by: java.lang.RuntimeException: Found incompatible ImageJ class at net.imagej.patcher.LegacyEnvironment.initialize(LegacyEnvironment.java:112) at net.imagej.patcher.LegacyEnvironment.applyPatches(LegacyEnvironment.java:494) at net.imagej.patcher.LegacyInjector.preinit(LegacyInjector.java:400) at net.imagej.patcher.LegacyInjector.preinit(LegacyInjector.java:379) at fiji.plugin.trackmate.TrackMatePluginTest.(TrackMatePluginTest.java:40) ... 28 more Caused by: java.lang.RuntimeException: Cannot load class: ij.gui.ImageWindow (loader: sun.misc.Launcher$AppClassLoader@18b4aac2) It appears that this class was already defined in the class loader! Please make sure that you initialize the LegacyService before using any ImageJ 1.x class. You can do that by adding this static initializer: static { LegacyInjector.preinit(); } To debug this issue, start the JVM with the option: -javaagent:/Users/tinevez/.m2/repository/net/imagej/ij1-patcher/1.2.6/ij1-patcher-1.2.6.jar To enforce pre-initialization, start the JVM with the option: -javaagent:/Users/tinevez/.m2/repository/net/imagej/ij1-patcher/1.2.6/ij1-patcher-1.2.6.jar=init at net.imagej.patcher.CodeHacker.javaAgentHint(CodeHacker.java:826) at net.imagej.patcher.CodeHacker.loadClass(CodeHacker.java:805) at net.imagej.patcher.CodeHacker.loadClasses(CodeHacker.java:853) at net.imagej.patcher.LegacyInjector.injectHooks(LegacyInjector.java:114) at net.imagej.patcher.LegacyEnvironment.initialize(LegacyEnvironment.java:100) ... 32 more Caused by: java.lang.ClassFormatError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "ij/gui/ImageWindow" at javassist.util.proxy.DefineClassHelper$Java7.defineClass(DefineClassHelper.java:182) at javassist.util.proxy.DefineClassHelper.toClass(DefineClassHelper.java:260) at javassist.ClassPool.toClass(ClassPool.java:1240) at javassist.CtClass.toClass(CtClass.java:1392) at net.imagej.patcher.CodeHacker.loadClass(CodeHacker.java:799) ... 35 more --- .../plugin/trackmate/TrackMatePluginTest.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java index 68e56be4b..6be53772e 100644 --- a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java +++ b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java @@ -35,13 +35,18 @@ public class TrackMatePluginTest { + static + { + net.imagej.patcher.LegacyInjector.preinit(); + } + @Test public void testTrackMateRegistration() { - TestTrackMatePlugin testPlugin = new TestTrackMatePlugin(); + final TestTrackMatePlugin testPlugin = new TestTrackMatePlugin(); testPlugin.setUp(); - ObjectService objectService = testPlugin.getLocalContext().service(ObjectService.class); + final ObjectService objectService = testPlugin.getLocalContext().service(ObjectService.class); - List trackMateInstances = objectService.getObjects(TrackMate.class); + final List trackMateInstances = objectService.getObjects(TrackMate.class); assertTrue(trackMateInstances.size() == 1); assertTrue(trackMateInstances.get(0) instanceof TrackMate); } @@ -50,10 +55,10 @@ private class TestTrackMatePlugin extends TrackMatePlugIn { @SuppressWarnings("unused") public void setUp() { - ImagePlus imp = IJ.createImage("Test Image", 256, 256, 10, 8); - Settings settings = createSettings(imp); - Model model = createModel(imp); - TrackMate trackMate = createTrackMate(model, settings); + final ImagePlus imp = IJ.createImage("Test Image", 256, 256, 10, 8); + final Settings settings = createSettings(imp); + final Model model = createModel(imp); + final TrackMate trackMate = createTrackMate(model, settings); } public Context getLocalContext() { From 6acda1ee69d52ca0c4fbf6f7f7cdea95f6242a86 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 22 Sep 2023 17:04:06 +0200 Subject: [PATCH 239/263] Fix reimporting labels with large IDs in 2D. The re-importing of labels from Tabkit to TrackMate could fail for 2D images and labels with a large index. For instance, it failed consistently when trying to re-import labels with an index larger than 65643. This problem roots in the getSpots() method of LabkitImporter. It relies on a trick: We get the new label image, and create spots from this label image. But we want the new spots to keep track of the index in the label image they were generated from. For this, in 2D, we use the SpotRoiUtils.from2DLabelingWithRoi() method. These methods accept an image as last argument used to read a value in the label image within the spot, that is normally used for the quality value of the new spot. But the SpotRoiUtils.from2DLabelingWithRoi() method converted the extra image to ImagePlus (because I was lazy). So the label image was effectively cast on ushort for an IntegerType image, hence the problem with the max label being 65453. The solution is to rewrite the from2DLabelingWithRoi() so that it does not rely on converting to ImagePlus, but on good old iteration with imglib2. --- .../trackmate/detection/SpotRoiUtils.java | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java index aecee2380..2793914dd 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java @@ -7,17 +7,15 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotRoi; -import ij.ImagePlus; import ij.gui.PolygonRoi; -import ij.measure.Measurements; import ij.process.FloatPolygon; +import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; import net.imglib2.algorithm.gauss3.Gauss3; import net.imglib2.converter.Converter; import net.imglib2.converter.Converters; import net.imglib2.img.Img; -import net.imglib2.img.display.imagej.ImageJFunctions; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; @@ -97,7 +95,7 @@ public static < T extends RealType< T > & NativeType< T >, S extends NumericType * the image in which to read the quality value. * @return a list of spots, with ROI. */ - public static < R extends IntegerType< R >, S extends NumericType< S > > List< Spot > from2DLabelingWithROI( + public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from2DLabelingWithROI( final ImgLabeling< Integer, R > labeling, final double[] origin, final double[] calibration, @@ -151,9 +149,6 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S // Quality image. final List< Spot > spots = new ArrayList<>( polygons.size() ); - final ImagePlus qualityImp = ( null == qualityImage ) - ? null - : ImageJFunctions.wrap( qualityImage, "QualityImage" ); // Simplify them and compute a quality. for ( final Polygon polygon : polygons ) @@ -170,19 +165,8 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S // Don't include ROIs that have been shrunk to < 1 pixel. if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) continue; - - // Measure quality. - final double quality; - if ( null == qualityImp ) - { - quality = fRoi.getStatistics().area; - } - else - { - qualityImp.setRoi( fRoi ); - quality = qualityImp.getStatistics( Measurements.MIN_MAX ).max; - } - + + // Create spot without quality value yet. final Polygon fPolygon = fRoi.getPolygon(); final double[] xpoly = new double[ fPolygon.npoints ]; final double[] ypoly = new double[ fPolygon.npoints ]; @@ -191,8 +175,28 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S xpoly[ i ] = calibration[ 0 ] * ( origin[ 0 ] + fPolygon.xpoints[ i ] - 0.5 ); ypoly[ i ] = calibration[ 1 ] * ( origin[ 1 ] + fPolygon.ypoints[ i ] - 0.5 ); } + final SpotRoi spot = SpotRoi.createSpot( xpoly, ypoly, -1. ); - spots.add( SpotRoi.createSpot( xpoly, ypoly, quality ) ); + // Measure quality. + final double quality; + if ( null == qualityImage ) + { + quality = fRoi.getStatistics().area; + } + else + { + final IterableInterval< S > iterable = spot.iterable( qualityImage, calibration ); + double max = Double.NEGATIVE_INFINITY; + for ( final S s : iterable ) + { + final double val = s.getRealDouble(); + if ( val > max ) + max = val; + } + quality = max; + } + spot.putFeature( Spot.QUALITY, quality ); + spots.add( spot ); } return spots; } From 408c55a2d81f9f93fb2ea2dbc1c357d73b35db21 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 5 Oct 2023 09:28:29 +0200 Subject: [PATCH 240/263] Don't go out of bounds when measuring spot quality in 2D. --- src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java index 2793914dd..88624986b 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java @@ -185,7 +185,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot } else { - final IterableInterval< S > iterable = spot.iterable( qualityImage, calibration ); + final IterableInterval< S > iterable = spot.iterable( Views.extendZero( qualityImage ), calibration ); double max = Double.NEGATIVE_INFINITY; for ( final S s : iterable ) { From 4af96fe8bbd87fce4bda7b5d4581b7a7632b22f8 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 16:45:15 +0200 Subject: [PATCH 241/263] Fix the legacy error issue? Moving the plugin implementation out of the test class and removing the legacy injector make the test pass in maven. --- .../plugin/trackmate/TestTrackMatePlugin.java | 22 ++++++++++++++++ .../plugin/trackmate/TrackMatePluginTest.java | 26 ------------------- 2 files changed, 22 insertions(+), 26 deletions(-) create mode 100644 src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java diff --git a/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java b/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java new file mode 100644 index 000000000..b1d1d68e4 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java @@ -0,0 +1,22 @@ +package fiji.plugin.trackmate; + +import org.scijava.Context; + +import fiji.plugin.trackmate.util.TMUtils; +import ij.IJ; +import ij.ImagePlus; + +class TestTrackMatePlugin extends TrackMatePlugIn { + + @SuppressWarnings("unused") + public void setUp() { + final ImagePlus imp = IJ.createImage("Test Image", 256, 256, 10, 8); + final Settings settings = createSettings(imp); + final Model model = createModel(imp); + final TrackMate trackMate = createTrackMate(model, settings); + } + + public Context getLocalContext() { + return TMUtils.getContext(); + } +} \ No newline at end of file diff --git a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java index 6be53772e..132c27dfe 100644 --- a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java +++ b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java @@ -26,20 +26,10 @@ import java.util.List; import org.junit.Test; -import org.scijava.Context; import org.scijava.object.ObjectService; -import fiji.plugin.trackmate.util.TMUtils; -import ij.IJ; -import ij.ImagePlus; - public class TrackMatePluginTest { - static - { - net.imagej.patcher.LegacyInjector.preinit(); - } - @Test public void testTrackMateRegistration() { final TestTrackMatePlugin testPlugin = new TestTrackMatePlugin(); @@ -50,20 +40,4 @@ public void testTrackMateRegistration() { assertTrue(trackMateInstances.size() == 1); assertTrue(trackMateInstances.get(0) instanceof TrackMate); } - - private class TestTrackMatePlugin extends TrackMatePlugIn { - - @SuppressWarnings("unused") - public void setUp() { - final ImagePlus imp = IJ.createImage("Test Image", 256, 256, 10, 8); - final Settings settings = createSettings(imp); - final Model model = createModel(imp); - final TrackMate trackMate = createTrackMate(model, settings); - } - - public Context getLocalContext() { - return TMUtils.getContext(); - } - - } } From 550acd2447a82bf86d0fe63f0961c849df48bcf0 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Dec 2023 17:39:00 +0100 Subject: [PATCH 242/263] The quality image in from2DThresholdWithROI is of RealType. --- .../java/fiji/plugin/trackmate/detection/SpotRoiUtils.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java index 88624986b..71ca8f2a3 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java @@ -23,7 +23,6 @@ import net.imglib2.type.NativeType; import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; -import net.imglib2.type.numeric.NumericType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; import net.imglib2.type.numeric.real.FloatType; @@ -45,7 +44,7 @@ public class SpotRoiUtils /** Douglas-Peucker polygon simplification max distance. */ private static final double DOUGLAS_PEUCKER_MAX_DISTANCE = 0.5; - public static < T extends RealType< T > & NativeType< T >, S extends NumericType< S > > List< Spot > from2DThresholdWithROI( + public static < T extends RealType< T > & NativeType< T >, S extends RealType< S > > List< Spot > from2DThresholdWithROI( final RandomAccessibleInterval< T > input, final double[] origin, final double[] calibration, @@ -76,7 +75,7 @@ public static < T extends RealType< T > & NativeType< T >, S extends NumericType * @param * the type of the quality image. Must be real, scalar. * @param labeling - * the labeling, must be zero-min and 2D.. + * the labeling, must be zero-min and 2D. * @param origin * the origin (min pos) of the interval the labeling was * generated from, used to reposition the spots from the zero-min @@ -165,7 +164,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot // Don't include ROIs that have been shrunk to < 1 pixel. if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) continue; - + // Create spot without quality value yet. final Polygon fPolygon = fRoi.getPolygon(); final double[] xpoly = new double[ fPolygon.npoints ]; From eaacf0d5ae3fa33ac584d7267a042dd2b87e737a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Dec 2023 17:45:47 +0100 Subject: [PATCH 243/263] All labeling methods now use the double[] origin to reposition spots. Instead of the interval, so that this is consistent across TrackMate. --- .../trackmate/detection/LabelImageDetector.java | 4 ++-- .../plugin/trackmate/detection/MaskUtils.java | 15 ++++++++------- .../plugin/trackmate/detection/SpotMeshUtils.java | 9 +++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index 863f6b255..01916cd25 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -154,7 +154,7 @@ else if ( input.numDimensions() == 3 ) { spots = SpotMeshUtils.from3DLabelingWithROI( labeling, - interval, + interval.minAsDoubleArray(), calibration, simplify, smoothingScale, @@ -164,7 +164,7 @@ else if ( input.numDimensions() == 3 ) { spots = MaskUtils.fromLabeling( labeling, - interval, + interval.minAsDoubleArray(), calibration ); } } diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index d05da3b40..8ccd50404 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -243,7 +243,7 @@ public static < T extends RealType< T > > List< Spot > fromThreshold( numThreads ); return fromLabeling( labeling, - interval, + interval.minAsDoubleArray(), calibration ); } @@ -254,8 +254,9 @@ public static < T extends RealType< T > > List< Spot > fromThreshold( * the type that backs-up the labeling. * @param labeling * the labeling, must be zero-min. - * @param interval - * the interval, used to reposition the spots from the zero-min + * @param origin + * the origin (min pos) of the interval the labeling was + * generated from, used to reposition the spots from the zero-min * labeling to the proper coordinates. * @param calibration * the physical calibration. @@ -263,7 +264,7 @@ public static < T extends RealType< T > > List< Spot > fromThreshold( */ public static < R extends IntegerType< R > > List< Spot > fromLabeling( final ImgLabeling< Integer, R > labeling, - final Interval interval, + final double[] origin, final double[] calibration ) { // Parse each component. @@ -288,9 +289,9 @@ public static < R extends IntegerType< R > > List< Spot > fromLabeling( for ( int d = 0; d < pos.length; d++ ) pos[ d ] = sum[ d ] / ( double ) region.size(); - final double x = calibration[ 0 ] * ( interval.min( 0 ) + pos[ 0 ] ); - final double y = calibration[ 1 ] * ( interval.min( 1 ) + pos[ 1 ] ); - final double z = calibration[ 2 ] * ( interval.min( 2 ) + pos[ 2 ] ); + final double x = calibration[ 0 ] * ( origin[ 0 ] + pos[ 0 ] ); + final double y = calibration[ 1 ] * ( origin[ 1 ] + pos[ 1 ] ); + final double z = calibration[ 2 ] * ( origin[ 2 ] + pos[ 2 ] ); double volume = region.size(); for ( int d = 0; d < calibration.length; d++ ) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 9605d29a3..7ef6dbe78 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -176,8 +176,9 @@ public static < T extends RealType< T > & NativeType< T >, S extends RealType< S * the type of the quality image. Must be real, scalar. * @param labeling * the labeling, must be zero-min and 3D. - * @param interval - * the interval, used to reposition the spots from the zero-min + * @param origin + * the origin (min pos) of the interval the labeling was + * generated from, used to reposition the spots from the zero-min * labeling to the proper coordinates. * @param calibration * the physical calibration. @@ -195,7 +196,7 @@ public static < T extends RealType< T > & NativeType< T >, S extends RealType< S */ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > from3DLabelingWithROI( final ImgLabeling< Integer, R > labeling, - final Interval interval, + final double[] origin, final double[] calibration, final boolean simplify, final double smoothingScale, @@ -216,7 +217,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot simplify, calibration, smoothingScale, - interval.minAsDoubleArray(), + origin, qualityImage ); if ( spot == null ) continue; From ec838c8d4ac660f6c920ecd61980e0f6aa531c12 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 8 Feb 2024 15:21:11 +0100 Subject: [PATCH 244/263] Update license blurbs. --- .../java/fiji/plugin/trackmate/Dimension.java | 4 +-- .../java/fiji/plugin/trackmate/Model.java | 4 +-- .../java/fiji/plugin/trackmate/Settings.java | 4 +-- src/main/java/fiji/plugin/trackmate/Spot.java | 4 +-- .../java/fiji/plugin/trackmate/SpotBase.java | 6 ++-- .../java/fiji/plugin/trackmate/SpotMesh.java | 21 ++++++++++++ .../java/fiji/plugin/trackmate/SpotRoi.java | 4 +-- .../trackmate/action/MeshSeriesExporter.java | 2 +- .../action/meshtools/MeshSmoother.java | 21 ++++++++++++ .../action/meshtools/MeshSmootherAction.java | 21 ++++++++++++ .../meshtools/MeshSmootherController.java | 21 ++++++++++++ .../action/meshtools/MeshSmootherModel.java | 21 ++++++++++++ .../action/meshtools/MeshSmootherPanel.java | 21 ++++++++++++ .../detection/LabelImageDetector.java | 4 +-- .../detection/LabelImageDetectorFactory.java | 4 +-- .../trackmate/detection/MaskDetector.java | 6 ++-- .../detection/MaskDetectorFactory.java | 4 +-- .../plugin/trackmate/detection/MaskUtils.java | 4 +-- .../trackmate/detection/Process2DZ.java | 21 ++++++++++++ .../detection/SpotDetectorFactoryBase.java | 4 +-- .../trackmate/detection/SpotMeshUtils.java | 21 ++++++++++++ .../trackmate/detection/SpotRoiUtils.java | 21 ++++++++++++ .../detection/ThresholdDetector.java | 4 +-- .../detection/ThresholdDetectorFactory.java | 4 +-- .../spot/Spot2DFitEllipseAnalyzer.java | 4 +-- .../spot/Spot2DMorphologyAnalyzerFactory.java | 4 +-- .../spot/Spot3DFitEllipsoidAnalyzer.java | 21 ++++++++++++ .../Spot3DFitEllipsoidAnalyzerFactory.java | 2 +- .../spot/Spot3DMorphologyAnalyzerFactory.java | 6 ++-- .../features/spot/Spot3DShapeAnalyzer.java | 6 ++-- .../spot/Spot3DShapeAnalyzerFactory.java | 6 ++-- .../spot/SpotContrastAndSNRAnalyzer.java | 4 +-- .../java/fiji/plugin/trackmate/gui/Icons.java | 4 +-- .../gui/components/ConfigureViewsPanel.java | 4 +-- .../gui/components/PanelSmoothContour.java | 21 ++++++++++++ .../trackmate/gui/editor/ImpBdvShowable.java | 21 ++++++++++++ .../trackmate/gui/editor/LabkitImporter.java | 21 ++++++++++++ .../trackmate/gui/editor/LabkitLauncher.java | 21 ++++++++++++ .../featureselector/AnalyzerSelection.java | 21 ++++++++++++ .../featureselector/AnalyzerSelectionIO.java | 21 ++++++++++++ .../gui/featureselector/AnalyzerSelector.java | 21 ++++++++++++ .../AnalyzerSelectorPanel.java | 23 ++++++++++++- .../gui/featureselector/FeatureTable.java | 23 ++++++++++++- .../gui/featureselector/package-info.java | 21 ++++++++++++ .../gui/wizard/TrackMateWizardSequence.java | 4 +-- .../descriptors/ConfigureViewsDescriptor.java | 4 +-- .../descriptors/SpotFilterDescriptor.java | 4 +-- .../fiji/plugin/trackmate/io/TmXmlReader.java | 4 +-- .../fiji/plugin/trackmate/io/TmXmlWriter.java | 4 +-- .../Spot3DMorphologyAnalyzerProvider.java | 6 ++-- .../fiji/plugin/trackmate/util/TMUtils.java | 4 +-- .../plugin/trackmate/util/WrapLayout.java | 21 ++++++++++++ .../trackmate/util/mesh/SpotMeshCursor.java | 21 ++++++++++++ .../trackmate/util/mesh/SpotMeshIterable.java | 21 ++++++++++++ .../trackmate/visualization/bvv/BVVUtils.java | 21 ++++++++++++ .../visualization/bvv/StupidMesh.java | 33 ++++++++----------- .../visualization/bvv/TrackMateBVV.java | 21 ++++++++++++ .../hyperstack/PaintSpotMesh.java | 21 ++++++++++++ .../hyperstack/PaintSpotRoi.java | 21 ++++++++++++ .../hyperstack/PaintSpotSphere.java | 21 ++++++++++++ .../hyperstack/SpotEditTool.java | 4 +-- .../visualization/hyperstack/SpotOverlay.java | 4 +-- .../hyperstack/TrackMatePainter.java | 21 ++++++++++++ .../plugin/trackmate/TestTrackMatePlugin.java | 23 ++++++++++++- .../plugin/trackmate/mesh/DebugZSlicer.java | 21 ++++++++++++ .../plugin/trackmate/mesh/DefaultMesh.java | 21 ++++++++++++ .../plugin/trackmate/mesh/Demo3DMesh.java | 21 ++++++++++++ .../trackmate/mesh/Demo3DMeshTrackMate.java | 21 ++++++++++++ .../plugin/trackmate/mesh/DemoContour.java | 21 ++++++++++++ .../plugin/trackmate/mesh/DemoHollowMesh.java | 21 ++++++++++++ .../trackmate/mesh/DemoPixelIteration.java | 21 ++++++++++++ .../trackmate/mesh/ExportMeshForDemo.java | 21 ++++++++++++ .../plugin/trackmate/mesh/MeshPlayground.java | 21 ++++++++++++ .../trackmate/mesh/TestEllipsoidFit.java | 21 ++++++++++++ 74 files changed, 926 insertions(+), 93 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Dimension.java b/src/main/java/fiji/plugin/trackmate/Dimension.java index b715ccc34..103caa203 100644 --- a/src/main/java/fiji/plugin/trackmate/Dimension.java +++ b/src/main/java/fiji/plugin/trackmate/Dimension.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java index 45c7a6762..7f9d1534b 100644 --- a/src/main/java/fiji/plugin/trackmate/Model.java +++ b/src/main/java/fiji/plugin/trackmate/Model.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/Settings.java b/src/main/java/fiji/plugin/trackmate/Settings.java index 8a5f011d9..1034eef99 100644 --- a/src/main/java/fiji/plugin/trackmate/Settings.java +++ b/src/main/java/fiji/plugin/trackmate/Settings.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index 0ecd99497..b0a678f2b 100644 --- a/src/main/java/fiji/plugin/trackmate/Spot.java +++ b/src/main/java/fiji/plugin/trackmate/Spot.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/SpotBase.java b/src/main/java/fiji/plugin/trackmate/SpotBase.java index 384e853b7..b97961d74 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotBase.java +++ b/src/main/java/fiji/plugin/trackmate/SpotBase.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2023 TrackMate developers. + * Copyright (C) 2010 - 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index aa0e3ad5e..6aa4da292 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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; import java.util.HashMap; diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index 6987c6228..00396adb2 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java index 51e66fb1f..07f0f8ef9 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2023 TrackMate developers. + * Copyright (C) 2010 - 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 diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java index 364501287..dcfe73f39 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.action.meshtools; import java.util.ArrayList; diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java index ab3000a6e..4fdc0965a 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.action.meshtools; import java.awt.Frame; diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java index faf08827b..25da09913 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.action.meshtools; import java.awt.Component; diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java index 4256cf163..abc4ac371 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherModel.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.action.meshtools; import net.imglib2.mesh.alg.TaubinSmoothing.TaubinWeightType; diff --git a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java index 7fbb2c647..4987a6f3b 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.action.meshtools; import java.awt.BorderLayout; diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index 01916cd25..8e9d0a126 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java index 76e9dd614..e9d79ee37 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java index 079e5d7de..43141e3ef 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetector.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2023 TrackMate developers. + * Copyright (C) 2010 - 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java index a84ee609c..eaed9e990 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 8ccd50404..ca2977323 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index 7b071fc6a..7040638ed 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.detection; import java.util.ArrayList; diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java index 2208b188f..30331f8b9 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 7ef6dbe78..0cfb3bdda 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.detection; import java.util.ArrayList; diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java index 71ca8f2a3..0612dc5d9 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.detection; import java.awt.Polygon; diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java index 50238194b..2e011934d 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java index ce5f85cfe..d14586080 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java index e660ca701..dfbb4fdc4 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DMorphologyAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DMorphologyAnalyzerFactory.java index a63a7604f..3af620dd8 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DMorphologyAnalyzerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DMorphologyAnalyzerFactory.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java index 0d1a95a38..3bc789492 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzer.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.features.spot; import static fiji.plugin.trackmate.features.spot.Spot3DFitEllipsoidAnalyzerFactory.ASPECTRATIO; diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzerFactory.java index fd1060346..064c74b02 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DFitEllipsoidAnalyzerFactory.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2023 TrackMate developers. + * Copyright (C) 2010 - 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 diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DMorphologyAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DMorphologyAnalyzerFactory.java index da4f3d75a..e6fdfe20f 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DMorphologyAnalyzerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DMorphologyAnalyzerFactory.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2023 TrackMate developers. + * Copyright (C) 2010 - 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java index 42b2eeaa4..890bbb2ae 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2023 TrackMate developers. + * Copyright (C) 2010 - 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzerFactory.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzerFactory.java index ca3898deb..228261fed 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzerFactory.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2023 TrackMate developers. + * Copyright (C) 2010 - 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java index f023beb21..6ae6e2f0a 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/SpotContrastAndSNRAnalyzer.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/gui/Icons.java b/src/main/java/fiji/plugin/trackmate/gui/Icons.java index bdd0e5725..1c40ca623 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/Icons.java +++ b/src/main/java/fiji/plugin/trackmate/gui/Icons.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java index b604918d9..d1b28fb42 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java b/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java index 20dc8b9ea..466ae4c5f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.components; import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index 6d2e6fde6..a3d44509f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.editor; import java.awt.Color; diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index c587b0608..3c625cd70 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.editor; import java.util.ArrayList; diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 9158f8b1d..3a1ba7ffc 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.editor; import java.awt.Component; diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java index c24dcb10b..3afc0cbd3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelection.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.featureselector; import static fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject.EDGES; diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectionIO.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectionIO.java index fe515f852..d899e729d 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectionIO.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectionIO.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.featureselector; import java.io.File; diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java index 0d58105c5..ac70fe09a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelector.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.featureselector; import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java index 24da785b3..ec7bca318 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/AnalyzerSelectorPanel.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.featureselector; import static fiji.plugin.trackmate.gui.Icons.APPLY_ICON; @@ -279,4 +300,4 @@ public MySpotAnalyzerProvider() } } -} \ No newline at end of file +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/FeatureTable.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/FeatureTable.java index 46d8e2db9..b7e6698a6 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/FeatureTable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/FeatureTable.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.featureselector; import static javax.swing.JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT; @@ -378,4 +399,4 @@ public Component getTableCellRendererComponent( return label; } } -} \ No newline at end of file +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/featureselector/package-info.java b/src/main/java/fiji/plugin/trackmate/gui/featureselector/package-info.java index e20f8c4b1..f18999b5a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/featureselector/package-info.java +++ b/src/main/java/fiji/plugin/trackmate/gui/featureselector/package-info.java @@ -1 +1,22 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.featureselector; diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index 2c7068988..4d0a89ba2 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java index aea1164a7..3dfa1a517 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java index 9f86f8f58..e484a6637 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SpotFilterDescriptor.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 28d91135a..3240ccb8d 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index 74be85e77..2e332c15c 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java b/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java index 86d9ec124..b8d605b45 100644 --- a/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java +++ b/src/main/java/fiji/plugin/trackmate/providers/Spot3DMorphologyAnalyzerProvider.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2023 TrackMate developers. + * Copyright (C) 2010 - 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index a3053c148..7bb6f0cc0 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java b/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java index ccd1bac73..aa1af51c0 100644 --- a/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java +++ b/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.util; import java.awt.Component; diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java index 809d29564..aebe3496b 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshCursor.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.util.mesh; import fiji.plugin.trackmate.SpotMesh; diff --git a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java index 912d92e56..58bd432c2 100644 --- a/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java +++ b/src/main/java/fiji/plugin/trackmate/util/mesh/SpotMeshIterable.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.util.mesh; import java.util.Iterator; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java index 331367a62..cea8ef42b 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.visualization.bvv; import bvv.vistools.Bvv; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java index 26d705670..c4f7938e1 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -1,29 +1,22 @@ /*- * #%L - * Volume rendering of bdv datasets + * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2018 - 2021 Tobias Pietzsch + * Copyright (C) 2010 - 2024 TrackMate developers. * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: + * 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. * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. + * 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. * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . * #L% */ package fiji.plugin.trackmate.visualization.bvv; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java index a7c024b5b..27140275c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.visualization.bvv; import java.awt.Color; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java index bdc2363b1..37d6d446d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotMesh.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.visualization.hyperstack; import java.awt.Color; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java index 0698db2cf..263540812 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotRoi.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.visualization.hyperstack; import java.awt.Color; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java index 38b5e5bff..cc62851c0 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/PaintSpotSphere.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.visualization.hyperstack; import java.awt.Graphics2D; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java index 58e3c3f8f..4ee2de83d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java index 0c2c9edbd..9b20ef711 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -8,12 +8,12 @@ * 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 * . diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java index 8f8280e01..95be2bdf8 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackMatePainter.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.visualization.hyperstack; import java.awt.Graphics2D; diff --git a/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java b/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java index b1d1d68e4..5cde8c717 100644 --- a/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java +++ b/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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; import org.scijava.Context; @@ -19,4 +40,4 @@ public void setUp() { public Context getLocalContext() { return TMUtils.getContext(); } -} \ No newline at end of file +} diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java index 9d28c8e35..7fa4019e5 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import java.io.File; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java index a7e037512..74bd896b7 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import java.util.List; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 7acfba8ee..e203d7972 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import java.awt.Color; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java index 52a32b51c..76ce297d8 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMeshTrackMate.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import fiji.plugin.trackmate.TrackMatePlugIn; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java index b268d0cb8..fe145ac2a 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import java.io.File; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java index 077c9f4e8..f51aad467 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import fiji.plugin.trackmate.Model; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index f3f02d2d9..e8d067e66 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import java.awt.Color; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java index 890b4a553..1d019b057 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import java.io.File; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java index 42d3bb3d4..b038f6d5b 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/MeshPlayground.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import java.io.File; diff --git a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java index 5a5037042..701afc407 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/TestEllipsoidFit.java @@ -1,3 +1,24 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.mesh; import static org.junit.Assert.assertArrayEquals; From fad1317785609c89af201bed144f2abf5836c499 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 8 Feb 2024 15:35:19 +0100 Subject: [PATCH 245/263] Temporarily disable enforcer checks. For the beta phase. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 8ef186bfd..90aa45511 100644 --- a/pom.xml +++ b/pom.xml @@ -134,6 +134,7 @@ + true fiji.plugin.trackmate gpl_v3 TrackMate developers. From 1e7f0268003f63ac981bd47c29242e566b4d6944 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 28 Mar 2024 20:17:38 +0100 Subject: [PATCH 246/263] The Process2DZ detector is cancelable. --- .../trackmate/detection/Process2DZ.java | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index 7040638ed..0e9c0d5da 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Set; +import org.scijava.Cancelable; + import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; @@ -65,7 +67,7 @@ */ public class Process2DZ< T extends RealType< T > & NativeType< T > > extends MultiThreadedBenchmarkAlgorithm - implements SpotDetector< T > + implements SpotDetector< T >, Cancelable { private static final String BASE_ERROR_MESSAGE = "[Process2DZ] "; @@ -84,6 +86,12 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > private final double smoothingScale; + private boolean isCanceled; + + private String cancelReason; + + private TrackMate trackmate; + /** * Creates a new {@link Process2DZ} detector. * @@ -151,6 +159,8 @@ public boolean checkInput() @Override public boolean process() { + isCanceled = false; + cancelReason = null; spots = null; /* @@ -171,7 +181,7 @@ public boolean process() // Execute segmentation and tracking. final Settings settingsFrame = settings.copyOn( imp ); - final TrackMate trackmate = new TrackMate( settingsFrame ); + this.trackmate = new TrackMate( settingsFrame ); trackmate.setNumThreads( numThreads ); trackmate.getModel().setLogger( Logger.VOID_LOGGER ); if ( !trackmate.checkInput() || !trackmate.process() ) @@ -278,4 +288,27 @@ public List< Spot > getResult() { return spots; } + + // --- org.scijava.Cancelable methods --- + + @Override + public boolean isCanceled() + { + return isCanceled; + } + + @Override + public void cancel( final String reason ) + { + isCanceled = true; + cancelReason = reason; + if ( trackmate != null ) + trackmate.cancel( reason ); + } + + @Override + public String getCancelReason() + { + return cancelReason; + } } From 6096c522e70c34444808d9eb1bc1af5655fb1d39 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 28 Mar 2024 13:08:42 -0500 Subject: [PATCH 247/263] Keep imagej-legacy out of the dependency tree This works around a test failure in TrackMatePluginTest caused by the original ImageJ classes being loaded too soon. There are other ways of working around this problem, such as creating a SciJava context more eagerly, but it's a distraction from the business of testing TrackMate, and anyway we don't need imagej-legacy on the classpath. The only reason labkit-ui has imagej-legacy as a dependency is for one line of code, which can be refactored to avoid it, so hopefully the exclusion will be able to disappear later after labkit-ui is updated. --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 90aa45511..b46cb35c2 100644 --- a/pom.xml +++ b/pom.xml @@ -161,6 +161,12 @@ sc.fiji labkit-ui 0.3.12-SNAPSHOT + + + net.imagej + imagej-legacy + + From 3a84a1b6ccffbb006b86b0e4f6035b7c6a94ce31 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 28 Mar 2024 13:18:25 -0500 Subject: [PATCH 248/263] POM: fix the dependencies * Avoid SNAPSHOT versions. * Factor out version pins to properties. * Avoid jogamp *-main uber-JARs. --- pom.xml | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index b46cb35c2..6ae15cd1c 100644 --- a/pom.xml +++ b/pom.xml @@ -148,7 +148,8 @@ 2.5.2 0.11.1 - 6.1.0 + 1.0.0 + 0.3.1 @@ -160,7 +161,6 @@ sc.fiji labkit-ui - 0.3.12-SNAPSHOT net.imagej @@ -181,7 +181,7 @@ net.imglib2 imglib2-mesh - 1.0.0-SNAPSHOT + ${imglib2-mesh.version} net.imagej @@ -190,18 +190,26 @@ sc.fiji bigvolumeviewer - 0.3.1 - - - org.jogamp.jogl - jogl-all-main - 2.3.2 - - - org.jogamp.gluegen - gluegen-rt-main - 2.3.2 - + ${bigvolumeviewer.version} + + + org.jogamp.jogl + jogl-all + + + org.jogamp.gluegen + gluegen-rt + + + org.jogamp.gluegen + gluegen-rt + ${scijava.natives.classifier.gluegen} + + + org.jogamp.jogl + jogl-all + ${scijava.natives.classifier.jogl} + From 4365b76dc6eeca5b9d58fa0b5d2c6b15c40b5a61 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 28 Mar 2024 13:24:22 -0500 Subject: [PATCH 249/263] Fix Javadoc errors --- .../java/fiji/plugin/trackmate/detection/MaskUtils.java | 2 -- .../java/fiji/plugin/trackmate/detection/SpotMeshUtils.java | 6 ------ src/main/java/fiji/plugin/trackmate/util/TMUtils.java | 2 ++ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index ca2977323..72b71f7a1 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -164,8 +164,6 @@ public static final long getThreshold( final Histogram1d< ? > hist ) * the type of the input image. Must be real, scalar. * @param input * the input image. - * @param interval - * the interval in the input image to analyze. * @param threshold * the threshold to apply to the input image. * @param numThreads diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 0cfb3bdda..3c3cac760 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -106,12 +106,6 @@ public class SpotMeshUtils * @param simplify * if true the meshes will be post-processed to be * smoother and contain less points. - * @param smoothingScale - * if strictly larger than 0, the input will be smoothed before - * creating the contour, resulting in smoother contours. The - * scale value sets the (Gaussian) filter radius and is specified - * in physical units. If 0 or lower than 0, no smoothing is - * applied. * @param qualityImage * the image in which to read the quality value. * @return a list of spots, with meshes. diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index 7bb6f0cc0..ea0792435 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -949,6 +949,8 @@ public static double standardDeviation( final DoubleArray data ) * Returns a string of the name of the image without the extension, with the * full path * + * @param settings + * A {@link Settings} object referencing the image * @return full name of the image without the extension */ public static String getImagePathWithoutExtension( final Settings settings ) From 7a6a2915918c85733ba08c356ed99bd127949a45 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 1 May 2024 18:08:27 +0200 Subject: [PATCH 250/263] A utility widget to specify a threshold on a probability value. --- .../gui/components/PanelProbaThreshold.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/components/PanelProbaThreshold.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/PanelProbaThreshold.java b/src/main/java/fiji/plugin/trackmate/gui/components/PanelProbaThreshold.java new file mode 100644 index 000000000..bf0212b17 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/components/PanelProbaThreshold.java @@ -0,0 +1,88 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 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.gui.components; + +import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; + +import java.util.function.Consumer; +import java.util.function.DoubleSupplier; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import fiji.plugin.trackmate.gui.displaysettings.SliderPanelDouble; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements; +import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; + +/** + * A utility widget that lets a user specify a threshold on a probability value, + * from 0 to 1. + */ +public class PanelProbaThreshold extends JPanel +{ + + private static final long serialVersionUID = 1L; + + private double threshold; + + private final SliderPanelDouble sliderPanel; + + private final BoundedDoubleElement thresholdEl; + + public PanelProbaThreshold( final double threshold ) + { + this.threshold = threshold; + setLayout( new BoxLayout( this, BoxLayout.X_AXIS ) ); + + final JLabel chckbxSmooth = new JLabel( "Proba threshold" ); + chckbxSmooth.setFont( SMALL_FONT ); + add( chckbxSmooth ); + add( Box.createHorizontalGlue() ); + + final DoubleSupplier getter = () -> getThreshold(); + final Consumer< Double > setter = v -> setThresholdPrivate( v ); + thresholdEl = StyleElements.boundedDoubleElement( "threshold", 0., 1., getter, setter ); + sliderPanel = StyleElements.linkedSliderPanel( thresholdEl, 3, 0.1 ); + sliderPanel.setFont( SMALL_FONT ); + + add( sliderPanel ); + } + + private void setThresholdPrivate( final double threshold ) + { + this.threshold = threshold; + } + + public void setThreshold( final double threshold ) + { + setThresholdPrivate( threshold ); + thresholdEl.getValue().setCurrentValue( threshold ); + sliderPanel.update(); + } + + public double getThreshold() + { + return threshold; + } +} From 4f55ac09c103030a6687e4a6fdf034bb2dd8c5b1 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 1 May 2024 18:37:34 +0200 Subject: [PATCH 251/263] Fix minor Eclipse warnings. --- .../plugin/trackmate/detection/SpotGlobalDetectorFactory.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java index a9950892a..51ab70cd4 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java @@ -21,7 +21,6 @@ */ package fiji.plugin.trackmate.detection; -import fiji.plugin.trackmate.util.TMUtils; import net.imglib2.Interval; import net.imglib2.type.NativeType; import net.imglib2.type.numeric.RealType; From 8c178da41360c3f5fb8636ccac4411e5a364f6d9 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 7 May 2024 17:27:58 +0200 Subject: [PATCH 252/263] Fix export label method call with the 2D + Z detector. --- src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index 0e9c0d5da..5d414d654 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -34,6 +34,7 @@ import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.TrackModel; import fiji.plugin.trackmate.action.LabelImgExporter; +import fiji.plugin.trackmate.action.LabelImgExporter.LabelIdPainting; import fiji.plugin.trackmate.util.TMUtils; import ij.ImagePlus; import net.imagej.ImgPlus; @@ -191,7 +192,7 @@ public boolean process() } // Get 2D+T masks - final ImagePlus lblImp = LabelImgExporter.createLabelImagePlus( trackmate, false, true, false ); + final ImagePlus lblImp = LabelImgExporter.createLabelImagePlus( trackmate, false, true, LabelIdPainting.LABEL_IS_TRACK_ID ); /* * Exposes tracked labels as a 3D image and segment them again with From 093f27398b7d938d42394059599ac438c14641a3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 21 May 2024 14:28:14 +0200 Subject: [PATCH 253/263] Clamp slider widget values. --- .../gui/displaysettings/SliderPanel.java | 15 +++++++++++++-- .../gui/displaysettings/SliderPanelDouble.java | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java index 0ca01174f..247fed7cd 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -72,9 +72,20 @@ public SliderPanel( final String name, final BoundedValue model, final int spinn setLayout( new BorderLayout( 10, 10 ) ); setPreferredSize( PANEL_SIZE ); - slider = new JSlider( SwingConstants.HORIZONTAL, model.getRangeMin(), model.getRangeMax(), model.getCurrentValue() ); + final int imin = model.getRangeMin(); + final int imax = model.getRangeMax(); + int ivalue = model.getCurrentValue(); + ivalue = Math.max( imin, ivalue ); + ivalue = Math.min( imax, ivalue ); + slider = new JSlider( SwingConstants.HORIZONTAL, imin, imax, ivalue ); + spinner = new JSpinner(); - spinner.setModel( new SpinnerNumberModel( model.getCurrentValue(), model.getRangeMin(), model.getRangeMax(), spinnerStepSize ) ); + final double min = model.getRangeMin(); + final double max = model.getRangeMax(); + double value = model.getCurrentValue(); + value = Math.min( max, value ); + value = Math.max( min, value ); + spinner.setModel( new SpinnerNumberModel( value, min, max, spinnerStepSize ) ); slider.addChangeListener( new ChangeListener() { diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java index 0817e51b8..67aef86b0 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -90,12 +90,20 @@ public SliderPanelDouble( setLayout( new BorderLayout( 10, 10 ) ); setPreferredSize( SliderPanel.PANEL_SIZE ); - dmin = model.getRangeMin(); - dmax = model.getRangeMax(); + final int imin = 0; + final int imax = sliderLength; + int ivalue = toSlider( model.getCurrentValue() ); + ivalue = Math.max( imin, ivalue ); + ivalue = Math.min( imax, ivalue ); + slider = new JSlider( SwingConstants.HORIZONTAL, imin, imax, ivalue ); - slider = new JSlider( SwingConstants.HORIZONTAL, 0, sliderLength, toSlider( model.getCurrentValue() ) ); spinner = new JSpinner(); - spinner.setModel( new SpinnerNumberModel( model.getCurrentValue(), dmin, dmax, spinnerStepSize ) ); + dmin = model.getRangeMin(); + dmax = model.getRangeMax(); + double value = model.getCurrentValue(); + value = Math.min( dmax, value ); + value = Math.max( dmin, value ); + spinner.setModel( new SpinnerNumberModel( value, dmin, dmax, spinnerStepSize ) ); slider.addChangeListener( new ChangeListener() { From 3431b7d939940b63ecdaf1e4158e169a866e62c4 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 Jul 2024 15:43:55 +0200 Subject: [PATCH 254/263] Remove duplicate setFont methods. --- .../trackmate/gui/displaysettings/SliderPanel.java | 10 ---------- .../gui/displaysettings/SliderPanelDouble.java | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java index 247fed7cd..bdb5f4fb2 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -161,16 +161,6 @@ public void setNumColummns( final int cols ) ( ( JSpinner.NumberEditor ) spinner.getEditor() ).getTextField().setColumns( cols ); } - @Override - public void setFont( final Font font ) - { - super.setFont( font ); - if ( spinner != null ) - spinner.setFont( font ); - if ( slider != null ) - slider.setFont( font ); - } - @Override public void setToolTipText( final String text ) { diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java index 67aef86b0..94f102da8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -202,16 +202,6 @@ public void setNumColummns( final int cols ) ( ( JSpinner.NumberEditor ) spinner.getEditor() ).getTextField().setColumns( cols ); } - @Override - public void setFont( final Font font ) - { - super.setFont( font ); - if ( spinner != null ) - spinner.setFont( font ); - if ( slider != null ) - slider.setFont( font ); - } - @Override public void setToolTipText( final String text ) { From b4ebc583eabf195c092bff724f35992e4cd16364 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 Jul 2024 15:50:47 +0200 Subject: [PATCH 255/263] Add required methods in SpotRoiUtils. We want to retrieve spots as a map feom labels to corresponding spots. --- .../trackmate/detection/SpotRoiUtils.java | 191 +++++++++++------- .../trackmate/gui/editor/LabkitImporter.java | 6 +- 2 files changed, 117 insertions(+), 80 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java index 0612dc5d9..0a4ba51f2 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java @@ -23,31 +23,29 @@ import java.awt.Polygon; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotRoi; import ij.gui.PolygonRoi; import ij.process.FloatPolygon; +import net.imagej.ImgPlus; +import net.imagej.axis.Axes; +import net.imagej.axis.AxisType; import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessibleInterval; -import net.imglib2.algorithm.gauss3.Gauss3; -import net.imglib2.converter.Converter; -import net.imglib2.converter.Converters; -import net.imglib2.img.Img; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; import net.imglib2.type.BooleanType; import net.imglib2.type.NativeType; -import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; -import net.imglib2.type.numeric.real.FloatType; -import net.imglib2.util.Util; import net.imglib2.view.Views; /** @@ -122,103 +120,142 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot final boolean simplify, final double smoothingScale, final RandomAccessibleInterval< S > qualityImage ) + { + final Map< Integer, List< Spot > > map = from2DLabelingWithROIMap( labeling, origin, calibration, simplify, qualityImage ); + final List< Spot > spots = new ArrayList<>(); + for ( final List< Spot > s : map.values() ) + spots.addAll( s ); + + return spots; + } + + /** + * Creates spots with ROIs from a 2D label image. The quality + * value is read from a secondary image, by taking the max value in each + * ROI. + *

      + * The spots are returned in a map, where the key is the integer value of + * the label they correspond to in the label image. Because one spot + * corresponds to one connected component in the label image, there might be + * several spots for a label, hence the values of the map are list of spots. + * + * @param + * the type that backs-up the labeling. + * @param + * the type of the quality image. Must be real, scalar. + * @param labeling + * the labeling, must be zero-min and 2D.. + * @param origin + * the origin (min pos) of the interval the labeling was + * generated from, used to reposition the spots from the zero-min + * labeling to the proper coordinates. + * @param calibration + * the physical calibration. + * @param simplify + * if true the polygon will be post-processed to be + * smoother and contain less points. + * @param qualityImage + * the image in which to read the quality value. + * @return a map linking the label integer value to the list of spots, with + * ROI, it corresponds to. + */ + public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > from2DLabelingWithROIMap( + final ImgLabeling< Integer, R > labeling, + final double[] origin, + final double[] calibration, + final boolean simplify, + final RandomAccessibleInterval< S > qualityImage ) { if ( labeling.numDimensions() != 2 ) throw new IllegalArgumentException( "Can only process 2D images with this method, but got " + labeling.numDimensions() + "D." ); final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); - final double[] sigmas = new double[ 2 ]; - for ( int d = 0; d < sigmas.length; d++ ) - sigmas[ d ] = smoothingScale / Math.sqrt( 2. ) / calibration[ d ]; - - - // Parse regions to create polygons on boundaries. - final List< Polygon > polygons = new ArrayList<>( regions.getExistingLabels().size() ); + /* + * Map of label in the label image to a collection of polygons around + * this label. Because 1 polygon correspond to 1 connected component, + * there might be several polygons for a label. + */ + final Map< Integer, List< Polygon > > polygonsMap = new HashMap<>( regions.getExistingLabels().size() ); final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); + // Parse regions to create polygons on boundaries. while ( iterator.hasNext() ) { final LabelRegion< Integer > region = iterator.next(); - - // Possibly smooth labels. - final RandomAccessibleInterval< BoolType > mask; - if (smoothingScale > 0.) - { - // Filter. - final Img< FloatType > filtered = Util.getArrayOrCellImgFactory( region, new FloatType() ).create( region ); - Gauss3.gauss( sigmas, region, filtered ); - - // To mask. - final double threshold = 0.5; - final Converter< FloatType, BoolType > converter = ( a, b ) -> b.set( a.getRealDouble() > threshold ); - mask = Converters.convertRAI( filtered, converter, new BoolType() ); - } - else - { - mask = region; - } - // Analyze in zero-min region. - final List< Polygon > pp = maskToPolygons( Views.zeroMin( mask ) ); + final List< Polygon > pp = maskToPolygons( Views.zeroMin( region ) ); // Translate back to interval coords. for ( final Polygon polygon : pp ) - polygon.translate( ( int ) mask.min( 0 ), ( int ) mask.min( 1 ) ); + polygon.translate( ( int ) region.min( 0 ), ( int ) region.min( 1 ) ); - polygons.addAll( pp ); + final Integer label = region.getLabel(); + polygonsMap.put( label, pp ); } - // Quality image. - final List< Spot > spots = new ArrayList<>( polygons.size() ); + // Storage for results. + final Map< Integer, List< Spot > > output = new HashMap<>( polygonsMap.size() ); // Simplify them and compute a quality. - for ( final Polygon polygon : polygons ) + for ( final Integer label : polygonsMap.keySet() ) { - final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON ); + final List< Spot > spots = new ArrayList<>( polygonsMap.size() ); + output.put( label, spots ); - // Create Spot ROI. - final PolygonRoi fRoi; - if ( simplify ) - fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE ); - else - fRoi = roi; + final List< Polygon > polygons = polygonsMap.get( label ); + for ( final Polygon polygon : polygons ) + { + final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON ); - // Don't include ROIs that have been shrunk to < 1 pixel. - if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) - continue; + // Create Spot ROI. + final PolygonRoi fRoi; + if ( simplify ) + fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE ); + else + fRoi = roi; - // Create spot without quality value yet. - final Polygon fPolygon = fRoi.getPolygon(); - final double[] xpoly = new double[ fPolygon.npoints ]; - final double[] ypoly = new double[ fPolygon.npoints ]; - for ( int i = 0; i < fPolygon.npoints; i++ ) - { - xpoly[ i ] = calibration[ 0 ] * ( origin[ 0 ] + fPolygon.xpoints[ i ] - 0.5 ); - ypoly[ i ] = calibration[ 1 ] * ( origin[ 1 ] + fPolygon.ypoints[ i ] - 0.5 ); - } - final SpotRoi spot = SpotRoi.createSpot( xpoly, ypoly, -1. ); + // Don't include ROIs that have been shrunk to < 1 pixel. + if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. ) + continue; - // Measure quality. - final double quality; - if ( null == qualityImage ) - { - quality = fRoi.getStatistics().area; - } - else - { - final IterableInterval< S > iterable = spot.iterable( Views.extendZero( qualityImage ), calibration ); - double max = Double.NEGATIVE_INFINITY; - for ( final S s : iterable ) + final Polygon fPolygon = fRoi.getPolygon(); + final double[] xpoly = new double[ fPolygon.npoints ]; + final double[] ypoly = new double[ fPolygon.npoints ]; + for ( int i = 0; i < fPolygon.npoints; i++ ) { - final double val = s.getRealDouble(); - if ( val > max ) - max = val; + xpoly[ i ] = calibration[ 0 ] * ( origin[ 0 ] + fPolygon.xpoints[ i ] - 0.5 ); + ypoly[ i ] = calibration[ 1 ] * ( origin[ 1 ] + fPolygon.ypoints[ i ] - 0.5 ); } - quality = max; + + final Spot spot = SpotRoi.createSpot( xpoly, ypoly, -1. ); + + // Measure quality. + final double quality; + if ( null == qualityImage ) + { + quality = fRoi.getStatistics().area; + } + else + { + final String name = "QualityImage"; + final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y }; + final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] }; + final String[] units = new String[] { "unitX", "unitY" }; + final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units ); + final IterableInterval< S > iterable = spot.iterable( qualityImgPlus ); + double max = Double.NEGATIVE_INFINITY; + for ( final S s : iterable ) + { + final double val = s.getRealDouble(); + if ( val > max ) + max = val; + } + quality = max; + } + spot.putFeature( Spot.QUALITY, quality ); + spots.add( spot ); } - spot.putFeature( Spot.QUALITY, quality ); - spots.add( spot ); } - return spots; + return output; } private static final double distanceSquaredBetweenPoints( final double vx, final double vy, final double wx, final double wy ) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 3c625cd70..fb77fdc46 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -33,7 +33,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.detection.MaskUtils; +import fiji.plugin.trackmate.detection.SpotRoiUtils; import net.imglib2.RandomAccessibleInterval; import net.imglib2.loops.LoopBuilder; import net.imglib2.roi.labeling.ImgLabeling; @@ -274,9 +274,9 @@ private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T indices.add( Integer.valueOf( i + 1 ) ); final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); - final Map< Integer, List< Spot > > spots = MaskUtils.from2DLabelingWithROIMap( + final Map< Integer, List< Spot > > spots = SpotRoiUtils.from2DLabelingWithROIMap( labeling, - Views.zeroMin( labeling ), + labeling.minAsDoubleArray(), calibration, simplify, rai ); From b2207c17e95bd4559e24c905883b4abb0f3aaba3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 Jul 2024 15:51:20 +0200 Subject: [PATCH 256/263] Remove unnecessary suppressWarning. --- .../java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java index a3d44509f..40fb780e0 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java @@ -81,7 +81,6 @@ public class ImpBdvShowable implements BdvShowable */ public static < T extends NumericType< T > > ImpBdvShowable fromImp( final ImagePlus imp ) { - @SuppressWarnings( "unchecked" ) final ImgPlus< T > src = TMUtils.rawWraps( imp ); if ( src.dimensionIndex( Axes.CHANNEL ) < 0 ) Views.addDimension( src ); From 19e981ac7702f39334402edc323c53d9a21e6b6a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 Jul 2024 15:58:40 +0200 Subject: [PATCH 257/263] Don't crash if the saved tracker is unknown to us. --- .../trackmate/gui/wizard/TrackMateWizardSequence.java | 2 +- .../plugin/trackmate/gui/wizard/WizardController.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java index 4d0a89ba2..320f237c9 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -396,7 +396,7 @@ private SpotTrackerDescriptor getTrackerConfigDescriptor() * Special case: are we dealing with the manual tracker? If yes, no * config, no detection. */ - if ( trackerFactory.getKey().equals( ManualTrackerFactory.TRACKER_KEY ) ) + if ( trackerFactory == null || trackerFactory.getKey().equals( ManualTrackerFactory.TRACKER_KEY ) ) { // Position sequence next and previous. next.put( chooseTrackerDescriptor, trackFilterDescriptor ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardController.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardController.java index 7fd221ed1..2393ba443 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardController.java @@ -204,7 +204,13 @@ private void exec( final Runnable runnable ) public void init() { - final WizardPanelDescriptor descriptor = sequence.current(); + WizardPanelDescriptor descriptor = sequence.current(); + if ( descriptor == null ) + { + sequence.setCurrent( sequence.configDescriptor().panelIdentifier ); + descriptor = sequence.configDescriptor(); + } + wizardPanel.btnPrevious.setEnabled( sequence.hasPrevious() ); wizardPanel.btnNext.setEnabled( sequence.hasNext() ); descriptor.aboutToDisplayPanel(); From ddd25203718a2ce14eaf6117571c18cda1caf3a2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 Jul 2024 16:38:35 +0200 Subject: [PATCH 258/263] Fixed one more bug with the labkit importer. When editing the whole movie, if the label of a new spot was using the label of an existing spot in another time-point, the existing one was removed. Because the new spot was identified as a modification of the existing one. The solution is to pass to the importer only the list of existing spots in the current time-frame. --- .../trackmate/gui/editor/LabkitImporter.java | 30 ++++++++++++++++++- .../trackmate/gui/editor/LabkitLauncher.java | 11 ++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index fb77fdc46..677022642 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -34,6 +34,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.detection.SpotRoiUtils; +import ij.IJ; import net.imglib2.RandomAccessibleInterval; import net.imglib2.loops.LoopBuilder; import net.imglib2.roi.labeling.ImgLabeling; @@ -48,6 +49,8 @@ public class LabkitImporter< T extends IntegerType< T > & NativeType< T > > { + private static final boolean DEBUG = false; + private final Model model; private final double[] calibration; @@ -148,6 +151,7 @@ else if ( novelSpotList == null || novelSpotList.isEmpty() ) * One I had in the previous spot list, but that has * disappeared. Remove it. */ + IJ.log( " - Removed spot " + str( previousSpot ) ); model.removeSpot( previousSpot ); } else @@ -212,6 +216,9 @@ else if ( target == previousSpot ) else throw new IllegalArgumentException( "The edge of a spot does not have the spot as source or target?!?" ); } + if ( DEBUG ) + IJ.log( " - Modified spot " + str( previousSpot ) + " -> " + str( mainNovelSpot ) ); + model.removeSpot( previousSpot ); // Deal with the other ones. @@ -225,6 +232,9 @@ else if ( target == previousSpot ) s.putFeature( Spot.POSITION_T, currentTimePoint * dt ); s.putFeature( Spot.QUALITY, -1. ); model.addSpotTo( s, Integer.valueOf( currentTimePoint ) ); + + if ( DEBUG ) + IJ.log( " - Added spot " + str( s ) ); } } @@ -235,6 +245,9 @@ private void addNewSpot( final Iterable< Spot > novelSpotList, final int current spot.putFeature( Spot.POSITION_T, currentTimePoint * dt ); spot.putFeature( Spot.QUALITY, -1. ); model.addSpotTo( spot, Integer.valueOf( currentTimePoint ) ); + + if ( DEBUG ) + IJ.log( " - Added spot " + str( spot ) ); } } @@ -276,10 +289,25 @@ private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); final Map< Integer, List< Spot > > spots = SpotRoiUtils.from2DLabelingWithROIMap( labeling, - labeling.minAsDoubleArray(), + new double[] { 0., 0. }, calibration, simplify, rai ); return spots; } + + private static final String str( final Spot spot ) + { + return spot.ID() + " (" + + roundToN( spot.getDoublePosition( 0 ), 1 ) + ", " + + roundToN( spot.getDoublePosition( 1 ), 1 ) + ", " + + roundToN( spot.getDoublePosition( 2 ), 1 ) + ") " + + "@ t=" + spot.getFeature( Spot.FRAME ).intValue(); + } + + private static double roundToN( final double num, final int n ) + { + final double scale = Math.pow( 10, n ); + return Math.round( num * scale ) / scale; + } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index 3a1ba7ffc..ff5212662 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -218,9 +218,18 @@ public void run() log.setStatus( "Re-importing from Labkit..." ); for ( int t = 0; t < nTimepoints; t++ ) { + // The spots of this time-point: + final Map< Integer, Spot > spotLabelsThisFrame = new HashMap<>(); + for ( final Integer label : spotLabels.keySet() ) + { + final Spot spot = spotLabels.get( label ); + if ( spot.getFeature( Spot.FRAME ).intValue() == t ) + spotLabelsThisFrame.put( label, spot ); + } + final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t ); final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t ); - reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t, spotLabels ); + reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t, spotLabelsThisFrame ); log.setProgress( t / ( double ) nTimepoints ); } log.setStatus( "" ); From c2823c68168bb15e980d4e3613581a6eae3fdd23 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 23 Jul 2024 11:17:56 +0200 Subject: [PATCH 259/263] Add a methods to return the map of label to spots in a labeling image. Counterpart to the same one in SpotRoiUtils. Nota: the methods signature order should be harmonized between the two utility classes. --- .../trackmate/detection/SpotMeshUtils.java | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 3c3cac760..7157f2d01 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -23,8 +23,11 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; @@ -183,7 +186,7 @@ public static < T extends RealType< T > & NativeType< T >, S extends RealType< S /** * Creates spots with meshes from a 3D label image. The labels * are possibly smoothed before creating the mesh. The quality value is read - * from a secondary image, by taking the max value in each ROI. + * from a secondary image, by taking the max value inside the mesh. * * @param * the type that backs-up the labeling. @@ -216,6 +219,57 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot final boolean simplify, final double smoothingScale, final RandomAccessibleInterval< S > qualityImage ) + { + final Map< Integer, List< Spot > > map = from3DLabelingWithROIMap( labeling, origin, calibration, simplify, smoothingScale, qualityImage ); + final List< Spot > spots = new ArrayList<>(); + for ( final List< Spot > s : map.values() ) + spots.addAll( s ); + + return spots; + } + + /** + * Creates spots with meshes from a 3D label image. The labels + * are possibly smoothed before creating the mesh. The quality value is read + * from a secondary image, by taking the max value inside the mesh. + *

      + * The spots are returned in a map, where the key is the integer value of + * the label they correspond to in the label image. In 3D, there is one spot + * per label, even for disconnected components, so the lists are made of one + * element for now. + * + * @param + * the type that backs-up the labeling. + * @param + * the type of the quality image. Must be real, scalar. + * @param labeling + * the labeling, must be zero-min and 3D. + * @param origin + * the origin (min pos) of the interval the labeling was + * generated from, used to reposition the spots from the zero-min + * labeling to the proper coordinates. + * @param calibration + * the physical calibration. + * @param simplify + * if true the meshes will be post-processed to + * contain less verrtices. + * @param smoothingScale + * if strictly larger than 0, the mask will be smoothed before + * creating the mesh, resulting in smoother meshes. The scale + * value sets the (Gaussian) filter radius and is specified in + * physical units. If 0 or lower than 0, no smoothing is applied. + * @param qualityImage + * the image in which to read the quality value. + * @return a map linking the label integer value to the list of spots, with + * meshes, it corresponds to. + */ + public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > from3DLabelingWithROIMap( + final ImgLabeling< Integer, R > labeling, + final double[] origin, + final double[] calibration, + final boolean simplify, + final double smoothingScale, + final RandomAccessibleInterval< S > qualityImage ) { if ( labeling.numDimensions() != 3 ) throw new IllegalArgumentException( "Can only process 3D images with this method, but got " + labeling.numDimensions() + "D." ); @@ -223,7 +277,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot // Parse regions to create meshes on label. final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling ); final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); - final List< Spot > spots = new ArrayList<>( regions.getExistingLabels().size() ); + final Map< Integer, List< Spot > > spots = new HashMap<>( regions.getExistingLabels().size() ); while ( iterator.hasNext() ) { final LabelRegion< Integer > region = iterator.next(); @@ -237,7 +291,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot if ( spot == null ) continue; - spots.add( spot ); + spots.put( region.getLabel(), Collections.singletonList( spot ) ); } return spots; } From 9535eaed7385a3ab94b48d39d47a3a224b30a76b Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 23 Jul 2024 11:19:09 +0200 Subject: [PATCH 260/263] Support editing in 3D in the LabKit editor. There is one discrepancy with the 2D part, linked to TrackMate: In 2D, one connected components will give one spot, even if several connected components belong to the same label. In 3D, it is possible to have one spot made of several disconnected components. Will have to document this. --- .../trackmate/gui/editor/LabkitImporter.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 677022642..95bb352dd 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -33,6 +33,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.detection.SpotMeshUtils; import fiji.plugin.trackmate.detection.SpotRoiUtils; import ij.IJ; import net.imglib2.RandomAccessibleInterval; @@ -287,13 +288,24 @@ private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T indices.add( Integer.valueOf( i + 1 ) ); final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices ); - final Map< Integer, List< Spot > > spots = SpotRoiUtils.from2DLabelingWithROIMap( - labeling, - new double[] { 0., 0. }, - calibration, - simplify, - rai ); - return spots; + + final boolean is3D = rai.numDimensions() > 2; + final double smoothingScale = -1.; // TODO + if ( is3D ) + return SpotMeshUtils.from3DLabelingWithROIMap( + labeling, + new double[] { 0., 0., 0. }, + calibration, + simplify, + smoothingScale, + rai ); + else + return SpotRoiUtils.from2DLabelingWithROIMap( + labeling, + new double[] { 0., 0. }, + calibration, + simplify, + rai ); } private static final String str( final Spot spot ) From cb3fc8322f67977e9633447a3b90523f1f791c8c Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 23 Jul 2024 11:19:18 +0200 Subject: [PATCH 261/263] Minor typo. --- .../java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index ff5212662..3e0214ab3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -133,7 +133,7 @@ protected void launch( final boolean singleTimePoint ) // Show LabKit. String title = "Editing TrackMate data for " + imp.getShortTitle(); if ( singleTimePoint ) - title += "at frame " + ( currentTimePoint + 1 ); + title += " at frame " + ( currentTimePoint + 1 ); final TrackMateLabkitFrame labkit = TrackMateLabkitFrame.show( model, title ); // Prepare re-importer. From 12dcb90d38786f17c72d330003372ab81d1fa8ff Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 23 Jul 2024 11:41:37 +0200 Subject: [PATCH 262/263] Add the smoothing scale param to the 2D labeling methods as well. Not implemented yet, but at least symmetric with the 2D case. --- .../plugin/trackmate/detection/SpotRoiUtils.java | 16 ++++++++++++++-- .../trackmate/gui/editor/LabkitImporter.java | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java index 0a4ba51f2..5e3078861 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java @@ -121,7 +121,13 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot final double smoothingScale, final RandomAccessibleInterval< S > qualityImage ) { - final Map< Integer, List< Spot > > map = from2DLabelingWithROIMap( labeling, origin, calibration, simplify, qualityImage ); + final Map< Integer, List< Spot > > map = from2DLabelingWithROIMap( + labeling, + origin, + calibration, + simplify, + smoothingScale, + qualityImage ); final List< Spot > spots = new ArrayList<>(); for ( final List< Spot > s : map.values() ) spots.addAll( s ); @@ -144,7 +150,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot * @param * the type of the quality image. Must be real, scalar. * @param labeling - * the labeling, must be zero-min and 2D.. + * the labeling, must be zero-min and 2D. * @param origin * the origin (min pos) of the interval the labeling was * generated from, used to reposition the spots from the zero-min @@ -154,6 +160,11 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot * @param simplify * if true the polygon will be post-processed to be * smoother and contain less points. + * @param smoothingScale + * if strictly larger than 0, the mask will be smoothed before + * creating the mesh, resulting in smoother meshes. The scale + * value sets the (Gaussian) filter radius and is specified in + * physical units. If 0 or lower than 0, no smoothing is applied. * @param qualityImage * the image in which to read the quality value. * @return a map linking the label integer value to the list of spots, with @@ -164,6 +175,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integ final double[] origin, final double[] calibration, final boolean simplify, + final double smoothingScale, final RandomAccessibleInterval< S > qualityImage ) { if ( labeling.numDimensions() != 2 ) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java index 95bb352dd..2cc4b82d2 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitImporter.java @@ -305,6 +305,7 @@ private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T new double[] { 0., 0. }, calibration, simplify, + smoothingScale, rai ); } From c075ffc038e742282843de5ceba3a3bf5dc07360 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 Jul 2024 17:57:25 +0200 Subject: [PATCH 263/263] Remove unused methods. --- .../detection/LabelImageDetector.java | 7 +- .../plugin/trackmate/detection/MaskUtils.java | 104 ------------------ 2 files changed, 3 insertions(+), 108 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index 8e9d0a126..015d08d05 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -162,10 +162,9 @@ else if ( input.numDimensions() == 3 ) } else { - spots = MaskUtils.fromLabeling( - labeling, - interval.minAsDoubleArray(), - calibration ); + throw new IllegalArgumentException( BASE_ERROR_MESSAGE + "Can only process 2D or 3D images. Got a " + + input.numDimensions() + "D image over: " + + Util.printInterval( interval ) ); } } diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 72b71f7a1..e6d632d9e 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -49,7 +49,6 @@ import net.imglib2.roi.labeling.LabelRegions; import net.imglib2.type.NativeType; import net.imglib2.type.logic.BitType; -import net.imglib2.type.numeric.IntegerType; import net.imglib2.type.numeric.RealType; import net.imglib2.type.numeric.integer.IntType; import net.imglib2.type.numeric.real.FloatType; @@ -202,109 +201,6 @@ public static final < T extends RealType< T > > ImgLabeling< Integer, IntType > return labeling; } - /** - * Creates spots from a grayscale image, thresholded to create a mask. A - * spot is created for each connected-component of the mask, with a size - * that matches the mask size. - * - * @param - * the type of the input image. Must be real, scalar. - * @param input - * the input image. - * @param interval - * the interval in the input image to analyze. - * @param calibration - * the physical calibration. - * @param threshold - * the threshold to apply to the input image. - * @param numThreads - * how many threads to use for multithreaded computation. - * @return a list of spots, without ROI. - */ - public static < T extends RealType< T > > List< Spot > fromThreshold( - final RandomAccessible< T > input, - final Interval interval, - final double[] calibration, - final double threshold, - final int numThreads ) - { - /* - * Crop. - */ - final IntervalView< T > crop = Views.interval( input, interval ); - final IntervalView< T > in = Views.zeroMin( crop ); - - // Get labeling from mask. - final ImgLabeling< Integer, IntType > labeling = toLabeling( - in, - threshold, - numThreads ); - return fromLabeling( - labeling, - interval.minAsDoubleArray(), - calibration ); - } - - /** - * Creates spots from a label image. - * - * @param - * the type that backs-up the labeling. - * @param labeling - * the labeling, must be zero-min. - * @param origin - * the origin (min pos) of the interval the labeling was - * generated from, used to reposition the spots from the zero-min - * labeling to the proper coordinates. - * @param calibration - * the physical calibration. - * @return a list of spots, without ROI. - */ - public static < R extends IntegerType< R > > List< Spot > fromLabeling( - final ImgLabeling< Integer, R > labeling, - final double[] origin, - final double[] calibration ) - { - // Parse each component. - final LabelRegions< Integer > regions = new LabelRegions<>( labeling ); - final Iterator< LabelRegion< Integer > > iterator = regions.iterator(); - final List< Spot > spots = new ArrayList<>( regions.getExistingLabels().size() ); - while ( iterator.hasNext() ) - { - final LabelRegion< Integer > region = iterator.next(); - final LabelRegionCursor cursor = region.localizingCursor(); - final int[] cursorPos = new int[ labeling.numDimensions() ]; - final long[] sum = new long[ 3 ]; - while ( cursor.hasNext() ) - { - cursor.fwd(); - cursor.localize( cursorPos ); - for ( int d = 0; d < sum.length; d++ ) - sum[ d ] += cursorPos[ d ]; - } - - final double[] pos = new double[ 3 ]; - for ( int d = 0; d < pos.length; d++ ) - pos[ d ] = sum[ d ] / ( double ) region.size(); - - final double x = calibration[ 0 ] * ( origin[ 0 ] + pos[ 0 ] ); - final double y = calibration[ 1 ] * ( origin[ 1 ] + pos[ 1 ] ); - final double z = calibration[ 2 ] * ( origin[ 2 ] + pos[ 2 ] ); - - double volume = region.size(); - for ( int d = 0; d < calibration.length; d++ ) - if ( calibration[ d ] > 0 ) - volume *= calibration[ d ]; - final double radius = ( labeling.numDimensions() == 2 ) - ? Math.sqrt( volume / Math.PI ) - : Math.pow( 3. * volume / ( 4. * Math.PI ), 1. / 3. ); - final double quality = region.size(); - spots.add( new SpotBase( x, y, z, radius, quality ) ); - } - - return spots; - } - /** * Creates spots by thresholding a grayscale image. A spot is created for * each connected-component object in the thresholded input, with a size