* 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 +322,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 +448,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 +459,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 +545,7 @@ public synchronized Spot moveSpotFrom( final Spot spotToMove, final Integer from * model.endUpdate(); * } * - * + * * @param spotToAdd * the spot to add. * @param toFrame @@ -593,8 +594,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 +628,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 +768,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 +812,7 @@ public Model copy() featureModel.getTrackFeatureShortNames(), featureModel.getTrackFeatureDimensions(), featureModel.getTrackFeatureIsInt() ); - + // Feature values are not copied. return copy; } @@ -841,7 +843,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 +855,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 ); @@ -958,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 instanceof SpotMesh ) ) + .forEach( s -> ( ( SpotMesh ) s ).resetZSliceCache() ); + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/Settings.java b/src/main/java/fiji/plugin/trackmate/Settings.java index 22cad10fa..1034eef99 100644 --- a/src/main/java/fiji/plugin/trackmate/Settings.java +++ b/src/main/java/fiji/plugin/trackmate/Settings.java @@ -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/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index 00bb2f5bd..b0a678f2b 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
+ * 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
+ * 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 "
+ + "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;
+ 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 )
+ {
+ 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 NavigableSet< Integer > frames = spots.keySet();
+ for ( final Integer frame : frames )
+ {
+ 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 ) )
+ {
+ 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( merged.vertices().size(), merged.triangles().size() );
+ Meshes.calculateNormals( merged, mesh );
+ try
+ {
+ PLYMeshIO.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/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 17b8d2b15..90e73da00 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;
@@ -87,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 );
@@ -173,7 +176,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/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/action/meshtools/MeshSmoother.java b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java
new file mode 100644
index 000000000..dcfe73f39
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java
@@ -0,0 +1,203 @@
+/*-
+ * #%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
+ * "
+ "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."
+ "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,233 +69,94 @@ 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 detector that created this spot does not support
- * ROIs or for 3D images.
- */
- private SpotRoi roi;
-
/*
- * 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 METHODS
*/
- 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 )
+ @Override
+ public default int compareTo( final Spot o )
{
- this( x, y, z, radius, quality, null );
+ return ID() - o.ID();
}
+
/**
- * 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.
+ * Returns a copy of this spot. The class and all fields will be identical,
+ * except for the {@link #ID()}.
+ *
+ * @return a new spot.
*/
- 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 );
- }
+ public Spot copy();
/**
- * 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.
+ * Scales the size of this spot by the specified ratio.
+ *
+ * @param alpha
+ * the scale.
*/
- public Spot( final RealLocalizable location, final double radius, final double quality )
- {
- this( location, radius, quality, null );
- }
+ public void scale( double alpha );
/**
- * 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.
+ * Returns an iterable that will iterate over all the pixels contained in
+ * this spot.
+ *
+ * @param ra
+ * 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 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.
@@ -363,24 +224,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( 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 (
@@ -395,9 +268,9 @@ public void copyFeatures( 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;
}
@@ -423,9 +296,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;
@@ -440,7 +313,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++ )
@@ -484,97 +357,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 = 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
*/
- 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 );
+
+ @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.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 ] );
+ }
+
+ @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 ] );
}
@@ -592,7 +600,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..b97961d74
--- /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 - 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
+ * 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 oldSpot
+ * 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
new file mode 100644
index 000000000..2da0821b2
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java
@@ -0,0 +1,391 @@
+/*-
+ * #%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
+ * 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, this, 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.
+ */
+ public void resetZSliceCache()
+ {
+ sliceMap = null;
+ }
+
+ /**
+ * 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 )
+ {
+ return Math.pow( 3. * MeshStats.volume( mesh ) / ( 4 * Math.PI ), 1. / 3. );
+ }
+
+ public double radius()
+ {
+ return radius( mesh );
+ }
+
+ /**
+ * Returns the volume of this mesh.
+ *
+ * @return the volume in physical units.
+ */
+ public double volume()
+ {
+ return MeshStats.volume( mesh );
+ }
+
+ @Override
+ public void scale( final double alpha )
+ {
+ final net.imglib2.mesh.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 );
+ }
+ this.boundingBox = Meshes.boundingBox( mesh );
+ }
+
+ @Override
+ public SpotMesh copy()
+ {
+ final BufferMesh meshCopy = new BufferMesh( mesh.vertices().size(), mesh.triangles().size() );
+ Meshes.copy( this.mesh, meshCopy );
+ return new SpotMesh( meshCopy, getFeature( Spot.QUALITY ), getName() );
+ }
+
+ @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.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 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.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();
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java
index 00d2556e6..00396adb2 100644
--- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java
+++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java
@@ -22,114 +22,238 @@
package fiji.plugin.trackmate;
import java.util.Arrays;
+import java.util.Iterator;
-import net.imagej.ImgPlus;
+import gnu.trove.list.array.TDoubleArrayList;
+import net.imglib2.Cursor;
+import net.imglib2.FinalInterval;
import net.imglib2.IterableInterval;
-import net.imglib2.RandomAccessibleInterval;
-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.Localizable;
+import net.imglib2.RandomAccess;
+import net.imglib2.RandomAccessible;
+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
+public class SpotRoi extends SpotBase
{
- /**
- * Polygon points X coordinates, in physical units.
- */
- public final double[] x;
+ /** Polygon points X coordinates, in physical units, centered (0,0). */
+ private final double[] x;
+
+ /** Polygon points Y coordinates, in physical units, centered (0,0). */
+ private 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;
+ }
/**
- * Polygon points Y coordinates, in physical units.
+ * 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
+ * the ID to use when creating the spot.
+ * @param x
+ * the spot contour X coordinates.
+ * @param y
+ * the spot contour Y coordinates.
*/
- public final double[] y;
-
- public SpotRoi( final double[] x, final double[] y )
+ public SpotRoi(
+ final int ID,
+ final double[] x,
+ final double[] y )
{
+ super( ID );
this.x = x;
this.y = 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() );
+ }
+
+ /**
+ * 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 )
+ {
+ 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.
+ * 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 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.
+ * @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[] toPolygonX( final double calibration, final double xcorner, final double spotXCenter, 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[] xp = new double[ x.length ];
- for ( int i = 0; i < xp.length; i++ )
+ xout.resetQuick();
+ yout.resetQuick();
+ for ( int i = 0; i < x.length; i++ )
{
- final double xc = ( spotXCenter + x[ i ] ) / calibration;
- xp[ i ] = ( xc - xcorner ) * magnification;
+ xout.add( x( i ) + sx + cx );
+ yout.add( y( i ) + sx + cy );
}
- return xp;
}
/**
- * Returns a new int
array containing the Y pixel coordinates
- * to which to paint this polygon.
+ * 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 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.
+ * @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.
+ * @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 double[] toPolygonY( final double calibration, final double ycorner, final double spotYCenter, final double magnification )
+ public double[][] toArray( final double cx, final double cy, final double sx, final double sy )
{
- final double[] yp = new double[ y.length ];
- for ( int i = 0; i < yp.length; i++ )
+ final double[] xout = new double[ x.length ];
+ final double[] yout = new double[ x.length ];
+ for ( int i = 0; i < x.length; i++ )
{
- final double yc = ( spotYCenter + y[ i ] ) / calibration;
- yp[ i ] = ( yc - ycorner ) * magnification;
+ xout[ i ] = x( i ) * sx + cx;
+ yout[ i ] = y( i ) * sy + cy;
}
- return yp;
+ return new double[][] { xout, yout };
}
- public < T > IterableInterval< T > sample( final Spot spot, final ImgPlus< T > img )
+ @Override
+ public < T extends RealType< T > > IterableInterval< T > iterable( final RandomAccessible< T > ra, final double[] calibration )
{
- return sample( spot.getDoublePosition( 0 ), spot.getDoublePosition( 1 ), img, img.averageScale( 0 ), img.averageScale( 1 ) );
+ return new SpotRoiIterable<>( this, ra, calibration );
}
- public < T > IterableInterval< T > sample( final double spotXCenter, final double spotYCenter, final RandomAccessibleInterval< T > img, final double xScale, final double yScale )
+ private static double radius( final double[] x, final double[] y )
{
- final double[] xp = toPolygonX( xScale, 0, spotXCenter, 1. );
- final double[] yp = toPolygonY( yScale, 0, spotYCenter, 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 Math.sqrt( area( x, y ) / Math.PI );
}
- public double radius()
+ private static double area( final double[] x, final double[] y )
{
- return Math.sqrt( area() / Math.PI );
+ return Math.abs( signedArea( x, y ) );
}
public double area()
{
- return Math.abs( signedArea( x, y ) );
+ return area( x, y );
}
+ @Override
public void scale( final double alpha )
{
for ( int i = 0; i < x.length; i++ )
@@ -144,7 +268,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 );
@@ -153,15 +277,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 );
}
/*
@@ -174,16 +293,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 };
}
@@ -191,9 +308,261 @@ 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 / 2.;
+ }
+
+ /*
+ * ITERABLE and ITERATOR.
+ */
+
+ private static final class SpotRoiIterable< T extends RealType< T > > implements IterableInterval< T >
+ {
+
+ private final FinalInterval interval;
- return ( a + x[ n - 1 ] * y[ 0 ] - x[ 0 ] * y[ n - 1 ] ) / 2.0;
+ /** 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
* 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 )
@@ -172,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 8ddb63ac9..e532311a2 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;
@@ -183,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
{
@@ -314,15 +317,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 +413,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 +450,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/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/IJRoiExporter.java b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java
index d1c18e4ea..9d2925711 100644
--- a/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java
+++ b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java
@@ -86,14 +86,13 @@ 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 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 SpotRoi sroi = ( SpotRoi ) spot;
+ 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/action/LabelImgExporter.java b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java
index 8b286be51..38add12ec 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;
@@ -166,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
@@ -203,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
@@ -237,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
@@ -285,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
@@ -319,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
@@ -367,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
@@ -401,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
@@ -454,8 +465,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 ) )
{
@@ -482,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
@@ -546,7 +556,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 +614,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 +627,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/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java
new file mode 100644
index 000000000..21027b385
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java
@@ -0,0 +1,176 @@
+/*-
+ * #%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
+ * null
if there
+ * was a problem during processing.
*/
public static final < R extends RealType< R > & NativeType< R > > Img< R > applyMedianFilter( final RandomAccessibleInterval< R > image )
{
@@ -410,7 +420,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 );
}
}
@@ -423,7 +433,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 );
}
}
@@ -436,7 +446,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 );
}
@@ -455,7 +465,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 );
}
}
@@ -468,7 +478,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 );
}
}
@@ -481,10 +491,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/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java
index 3c54dceef..e341bd667 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
*