From 6ff747e39622322b8f68646c98f7f8d958d689f8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 13 Apr 2023 19:30:01 +0200 Subject: [PATCH 001/371] 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 41da46dd9..05dabfa73 100644 --- a/pom.xml +++ b/pom.xml @@ -231,6 +231,14 @@ net.imagej imagej-common + + net.imagej + imagej-mesh + + + net.imagej + imagej-mesh-io + From 4a3a2985aa8d6111d980349b757ff3cc5fc37bb8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 13 Apr 2023 19:30:22 +0200 Subject: [PATCH 002/371] 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 e4f3283a913e06a2bdc3ff259ca0f640bfee7b52 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 14 Apr 2023 15:49:08 +0200 Subject: [PATCH 003/371] 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 97a1714241f1642f8d93d768e26be4271c872b66 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 14 Apr 2023 18:29:11 +0200 Subject: [PATCH 004/371] 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 3baedb0de5a2fec9e568b7b393494c0b47cc683e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Fri, 14 Apr 2023 20:43:36 +0200 Subject: [PATCH 005/371] 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 77b7bfdbf50f89bb59482c2c6ff0f32b78d08d53 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 16 Apr 2023 21:15:09 +0200 Subject: [PATCH 006/371] 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 05dabfa73..462530436 100644 --- a/pom.xml +++ b/pom.xml @@ -234,6 +234,7 @@ net.imagej imagej-mesh + 0.8.2-SNAPSHOT net.imagej From aa27c9ea1fdcbc4c3b813c36af91bd6bb7cf3769 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 16 Apr 2023 21:16:21 +0200 Subject: [PATCH 007/371] 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 9da54f0b486d482d2f20a73602143f6c9caf609f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:06:35 +0200 Subject: [PATCH 008/371] 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 a6f91bb9df4a140021e0a06e4834c917022d7c24 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:07:42 +0200 Subject: [PATCH 009/371] Spot objects may have a SpotMesh. --- src/main/java/fiji/plugin/trackmate/Spot.java | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index e0e1ffb37..754819f89 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. */ From 2df52c418f5c9586c7e3c76bc7ed6f0472684dcc Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:08:30 +0200 Subject: [PATCH 010/371] Utility method for SpotRoi: generate XY coordinates and write them in holders provided by the user. --- .../java/fiji/plugin/trackmate/SpotRoi.java | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index 3f081e8c1..b73cb2b65 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; @@ -110,6 +111,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 447749c29cf43253c6d29845822187e734c79fb5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:10:00 +0200 Subject: [PATCH 011/371] MaskUtils have method to create spots with meshes for 3D masks and label images. --- pom.xml | 2 +- .../detection/LabelImageDetector.java | 2 +- .../plugin/trackmate/detection/MaskUtils.java | 132 +++++++++++++++--- 3 files changed, 112 insertions(+), 24 deletions(-) diff --git a/pom.xml b/pom.xml index 462530436..3545d3a6d 100644 --- a/pom.xml +++ b/pom.xml @@ -234,13 +234,13 @@ net.imagej imagej-mesh - 0.8.2-SNAPSHOT net.imagej imagej-mesh-io + sc.fiji diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index fc1e522ea..c12316ca2 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -135,7 +135,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 1943be802..88da3c624 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -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.Cursor; import net.imglib2.Interval; import net.imglib2.IterableInterval; @@ -52,6 +57,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.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; @@ -59,6 +65,7 @@ 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; @@ -116,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: @@ -149,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 @@ -310,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 ) ); } @@ -400,7 +407,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 ) ); } @@ -408,8 +415,8 @@ 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. @@ -444,12 +451,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." ); } /** @@ -475,15 +486,15 @@ 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 ); @@ -519,7 +530,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, @@ -552,7 +563,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() ); @@ -619,6 +629,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 ); @@ -1125,9 +1202,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; @@ -1215,4 +1292,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 da08120942841fbf8a34d71fea475c07194fe6dc Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:10:28 +0200 Subject: [PATCH 012/371] Mask, label and threshold detectors support creating 3D meshes for their spots. --- .../trackmate/detection/LabelImageDetector.java | 2 ++ .../detection/LabelImageDetectorFactory.java | 5 ++--- .../detection/MaskDetectorFactory.java | 5 ++--- .../trackmate/detection/ThresholdDetector.java | 17 +++-------------- .../detection/ThresholdDetectorFactory.java | 5 ++--- 5 files changed, 11 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 c12316ca2..cc5578a19 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -136,6 +136,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 0e498eb06..56af83116 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java @@ -66,8 +66,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." + ""; @@ -144,5 +144,4 @@ public ImageIcon getIcon() { return ThresholdDetectorFactory.ICON; } - } diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java index edd685cb3..1c03d1163 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -62,9 +62,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 82e3f14a2..58dfa0337 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 c9d07ab49..3fce94968 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -64,9 +64,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." + ""; From a56286242ddc77c3130b5d9f08e74b9f13d36db5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:15:10 +0200 Subject: [PATCH 013/371] 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 3c667e9a1..59f6eaa8f 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 ) @@ -248,86 +256,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 42dbac50839d9fc89d316694712e02a2851d4a05 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 20:15:24 +0200 Subject: [PATCH 014/371] 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 d728b3c8464af6c28d6c525c1b58748f7db566ad Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 23:41:21 +0200 Subject: [PATCH 015/371] 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 88da3c624..6d93eba83 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -426,7 +426,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 d5d28d2a3a9913e57818e6dd482793ff01c333df Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 17 Apr 2023 23:41:49 +0200 Subject: [PATCH 016/371] 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 079a87f729ab5b5622582c14afd2089b70a9a9b8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 18 Apr 2023 22:59:32 +0200 Subject: [PATCH 017/371] 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 ce51b79f14a2b009598b42e5f11099538444d43d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 18 Apr 2023 23:00:16 +0200 Subject: [PATCH 018/371] 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 7055c94bcbe146e61d6bde42cacf612768dec4d0 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 18 Apr 2023 23:00:37 +0200 Subject: [PATCH 019/371] 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 d51687805242f7c36c6a86ca46ed25761f127d5f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 18 Apr 2023 23:02:15 +0200 Subject: [PATCH 020/371] 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 0486b5a9cd8af9f58738fa048d74204166829fbf Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 19 Apr 2023 22:57:25 +0200 Subject: [PATCH 021/371] 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 0fa0b461c18275a390ef77896509c7c77f3f6993 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 19 Apr 2023 22:57:35 +0200 Subject: [PATCH 022/371] 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 a5fc0e7834baf487ba95b38b221f1b26aa8bd9ac Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 19 Apr 2023 22:58:03 +0200 Subject: [PATCH 023/371] 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 242180d4b1fc9404e3cfdaea7a3a9a9f68979da3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 19 Apr 2023 22:58:17 +0200 Subject: [PATCH 024/371] 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 36fc95b36eaa0c32539b05d2e4ac49332ee7b30c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 01:06:31 +0200 Subject: [PATCH 025/371] 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 ae9aca6f0ea70cceaa01446ea64f7853b7843522 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:15:34 +0200 Subject: [PATCH 026/371] 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 a32a637af5ac7376d2b356750b8afdf7855ff5c3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:15:49 +0200 Subject: [PATCH 027/371] 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 cd4a306c417dd41d6cc4827213ac50f12985c253 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:15:56 +0200 Subject: [PATCH 028/371] 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 687b07ab5a859e26337e3441acceec1fa81a6348 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:53:20 +0200 Subject: [PATCH 029/371] 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 a055a8a63cf490e13bb955863a9a7098cde4b4dc Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 20 Apr 2023 21:53:48 +0200 Subject: [PATCH 030/371] Implement quality measurement on the 3D object. Without using the mesh actually, we use the bitmask provided. --- .../plugin/trackmate/detection/MaskUtils.java | 32 ++++++++++++------- .../trackmate/util/mesh/SpotMeshCursor.java | 3 +- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index 6d93eba83..a18ee08e3 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; @@ -57,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.LabelRegions; @@ -65,7 +64,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; @@ -652,7 +650,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, @@ -662,12 +660,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(); @@ -688,8 +680,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/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 4339143a421b134c3d3168e99acb0f9a6b3fd3f8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:01:55 +0200 Subject: [PATCH 031/371] 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 754819f89..4a72faa5d 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 b73cb2b65..fd51115ef 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() ); @@ -168,6 +169,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 ); @@ -178,6 +180,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 e1dfd06b3b8a9b28016386629108474ef69d10e8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:02:08 +0200 Subject: [PATCH 032/371] 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 7a4d8cd70e60127c0b1515f9dd099ad30a401a4c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:02:45 +0200 Subject: [PATCH 033/371] 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 d20f762ad..b99b65b19 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 8ccff568e076ef5d72ff1fdb6ca3ed8ca2b3480e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:03:47 +0200 Subject: [PATCH 034/371] 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 c385b1c71..90b17377c 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 189d20a1d052aa069f533c16c710e4516ee55412 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 00:03:59 +0200 Subject: [PATCH 035/371] Add a TODO for ellipse fit feature. --- .../trackmate/features/spot/SpotFitEllipseAnalyzer.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 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 65c87bc45..39ff2f14a 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; From 3b42bd9b1381a0406e53c922c2d735dfa6e45582 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 18:55:42 +0200 Subject: [PATCH 036/371] 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 3545d3a6d..8c30e9b97 100644 --- a/pom.xml +++ b/pom.xml @@ -238,6 +238,7 @@ net.imagej imagej-mesh-io + 0.1.3-SNAPSHOT From 6dd9ebbc6a5eda85da2a2b6a8d919cf934e0d839 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 19:26:13 +0200 Subject: [PATCH 037/371] 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 | 89 +++++++++++++++++-- 2 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 66ac1739b..f3ad1540e 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 */ @@ -192,6 +202,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 @@ -208,7 +219,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; @@ -376,7 +387,6 @@ public Model getModel() ok = false; // Track features - try { final Map< Integer, Map< String, Double > > savedFeatureMap = readTrackFeatures( modelElement ); @@ -926,7 +936,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() ); @@ -1187,6 +1196,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 4735340a7..ac6b6fece 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -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 */ @@ -242,6 +255,8 @@ public void appendModel( final Model model ) modelElement.addContent( filteredTrackElement ); root.addContent( modelElement ); + + writeSpotMeshes( model.getSpots().iterable( false ) ); } /** @@ -701,11 +716,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() ); @@ -744,8 +755,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 5fb39e98074a643cf2b2feaa7971c06dfb98e2e4 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 20:24:38 +0200 Subject: [PATCH 038/371] 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 f3ad1540e..f743be405 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; @@ -947,6 +948,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; } @@ -1196,28 +1247,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 c9abcd8a407f7a989bdedda6453e8000d47cdc04 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 21 Apr 2023 20:41:28 +0200 Subject: [PATCH 039/371] 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 f743be405..a3e37a1c8 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 { @@ -969,7 +970,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 726dce3b2545e0834f5d99af5cd6bdda5de69bd5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 22 Apr 2023 04:35:47 +0200 Subject: [PATCH 040/371] 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 f00e338666f3f0fd94c413ab1826f0c868cf95a8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 22 Apr 2023 04:37:03 +0200 Subject: [PATCH 041/371] 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 b1cccc18643b8aa98624558f36a43318bd4a44ec Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 22 Apr 2023 04:37:31 +0200 Subject: [PATCH 042/371] 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 aa117f92b5fccd4228182574403315ad9d0ff73d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 27 Apr 2023 19:15:45 +0200 Subject: [PATCH 043/371] 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 4df1fcf5626121d3e92563b843f1f79b28eae410 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:33:05 +0200 Subject: [PATCH 044/371] 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 a18ee08e3..9e0804625 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; @@ -449,6 +450,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 ); @@ -461,6 +465,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 b3fc32e0fed412a901138bd8cbd112390564cd08 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:33:31 +0200 Subject: [PATCH 045/371] 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 662f6d3cd09b548ef469ef5bb90793585dc1b64d Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:33:46 +0200 Subject: [PATCH 046/371] 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 59f6eaa8f..86438b555 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 ); } /* @@ -282,7 +282,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 11942268aa1caf685242d9c00a9377dc7dd5ecc2 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:34:00 +0200 Subject: [PATCH 047/371] 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 94917f2fbcc044f481afe2ad8e994e7a64a37d2e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 27 Apr 2023 22:36:13 +0200 Subject: [PATCH 048/371] 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 015d539c77a7bfc454c9313d49d8502cb1d31aa8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 28 Apr 2023 18:45:24 +0200 Subject: [PATCH 049/371] 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 9e0804625..4d6cdfd5d 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -477,17 +477,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 d1c0c9a09d873866b03b612b7ff28440ef7d624c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 28 Apr 2023 18:45:50 +0200 Subject: [PATCH 050/371] 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 7922ac229d9092de0595a71cd0fd6ddf8c39a549 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 28 Apr 2023 18:46:00 +0200 Subject: [PATCH 051/371] 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 4a75d9d12236af88ad66564aa4d282479450d09c Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 29 Apr 2023 22:12:02 +0200 Subject: [PATCH 052/371] 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 c5c135d89bef5f27190d5dcc06ca9a5438ed126c Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 29 Apr 2023 22:12:27 +0200 Subject: [PATCH 053/371] 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 d542aeaa53797108cfea465a6947ac22b83ee70a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 17:33:13 +0200 Subject: [PATCH 054/371] 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 a86de5528997f123002c375980a8d484818854a2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 17:33:21 +0200 Subject: [PATCH 055/371] 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 3b2d7b9a1a4ce815d3dfc344dd677754360b20ee Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 17:33:32 +0200 Subject: [PATCH 056/371] 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 7141c7d8e676392c08fd1116c11ffd469ff8b619 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 21:42:39 +0200 Subject: [PATCH 057/371] 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 a3e37a1c8..dcf0ca3fb 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 */ @@ -204,7 +203,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 @@ -951,6 +949,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. @@ -973,7 +972,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 ac6b6fece..c6801aa3b 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 { @@ -781,21 +782,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(); @@ -815,7 +818,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 b99b65b19..cc3dabc42 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 86438b555..ab5523a5b 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 705e9bc28cc49e89fd36c6ed5034d9ed40fb5a61 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 22:29:46 +0200 Subject: [PATCH 058/371] 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 1c03d1163..f348555d2 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -37,6 +37,8 @@ import net.imagej.ImgPlus; 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; @@ -79,14 +81,15 @@ public boolean has2Dsegmentation() @Override public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, Object > settings, 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, @@ -95,6 +98,27 @@ public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, 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 a6744f199eb7ea67e6a3c33d12a66be0274c3db6 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 2 May 2023 23:06:34 +0200 Subject: [PATCH 059/371] 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 ab5523a5b..d87505669 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 ); } @@ -267,23 +267,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() ) { @@ -292,6 +278,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 9d436d21e901bb28f6a666e616485533786e99d5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 3 May 2023 18:04:40 +0200 Subject: [PATCH 060/371] 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 efa3241a56a8a8df7b29d5be934659b4ef4fdd2f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 3 May 2023 18:12:58 +0200 Subject: [PATCH 061/371] 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 3c4289686..007f39692 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 f9e97310cbfceff85d77eaae0ad4bc174acc4bf5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 18:43:06 +0200 Subject: [PATCH 062/371] 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 007f39692..014489c00 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 dcf0ca3fb..601bb1ba8 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 @@ -972,20 +971,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 7bcf96b0a95f39a36fdb6d7beeb039265132a3ca Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 18:43:44 +0200 Subject: [PATCH 063/371] 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 44aedf3c323903fa6829452b4e3c1d46cc543b41 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 18:44:23 +0200 Subject: [PATCH 064/371] 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 | 4 ++-- 2 files changed, 9 insertions(+), 8 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 1bdcb4c9a..58a2b964e 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 8258d7749..5e29b7d5a 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 * . From c37895e9be5a0fed5fb5b66bfa0bd2e1769ccd81 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:14:34 +0200 Subject: [PATCH 065/371] Use ImgLib2 Cast util to wrap an ImagePlus in an ImgPlus. --- src/main/java/fiji/plugin/trackmate/util/TMUtils.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index 89b28aaf9..0a203f35e 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -57,7 +57,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; /** @@ -200,12 +200,9 @@ else if ( obj instanceof Logger ) * the image plus to wrap. * @return the ImgPlus wrapping the input. */ - @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 937c10296a3b88e0c7f9aa5d5119072f5dabec46 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:15:38 +0200 Subject: [PATCH 066/371] 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 16afaf00ac65b1bbf231d6251aba7f44abd650ef Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:16:33 +0200 Subject: [PATCH 067/371] 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 9781af4b1ad4778a9c15ca9116b6c010cd0dd905 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:17:30 +0200 Subject: [PATCH 068/371] 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 cc3dabc42..4b547fc4c 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 29e4aed081061c4d8def7fec6fadb3dfdc3c465b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:18:12 +0200 Subject: [PATCH 069/371] 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 4d6cdfd5d..fd3f6abd3 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.LabelRegions; @@ -450,9 +447,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 ); @@ -465,48 +459,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 @@ -716,15 +668,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 ) @@ -733,19 +694,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 b5b96c120818dc754fb83d8fea813d44a42fc265 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:18:34 +0200 Subject: [PATCH 070/371] 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 e5205d53483c3b3741c0e52941e942e92f03cd09 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 4 May 2023 21:18:48 +0200 Subject: [PATCH 071/371] 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 ed60e25860bab1ad51c1fb73f6ae3c5129ab3495 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 10:28:05 +0200 Subject: [PATCH 072/371] 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 fd3f6abd3..118caf53b 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; @@ -60,7 +56,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; @@ -71,14 +66,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 ) @@ -665,46 +656,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; @@ -796,7 +751,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 @@ -1305,16 +1260,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 268107967840f79a9b3269273c23382edee29218 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 10:33:03 +0200 Subject: [PATCH 073/371] 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 a9f676c2e969e1906762c7e71354678fd3d6d5a8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 17:57:12 +0200 Subject: [PATCH 074/371] Rename 2D morphology analyzers. To make room for the 3D analyzers. --- .../java/fiji/plugin/trackmate/Settings.java | 18 ++++++++++++++++-- .../{ConvexHull.java => ConvexHull2D.java} | 2 +- ...yzer.java => Spot2DFitEllipseAnalyzer.java} | 16 ++++++++-------- ...va => Spot2DFitEllipseAnalyzerFactory.java} | 6 +++--- ...va => Spot2DMorphologyAnalyzerFactory.java} | 8 ++++---- ...eAnalyzer.java => Spot2DShapeAnalyzer.java} | 16 ++++++++-------- ...ry.java => Spot2DShapeAnalyzerFactory.java} | 6 +++--- .../descriptors/SpotFilterDescriptor.java | 15 ++++++++------- .../fiji/plugin/trackmate/io/TmXmlReader.java | 8 ++++---- ...a => Spot2DMorphologyAnalyzerProvider.java} | 14 +++++++------- .../SpotFeatureComputationBenchmark.java | 4 ++-- .../interactivetests/TmXmlReaderTestDrive.java | 4 ++-- 12 files changed, 66 insertions(+), 51 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 79e925cd7..10bcc4fa3 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; @@ -532,24 +533,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 b8eac30b9..d89bbaf44 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 39ff2f14a..ca6ef2c20 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 68015185c..9b3685525 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 fe0bb57d5..a26fc22da 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 09ecab5cc..77d57fa8a 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 5a2348904..00be635b3 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 ddd7a4113..5653ea525 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 601bb1ba8..ee72af07d 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; @@ -437,7 +437,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() ) ); } /** @@ -482,7 +482,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 ) @@ -1377,7 +1377,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 970beeeab..5da91ae5f 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 c47651532..02e44e3a6 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 66569aa4b..ad22391b6 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 f83d33784aa5d28ae56acd4f1eae930fd38eb295 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 17:58:28 +0200 Subject: [PATCH 075/371] Add flag for detectors that can return 3D shape, and interfaces for analyzers that can exploit it. --- .../detection/LabelImageDetectorFactory.java | 6 ++ .../detection/MaskDetectorFactory.java | 17 ++---- .../detection/SpotDetectorFactoryBase.java | 20 ++++++- .../detection/ThresholdDetectorFactory.java | 6 ++ .../spot/Spot3DMorphologyAnalyzerFactory.java | 34 +++++++++++ .../descriptors/SpotFilterDescriptor.java | 22 +++++++ .../Spot3DMorphologyAnalyzerProvider.java | 58 +++++++++++++++++++ 7 files changed, 151 insertions(+), 12 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 56af83116..da8ba4abb 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetectorFactory.java @@ -115,6 +115,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 f348555d2..df548f3e3 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -72,12 +72,6 @@ public class MaskDetectorFactory< T extends RealType< T > & NativeType< T > > ex public static final String URL_DOC = "https://imagej.net/plugins/trackmate/detectors/trackmate-mask-detector"; - @Override - public boolean has2Dsegmentation() - { - return true; - } - @Override public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, Object > settings, final Interval interval, final int frame ) { @@ -116,19 +110,20 @@ public void convert( final T input, final T output ) output.setReal( input.getRealDouble() > 0. ? 1. : 0. ); } }; - return Converters.convert( input, c, img.firstElement().createVariable() ); + return Converters.convert( input, c, input.getType() ); } + @Override - public String getKey() + public ConfigurationPanel getDetectorConfigurationPanel( final Settings lSettings, final Model model ) { - return DETECTOR_KEY; + return new MaskDetectorConfigurationPanel( lSettings, model ); } @Override - public ConfigurationPanel getDetectorConfigurationPanel( final Settings lSettings, final Model model ) + public String getKey() { - return new MaskDetectorConfigurationPanel( lSettings, model ); + return DETECTOR_KEY; } @Override diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java index ae4d44083..18a830080 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java @@ -58,7 +58,7 @@ 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. * @@ -69,4 +69,22 @@ 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 3D + * mesh. + */ + public default boolean has3Dsegmentation() + { + return false; + } } diff --git a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java index 3fce94968..58368bda6 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -107,6 +107,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 5653ea525..c35f34dca 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 f6ceb013d6d4a1ca589dd0e5f5952db0272c088e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 20:53:16 +0200 Subject: [PATCH 076/371] Add ops as a dependency. --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index 8c30e9b97..ba50ecb74 100644 --- a/pom.xml +++ b/pom.xml @@ -240,6 +240,10 @@ imagej-mesh-io 0.1.3-SNAPSHOT + + net.imagej + imagej-ops + From 05122b093e07077d70384479301418435f7ca77a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 20:53:30 +0200 Subject: [PATCH 077/371] 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 1ccc61d70..2d66cf25d 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 0a203f35e..04bf3932b 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -615,6 +615,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 c68495f3175417535bba11c20b5328b125adf8d1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 5 May 2023 20:53:57 +0200 Subject: [PATCH 078/371] 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 bf47703ce0e31a17bb7b0a73bc5ae980ab98f712 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 6 May 2023 19:37:48 +0200 Subject: [PATCH 079/371] 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 6b9a43305a3932743f3ef3801b5863b19424097e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 6 May 2023 19:39:01 +0200 Subject: [PATCH 080/371] 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 f0c3dde2664de1fc5f784ba186730a3a47bae105 Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Sun, 7 May 2023 11:52:55 +0200
Subject: [PATCH 081/371] 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 47d6b5c90efedc0704b77547448da8aa31747f33 Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Sun, 7 May 2023 11:53:27 +0200
Subject: [PATCH 082/371] 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 74f15e5f17a8e5e0e42a60581ad0edd2a554e646 Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Sun, 7 May 2023 12:53:00 +0200
Subject: [PATCH 083/371] 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 cf9b91b74d890d7eb7b943338280b02702abfd8b Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Sun, 7 May 2023 12:53:25 +0200
Subject: [PATCH 084/371] 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 eca00a43815ab6fd3104a06c8e58249538050064 Mon Sep 17 00:00:00 2001
From: Jean-Yves TINEVEZ 
Date: Mon, 8 May 2023 21:55:39 +0200
Subject: [PATCH 085/371] 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  |   4 +-
 .../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, 1262 insertions(+), 1207 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 014489c00..06e128586 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 4a72faa5d..784f74da4 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,28 +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 source spot to this spot. - * + * Copy some of the features values of the specified spot to this spot. + * * @param src - * the source spot. + * the spot to copy feature values from. * @param features - * the features to copy. + * 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 ( @@ -448,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; } @@ -476,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; @@ -493,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++ ) @@ -537,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 ] ); } @@ -645,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 fd51115ef..60b8e6673 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() ); } /** @@ -154,36 +182,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 @@ -201,7 +223,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 ); @@ -210,15 +232,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 14a285638..f31879dbe 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; @@ -316,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 ) ); + } } } @@ -411,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 ) ) @@ -448,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/IJRoiExporter.java b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java index aa1924702..fe50d16b1 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 e7ab744c3..fa5b8d5a9 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; @@ -470,8 +469,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 ) ) { @@ -569,7 +567,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 ) ) @@ -627,12 +625,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; } @@ -640,7 +638,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 917243db4..ec9e22128 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 4a2d3b916..ecfd7588b 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; @@ -175,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 22088dd65..b2c08ea1e 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 c9c8e6404..30f252b80 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java @@ -38,6 +38,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; @@ -443,7 +444,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 ); } } @@ -456,7 +457,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 ); } } @@ -469,7 +470,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 ); } @@ -488,7 +489,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 ); } } @@ -501,7 +502,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 ); } } @@ -514,10 +515,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 118caf53b..b43b5b2a1 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; @@ -56,6 +60,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; @@ -306,7 +311,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; @@ -395,7 +400,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; @@ -599,7 +604,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 ) { @@ -1260,4 +1265,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 326232c7d..adc538588 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 96b09c25f..9d84eb4c7 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; @@ -401,7 +402,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 d89bbaf44..5bd427a8c 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 ca6ef2c20..14ae3778c 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 77d57fa8a..0e06403bf 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 90b17377c..786f9712e 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 d3716d8a9..ea3e995bb 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 0cafdde7d..77adbbf22 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 @@ -276,11 +276,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 ae6bdb22c..bfbc94448 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 ee72af07d..112d1d1b6 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; @@ -218,7 +219,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; @@ -953,7 +954,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 )) { @@ -971,8 +972,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 ) { @@ -1213,23 +1219,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 ]; @@ -1244,10 +1242,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 c6801aa3b..a65698ac5 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; @@ -741,9 +742,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(); @@ -766,7 +767,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; @@ -790,10 +791,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 afc71de59..487cf62cb 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; @@ -244,17 +245,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 4159f8cdf..ae617a171 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java @@ -299,19 +299,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 ) ); } @@ -319,20 +319,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 b661a55a1..eaf3a5d3c 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 4b547fc4c..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 - 2026 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 d87505669..1323808f2 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -268,7 +268,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() ) @@ -278,18 +280,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 6c05a893f..34cac6d7c 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 9985f7b26..c111fbfd2 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 906c3961a..ee24ddf1d 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 f64329a91..5ccc8d78a 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 72f9ad84b..ccb2ed689 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 a3115f739..e5a149739 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 5644982d4..07a2b8dab 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 e35c873b0..f1e6ad8ab 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 2689123ff..0fc21667c 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 e81cac2c4..75bc6a444 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 6d2cc1c4e..ff56c78af 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 0a6d0fb44..94d509888 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 4d158f5ad..72283edff 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 4cf4c23dd..2a561a366 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 ea47c0dd8..641af6c30 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 406e3f0df..4e5b1708c 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 15df40465..f8d32ad63 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 a00798d47..692fbb9cb 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 c1d985f3f..8934b5e3b 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 d4572ce3f..151491f5d 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 449152321..d6dcd5241 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 6096f0685..66a290125 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 24109ada6557c937aa4700a67ed2be75824f5cfe Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 22:04:14 +0200 Subject: [PATCH 086/371] 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 be644b415788c3faf7db5134705ce0910a2a5598 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 22:10:22 +0200 Subject: [PATCH 087/371] 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 60b8e6673..cdbb084b0 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 54ebceee64ddc7b4a781bba4fd9407b0a5fa4233 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 22:16:52 +0200 Subject: [PATCH 088/371] Remove unused interfaces. --- .../trackmate/graph/OutputFunction.java | 35 --------------- .../trackmate/graph/StringFormater.java | 43 ------------------- 2 files changed, 78 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 9bc0f3448..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 - 2026 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 9a41935f4..000000000 --- a/src/main/java/fiji/plugin/trackmate/graph/StringFormater.java +++ /dev/null @@ -1,43 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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 > -{ - - /** - * Converts the given instance to a string representation. - * - * @param instance - * the instance to convert. - * @return the string representation. - */ - public String toString( V instance ); - -} From 620ae6062afd152bc53aba592473c640676cc93a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 11 May 2023 16:51:59 +0200 Subject: [PATCH 089/371] 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 b43b5b2a1..26b93093a 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -661,7 +661,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; @@ -1296,6 +1296,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. */ @@ -1303,7 +1305,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 ); @@ -1342,8 +1345,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 ed9624494ee7c3e37b28ffc7046cad4ee2fc3580 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 11 May 2023 17:05:07 +0200 Subject: [PATCH 090/371] 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 2d6c677a8..0db3d952b 100644 --- a/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java +++ b/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java @@ -168,6 +168,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 5a0ed2100facff03b087973e65e7ca0b50f45877 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 12 May 2023 18:30:34 +0200 Subject: [PATCH 091/371] 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, 186 insertions(+), 177 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index cdbb084b0..865f52342 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, @@ -63,7 +63,7 @@ public SpotRoi( * 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 @@ -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,106 +161,77 @@ public double realMax( final int d ) } /** - * 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: * - * @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 spotXCenter - * the X coordinate of the spot center. - * @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. + *

+	 * 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 spotYCenter - * the spot Y center in physical units. - * @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[] 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. + * 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 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. + * 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 ); } @@ -263,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 }; } @@ -280,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 fe50d16b1..177bd5e30 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 5bd427a8c..1fb4d8758 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 14ae3778c..9733b52fa 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 0e06403bf..3cd161a0e 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 a65698ac5..48630c890 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -745,14 +745,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 ae617a171..c8a513289 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java @@ -25,7 +25,6 @@ import java.awt.Color; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -303,9 +302,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 { @@ -322,10 +320,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 607b2514efff7e8bad9f1b5efe03a0ad1d6bde96 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 12 May 2023 18:43:16 +0200 Subject: [PATCH 092/371] 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 c602aa296..92b16e45a 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, displaySettings ); 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 825f74480..62773e219 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 538cf9896ccf557d5bb346e8a04bbbc8e0dd34fd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 12 May 2023 19:11:51 +0200 Subject: [PATCH 093/371] 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 48630c890..8bb13b3f0 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -795,7 +795,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 86887517e15f2500ad2edad7a6da5c45d5397027 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 12 May 2023 20:09:10 +0200 Subject: [PATCH 094/371] 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 11931f03f..6c6e3b96c 100644 --- a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java +++ b/src/main/java/fiji/plugin/trackmate/io/IOUtils.java @@ -617,4 +617,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 26bc029365691187cd3867c9d456b327ce9eb298 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 15:54:32 +0200 Subject: [PATCH 095/371] 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 112d1d1b6..61c820a81 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -979,6 +979,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 a64cff420aef016a5d9bd212e02e9b9944a31858 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 16:01:22 +0200 Subject: [PATCH 096/371] Properly read 3d morphology analyzers declarations. --- .../fiji/plugin/trackmate/io/TmXmlReader.java | 34 ++++++++++++------- .../TmXmlReaderTestDrive.java | 6 ++-- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 61c820a81..f5bd2ec47 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; @@ -438,7 +439,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() ) ); } /** @@ -470,11 +472,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 spotMorphologyAnalyzerProvider - * the spot morphology analyzer provider, required to instantiate - * the saved SpotMorphologyAnalyzers. If null, will - * skip reading spot morphology analyzers. - * @return a new, configured {@link Settings} object. + * @param spot2DMorphologyAnalyzerProvider + * the spot 2D morphology provider. + * @param spot3DMorphologyAnalyzerProvider + * the spot 3D morphology provider. */ public Settings readSettings( final ImagePlus imp, @@ -483,7 +484,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 ) @@ -530,7 +532,8 @@ public Settings readSettings( spotAnalyzerProvider, edgeAnalyzerProvider, trackAnalyzerProvider, - spotMorphologyAnalyzerProvider ); + spot2DMorphologyAnalyzerProvider, + spot3DMorphologyAnalyzerProvider ); return settings; } @@ -1392,7 +1395,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 ); @@ -1443,11 +1447,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 ad22391b6..1c8cc48fd 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 99c3c740f5397be5cf0f5e81a773f9f2dd62cb6a Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 16:06:37 +0200 Subject: [PATCH 097/371] 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 92b16e45a..bc5c4623c 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, displaySettings ); 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 b900ad89d..1466a4b1d 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 67a291b31cac9f14a4359865c38301cb892ea6a9 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 16:21:48 +0200 Subject: [PATCH 098/371] 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 f91674d631730d32d49ce52a4d72a8706d6e937c Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 17:44:10 +0200 Subject: [PATCH 099/371] 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 ade66734986b13af12edb701fe5ef4d67b6cd6c0 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 17:52:31 +0200 Subject: [PATCH 100/371] 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 | 994 ++---------------- .../trackmate/detection/SpotMeshUtils.java | 361 +++++++ .../trackmate/detection/SpotRoiUtils.java | 707 +++++++++++++ .../detection/ThresholdDetector.java | 18 +- 6 files changed, 1246 insertions(+), 923 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 cc5578a19..e93d895d2 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -135,11 +135,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 26b93093a..cece4b3a0 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -21,29 +21,16 @@ */ 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.Cursor; import net.imglib2.Interval; -import net.imglib2.IterableInterval; import net.imglib2.RandomAccess; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; @@ -58,7 +45,6 @@ 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.logic.BitType; import net.imglib2.type.logic.BoolType; import net.imglib2.type.numeric.IntegerType; @@ -71,12 +57,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 @@ -309,7 +289,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 ) ); } @@ -318,9 +298,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 @@ -399,7 +379,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 ) ); } @@ -408,23 +388,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. @@ -434,359 +414,116 @@ 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( + public static < T extends RealType< T >, S extends RealType< S > > List< Spot > fromMaskWithROI( final RandomAccessible< T > input, final Interval interval, final double[] calibration, - final double threshold, 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 ) ); - } - } - } - - /** - * 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 ]; + throw new IllegalArgumentException( "Can only process 2D or 3D images with this method, but got " + input.numDimensions() + "D." ); } - final FloatPolygon simplifiedPolygon = new FloatPolygon( sX, sY ); - final PolygonRoi fRoi = new PolygonRoi( simplifiedPolygon, PolygonRoi.POLYGON ); - return fRoi; } /** @@ -815,567 +552,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 58dfa0337..427290826 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 1ea0c59f783e43cca47140f9bddcd075d35778a8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 15 May 2023 18:06:47 +0200 Subject: [PATCH 101/371] 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 70e5f680cd918a8aca2733a678950b36b606c111 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 9 May 2023 22:33:12 +0200 Subject: [PATCH 102/371] Add ui-behaviour as a dependency. --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index ba50ecb74..4467ead0f 100644 --- a/pom.xml +++ b/pom.xml @@ -285,6 +285,10 @@ org.scijava scijava-listeners + + org.scijava + ui-behaviour + From 34d72644cfdf9b0b2cb5ead094ce3500ab25bd5b Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 9 May 2023 22:33:25 +0200 Subject: [PATCH 103/371] 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 e29747c2c..8a4c69faf 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/Icons.java +++ b/src/main/java/fiji/plugin/trackmate/gui/Icons.java @@ -207,4 +207,8 @@ public class Icons public static final ImageIcon SEGMENTATION_EDITOR_ICON_64x64 = new ImageIcon( Icons.class.getResource( "images/Segmentation-editor-logo_v2-64px.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 104/371] 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 9677da45a26592f9211febfde6da77e346e6cdff Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 9 May 2023 22:36:39 +0200 Subject: [PATCH 105/371] 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 27900492c69e6f5fd0b49f04cbf4b5241edb76a8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 10 May 2023 16:17:20 +0200 Subject: [PATCH 106/371] 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 9834f312e5cca85858f2d59e38de3edfe1771619 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 10 May 2023 16:44:11 +0200 Subject: [PATCH 107/371] 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 3d3287bf4..a7dd90341 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java @@ -32,6 +32,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; @@ -171,10 +173,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 41a5564a14a49b3835d891b928cd2a051294ece5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 10 May 2023 16:44:34 +0200 Subject: [PATCH 108/371] 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 abda53d9624002af42b4f3a335ed3c0c8c7112d1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 10 May 2023 16:57:24 +0200 Subject: [PATCH 109/371] 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 c35f34dca..4b8dd4df5 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 0cb6daf376c81a7731eb9070ad3bea08bbcdb3f5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 18 May 2023 18:19:07 +0200 Subject: [PATCH 110/371] Minor tweak of javadoc and error reporting. --- .../plugin/trackmate/detection/SpotDetector.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetector.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetector.java index d687a2bc3..eb22e109a 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 From 1532786e01ac06c0c0dcd236eb404f2ef5836a45 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 18 May 2023 18:20:01 +0200 Subject: [PATCH 111/371] 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 7e62cf5d29f62c7bd86ea827742e1dd3a7b820ca Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 16:05:43 +0200 Subject: [PATCH 112/371] 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 422b04b062ef4339439f27b8c5930252568e76c7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 16:06:29 +0200 Subject: [PATCH 113/371] 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 4b8dd4df5..dda515b2b 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 2682bef2d8435c987349fcbc94741202223289f6 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:03:26 +0200 Subject: [PATCH 114/371] 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 865f52342..f3cd19719 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 2bb2b648447683590a07c906d28bd726683f18f8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:03:48 +0200 Subject: [PATCH 115/371] 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 784f74da4..9b5d88620 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 29eec470aeea94db60e36ac5a38c4b69cac8f78c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:16:34 +0200 Subject: [PATCH 116/371] 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 7bccdf7af6e57e7f79d80aa92062a8d9373e910c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:27:06 +0200 Subject: [PATCH 117/371] 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 72b5958fad5259546a8b24524202a4a11084c984 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 21:55:39 +0200 Subject: [PATCH 118/371] 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 f3cd19719..1f1bd7170 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 5e29b7d5a..2656e67a9 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java @@ -409,7 +409,6 @@ public void keyPressed( final KeyEvent e ) break; } } - } @Override From 1aa59a095387dd99105022c95f30be06e4f6c0c3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:38:23 +0200 Subject: [PATCH 119/371] 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 04bf3932b..5c1a6d7f1 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -57,7 +57,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; /** @@ -202,7 +201,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 706a1257a42ef85d08e0cf8c11477814bfab6459 Mon Sep 17 00:00:00 2001 From: tpietzsch Date: Tue, 18 Apr 2023 15:54:48 -0500 Subject: [PATCH 120/371] 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 4467ead0f..5ffcc4232 100644 --- a/pom.xml +++ b/pom.xml @@ -244,6 +244,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 a260cad3a5ecb331794ffe3e9d94a00f2ffcc63c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 21 May 2023 18:44:28 +0200 Subject: [PATCH 121/371] Add jogl deps. --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 5ffcc4232..7f4debece 100644 --- a/pom.xml +++ b/pom.xml @@ -249,6 +249,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 cc1edfd1f43cbcefba96aa5fdd40dcbe50534fd2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 21 May 2023 18:49:02 +0200 Subject: [PATCH 122/371] 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 c59ffef535f48089d7e9108faf72e229569e942d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 May 2023 15:29:42 +0200 Subject: [PATCH 123/371] 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 f96c4a4b69077c73b13b2e47de76b6b5f7518ae6 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 May 2023 15:04:17 +0200 Subject: [PATCH 124/371] 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 617c2fd09a0bd34b44fe751235b145b30f27464d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 May 2023 15:05:50 +0200 Subject: [PATCH 125/371] 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 885615ad9bbd3c2f43ae59b51af0af6f80aa9e60 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 May 2023 18:12:28 +0200 Subject: [PATCH 126/371] 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 f94d305b0266b50a602dda28220550ba02304fa3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 May 2023 18:12:53 +0200 Subject: [PATCH 127/371] 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 20265b2a19d5ed684cf6096d8ca72345dea7ea0f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 24 May 2023 13:34:40 +0200 Subject: [PATCH 128/371] 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 f5bd2ec47..8947cb87c 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -988,7 +988,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 a87b60513dcd8c954d5bd20c4e4a63c0c9cc82be Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 24 May 2023 13:35:44 +0200 Subject: [PATCH 129/371] 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 c8a513289..eca36b32a 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTracker.java @@ -204,7 +204,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 97fdb8b5810cca5c55ecda1db92a614eed16cf08 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 2 Jun 2023 15:51:15 +0200 Subject: [PATCH 130/371] 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 6738b298dbb9d233ba693b02b8f18eadeeb582a5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 30 May 2023 17:01:25 +0200 Subject: [PATCH 131/371] 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 1d615a9130f504735b3c3d20f60002b47e876700 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 2 Jun 2023 12:08:29 +0200 Subject: [PATCH 132/371] 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 41cf2cb97f4180e2ea4adb417c3bf777db3ac40d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 2 Jun 2023 16:32:25 +0200 Subject: [PATCH 133/371] 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 5979ad371b3e4fd8a375a0c74f126d2adce2133f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 2 Jun 2023 16:50:38 +0200 Subject: [PATCH 134/371] 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 c463b8e08920b1e61321de0361e54ab761e3d06f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 3 Jun 2023 16:06:41 +0200 Subject: [PATCH 135/371] Add a button to launch a 3D view on the GUI. --- .../java/fiji/plugin/trackmate/gui/Icons.java | 1 + .../gui/components/ConfigureViewsPanel.java | 6 +++ .../gui/wizard/TrackMateWizardSequence.java | 37 ++++++++++++++++++ .../descriptors/ConfigureViewsDescriptor.java | 10 +++-- .../gui/images/TrackMateBVV-logo-16x16.png | Bin 0 -> 3527 bytes 5 files changed, 50 insertions(+), 4 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 8a4c69faf..328d45f19 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/Icons.java +++ b/src/main/java/fiji/plugin/trackmate/gui/Icons.java @@ -211,4 +211,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 e2184cfbf..79ef04f49 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -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, @@ -358,6 +359,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 bc5c4623c..a4c6c9584 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -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(), @@ -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 4af33ba7e..c33ec8fa3 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 785ea9c8f07e0dfbae34c286582416e5fb8fbf82 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 6 Jun 2023 15:33:18 +0200 Subject: [PATCH 136/371] 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 8746328b4ddd6de5a7580501ff26b517b77a34cd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 6 Jun 2023 16:55:18 +0200 Subject: [PATCH 137/371] 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 425db020e7a636265f9876935260f04cd50d66d3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 7 Jun 2023 18:36:39 +0200 Subject: [PATCH 138/371] 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 9b5d88620..2878f257b 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 47b6e77ea4fd1145da3929cc1e4b407467f92b23 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 7 Jun 2023 18:36:52 +0200 Subject: [PATCH 139/371] 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 4fafc652db09a4105a936c2008133dd9db5d12cd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 7 Jun 2023 18:37:39 +0200 Subject: [PATCH 140/371] 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 95557bdeeb7bd3f112fcf0634574be88dc544f63 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 8 Jun 2023 18:45:38 +0200 Subject: [PATCH 141/371] 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 a4c6c9584..ead589172 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 e5d9f5163f065b7a891a8c762e9869c653ad32b9 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:39:13 +0200 Subject: [PATCH 142/371] 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 a0d0bf53506c55f54916f5ae69038499b4adfc26 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:39:29 +0200 Subject: [PATCH 143/371] 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 b30793a97013703c6a391403fc654e63b861a59f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:39:48 +0200 Subject: [PATCH 144/371] 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 ffabe8810a37836dad89eb4b837430eb115ef5f0 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:43:37 +0200 Subject: [PATCH 145/371] 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 ead589172..1bab95e55 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 d992fb977ac7c848a8ddadb720afef39547866fe Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:44:06 +0200 Subject: [PATCH 146/371] 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 7148c33eeb22c1aaaf0ee639f061f717ddb4ffbe Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:44:25 +0200 Subject: [PATCH 147/371] 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 b5f78b825fb5c0f12458ca2cb28d82ede4447199 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 9 Jun 2023 15:44:44 +0200 Subject: [PATCH 148/371] 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 47d0429242bf51e08cd96e5a74e28323e5373f26 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Jul 2023 18:10:14 +0200 Subject: [PATCH 149/371] 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 7f4debece..a32c21aa9 100644 --- a/pom.xml +++ b/pom.xml @@ -247,7 +247,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 1bab95e55..f86c1d3a8 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 812c2ab3cd76100ae918d9bd7534ffaf83877667 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Jul 2023 18:34:28 +0200 Subject: [PATCH 150/371] 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 2e5433227b5154e97a6022f8d03ebcf1d2f71a3f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 11 Sep 2023 17:40:50 +0200 Subject: [PATCH 151/371] 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 5c1a6d7f1..2e1aedb56 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -199,9 +199,10 @@ else if ( obj instanceof Logger ) * the image plus to wrap. * @return the ImgPlus wrapping the input. */ + @SuppressWarnings( "unchecked" ) public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) { - return ImagePlusAdapter.wrapImgPlus( imp ); + return ( ImgPlus< T > ) ImagePlusAdapter.wrapImgPlus( imp ); } /** From 52938c881727247ff7b40bdf24c13853c3ecabda Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 8 May 2023 21:55:39 +0200 Subject: [PATCH 152/371] 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 58a2b964e..1bd4224d0 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 cc2cf0ccc9b41920b891e21bc4a3fa426e8f560a Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sat, 13 May 2023 17:52:31 +0200 Subject: [PATCH 153/371] 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 cece4b3a0..c80e42e10 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -415,11 +415,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 7b72c8b7bbd243534068e3a166111139506a0575 Mon Sep 17 00:00:00 2001 From: tpietzsch Date: Tue, 18 Apr 2023 15:54:48 -0500 Subject: [PATCH 154/371] 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 395834aef2a39b25611e74c77a55c8af518ff3eb Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 1 Sep 2023 10:44:23 +0200 Subject: [PATCH 155/371] 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 | 14 +- .../java/fiji/plugin/trackmate/SpotMesh.java | 38 +-- .../trackmate/action/MeshSeriesExporter.java | 20 +- .../trackmate/detection/Process2DZ.java | 13 +- .../trackmate/detection/SpotMeshUtils.java | 18 +- .../spot/Spot3DFitEllipsoidAnalyzer.java | 8 +- .../features/spot/Spot3DShapeAnalyzer.java | 20 +- .../fiji/plugin/trackmate/io/TmXmlReader.java | 14 +- .../fiji/plugin/trackmate/io/TmXmlWriter.java | 10 +- .../trackmate/util/mesh/EllipsoidFitter.java | 310 ------------------ .../trackmate/util/mesh/SpotMeshCursor.java | 2 +- .../trackmate/visualization/bvv/BVVUtils.java | 10 +- .../visualization/bvv/Icosahedron.java | 18 +- .../visualization/bvv/StupidMesh.java | 12 +- .../hyperstack/ModelEditActions.java | 17 +- .../hyperstack/PaintSpotMesh.java | 4 +- .../plugin/trackmate/mesh/DebugZSlicer.java | 6 +- .../plugin/trackmate/mesh/DefaultMesh.java | 2 +- .../plugin/trackmate/mesh/Demo3DMesh.java | 29 +- .../trackmate/mesh/ExportMeshForDemo.java | 5 +- .../plugin/trackmate/mesh/MeshPlayground.java | 16 +- .../trackmate/mesh/TestEllipsoidFit.java | 12 +- 22 files changed, 130 insertions(+), 468 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/util/mesh/EllipsoidFitter.java diff --git a/pom.xml b/pom.xml index a32c21aa9..345d91843 100644 --- a/pom.xml +++ b/pom.xml @@ -232,13 +232,8 @@ imagej-common - net.imagej - imagej-mesh - - - net.imagej - imagej-mesh-io - 0.1.3-SNAPSHOT + net.imglib2 + imglib2-mesh net.imagej @@ -247,7 +242,6 @@ sc.fiji bigvolumeviewer - 0.3.1 org.jogamp.jogl @@ -306,6 +300,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..b62b5cf32 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -6,21 +6,20 @@ 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.Mesh; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.Triangles; +import net.imglib2.mesh.Vertices; +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.impl.nio.BufferMesh; import net.imglib2.type.numeric.RealType; -import net.imglib2.util.Intervals; public class SpotMesh extends SpotBase { @@ -47,7 +46,7 @@ public SpotMesh( /** * Creates a new spot from the specified mesh. Its position and radius are * calculated from the mesh. - * + * * @param quality * @param name * @param m @@ -61,7 +60,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; @@ -84,14 +83,14 @@ 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 ); } /** * 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 */ @@ -118,14 +117,14 @@ 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 ); } /** * 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() @@ -283,13 +282,13 @@ 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 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() ); } @@ -388,9 +387,4 @@ private static final Map< Integer, Slice > buildSliceMap( return sliceMap; } - - 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/action/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java index abe3a263c..bd7354e12 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.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 * . @@ -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.Mesh; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.impl.nio.BufferMesh; +import net.imglib2.mesh.io.ply.PLYMeshIO; +import net.imglib2.mesh.view.TranslateMesh; public class MeshSeriesExporter extends AbstractTMAction { @@ -105,8 +105,6 @@ public static void exportMeshesToFileSeries( final SpotCollection spots, final F 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 ) @@ -124,11 +122,11 @@ 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 { - 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..ccca0a0e1 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -11,17 +11,18 @@ 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; -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.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; @@ -36,7 +37,7 @@ * 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 @@ -62,7 +63,7 @@ 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. @@ -135,7 +136,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 diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index dc8683583..92f448a46 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.Mesh; +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; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; @@ -30,7 +30,7 @@ /** * 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 @@ -109,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. @@ -262,7 +262,7 @@ private static < S extends RealType< S > > Spot regionToSpotMesh( /** * Creates a {@link SpotMesh} from a {@link Mesh}. - * + * * @param * the type of the quality image. * @param mesh @@ -294,7 +294,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; 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..b9161db59 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.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 ); @@ -88,7 +88,7 @@ else if ( drAB < SHAPE_CLASS_TOLERANCE ) } else - { + { // Assume plain sphere. x0 = 0.; y0 = 0.; 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..9472454d8 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,8 @@ 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.alg.hull.ConvexHull; +import net.imglib2.mesh.impl.naive.NaiveDoubleMesh; import net.imglib2.type.numeric.RealType; public class Spot3DShapeAnalyzer< T extends RealType< T > > extends AbstractSpotFeatureAnalyzer< T > @@ -34,15 +32,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 +50,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 8947cb87c..6bf80a4ab 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.Mesh; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.impl.nio.BufferMesh; +import net.imglib2.mesh.io.ply.PLYMeshIO; public class TmXmlReader { @@ -972,8 +972,8 @@ private SpotCollection getSpots( final Element modelElement ) // Deserialize mesh. try { - final Mesh m = PLY_MESH_IO.open( zipFile.getInputStream( entry ) ); - final BufferMesh mesh = new BufferMesh( ( int ) m.vertices().size(), ( int ) m.triangles().size() ); + final Mesh m = PLYMeshIO.open( zipFile.getInputStream( entry ) ); + 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 @@ -1227,7 +1227,7 @@ private Spot createSpotFrom( final Element spotEl ) { // Read id. final int ID = readIntAttribute( spotEl, SPOT_ID_ATTRIBUTE_NAME, logger ); -// +// final List< Attribute > atts = spotEl.getAttributes(); removeAttributeFromName( atts, SPOT_ID_ATTRIBUTE_NAME ); diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java index 8bb13b3f0..c8bfffe50 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.Mesh; +import net.imglib2.mesh.io.ply.PLYMeshIO; +import net.imglib2.mesh.view.TranslateMesh; public class TmXmlWriter { - static final PLYMeshIO PLY_MESH_IO = new PLYMeshIO(); - static final String MESH_FILE_EXTENSION = ".meshes"; /** Zip compression level (0-9) */ @@ -797,7 +795,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..16b22edc7 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.Mesh;
+import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.impl.nio.BufferMesh;
+import net.imglib2.mesh.view.TranslateMesh;
 import net.imglib2.type.Type;
 import net.imglib2.type.numeric.ARGBType;
 
@@ -30,7 +30,7 @@ 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 );
 		}
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..1b19c3a4b 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/Icosahedron.java
@@ -1,18 +1,18 @@
 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.Mesh;
+import net.imglib2.mesh.Meshes;
+import net.imglib2.mesh.Triangle;
+import net.imglib2.mesh.impl.naive.NaiveDoubleMesh;
+import net.imglib2.mesh.impl.nio.BufferMesh;
 
 /**
  * Icosahedron spheres.
  * 

* Based on https://github.com/caosdoar/spheres - * + * * @author Jean-Yves Tinevez */ public class Icosahedron @@ -71,8 +71,8 @@ public static final Mesh core() 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 int nVerticesOut = 6 * core.triangles().size(); + final int nTrianglesOut = 4 * core.triangles().size(); final BufferMesh out = new BufferMesh( nVerticesOut, nTrianglesOut ); final double[] tmpIn = new double[ 3 ]; @@ -127,7 +127,7 @@ public static BufferMesh sphere( final RealLocalizable center, final double radi mesh = refine( mesh ); scale( mesh, center, radius ); - final BufferMesh out = new BufferMesh( ( int ) mesh.vertices().size(), ( int ) mesh.triangles().size() ); + final BufferMesh out = new BufferMesh( mesh.vertices().size(), mesh.triangles().size() ); Meshes.calculateNormals( mesh, out ); return out; } 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..7009eae4d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/StupidMesh.java @@ -6,13 +6,13 @@ * %% * 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 @@ -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.impl.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; @@ -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/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java index 1bd4224d0..06e9a15e6 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.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 * . @@ -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; @@ -294,16 +293,12 @@ public void changeSpotRadius( final boolean increase, final boolean fast ) // 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 + // Scale spot + target.putFeature( Spot.RADIUS, newRadius ); + if ( !( target instanceof SpotBase ) ) { final double alpha = newRadius / radius; - shape.scale( alpha ); - target.putFeature( Spot.RADIUS, shape.radius() ); + target.scale( alpha ); } model.beginUpdate(); 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..cc2f76a3d 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.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..018f34d75 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -14,21 +14,21 @@ 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.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.impl.naive.NaiveDoubleMesh.Triangles; +import net.imglib2.mesh.io.ply.PLYMeshIO; +import net.imglib2.mesh.io.stl.STLMeshIO; import net.imglib2.roi.labeling.ImgLabeling; import net.imglib2.roi.labeling.LabelRegion; import net.imglib2.roi.labeling.LabelRegions; @@ -112,7 +112,7 @@ 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 net.imglib2.mesh.impl.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices(); final Triangles triangles = mesh.triangles(); // Coords as X Y Z @@ -198,11 +198,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..22b3f4d58 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.Mesh; +import net.imglib2.mesh.Meshes; +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; @@ -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 ) @@ -107,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 c5fabdd62..cd7c0fbf6 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.Mesh; +import net.imglib2.mesh.alg.EllipsoidFitter; +import net.imglib2.mesh.alg.EllipsoidFitter.Ellipsoid; +import net.imglib2.mesh.impl.naive.NaiveDoubleMesh; public class TestEllipsoidFit { @@ -17,13 +17,13 @@ public class TestEllipsoidFit 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 Ellipsoid fit = EllipsoidFitter.fit( mesh ); final double[] arr = new double[ 3 ]; From 4be3e4697f2c0231b853ba04e9d4ca5f43b99388 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 12 Sep 2023 11:21:14 +0200 Subject: [PATCH 156/371] Use the scale method of the spot interface. We don't have SpotShape anymore. --- .../visualization/hyperstack/ModelEditActions.java | 9 +++------ 1 file changed, 3 insertions(+), 6 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 06e9a15e6..45cb65ebb 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java @@ -286,20 +286,17 @@ 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; + // Actually scale the spot. + target.scale( radius / newRadius ); + // Scale spot target.putFeature( Spot.RADIUS, newRadius ); - if ( !( target instanceof SpotBase ) ) - { - final double alpha = newRadius / radius; - target.scale( alpha ); - } model.beginUpdate(); try From 1c96be7621bd6ab56f6b49e2cba6bb5cbddcfa72 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 12 Sep 2023 11:50:34 +0200 Subject: [PATCH 157/371] 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 f86c1d3a8..189bde70a 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 619a43d5be8535d948ef8085420f623aa95d0dce Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 12 Sep 2023 11:51:10 +0200 Subject: [PATCH 158/371] 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 89ed861556eaadaf7e68ddfd527948929247654d Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 12 Sep 2023 11:51:30 +0200 Subject: [PATCH 159/371] 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 82b63639ea5ed4d8a69691f508b95fd5f54c9395 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 19 Sep 2023 15:06:48 +0200 Subject: [PATCH 160/371] Use the new imglib2-mesh master branch in the imglib2 org. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 52 +++---------------- .../trackmate/detection/SpotMeshUtils.java | 5 +- .../features/spot/Spot3DShapeAnalyzer.java | 7 +-- .../trackmate/visualization/bvv/BVVUtils.java | 3 +- .../plugin/trackmate/mesh/Demo3DMesh.java | 3 +- 5 files changed, 16 insertions(+), 54 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index b62b5cf32..b998af328 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -12,8 +12,8 @@ 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.Triangles; import net.imglib2.mesh.Vertices; import net.imglib2.mesh.alg.zslicer.RamerDouglasPeucker; import net.imglib2.mesh.alg.zslicer.Slice; @@ -194,47 +194,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 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 +209,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++ ) { @@ -303,14 +263,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 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/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 92f448a46..4af121bfc 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java @@ -13,6 +13,7 @@ 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; @@ -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/Spot3DShapeAnalyzer.java b/src/main/java/fiji/plugin/trackmate/features/spot/Spot3DShapeAnalyzer.java index 9472454d8..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,6 +23,7 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; +import net.imglib2.mesh.MeshStats; import net.imglib2.mesh.alg.hull.ConvexHull; import net.imglib2.mesh.impl.naive.NaiveDoubleMesh; import net.imglib2.type.numeric.RealType; @@ -52,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/visualization/bvv/BVVUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java index 16b22edc7..331367a62 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -17,6 +17,7 @@ import net.imglib2.mesh.Mesh; import net.imglib2.mesh.Meshes; 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; @@ -34,7 +35,7 @@ public static final StupidMesh createMesh( final Spot spot ) 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/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java index 018f34d75..7e7cdf5fb 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/Demo3DMesh.java @@ -26,7 +26,6 @@ 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.impl.naive.NaiveDoubleMesh.Triangles; import net.imglib2.mesh.io.ply.PLYMeshIO; import net.imglib2.mesh.io.stl.STLMeshIO; import net.imglib2.roi.labeling.ImgLabeling; @@ -113,7 +112,7 @@ static Mesh debugMesh( final long[] min, final long[] max ) { final NaiveDoubleMesh mesh = new NaiveDoubleMesh(); final net.imglib2.mesh.impl.naive.NaiveDoubleMesh.Vertices vertices = mesh.vertices(); - final Triangles triangles = mesh.triangles(); + final net.imglib2.mesh.impl.naive.NaiveDoubleMesh.Triangles triangles = mesh.triangles(); // Coords as X Y Z From 9aa5b4a3edab0d35b978f71504179d3ff5600ac3 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 16:37:43 +0200 Subject: [PATCH 161/371] Abide to recent changes in imglib2-mesh. --- .../plugin/trackmate/detection/SpotMeshUtils.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index 4af121bfc..f823d8d50 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; @@ -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 0f00eb2f2cf9e90060d2c39a33c0bb9ad2aa9259 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 18:21:01 +0200 Subject: [PATCH 162/371] Fix all javadoc warnings. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 9 +- .../java/fiji/plugin/trackmate/SpotRoi.java | 3 + .../plugin/trackmate/TrackMatePlugIn.java | 20 +-- .../plugin/trackmate/action/CTCExporter.java | 20 +-- .../action/CaptureOverlayAction.java | 4 +- .../trackmate/action/LabelImgExporter.java | 77 ++++----- .../trackmate/action/TrackMateAction.java | 6 +- .../action/closegaps/GapClosingMethod.java | 18 +- .../trackmate/action/fit/SpotFitterPanel.java | 8 +- .../trackmate/detection/DetectionUtils.java | 30 ++-- .../plugin/trackmate/detection/MaskUtils.java | 14 +- .../trackmate/detection/Process2DZ.java | 1 + .../detection/SpotDetectorFactory.java | 13 +- .../detection/SpotDetectorFactoryBase.java | 4 +- .../detection/SpotGlobalDetectorFactory.java | 13 +- .../trackmate/detection/SpotMeshUtils.java | 2 +- .../trackmate/features/FeatureAnalyzer.java | 16 +- .../trackmate/features/FeatureUtils.java | 19 ++- .../features/SpotFeatureCalculator.java | 8 +- .../features/TrackFeatureCalculator.java | 9 +- .../features/edges/EdgeAnalyzer.java | 8 +- .../spot/Spot2DFitEllipseAnalyzer.java | 13 +- .../spot/SpotAnalyzerFactoryBase.java | 8 +- .../features/track/TrackAnalyzer.java | 6 +- .../plugin/trackmate/graph/GraphUtils.java | 43 +++-- .../graph/SortedDepthFirstIterator.java | 27 +-- .../fiji/plugin/trackmate/gui/GuiUtils.java | 14 +- .../gui/components/ConfigurationPanel.java | 15 +- .../components/FeatureDisplaySelector.java | 10 +- .../gui/components/FilterGuiPanel.java | 27 +-- .../trackmate/gui/components/FilterPanel.java | 4 +- .../gui/components/InitFilterPanel.java | 14 +- .../tracker/JPanelFeatureSelectionGui.java | 10 +- .../gui/displaysettings/Colormap.java | 39 +++-- .../gui/displaysettings/SliderPanel.java | 8 +- .../displaysettings/SliderPanelDouble.java | 4 +- .../featureselector/AnalyzerSelection.java | 1 + .../trackmate/gui/wizard/WizardSequence.java | 12 +- .../fiji/plugin/trackmate/io/IOUtils.java | 37 ++-- .../fiji/plugin/trackmate/io/TmXmlReader.java | 31 ++-- .../fiji/plugin/trackmate/io/TmXmlWriter.java | 18 +- .../tracking/SpotTrackerFactory.java | 7 +- .../trackmate/tracking/jaqaman/LAPUtils.java | 69 ++++---- .../costmatrix/DefaultCostMatrixCreator.java | 10 +- .../JaqamanLinkingCostMatrixCreator.java | 12 +- .../JaqamanSegmentCostMatrixCreator.java | 12 +- .../jaqaman/costmatrix/SparseCostMatrix.java | 30 ++-- .../tracking/kalman/KalmanTracker.java | 17 +- .../plugin/trackmate/util/ChartExporter.java | 14 +- .../trackmate/util/OnRequestUpdater.java | 10 +- .../util/SpotNeighborhoodCursor.java | 20 +-- .../fiji/plugin/trackmate/util/TMUtils.java | 158 +++++++----------- .../visualization/TrackMateModelView.java | 8 +- .../hyperstack/HyperStackDisplayer.java | 12 +- .../hyperstack/SpotEditTool.java | 22 +-- .../visualization/hyperstack/SpotOverlay.java | 11 +- .../hyperstack/TrackMatePainter.java | 5 +- .../visualization/trackscheme/SaveAction.java | 10 +- .../trackscheme/SpotIconGrabber.java | 8 +- .../trackscheme/SpotImageUpdater.java | 4 +- .../trackscheme/TrackScheme.java | 27 +-- .../TrackSchemeKeyboardHandler.java | 4 +- 62 files changed, 538 insertions(+), 575 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index b998af328..1a906b744 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -48,8 +48,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, @@ -92,7 +95,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 ) { @@ -189,7 +194,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 1f1bd7170..c160c5281 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 a7dd90341..41c494a3b 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -130,11 +130,11 @@ else if ( imp.getType() == ImagePlus.COLOR_RGB ) * launched by this plugin. * * @param trackmate - * the {@link TrackMate} instance to use. + * the TrackMate instance. * @param selectionModel - * the {@link SelectionModel} to use. + * the selection model. * @param displaySettings - * the {@link DisplaySettings} to use. + * the display settings. * @return a new sequence. */ protected WizardSequence createSequence( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings ) @@ -148,7 +148,7 @@ protected WizardSequence createSequence( final TrackMate trackmate, final Select * {@link TrackMate} instance. * * @param imp - * the {@link ImagePlus} to operate on. + * the image the tracking data will be created on. * @return a new {@link Model} instance. */ protected Model createModel( final ImagePlus imp ) @@ -183,11 +183,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 use. + * the model to create the TrackMate instance with. * @param settings - * the settings to use. + * the settings to create the TrackMate instance with. * @return a new {@link TrackMate} instance. */ protected TrackMate createTrackMate( final Model model, final Settings settings ) diff --git a/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java b/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java index f31879dbe..e532311a2 100644 --- a/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/CTCExporter.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -78,7 +78,7 @@ * Cell-Tracking-Challenge convention. *

* See http://celltrackingchallenge.net/ - * + * * @author Jean-Yves Tinevez * */ @@ -174,7 +174,7 @@ public static String exportAll( final String exportRootFolder, final TrackMate t /** * Saves the settings part as XML for reference. - * + * * @param exportRootFolder * the root folder for exporting. * @param saveId @@ -185,7 +185,7 @@ public static String exportAll( final String exportRootFolder, final TrackMate t * @param logger * a logger to report progress. * @throws IOException - * if a problem happens during writing. + * if there's any problem writing. */ public static void exportSettingsFile( final String exportRootFolder, final int saveId, final TrackMate trackmate, final Logger logger ) throws IOException { @@ -205,7 +205,7 @@ public static void exportSettingsFile( final String exportRootFolder, final int * For instance the first id return will be '1', which means that the * original image data will be saved under the folder '01'. If '01' already * exists, then this method will return 2, etc. - * + * * @param exportRootFolder * the root folder in which to export the data. * @return an integer id that can be passed in the other method of this @@ -272,7 +272,7 @@ public static void exportOriginalImageData( final String exportRootFolder, final *

* Only exports the spots that have a ROI, and write only the frames that * have at least one spot with a ROI. - * + * * @param exportRootFolder * the root of the export folder. * @param saveId @@ -342,7 +342,7 @@ public static void exportSegmentationData( final String exportRootFolder, final final Function< Long, String > tifNameGen = nFrames > 999 ? i -> String.format( "man_seg%04d.tif", i ) : i -> String.format( "man_seg%03d.tif", i ); - + // Only save frames with spots in. for ( final int frame : framesToWrite ) { @@ -512,7 +512,7 @@ public static String exportTrackingData( final String exportRootFolder, final in /** * Returns the folder in which the tracking data will be exported. - * + * * @param exportRootFolder * the root folder to export in. * @param saveId @@ -563,7 +563,7 @@ private static final Model sanitizeAndCopy( final Model model ) final Spot s2 = spots.get( j ); final double r2 = s2.getFeature( Spot.RADIUS ).doubleValue(); final double d = Math.sqrt( s1.squareDistanceTo( s2 ) ); - + if ( fudgeFactor * r1 > ( d + r2 ) || fudgeFactor * r2 > ( d + r1 ) ) { // They overlap too much. We must fix this. @@ -613,7 +613,7 @@ private static final Model sanitizeAndCopy( final Model model ) else sources.add( trackModel.getEdgeSource( edge ) ); } - + model.beginUpdate(); try { diff --git a/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java b/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java index 1361f375c..87e939a33 100644 --- a/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -154,7 +154,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo * @param last * the last frame, inclusive, to capture. * @param logger - * a {@link Logger} to report capture progress. + * 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 fa5b8d5a9..38add12ec 100644 --- a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -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 @@ -211,7 +211,7 @@ public static final ImagePlus createLabelImagePlus( * different from the track IDs and different for each spot. * @param labelIdPainting * specifies how to paint the label ID of spots. - * + * * @return a new {@link ImagePlus}. */ public static final ImagePlus createLabelImagePlus( @@ -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 @@ -285,12 +285,11 @@ public static final ImagePlus createLabelImagePlus( * nZSlices, nFrames) as a 4 element long array. Spots outside * these dimensions are ignored. * @param calibration - * the desired calibration of the output image (pixel width, - * pixel height, pixel depth, frame interval) as a 4 element - * double array. + * 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 @@ -298,7 +297,7 @@ public static final ImagePlus createLabelImagePlus( * different from the track IDs and different for each spot. * @param labelIdPainting * specifies how to paint the label ID of spots. - * + * * @return a new {@link ImagePlus}. */ public static final ImagePlus createLabelImagePlus( @@ -323,12 +322,11 @@ public static final ImagePlus createLabelImagePlus( * nZSlices, nFrames) as a 4 element int array. Spots outside * these dimensions are ignored. * @param calibration - * the desired calibration of the output image (pixel width, - * pixel height, pixel depth, frame interval) as a 4 element - * double array. + * 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 @@ -375,12 +373,11 @@ public static final ImagePlus createLabelImagePlus( * nZSlices, nFrames) as a 4 element long array. Spots outside * these dimensions are ignored. * @param calibration - * the desired calibration of the output image (pixel width, - * pixel height, pixel depth, frame interval) as a 4 element - * double array. + * 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 @@ -388,7 +385,7 @@ public static final ImagePlus createLabelImagePlus( * different from the track IDs and different for each spot. * @param labelIdPainting * specifies how to paint the label ID of spots. - * + * * @return a new {@link Img}. */ public static final Img< FloatType > createLabelImg( @@ -413,12 +410,11 @@ public static final Img< FloatType > createLabelImg( * nZSlices, nFrames) as a 4 element long array. Spots outside * these dimensions are ignored. * @param calibration - * the desired calibration of the output image (pixel width, - * pixel height, pixel depth, frame interval) as a 4 element - * double. + * 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 @@ -485,34 +481,27 @@ public static final Img< FloatType > createLabelImg( /** - * Creates a new label {@link ImgPlus} of specified pixel type where the - * spots are painted with an ID. All visible spots are painted, whether they - * are in a track or not. - * + * Creates a new label {@link ImgPlus} of {@link FloatType} where the spots + * are painted with an ID. All visible spots are painted, whether they are + * in a track or not. + * * @param spots * the spots to paint. * @param dimensions * 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 desired calibration of the output image (pixel width, - * pixel height, pixel depth, frame interval) as a 4 element - * double array. * @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 * defaults to {@link LabelIdPainting#LABEL_IS_SPOT_ID}. - * @param outputType - * the output pixel type. * @param logger * a {@link Logger} instance, to report progress of the export * process. - * @param - * the pixel type of the output image. + * * @return a new {@link ImgPlus}. */ public static < T extends RealType< T > & NativeType< T > > ImgPlus< T > createLabelImg( diff --git a/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java b/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java index 6408dc6a1..d9c6dc335 100644 --- a/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -54,9 +54,9 @@ public interface TrackMateAction /** * Sets the logger that will receive logs when this action is executed. - * + * * @param logger - * the logger to use. + * 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 ecfd7588b..90e73da00 100644 --- a/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java +++ b/src/main/java/fiji/plugin/trackmate/action/closegaps/GapClosingMethod.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -68,7 +68,7 @@ public GapClosingParameter( final String name, final double value, final double * Returns the list of parameters required to configure this method. *

* The list will be used to autogenerate a configuration panel. - * + * * @return a list of parameters. */ public default List< GapClosingParameter > getParameters() @@ -86,11 +86,11 @@ public default List< GapClosingParameter > getParameters() /** * Performs the gap closing. - * + * * @param trackmate - * the TrackMate instance to operate on. + * the trackmate instance to operate on. * @param logger - * a logger to log messages to. + * a logger instance to echoes the gap-closing process. */ public void execute( TrackMate trackmate, Logger logger ); @@ -101,7 +101,7 @@ public default List< GapClosingParameter > getParameters() * frames within a track. Gaps are returned as a list of edges. Each edge in * this list has a source spot and a target spot separated by strictly more * than 1 frame. - * + * * @param model * the model to search for gaps. * @return a list of edges corresponding to gaps. @@ -142,7 +142,7 @@ public static List< DefaultWeightedEdge > getAllGaps( final Model model ) * linked be edges. The spots are added in time order, from the frame * just after the source spot, to the frame just before the target spot. The * source and target spot are not in the list. - * + * * @param model * the model. * @param edge @@ -162,7 +162,7 @@ public static List< Spot > interpolate( final Model model, final DefaultWeighted final double[] tPos = new double[ 3 ]; target.localize( tPos ); final int tt = target.getFeature( Spot.FRAME ).intValue(); - + final List< Spot > interpolatedSpots = new ArrayList<>( Math.abs( tt - st ) - 1 ); final int presign = tt > st ? 1 : -1; @@ -203,7 +203,7 @@ static void interpolateFeature( final Spot targetSpot, final Spot spot1, final S * but configured with a small ROI centered on the specified spot, with a * radius proportional to the radius of the specified spot, and set to * operate only on the frame in which the specified spot it. - * + * * @param spot * the spot to read the coordinates and the frame from. * @param neighborhoodFactor 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 eca2cc43f..38c20c166 100644 --- a/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterPanel.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -200,9 +200,9 @@ public SpotFitterPanel( final List< String > fits, final List< String > docs, fi } /** - * Get the selected channel from the slider. 1-based. - * - * @return the selected channel index. + * Returns the selected channel. 1-based. + * + * @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 30f252b80..4c54f41fc 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -295,11 +295,11 @@ public static final Img< FloatType > createLoGKernel( final double radius, final * the interval in the source image to copy. * @param factory * a factory used to build the float image. - * @param - * the pixel type of the source image. * @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 ) { @@ -353,18 +353,19 @@ public static final Interval squeeze( final Interval interval ) /** * Applies a simple 3x3 median filter to the target image. - * - * @param image - * the input image. + * * @param - * the pixel type of the input and output images. - * @return a new image, or null if the filtering failed. + * 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 ) { final MedianFilter2D< R > medFilt = new MedianFilter2D<>( image, 1 ); if ( !medFilt.checkInput() || !medFilt.process() ) - return null; + { return null; } return medFilt.getResult(); } @@ -593,9 +594,6 @@ public static final < T extends RealType< T > > void normalize( final Iterable< * * @param img * the image to wrap. - * @param - * the type of pixel in the input image. - * @return a new ImagePlus. */ public static < T extends RealType< T > & NativeType< T > > ImagePlus wrap( final ImgPlus< T > img ) { @@ -632,11 +630,11 @@ public static < T extends RealType< T > & NativeType< T > > ImagePlus wrap( fina * @param c * the channel to extract (0-based). If negative, all channels * are included. - * @param namegen + * @param nameGen * a generator for the name of the output ImagePlus. * @return a new list of ImagePlus. */ - public static < T extends RealType< T > & NativeType< T > > List< ImagePlus > splitSingleTimePoints( final ImgPlus< T > img, final Interval interval, final int c, final Function< Long, String > namegen ) + public static < T extends RealType< T > & NativeType< T > > List< ImagePlus > splitSingleTimePoints( final ImgPlus< T > img, final Interval interval, final int c, final Function< Long, String > namegen2 ) { final int zIndex = img.dimensionIndex( Axes.Z ); final int cIndex = img.dimensionIndex( Axes.CHANNEL ); diff --git a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index c80e42e10..ce93b76c7 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -299,14 +299,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 type of the quality image. Must be real, scalar. + * 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 ccca0a0e1..f9e11429d 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -41,6 +41,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 5bb5d9afd..b67a1884d 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactory.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -39,7 +39,7 @@ * @author Jean-Yves Tinevez * * @param - * the pixel type. + * the pixel type in the image processed by the detector. */ public interface SpotDetectorFactory< T extends RealType< T > & NativeType< T > > extends SpotDetectorFactoryBase< T > { @@ -60,9 +60,8 @@ public interface SpotDetectorFactory< T extends RealType< T > & NativeType< T > * is 3D without time, then the interval must be 3D), not * channel. * @param frame - * the frame index in the source image to operate on. - * @return a new {@link SpotDetector} configured to operate on the given - * target frame. + * the frame index in the source image to operate on + * @return a new detector. */ public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, Object > settings, 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 18a830080..10d182de1 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryBase.java @@ -71,8 +71,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 efafcc3ee..f62f7b8b9 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -36,7 +36,7 @@ * @author Jean-Yves Tinevez * * @param - * the pixel type. + * the pixel type in the image processed by the detector. */ public interface SpotGlobalDetectorFactory< T extends RealType< T > & NativeType< T > > extends SpotDetectorFactoryBase< T > { @@ -54,9 +54,8 @@ public interface SpotGlobalDetectorFactory< T extends RealType< T > & NativeType * 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), not channel. - * @return a new {@link SpotGlobalDetector}. + * then the interval must be 3D). + * @return a new detector. */ public SpotGlobalDetector< T > getDetector( final ImgPlus< T > img, final Map< String, Object > settings, 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 f823d8d50..652da5d4b 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 27af7bfad..8f22b8513 100644 --- a/src/main/java/fiji/plugin/trackmate/features/FeatureAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/FeatureAnalyzer.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -32,29 +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 dimensions. + * + * @return the map of feature dimension. */ public Map< String, Dimension > getFeatureDimensions(); @@ -62,8 +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 of isIntFeature flags. + * + * @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 9d84eb4c7..3622346e8 100644 --- a/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java +++ b/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -163,17 +163,18 @@ public static final Map< String, String > collectFeatureKeys( final TrackMateObj } /** - * Collects all defined feature values for the given feature key and object - * type. Missing or undefined values are not included. + * Collect feature values from the specified model. Missing or undefined + * values are not included. * * @param featureKey - * the feature key to collect values for. + * the key of the feature to collect values from. * @param target - * the TrackMate object type. + * the type of object the feature is defined for. * @param model - * the TrackMate model. + * the model to read from. * @param visibleOnly - * if true, only values for visible objects are collected. + * 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 f882bf744..74977f052 100644 --- a/src/main/java/fiji/plugin/trackmate/features/SpotFeatureCalculator.java +++ b/src/main/java/fiji/plugin/trackmate/features/SpotFeatureCalculator.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -127,11 +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 spots to compute features for. + * the spot collection. * @param doLogIt - * whether we should report progress to model's logger. + * 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 80f46fffe..a103b427d 100644 --- a/src/main/java/fiji/plugin/trackmate/features/TrackFeatureCalculator.java +++ b/src/main/java/fiji/plugin/trackmate/features/TrackFeatureCalculator.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -114,12 +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 tracks to compute features for. + * the IDs of the track to compute the features of. * @param doLogIt - * whether to log the feature computation progress to the model's - * logger. + * 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 5350db7dc..a1a062ecc 100644 --- a/src/main/java/fiji/plugin/trackmate/features/edges/EdgeAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/edges/EdgeAnalyzer.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -35,7 +35,7 @@ * edge of a TrackMate model. An edge, or a link, is the single link that exists * between two spots after tracking. * - * @author Jean-Yves Tinevez + * @author Jean-Yves Tinevez <jeanyves.tinevez@gmail.com> */ public interface EdgeAnalyzer extends Benchmark, FeatureAnalyzer, MultiThreaded { @@ -66,8 +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 true if this is a local edge analyzer. + * + * @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 9733b52fa..e660ca701 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/Spot2DFitEllipseAnalyzer.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -235,13 +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 input matrix - * @return the pseudoinverse of the input matrix + * 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 2b5836a43..80004d10e 100644 --- a/src/main/java/fiji/plugin/trackmate/features/spot/SpotAnalyzerFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/features/spot/SpotAnalyzerFactoryBase.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -30,7 +30,7 @@ * Top-level interface for spot analyzer factories, both classical and * morphological. They are separated so that clients can deal separately with * classical spots (spheres) and spots with ROIs. - * + * * @author Jean-Yves Tinevez - 2020 */ public interface SpotAnalyzerFactoryBase< T extends RealType< T > & NativeType< T > > extends FeatureAnalyzer @@ -53,9 +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 {@link SpotAnalyzer} ready to operate on the given frame and - * channel. + * @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 3758e88c3..6e207a605 100644 --- a/src/main/java/fiji/plugin/trackmate/features/track/TrackAnalyzer.java +++ b/src/main/java/fiji/plugin/trackmate/features/track/TrackAnalyzer.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -80,8 +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 true if this is a local track analyzer. + * + * @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 5de140303..5bdd82da1 100644 --- a/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java +++ b/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -32,6 +32,7 @@ import java.util.TreeSet; import java.util.function.Supplier; +import org.jgrapht.alg.util.NeighborCache; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.jgrapht.graph.SimpleWeightedGraph; @@ -48,10 +49,6 @@ public class GraphUtils * * @param directedGraph * the {@link SimpleDirectedWeightedGraph} to be converted - * @param - * the vertex type. - * @param - * the edge type. * @return a {@link SimpleWeightedGraph} with the same vertices and edges as * the input graph, but with undirected edges and the maximum weight * between any two vertices @@ -88,12 +85,13 @@ public static < V, E > SimpleWeightedGraph< V, E > convertToSimpleWeightedGraph( } /** - * Returns a pretty-print string representation of a {@link TrackModel}, as - * long it is a tree (each spot must not have more than one predecessor). - * + * Pretty-prints a model. + * * @param model - * the track model to represent as a string. - * @return a string representation of the given track 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). * @throws IllegalArgumentException * if the given graph is not a tree. */ @@ -425,4 +423,25 @@ private static char[] makeChars( final int width, final char c ) Arrays.fill( chars, c ); return chars; } + + /** + * 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 ) + { + final HashSet< Spot > sibblings = new HashSet<>(); + final Set< Spot > predecessors = cache.predecessorsOf( spot ); + for ( final Spot predecessor : predecessors ) + sibblings.addAll( cache.successorsOf( predecessor ) ); + + return sibblings; + } + } diff --git a/src/main/java/fiji/plugin/trackmate/graph/SortedDepthFirstIterator.java b/src/main/java/fiji/plugin/trackmate/graph/SortedDepthFirstIterator.java index d99fb901c..c97cb348d 100644 --- a/src/main/java/fiji/plugin/trackmate/graph/SortedDepthFirstIterator.java +++ b/src/main/java/fiji/plugin/trackmate/graph/SortedDepthFirstIterator.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -133,7 +133,8 @@ private static enum VisitColor * @param startVertex * the vertex iteration to be started. * @param comparator - * the comparator to sort the vertices when branching. + * 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 @@ -277,11 +278,8 @@ 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. - * - * @param vertex - * the vertex whose unseen children to add. */ protected void addUnseenChildrenOf( final V vertex ) { @@ -356,29 +354,12 @@ private boolean isConnectedComponentExhausted() } } - /** - * Executed when we encounter a vertex for the first time. - * - * @param vertex - * the vertex encountered. - * @param edge - * the edge via which we encountered it. - */ protected void encounterVertex( final V vertex, final E edge ) { seen.put( vertex, VisitColor.WHITE ); stack.addLast( vertex ); } - /** - * Executed when we encounter a vertex that has already been seen, but is - * still WHITE (meaning it is on the stack). - * - * @param vertex - * the vertex encountered again. - * @param edge - * the edge via which we encountered it. - */ 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 fa9608cb1..f19dd537b 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -117,7 +117,7 @@ public static Color textColorForBackground( final Color backgroundColor ) } /** - * Distance between two colors in CIELab space. + * Distance between two colors. *

* Adapted from * https://stackoverflow.com/questions/9018016/how-to-compare-two-colors-for-similarity-difference @@ -178,12 +178,12 @@ public static final float[] toCIELab( 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 next to. + * 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/ConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigurationPanel.java index 902ece95f..583206e2a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigurationPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigurationPanel.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -28,7 +28,7 @@ /** * The mother class for all the configuration panels. - * + * * @author Jean-Yves Tinevez * */ @@ -46,16 +46,17 @@ public abstract class ConfigurationPanel extends JPanel /** * Echoes the parameters of the given settings on this panel. - * + * * @param settings - * the settings map to use to set the values of this panel. + * the settings as a map. */ public abstract void setSettings( final Map< String, Object > settings ); /** - * Collects the current values of this panel into a settings map. - * - * @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 0f7a06468..019204688 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/FeatureDisplaySelector.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/FeatureDisplaySelector.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -139,12 +139,14 @@ private double[] autoMinMax( final TrackMateObject target ) /** * Returns a {@link CategoryJComboBox} that lets a user select among all - * available features in TrackMate. + * 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 {@link Model} to read features already computed. + * the model to read existing features from. * @param settings - * the {@link Settings} to read features that can be computed. + * 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 5f51e0dfb..89b6f7410 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/FilterGuiPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/FilterGuiPanel.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -105,10 +105,10 @@ public class FilterGuiPanel extends JPanel implements ChangeListener * CONSTRUCTOR */ - public FilterGuiPanel( - final Model model, - final Settings settings, - final TrackMateObject target, + public FilterGuiPanel( + final Model model, + final Settings settings, + final TrackMateObject target, final List< FeatureFilter > filters, final String defaultFeature, final FeatureDisplaySelector featureSelector ) @@ -184,11 +184,11 @@ public FilterGuiPanel( lblInfo = new JLabel(); lblInfo.setFont( SMALL_FONT ); buttonsPanel.add( lblInfo ); - + /* * Color for spots. */ - + final JPanel coloringPanel = featureSelector.createSelectorFor( target ); coloringPanel.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); bottomPanel.add( coloringPanel, BorderLayout.CENTER ); @@ -209,7 +209,7 @@ public FilterGuiPanel( lblTop.setVisible( false ); // For now logger = new ProgressBarLogger(); - + // On close GuiUtils.addOnClosingEvent( this, () -> updater.quit() ); } @@ -238,9 +238,9 @@ public void stateChanged( final ChangeEvent e ) } /** - * Returns the thresholds currently set by this GUI. - * - * @return the thresholds. + * Returns the filters currently set by this GUI. + * + * @return the list of filters. */ public List< FeatureFilter > getFeatureFilters() { @@ -252,7 +252,7 @@ public List< FeatureFilter > getFeatureFilters() * 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. */ @@ -266,7 +266,8 @@ public void addChangeListener( final ChangeListener listener ) * * @param listener * the listener to remove. - * @return true if the listener was in listener collection of this instance. + * @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 dd0ad6a30..9b5d23047 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/FilterPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/FilterPanel.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -311,7 +311,7 @@ public FeatureFilter getFilter() * 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. */ 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 b6d695542..247cc96bb 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/InitFilterPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/InitFilterPanel.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -59,13 +59,13 @@ public class InitFilterPanel extends JPanel private double[] values; /** - * Creates a panel to set an initial feature filter on quality. + * Default constructor, initialize component. * * @param filter - * the initial feature filter to display. + * the filter to initialize the panel with. * @param valueCollector - * a function that, given a feature key, returns an array of - * double values for all spots for that feature. + * a function that can return the value collection of a specified + * feature. */ public InitFilterPanel( final FeatureFilter filter, final Function< String, double[] > valueCollector ) { @@ -139,8 +139,8 @@ public void refresh() /** * Returns the feature threshold on quality set by this panel. - * - * @return 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 fb25a4987..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 @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -84,13 +84,13 @@ public JPanelFeatureSelectionGui() */ /** - * Sets the features and their names that should be presented by this GUI. + * 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 keys of the features. + * the features to add in the GUI. * @param featureNames - * a map from feature key to feature name + * 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 a1b1f6a9d..7fd52abd9 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -35,7 +35,7 @@ * This class implements a {@link PaintScale} that generate colors interpolated * within a list of given color, using a linear scale. * - * @author Jean-Yves Tinevez - Sept 2010 + * @author Jean-Yves Tinevez <jeanyves.tinevez@gmail.com> - Sept 2010 */ public class Colormap implements PaintScale, Serializable { @@ -158,18 +158,17 @@ public static List< Colormap > getAvailableLUTs() */ /** - * Creates a paint scale with given lower and upper bound, and a specified + * Create a paint scale with given lower and upper bound, and a specified * default color. - * + * * @param name - * the name of this colormap. + * the name of the colormap. * @param lowerBound - * the lower bound of the scale. + * the lower bound. * @param upperBound - * the upper bound of the scale. + * the upper bound. * @param defaultColor - * the default color to return when no color is defined in the - * scale. + * a default color. */ public Colormap( final String name, final double lowerBound, final double upperBound, final Color defaultColor ) { @@ -180,15 +179,15 @@ public Colormap( final String name, final double lowerBound, final double upperB } /** - * Creates a paint scale with a given lower and upper bound and a default + * Create a paint scale with a given lower and upper bound and a default * black color. - * + * * @param name - * the name of this colormap. + * the name of the colormap. * @param lowerBound - * the lower bound of the scale. + * the lower bound. * @param upperBound - * the upper bound of the scale. + * the upper bound. */ public Colormap( final String name, final double lowerBound, final double upperBound ) { @@ -196,11 +195,11 @@ public Colormap( final String name, final double lowerBound, final double upperB } /** - * Creates a paint scale with a lower bound of 0, an upper bound of 1 and a + * Create a paint scale with a lower bound of 0, an upper bound of 1 and a * default black color. - * + * * @param name - * the name of this colormap. + * the colormap name. */ public Colormap( final String name ) { @@ -217,15 +216,15 @@ public String getName() } /** - * Adds a color to the color list of this paint scale, at the position given + * Add a color to the color list of this paint scale, at the position given * 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 to add. + * 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 bcb0361fb..6b478253d 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -62,7 +62,7 @@ public class SliderPanel extends JPanel implements BoundedValue.UpdateListener * @param model * the value that is modified. * @param spinnerStepSize - * the step size for the spinner. + * 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 9016aa70f..c0a3686d9 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -77,7 +77,7 @@ public interface RangeListener * @param model * the value that is modified. * @param spinnerStepSize - * the step size of the spinner. + * 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 6109e43f7..51660bf66 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardSequence.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -39,7 +39,7 @@ public interface WizardSequence * Launches the wizard to play this sequence. * * @param title - * the title to show in the wizard window. + * 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 ) @@ -94,7 +94,7 @@ public default void onClose() /** * Returns the descriptor in charge of logging events. It can be accessed * out of the normal sequence by a special button in the wizard. - * + * * @return the descriptor in charge of logging events. */ public WizardPanelDescriptor logDescriptor(); @@ -102,7 +102,7 @@ public default void onClose() /** * Returns the descriptor in charge of configure the views. It can be * accessed out of the normal sequence by a special button in the wizard. - * + * * @return the descriptor in charge of configuring the views. */ public WizardPanelDescriptor configDescriptor(); @@ -127,7 +127,7 @@ public default void onClose() /** * Returns the panel in charge of saving the data. - * + * * @return the panel in charge of saving the data. */ public WizardPanelDescriptor save(); @@ -136,7 +136,7 @@ public default void onClose() * Position the sequence so that its current descriptor is the one with the * specified identifier. If the identifier is unknown to the sequence, do * nothing. - * + * * @param panelIdentifier * the descriptor identifier. */ diff --git a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java b/src/main/java/fiji/plugin/trackmate/io/IOUtils.java index 6c6e3b96c..40ac3e8b5 100644 --- a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java +++ b/src/main/java/fiji/plugin/trackmate/io/IOUtils.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -306,18 +306,17 @@ protected JDialog createDialog( final Component lParent ) throws HeadlessExcepti } /** - * Reads and return an integer attribute from a JDom {@link Element}, and + * 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 JDom element to read from. + * the element to read from. * @param name - * the name of the attribute to read. + * the name of the integer attribute. * @param logger - * a {@link Logger} to report errors to. - * @return the integer value of the attribute, or 0 if not found or of the - * wrong type. + * 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 ) { @@ -499,12 +498,11 @@ public static final boolean readStringAttribute( final Element element, final Ma * double, an error is returned. * * @param element - * the JDom element to read from. + * the element to unmarshall. * @param map - * the map to populate. + * the map the unmarshalled info will be added to. * @param errorHolder - * a string builder to append error messages to if something goes - * wrong. + * 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. */ @@ -564,7 +562,7 @@ public static final boolean writeDownsamplingFactor( final Map< String, Object > } /** - * Adds a parameter attribute to the given element, taken from the given + * Add a parameter attribute to the given element, taken from the given * settings map. Basic checks are made to ensure that the parameter value * can be found and is of the right class. * @@ -577,8 +575,7 @@ public static final boolean writeDownsamplingFactor( final Map< String, Object > * @param expectedClass * the expected class for the value * @param errorHolder - * a string builder to append error messages to if something goes - * wrong. + * 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. @@ -606,11 +603,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 to marshall. + * the map. * @param element - * the JDom element to update. + * 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 6bf80a4ab..03bc29169 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -196,8 +196,8 @@ public class TmXmlReader */ /** - * Initialize this reader to read the specified file. - * + * Initializes this reader to read the file given in argument. + * * @param file * the file to read. */ @@ -233,8 +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 saved log text. + * + * @return the log. */ public String getLog() { @@ -450,8 +450,7 @@ public Settings readSettings( final ImagePlus imp ) * file. * * @param imp - * the image to create the settings for, may be - * null. + * the image to store in the new Settings object. * @param detectorProvider * the detector provider, required to configure the settings with * a correct SpotDetectorFactory. If @@ -476,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, @@ -540,8 +540,8 @@ public Settings readSettings( /** * Returns the version string stored in the file. - * - * @return the version string. + * + * @return the version string stored in the file. */ public String getVersion() { @@ -1018,15 +1018,16 @@ private SpotCollection getSpots( final Element modelElement ) } /** - * Loads the tracks, the track features and the ID of the filtered tracks + * Load the tracks, the track features and the ID of the filtered tracks * into the model specified. The track collection element is expected to be * found as a child of the specified element. * * @param modelElement - * the xml element containing the track collection data. + * the element to read from. * @param model - * the model to populate with tracks. - * @return true if reading tracks was successful, false otherwise. + * 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 c8bfffe50..4d0299576 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlWriter.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -176,7 +176,7 @@ public TmXmlWriter( final File file ) * @param file * the xml file to write to, will be overwritten. * @param logger - * the logger to use to report progress and write errors. + * a logger instance to log writing progress and errors. */ public TmXmlWriter( final File file, final Logger logger ) { @@ -193,13 +193,15 @@ 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) - * @throws FileNotFoundException - * if the file cannot be created or opened. - * @throws IOException - * if an I/O error occurs. */ public void writeToFile() throws FileNotFoundException, IOException { diff --git a/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactory.java b/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactory.java index 0408ed586..abf224ac7 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactory.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -52,5 +52,4 @@ public interface SpotTrackerFactory extends TrackMateFactoryBase< SpotTrackerFac * @return a new configuration panel. */ public ConfigurationPanel getTrackerConfigurationPanel( final Model model ); - } 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 d38de90e2..09a714ed3 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/LAPUtils.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/jaqaman/LAPUtils.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -102,7 +102,7 @@ public class LAPUtils /** * Utility method to put a value in a map, contained in a mother map. Here * it is mainly use to feed feature penalties to LAP tracker settings map. - * + * * @param motherMap * the mother map * @param motherKey @@ -138,7 +138,7 @@ public static final boolean addFeaturePenaltyToSettings( final Map< ?, ? > mothe /** * Returns a new settings map filled with default values suitable for the * trajectory segments linking (gap/split/merge). - * + * * @return a new map. */ public static final Map< String, Object > getDefaultSegmentSettingsMap() @@ -208,18 +208,18 @@ 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 first spot. + * the source spot. * @param s1 - * the second spot. + * the target spot. * @param distanceCutOff - * the distance cutoff beyond which the cost is set to blocking - * value. + * the distance cutoff. * @param blockingValue * the blocking value. * @param featurePenalties - * the map of feature penalties. + * 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 ) @@ -250,15 +250,18 @@ public static final double computeLinkingCostFor( final Spot s0, final Spot s1, * 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. - * @param linking - * whether to check linking settings as well - * @return true if the settings map is valid. + * @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, final boolean linking ) { @@ -270,12 +273,12 @@ public static final boolean checkSettingsValidity( final Map< String, Object > s boolean ok = true; // Linking - if ( linking ) - { - ok = ok & checkParameter( settings, KEY_LINKING_MAX_DISTANCE, Double.class, errorHolder ); - ok = ok & checkFeatureMap( settings, KEY_LINKING_FEATURE_PENALTIES, errorHolder ); - } - // Gap-closing + if (linking) + { + ok = ok & checkParameter( settings, KEY_LINKING_MAX_DISTANCE, Double.class, errorHolder ); + ok = ok & checkFeatureMap( settings, KEY_LINKING_FEATURE_PENALTIES, errorHolder ); + } + // Gap-closing ok = ok & checkParameter( settings, KEY_ALLOW_GAP_CLOSING, Boolean.class, errorHolder ); ok = ok & checkParameter( settings, KEY_GAP_CLOSING_MAX_DISTANCE, Double.class, errorHolder ); ok = ok & checkParameter( settings, KEY_GAP_CLOSING_MAX_FRAME_GAP, Integer.class, errorHolder ); @@ -292,14 +295,14 @@ public static final boolean checkSettingsValidity( final Map< String, Object > s ok = ok & checkParameter( settings, KEY_CUTOFF_PERCENTILE, Double.class, errorHolder ); ok = ok & checkParameter( settings, KEY_ALTERNATIVE_LINKING_COST_FACTOR, Double.class, errorHolder ); ok = ok & checkParameter( settings, KEY_BLOCKING_VALUE, Double.class, errorHolder ); - - // Check keys + + // Check keys final List< String > mandatoryKeys = new ArrayList<>(); if ( linking ) - { - mandatoryKeys.add( KEY_LINKING_MAX_DISTANCE ); - } - mandatoryKeys.add( KEY_ALLOW_GAP_CLOSING ); + { + mandatoryKeys.add( KEY_LINKING_MAX_DISTANCE ); + } + mandatoryKeys.add( KEY_ALLOW_GAP_CLOSING ); mandatoryKeys.add( KEY_GAP_CLOSING_MAX_DISTANCE ); mandatoryKeys.add( KEY_GAP_CLOSING_MAX_FRAME_GAP ); mandatoryKeys.add( KEY_ALLOW_TRACK_SPLITTING ); @@ -311,16 +314,18 @@ public static final boolean checkSettingsValidity( final Map< String, Object > s mandatoryKeys.add( KEY_BLOCKING_VALUE ); final List< String > optionalKeys = new ArrayList<>(); if ( linking ) - { - optionalKeys.add( KEY_LINKING_FEATURE_PENALTIES ); - } - optionalKeys.add( KEY_GAP_CLOSING_FEATURE_PENALTIES ); + { + optionalKeys.add( KEY_LINKING_FEATURE_PENALTIES ); + } + optionalKeys.add( KEY_GAP_CLOSING_FEATURE_PENALTIES ); optionalKeys.add( KEY_SPLITTING_FEATURE_PENALTIES ); optionalKeys.add( KEY_MERGING_FEATURE_PENALTIES ); - optionalKeys.add( KEY_KALMAN_SEARCH_RADIUS ); + optionalKeys.add( KEY_KALMAN_SEARCH_RADIUS ); ok = ok & checkMapKeys( settings, mandatoryKeys, optionalKeys, errorHolder ); - return ok; + return ok; } + + /** * Check the validity of a feature penalty map in a settings map. @@ -404,7 +409,7 @@ public static final void echoMatrix( final double[][] m ) /** * Display the cost matrix solved by the Hungarian algorithm in the LAP * approach. - * + * * @param costs * the cost matrix * @param nSegments 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 0d9eaaae4..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 @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -31,13 +31,13 @@ /** * A {@link CostMatrixCreator} that build a cost matrix from 3 lists containing * the sources, the targets and the associated costs. - * + * * @author Jean-Yves Tinevez - 2014 - * + * * @param - * the type of the source objects (rows). + * the type of sources. * @param - * the type of the target objects (columns). + * 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 435249f58..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 @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -31,13 +31,13 @@ * A {@link CostMatrixCreator} that can generate a cost matrix from a list of * sources, a list of targets and a {@link CostFunction} that can generate a * cost for any combination. - * + * * @author Jean-Yves Tinevez - 2014 - * + * * @param - * the type of the source objects. + * the type of sources. * @param - * the type of the target objects. + * the type of targets. */ public class JaqamanLinkingCostMatrixCreator< K extends Comparable< K >, J extends Comparable< J > > implements CostMatrixCreator< K, J > { @@ -167,7 +167,7 @@ public String getErrorMessage() * Careful, it can be null if not acceptable costs have been * found for the specified configuration. In that case, the lists returned * by {@link #getSourceList()} and {@link #getTargetList()} are empty. - * + * * @return a new {@link SparseCostMatrix} or null. */ @Override 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 554e89d6a..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 @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -68,9 +68,9 @@ * non-infinite costs. *

  • Costs are based on square distance +/- feature penalties. * - * + * * @author Jean-Yves Tinevez - 2014 - * + * */ public class JaqamanSegmentCostMatrixCreator implements CostMatrixCreator< Spot, Spot >, MultiThreaded { @@ -100,9 +100,11 @@ public class JaqamanSegmentCostMatrixCreator implements CostMatrixCreator< Spot, * segment linking cost matrix. * * @param graph - * the track segment graph. + * the graph from which connected components (segments) will be + * extracted. * @param settings - * the settings map. + * 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 9124f1bf2..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 @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -47,7 +47,7 @@ * Volgenant paper: Volgenant. Linear and semi-assignment problems: A core * oriented approach. Computers & Operations Research (1996) vol. 23 (10) pp. * 917-932 - * + * * @author Jean-Yves Tinevez - 2014 */ public class SparseCostMatrix @@ -101,12 +101,12 @@ 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. *
    - * + * * @param cc * the cost array. * @param kk @@ -114,7 +114,7 @@ public class SparseCostMatrix * @param number * the number of element for each row. * @param nCols - * the number of columns in the matrix. + * 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 @@ -306,7 +306,7 @@ public String toString( final List< ? > rows, final List< ? > columns ) * Computes the total cost for an assignment specified by row. It is * supposed that row i is assigned to column * rowAssignment[i]. - * + * * @param rowAssignment * the assignment, specified by row. * @return the total cost for this assignment. @@ -327,7 +327,7 @@ public double totalAssignmentCost( final int[] rowAssignment ) * Creates and returns a new double[][] matrix representing a * non-sparse version of this cost matrix. Missing costs are replace by * {@link Double#MAX_VALUE}. - * + * * @return a new double[][] */ public double[][] toFullMatrix() @@ -355,7 +355,7 @@ public double[][] toFullMatrix() * Returns the value stored by this matrix at the specified row and column. * If a value is not present in the sparse matrix, the specified missing * value is returned. - * + * * @param i * the row. * @param j @@ -376,7 +376,7 @@ public final double get( final int i, final int j, final double missingValue ) /** * Exposes the array of all the non-infinite costs. - * + * * @return the costs. */ public double[] getCosts() @@ -397,14 +397,14 @@ public int getNRows() /** * Returns the vertical concatenation of this matrix with the specified one. * So that if this matrix is A and the specified matrix is B, you get - * + * *
     	 * -----
     	 * | A |
     	 * | B |
     	 * -----
     	 * 
    - * + * * @param B * the matrix to concatenate this matrix with * @return a new sparse matrix. @@ -438,13 +438,13 @@ public final SparseCostMatrix vcat( final SparseCostMatrix B ) /** * Returns the horizontal concatenation of this matrix with the specified * one. So that if this matrix is A and the specified matrix is B, you get - * + * *
     	 * -------
     	 * | A B |
     	 * -------
     	 * 
    - * + * * @param B * the matrix to concatenate this matrix with * @return a new sparse matrix. @@ -493,7 +493,7 @@ public final SparseCostMatrix hcat( final SparseCostMatrix B ) /** * Returns the transpose of this matrix. - * + * * @return a new sparse matrix. */ public final SparseCostMatrix transpose() @@ -556,7 +556,7 @@ public final SparseCostMatrix transpose() /** * Replace all the non-infinite values of this matrix by the specified * value. - * + * * @param value * the value to write in this matrix. */ 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 487cf62cb..1cb6ce27c 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/kalman/KalmanTracker.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -86,19 +86,20 @@ public class KalmanTracker implements SpotTracker, Benchmark, Cancelable */ /** - * Create a new Kalman tracker. - * + * Creates a new Kalman tracker. + * * @param spots * the spots to track. * @param maxSearchRadius - * the maximum search radius when growing a track. + * the maximal search radius to continue a track, in physical + * units. * @param maxFrameGap - * the maximum frame gap to bridge. + * the max frame gap when detections are missing, after which a + * track will be stopped. * @param initialSearchRadius - * the initial search radius to create a track. + * the initial search radius to nucleate new tracks. * @param featurePenalties - * feature penalties to use in the cost function. Can be - * null. + * 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 5b8d88a03..3da46c7be 100644 --- a/src/main/java/fiji/plugin/trackmate/util/ChartExporter.java +++ b/src/main/java/fiji/plugin/trackmate/util/ChartExporter.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -43,15 +43,15 @@ /** * A collection of static utilities made to export a JPanel to various scalable * file format. - * + * * @author Jean-Yves Tinevez, 2011 - 2021 */ public class ChartExporter { /** - * Exports a JFreeChart to SVG. - * + * Export a JFreeChart to SVG. + * * @param svgFile * the target svg file. * @param chart @@ -61,9 +61,7 @@ public class ChartExporter * @param height * the height of the panel the chart is painted in. * @throws UnsupportedEncodingException - * if the encoding is not supported. - * @throws IOException - * on IO error. + * 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 { @@ -86,7 +84,7 @@ public static void exportChartAsSVG( final File svgFile, final JFreeChart chart, /** * Export a JFreeChart to PDF. - * + * * @param pdfFile * the target pdf file. * @param chart diff --git a/src/main/java/fiji/plugin/trackmate/util/OnRequestUpdater.java b/src/main/java/fiji/plugin/trackmate/util/OnRequestUpdater.java index a9f292818..01cf189a9 100644 --- a/src/main/java/fiji/plugin/trackmate/util/OnRequestUpdater.java +++ b/src/main/java/fiji/plugin/trackmate/util/OnRequestUpdater.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -68,9 +68,9 @@ * not optimal, and that for general heavy use refreshing, another solution must * be sought. In the meantime, it is recommended that this class is used for * simple purpose. - * + * * @author Albert Cardona - * + * */ public class OnRequestUpdater extends Thread { @@ -81,9 +81,9 @@ public class OnRequestUpdater extends Thread /** * Constructor autostarts thread - * + * * @param refreshable - * the refreshable target + * 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 ac63b1e78..89185f867 100644 --- a/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhoodCursor.java +++ b/src/main/java/fiji/plugin/trackmate/util/SpotNeighborhoodCursor.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -61,24 +61,22 @@ public SpotNeighborhoodCursor( final SpotNeighborhood< T > sn ) /** * Stores the relative calibrated position with respect to the * neighborhood center. - * + * * @param position - * the array in which to write the position. + * an array to store to relative position in. */ 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 ] ); - } } /** * Returns the square distance measured from the center of the domain to the * current cursor position, in calibrated units. - * - * @return the square distance in calibrated units. + * + * @return the square distance. */ public double getDistanceSquared() { @@ -99,8 +97,8 @@ public double getDistanceSquared() *

    * 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 angle in radians. + * + * @return the inclination. */ public double getTheta() { @@ -117,8 +115,8 @@ public double getTheta() * 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 angle in radians. + * + * @return the azimuth. */ public double getPhi() { diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index 2e1aedb56..75c467bb4 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -45,10 +45,9 @@ import fiji.plugin.trackmate.Dimension; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.Spot; import ij.IJ; import ij.ImagePlus; -import ij.gui.Roi; import net.imagej.ImgPlus; import net.imagej.ImgPlusMetadata; import net.imagej.axis.Axes; @@ -60,7 +59,7 @@ import net.imglib2.util.Util; /** - * List of static utilities for {@link fiji.plugin.trackmate.TrackMate}. + * List of static utilities for TrackMate. */ public class TMUtils { @@ -73,56 +72,18 @@ public class TMUtils * STATIC METHODS */ - /** - * Returns an {@link Interval} that corresponds to the ROI in the specified - * image. - *

    - * If the image has no ROI, the interval returned is null. For - * 3D images the interval extends over all Z. The interval does not include - * the time dimension nor the channel dimension. It is 2D for 2D images, and - * 3D for 3D images regardless of the presence of C and T. - * - * @param imp - * the image. - * @return a new interval, or null if the image has no ROI. - */ - public static Interval createROIInterval( final ImagePlus imp ) - { - final Roi roi = imp.getRoi(); - if ( roi == null ) - return null; - - final boolean is3D = !DetectionUtils.is2D( imp ); - final long[] min = new long[ is3D ? 3 : 2 ]; - final long[] max = new long[ min.length ]; - - min[ 0 ] = roi.getBounds().x; - max[ 0 ] = roi.getBounds().x + roi.getBounds().width - 1; - min[ 1 ] = roi.getBounds().y; - max[ 1 ] = roi.getBounds().y + roi.getBounds().height - 1; - if ( is3D ) - { - min[ 2 ] = 0; - max[ 2 ] = imp.getNSlices(); - } - return new FinalInterval( min, max ); - } - /** * Returns a new map sorted by its values. - *

    - * The returned map is a {@link LinkedHashMap}, which preserves the - * ordering. * - * @param map - * the map to sort. - * @param comparator - * the comparator to use to sort the values. - * @return a new map sorted by its values. * @param - * the key type. + * the type of keys in the map. * @param - * the value type. + * 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 ) { @@ -147,14 +108,12 @@ public int compare( final Entry< K, V > o1, final Entry< K, V > o2 ) /** * Generates a string representation of a map, typically a settings map. - *

    - * This method is recursive, and will indent sub-maps. * * @param map - * the map to represent as a string. + * the map. * @param indent - * the indentation level. - * @return a string representation of the map. + * the indent size to use. + * @return a representation of the map. */ public static final String echoMap( final Map< String, Object > map, final int indent ) { @@ -191,13 +150,14 @@ 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}, abiding to a + * returned type. * + * @param + * the pixel type in the returned image. * @param imp - * the image plus to wrap. - * @return the ImgPlus wrapping the input. + * the {@link ImagePlus} to wrap. + * @return a wrapped {@link ImgPlus}. */ @SuppressWarnings( "unchecked" ) public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) @@ -206,7 +166,7 @@ public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) } /** - * Checks that the given map has all some keys. Two String collection allows + * Check that the given map has all some keys. Two String collection allows * specifying that some keys are mandatory, other are optional. * * @param map @@ -219,10 +179,10 @@ public static final < T > ImgPlus< T > rawWraps( final ImagePlus imp ) * be null. * @param errorHolder * will be appended with an error message. - * @param - * the type of the keys. * @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 ) { @@ -317,18 +277,17 @@ 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 by iterating over the key collection. + * 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 keys. + * the collection of keys. * @param mapping * the mapping. - * @param - * the key type. - * @param - * the value type. - * @return the list of values mapped to the keys. - * + * @return a new list of values. */ public static final < J, K > List< K > getArrayFromMaping( final Collection< J > keys, final Map< J, K > mapping ) { @@ -348,9 +307,8 @@ public static final < J, K > List< K > getArrayFromMaping( final Collection< J > * is not found, then the calibration for this axis takes the value of 1. * * @param img - * the image plus metadata. - * @return a 3-elements double array with the spatial calibration in X, Y, - * Z. + * the image metadata object. + * @return a new double array. */ public static final double[] getSpatialCalibration( final ImgPlusMetadata img ) { @@ -384,14 +342,13 @@ public static double[] getSpatialCalibration( final ImagePlus imp ) * the values array. Taken from commons-math. * * @param values - * the values to compute the percentile from. + * the values. * @param p - * the percentile to compute, between 0 and 1. - * @return the pth percentile. + * 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 ); @@ -434,6 +391,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)). @@ -503,8 +476,8 @@ private static final int[] histogram( final double data[], final int nBins ) * thresholding method. * * @param data - * the data to threshold. - * @return the threshold value. + * the data. + * @return the Otsu threshold. */ public static final double otsuThreshold( final double[] data ) { @@ -516,10 +489,10 @@ public static final double otsuThreshold( final double[] data ) * thresholding method with a given bin number. * * @param data - * the data to threshold. - * @param nBins - * the number of bins to use for the histogram. - * @return the threshold value. + * 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 ) { @@ -591,12 +564,12 @@ private static final int otsuThresholdIndex( final int[] hist, final int nPoints * Otherwise, default units are used. * * @param dimension - * the dimension to get the unit for. + * the dimension. * @param spaceUnits - * the spatial units to use for space-related dimensions. + * the space units. * @param timeUnits - * the time units to use for time-related dimensions. - * @return a String representing the unit for the given dimension. + * the time units. + * @return the units for the specified dimension. */ public static final String getUnitsFor( final Dimension dimension, final String spaceUnits, final String timeUnits ) { @@ -839,9 +812,9 @@ public static final Interval getInterval( final ImgPlus< ? > img, final Settings } /** - * Obtains and cache the SciJava {@link Context} in use by ImageJ. + * Obtains the SciJava {@link Context} in use by ImageJ. * - * @return the SciJava context + * @return the context. */ public static Context getContext() { @@ -979,9 +952,6 @@ 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 - * the settings object from which to read the image folder and - * image file name. * @return full name of the image without the extension */ public static String getImagePathWithoutExtension( final Settings settings ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java index b2b9831ea..591afb6c3 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -51,15 +51,15 @@ public interface TrackMateModelView /** * Centers the view on the given spot. - * + * * @param spot - * the spot to center on. + * 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 83ac11865..ed5c9bb48 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -75,10 +75,9 @@ public HyperStackDisplayer( final Model model, final SelectionModel selectionMod /** * Hook for subclassers. Instantiate here the overlay you want to use for * the spots. - * + * * @param displaySettings - * the display settings to use in the overlay. - * + * the display settings. * @return the spot overlay */ protected SpotOverlay createSpotOverlay( final DisplaySettings displaySettings ) @@ -89,10 +88,9 @@ protected SpotOverlay createSpotOverlay( final DisplaySettings displaySettings ) /** * Hook for subclassers. Instantiate here the overlay you want to use for * the spots. - * + * * @param displaySettings - * the display settings to use in the overlay. - * + * 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 2656e67a9..58e3c3f8f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -118,8 +118,8 @@ public void imageClosed( final ImagePlus imp ) /** * Returns the singleton instance for this tool. If it was not previously * instantiated, this calls instantiates it. - * - * @return the singleton instance of this tool. + * + * @return the instance. */ public static SpotEditTool getInstance() { @@ -130,9 +130,11 @@ public static SpotEditTool getInstance() } /** - * Returns true if the tool is currently present in ImageJ toolbar. - * - * @return true if the tool is launched. + * 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() { @@ -191,11 +193,11 @@ protected void registerTool( final ImageCanvas canvas ) } /** - * Registers 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 + * the displayer to register. */ public void register( final HyperStackDisplayer displayer ) { @@ -324,7 +326,7 @@ public void keyPressed( final KeyEvent e ) e.consume(); break; } - + // Delete currently edited spot case KeyEvent.VK_DELETE: { 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 1323808f2..0c2c9edbd 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -237,15 +237,6 @@ public void drawOverlay( final Graphics g ) g2d.setFont( originalFont ); } - /** - * Draws an extra layer on top of the spots. The default implementation does - * nothing. - * - * @param g2d - * the graphics device. - * @param frame - * the frame currently drawn. - */ 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 80c986c17..c15e1729c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SaveAction.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SaveAction.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -79,15 +79,15 @@ public SaveAction( final TrackScheme trackScheme ) /** * Saves XML+PNG format. - * + * * @param frame - * the TrackScheme frame to capture from. + * the TrackScheme frame to capture. * @param filename * the file to save to. * @param bg - * the background color, or null for transparent. + * the color of the background in the exported image. * @throws IOException - * if something goes wrong when writing the file. + * 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 430919bd1..7c928259a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotIconGrabber.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotIconGrabber.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -74,7 +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-encoded PNG image string representing the spot. + * @return a Base64 representation of the spot image. */ public String getImageString( final Spot spot, final double radiusFactor ) { @@ -125,7 +125,7 @@ public String getImageString( final Spot spot, final double radiusFactor ) /** * Returns a 2D slice extract around the specified coordinates. - * + * * @param x * top-left x coordinate. * @param y @@ -172,7 +172,7 @@ public final Img< T > grabImage( final long x, final long y, final long slice, f /** * Returns a 3D cropped copy around the specified coordinates. - * + * * @param x * top-left x coordinate. * @param y 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 ca8dd909d..f092cbb12 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotImageUpdater.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/SpotImageUpdater.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -58,7 +58,7 @@ public SpotImageUpdater( final Settings settings ) * group calls to this method for spots that belong to the same frame. * * @param spot - * the spot for which we want the image string. + * 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 1ae255b95..7ac53f325 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -168,7 +168,7 @@ public TrackScheme( final Model model, final SelectionModel selectionModel, fina final String title = "TrackScheme"; gui.setTitle( title ); gui.setSize( DEFAULT_SIZE ); - + displaySettings.listeners().add( () -> doTrackStyle() ); gui.addWindowListener( new WindowAdapter() { @@ -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() { @@ -208,10 +210,9 @@ public int getUnlaidSpotColumn() /** * Returns the first free column for the target row. - * + * * @param frame - * the target row. - * + * the row. * @return the first free column for the target row. */ public int getNextFreeColumn( final int frame ) @@ -226,8 +227,8 @@ public int getNextFreeColumn( final int frame ) /** * Returns the GUI frame controlled by this class. - * - * @return the GUI frame. + * + * @return the GUI. */ public TrackSchemeFrame getGUI() { @@ -237,8 +238,8 @@ public TrackSchemeFrame getGUI() /** * Returns the {@link JGraphXAdapter} that serves as a model for the graph * displayed in this frame. - * - * @return the graph adapter. + * + * @return the adapter. */ public JGraphXAdapter getGraph() { @@ -247,7 +248,7 @@ public JGraphXAdapter getGraph() /** * Returns the graph layout in charge of arranging the cells on the graph. - * + * * @return the graph layout. */ public TrackSchemeGraphLayout getGraphLayout() @@ -1301,7 +1302,7 @@ public void removeSelectedLinkCells() edgeCells.add( obj ); } - + graph.getModel().beginUpdate(); try { 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 52cc855b9..7d11d151f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 @@ -100,7 +100,7 @@ protected InputMap getInputMap( final int condition ) /** * Returns the mapping between JTree's input map and JGraph's actions. - * + * * @return the action map. */ protected ActionMap createActionMap() From 98b77977db0d5d36bcfab37debc17ed92b443cd0 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 18:53:42 +0200 Subject: [PATCH 163/371] 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 652da5d4b..fd8006101 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 ce14c2c181f21ce943b9406fa6fcb1aaa0d8d52a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 17 Nov 2023 16:19:06 +0100 Subject: [PATCH 164/371] Make the mesh of a SpotMesh setable. Also stop exposing the bounding-box. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 23 +++++++++++++++---- .../trackmate/util/mesh/SpotMeshCursor.java | 14 ++++++----- .../trackmate/util/mesh/SpotMeshIterable.java | 4 ++-- .../hyperstack/PaintSpotMesh.java | 6 +++-- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 1a906b744..27ec67382 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -29,12 +29,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, @@ -61,19 +61,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, @@ -87,6 +92,14 @@ public SpotMesh( // Bounding box, also centered on (0,0,0) this.boundingBox = Meshes.boundingBox( mesh ); + + // Slice cache. + resetZSliceCache(); + } + + public RealInterval getBoundingBox() + { + return boundingBox; } /** 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 72cde372fed248da8e2d832f7e0b987d08586dc8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 17 Nov 2023 16:19:37 +0100 Subject: [PATCH 165/371] 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 27ec67382..1284421bb 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -145,7 +145,7 @@ public SpotMesh( final int ID, final BufferMesh mesh ) * * @return the mesh. */ - public Mesh getMesh() + public BufferMesh getMesh() { return mesh; } From 802f410a6531653ddb7d36b274ec892589de0a2c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 17 Nov 2023 16:20:06 +0100 Subject: [PATCH 166/371] 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 c531dbb34..d4a1b7a6e 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java @@ -1113,6 +1113,7 @@ public static < E > JComboBox< E > linkedComboBoxEnumSelector( final EnumElement if ( e != model.getSelectedItem() ) model.setSelectedItem( e ); } ); + cb.setSelectedItem( element.getValue() ); return cb; } From e51487eaefc069a197364d992b1fef3c0eb01055 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 17 Nov 2023 16:20:26 +0100 Subject: [PATCH 167/371] 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 b20c3668b5640fc817dedd534b0504749993883e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 19 Nov 2023 19:34:11 +0100 Subject: [PATCH 168/371] 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 32b6d1007f8b3b1c141d49da36a40502313ec0f3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 10:11:35 +0100 Subject: [PATCH 169/371] 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 8d7c8197d839808e7e2ffea4cd27cce8904a0787 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 10:34:54 +0100 Subject: [PATCH 170/371] 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 1cf63d5f9859ab1592df0cf15693a93726085386 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 10:42:25 +0100 Subject: [PATCH 171/371] 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 cfc801869e3f1712057d094d6aa3577b7cb13277 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 16:55:31 +0100 Subject: [PATCH 172/371] 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 6bce463ffa65b33ad8d9d0bccfed0bef81249755 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 17:33:33 +0100 Subject: [PATCH 173/371] 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 16b881cf550607b3926a03903bf03c94d420619c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 20 Nov 2023 17:44:31 +0100 Subject: [PATCH 174/371] 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 778911a079c4441932339c18f920d74e34d92509 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 22 Nov 2023 16:18:53 +0100 Subject: [PATCH 175/371] 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 7fcf063252053f68968ae32cf0e8cf8c95e10e52 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 22 Nov 2023 18:12:56 +0100 Subject: [PATCH 176/371] 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 ce93b76c7..053248372 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -410,6 +410,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. @@ -419,6 +424,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 ) { @@ -443,6 +449,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 f9e11429d..4c77dc07b 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -62,6 +62,8 @@ public class Process2DZ< T extends RealType< T > & NativeType< T > > private List< Spot > spots; + private final double smoothingScale; + /** * Creates a new {@link Process2DZ} detector. * @@ -85,13 +87,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 @@ -149,7 +153,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 fd8006101..3beef3385 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 5a86bba3c4a2bcfab92d633c8511e01ea8a19268 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 22 Nov 2023 18:13:22 +0100 Subject: [PATCH 177/371] 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 e93d895d2..878b2f6c0 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 @@ -150,6 +154,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 da8ba4abb..6a5a297f7 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 java.util.HashMap; import java.util.Map; @@ -82,6 +83,7 @@ public class LabelImageDetectorFactory< T extends RealType< T > & NativeType< T public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, Object > settings, 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 ); @@ -90,7 +92,8 @@ public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, 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 df548f3e3..101432c0c 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskDetectorFactory.java @@ -76,18 +76,19 @@ public class MaskDetectorFactory< T extends RealType< T > & NativeType< T > > ex public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, Object > settings, 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; } @@ -150,6 +151,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 427290826..72987e764 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 58368bda6..ace9a7e13 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -76,6 +76,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"; /* @@ -87,6 +95,7 @@ public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, { 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 ); @@ -96,7 +105,8 @@ public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, interval, calibration, intensityThreshold, - simplifyContours ); + simplifyContours, + smoothingScale ); detector.setNumThreads( 1 ); return detector; } @@ -156,6 +166,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 77adbbf22..5463f3d0a 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 */ @@ -129,10 +135,10 @@ protected ThresholdDetectorConfigurationPanel( setPreferredSize( new Dimension( 300, 511 ) ); final GridBagLayout gridBagLayout = new GridBagLayout(); - gridBagLayout.rowHeights = new int[] { 0, 0, 0, 0, 0, 0, 0, 150 }; + 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, 0.1 }; + 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, ThresholdDetectorFactory.ICON, JLabel.CENTER ); @@ -229,6 +235,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 ) @@ -243,7 +259,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 ); /* @@ -301,11 +317,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 4c5eb62c911556bf8dbfa3fd09ce7bb754482d8e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 19:59:23 +0100 Subject: [PATCH 178/371] 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 75c467bb4..a3de00bd5 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -245,8 +245,39 @@ public static final String checkSettings( final Map< String, Object > settings, } /** - * 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. @@ -267,12 +298,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 3e8e882ea1a77e3c92c6829c0386a5b1947bae12 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 19:59:57 +0100 Subject: [PATCH 179/371] 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 6b478253d..4267ec8af 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -127,6 +127,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 ); } @@ -156,6 +172,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 c0a3686d9..9d0f4515b 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -154,6 +154,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 ); } @@ -197,6 +213,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 0ca14df0d1d1e40d8050a39a1d3e4365e1004b42 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 20:00:23 +0100 Subject: [PATCH 180/371] 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 f7e5ee460d7243b5a604eff47725e1ef338a246f Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 20:04:35 +0100 Subject: [PATCH 181/371] 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 053248372..f40f90e48 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -34,6 +34,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; @@ -42,14 +43,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.LabelRegions; +import net.imglib2.type.NativeType; 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; +import net.imglib2.type.numeric.real.FloatType; import net.imglib2.util.Util; import net.imglib2.view.IntervalView; import net.imglib2.view.Views; @@ -170,20 +174,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. @@ -230,8 +231,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, @@ -329,8 +339,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 ); @@ -419,7 +436,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, @@ -428,34 +445,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 ); } /** @@ -479,35 +478,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 ); } @@ -520,8 +548,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 3beef3385..b4f87434c 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 9fc05e956b041baed5c5e5e4977f985a040f77dd Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Nov 2023 20:06:36 +0100 Subject: [PATCH 182/371] Implement the smoothing scale in the threshold, mask and label detectors. --- .../trackmate/detection/LabelImageDetector.java | 3 ++- .../trackmate/detection/ThresholdDetector.java | 1 + .../detection/ThresholdDetectorFactory.java | 2 +- .../LabelImageDetectorConfigurationPanel.java | 8 +++----- .../detector/MaskDetectorConfigurationPanel.java | 5 +---- .../ThresholdDetectorConfigurationPanel.java | 13 ++++++++++--- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java index 878b2f6c0..73431ee0d 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -142,9 +142,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/ThresholdDetector.java b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetector.java index 72987e764..43d2de6dc 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 ace9a7e13..c1d585a00 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/ThresholdDetectorFactory.java @@ -95,7 +95,7 @@ public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, { 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 ); 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 454f142a1..061379c08 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 1cb691cf0..fc6bf261c 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 5463f3d0a..3bb0c6315 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 @@ -327,13 +327,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 4d3af54c05faacc3025ba081263142b723e6db45 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 28 Nov 2023 16:08:59 +0100 Subject: [PATCH 183/371] 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 4267ec8af..a4c6ac131 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}. @@ -180,6 +182,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 9d0f4515b..fde05c31b 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}. @@ -221,6 +223,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 21ce5db4bde6350e6b850f8143e43e91b8016db6 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 29 Nov 2023 16:12:16 +0100 Subject: [PATCH 184/371] Fix incorrect documentation. --- .../trackmate/detection/SpotGlobalDetectorFactory.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java index f62f7b8b9..840b3ffe3 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotGlobalDetectorFactory.java @@ -23,6 +23,7 @@ import java.util.Map; +import fiji.plugin.trackmate.util.TMUtils; import net.imagej.ImgPlus; import net.imglib2.Interval; import net.imglib2.type.NativeType; @@ -51,11 +52,14 @@ public interface SpotGlobalDetectorFactory< T extends RealType< T > & NativeType * the settings map, used to configure the detector. * @param interval * the interval that determines the region in the source image to - * operate on. This must not have a dimension for time + * operate on. This must have a dimension for time, to + * specify what time-points to process, but not for channels * (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). + * 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 ImgPlus< T > img, final Map< String, Object > settings, final Interval interval ); } From b0d67255330cdc26746e2e50172d3bc7c3d7f5ec Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Dec 2023 11:27:47 +0100 Subject: [PATCH 185/371] 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 f40f90e48..10d5fa65d 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -490,7 +490,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, @@ -517,7 +517,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 bb1a244ce573fb567e48321cee6f93724c73da7a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Dec 2023 17:06:20 +0100 Subject: [PATCH 186/371] 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 2f20b8022f3de734a54ef5634d4c6a6098164631 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 5 Feb 2024 16:46:54 +0100 Subject: [PATCH 187/371] 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 4c77dc07b..ad59ba171 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -15,9 +15,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; @@ -50,7 +50,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; @@ -68,8 +68,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. @@ -77,13 +77,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, @@ -101,9 +104,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; @@ -122,8 +141,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 b9ccb2cfff6b18076d538e7f12d2e7a26e42b8a2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 5 Feb 2024 17:02:52 +0100 Subject: [PATCH 188/371] 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 ad59ba171..0662892e2 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -244,8 +244,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 4d23401945a1315a86a516c9f4da4770d4b58a27 Mon Sep 17 00:00:00 2001 From: tpietzsch Date: Tue, 18 Apr 2023 15:54:48 -0500 Subject: [PATCH 189/371] 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 abb645f54b93ffa8a5f5f1d3e435ff81aba2afb3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 1 Sep 2023 18:08:14 +0200 Subject: [PATCH 190/371] 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 79ef04f49..9e3805f48 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -387,11 +387,15 @@ public ConfigureViewsPanel( btnLabKit.setText( "Launch spot editor" ); btnLabKit.setIcon( GuiUtils.scaleImage( Icons.SEGMENTATION_EDITOR_ICON_64x64, 16, 16 ) ); 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 f4efef8e1458defbdebbf987e1d84fa82c2b8c7e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 19 Sep 2023 15:18:06 +0200 Subject: [PATCH 191/371] 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 c8b86088c..cc88585bb 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 b2bee96333b327a039b09162d8001e8c6b3fdb14 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 22 Sep 2023 17:04:06 +0200 Subject: [PATCH 192/371] 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 3a80cefa54d2ef7143898eb9286dc0bb4fa29139 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 5 Oct 2023 09:28:29 +0200 Subject: [PATCH 193/371] 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 da483ba9c35b1d4d90d605e1736b3a6f6b0cd593 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Sun, 8 Oct 2023 16:45:15 +0200 Subject: [PATCH 194/371] 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 cc88585bb..a9ee0de23 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 9b86355ae853a3d7a39a52fa5fadafdb41d1d23e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Dec 2023 17:39:00 +0100 Subject: [PATCH 195/371] 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 c0a9fade8d4854aac25f3bed447316bcfc7dbf9d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 4 Dec 2023 17:45:47 +0100 Subject: [PATCH 196/371] 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 73431ee0d..6c5465690 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -152,7 +152,7 @@ else if ( input.numDimensions() == 3 ) { spots = SpotMeshUtils.from3DLabelingWithROI( labeling, - interval, + interval.minAsDoubleArray(), calibration, simplify, smoothingScale, @@ -162,7 +162,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 10d5fa65d..f1c88ff81 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -244,7 +244,7 @@ public static < T extends RealType< T > > List< Spot > fromThreshold( numThreads ); return fromLabeling( labeling, - interval, + interval.minAsDoubleArray(), calibration ); } @@ -255,8 +255,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. @@ -264,7 +265,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. @@ -289,9 +290,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 b4f87434c..f14e22773 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 7b4d0dbed35a0097a662b8182165ab2117d47b81 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 8 Feb 2024 15:21:11 +0100 Subject: [PATCH 197/371] Update license blurbs. --- .../java/fiji/plugin/trackmate/Dimension.java | 4 +-- .../java/fiji/plugin/trackmate/Model.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 ++++++++++++ .../trackmate/detection/MaskDetector.java | 6 ++-- .../plugin/trackmate/detection/MaskUtils.java | 4 +-- .../trackmate/detection/Process2DZ.java | 21 ++++++++++++ .../trackmate/detection/SpotMeshUtils.java | 21 ++++++++++++ .../trackmate/detection/SpotRoiUtils.java | 21 ++++++++++++ .../detection/ThresholdDetector.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 +-- .../gui/components/PanelSmoothContour.java | 21 ++++++++++++ .../gui/displaysettings/SliderPanel.java | 10 ------ .../displaysettings/SliderPanelDouble.java | 10 ------ .../trackmate/gui/editor/LabkitLauncher.java | 2 +- .../editor/labkit/model/ImpBdvShowable.java | 22 +++++++++++- .../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 ++++++++++++ .../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 +-- .../trackmate/util/mesh/SpotMeshCursor.java | 21 ++++++++++++ .../trackmate/util/mesh/SpotMeshIterable.java | 21 ++++++++++++ .../trackmate/visualization/bvv/BVVUtils.java | 21 ++++++++++++ .../visualization/bvv/StupidMesh.java | 34 ++++++++----------- .../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 ++++++++++++ 65 files changed, 847 insertions(+), 97 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Dimension.java b/src/main/java/fiji/plugin/trackmate/Dimension.java index 2d66cf25d..a1c6cd3b9 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 06e128586..5eedf49d7 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/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index 2878f257b..d0b95ac22 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 1284421bb..2da0821b2 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 c160c5281..1bf39eec3 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 bd7354e12..21027b385 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/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/MaskUtils.java b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java index f1c88ff81..153f42358 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 0662892e2..2419a935d 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/SpotMeshUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotMeshUtils.java index f14e22773..f315d4a4a 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 43d2de6dc..65bd1e83a 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/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 a26fc22da..5305bf924 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 b9161db59..648af607a 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 786f9712e..fe95c1cfc 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/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/displaysettings/SliderPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java index a4c6ac131..ea0b8e0e2 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -154,16 +154,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 fde05c31b..4109fa9eb 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -195,16 +195,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/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java index e8ad9963e..c9dbe822c 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -2,7 +2,7 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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/gui/editor/labkit/model/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/ImpBdvShowable.java index f6f114804..38e610cd8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/ImpBdvShowable.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/ImpBdvShowable.java @@ -20,6 +20,27 @@ * #L% */ package fiji.plugin.trackmate.gui.editor.labkit.model; +/*- + * #%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% + */ import java.awt.Color; import java.util.Arrays; @@ -78,7 +99,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 ); 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/descriptors/ConfigureViewsDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java index c33ec8fa3..d1a624e19 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 dda515b2b..2151b08a2 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 03bc29169..1582e531f 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 4d0299576..d92f048cd 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 a3de00bd5..6ea1495cd 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/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 7009eae4d..1661ca793 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,23 @@ /*- * #%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 cd7c0fbf6..07e5b823b 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 ec6246cee0fb0d1910c17bf74b903b3fd2ef7316 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 8 Feb 2024 15:35:19 +0100 Subject: [PATCH 198/371] 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 345d91843..5113f614f 100644 --- a/pom.xml +++ b/pom.xml @@ -164,6 +164,7 @@ + true fiji.plugin.trackmate gpl_v3 TrackMate developers. From 195d762389c8ab608502db92b5333b1f50b513c5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 28 Mar 2024 20:17:38 +0100 Subject: [PATCH 199/371] 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 2419a935d..8bb62d906 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; @@ -66,7 +68,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] "; @@ -85,6 +87,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. * @@ -152,6 +160,8 @@ public boolean checkInput() @Override public boolean process() { + isCanceled = false; + cancelReason = null; spots = null; /* @@ -172,7 +182,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() ) @@ -279,4 +289,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 1cacb59b63ea126d9f173f63f07aa0daad17adf5 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 28 Mar 2024 13:18:25 -0500 Subject: [PATCH 200/371] POM: fix the dependencies * Avoid SNAPSHOT versions. * Factor out version pins to properties. * Avoid jogamp *-main uber-JARs. --- pom.xml | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 5113f614f..4d436a357 100644 --- a/pom.xml +++ b/pom.xml @@ -245,15 +245,23 @@ bigvolumeviewer - org.jogamp.jogl - jogl-all-main - 2.3.2 - - - org.jogamp.gluegen - gluegen-rt-main - 2.3.2 - + 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 b9ba926a299667c62d5198158a763892009a1d1d Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 28 Mar 2024 13:24:22 -0500 Subject: [PATCH 201/371] 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 153f42358..ef8aa9302 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -165,8 +165,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 f315d4a4a..2c1b0d33b 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 6ea1495cd..72f7443bf 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -978,6 +978,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 91d254d49bd5fb4acb737fce38d50d669d4bbab8 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 1 May 2024 18:08:27 +0200 Subject: [PATCH 202/371] 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 dd073a3c13b776e742edc3f323b1fc6a6c25ef23 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 21 May 2024 14:28:14 +0200 Subject: [PATCH 203/371] Clamp slider widget values. --- .../gui/displaysettings/SliderPanel.java | 19 +++++++++++++------ .../displaysettings/SliderPanelDouble.java | 16 ++++++++++++---- 2 files changed, 25 insertions(+), 10 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 ea0b8e0e2..a9da3e5f6 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java @@ -72,13 +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 ); + + 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 = new JSpinner(); - - final int min = model.getRangeMin(); - final int max = model.getRangeMax(); - final int val = Math.max( Math.min( model.getCurrentValue(), max ), min ); - spinner.setModel( new SpinnerNumberModel( val, min, max, spinnerStepSize ) ); + 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 4109fa9eb..f707d9d96 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java @@ -90,13 +90,21 @@ public SliderPanelDouble( setLayout( new BorderLayout( 10, 10 ) ); setPreferredSize( SliderPanel.PANEL_SIZE ); + 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 ); + + spinner = new JSpinner(); dmin = model.getRangeMin(); dmax = model.getRangeMax(); - final double val = Math.min( Math.max( model.getCurrentValue(), dmin ), dmax ); - slider = new JSlider( SwingConstants.HORIZONTAL, 0, sliderLength, toSlider( val ) ); - spinner = new JSpinner(); - spinner.setModel( new SpinnerNumberModel( val, dmin, dmax, spinnerStepSize ) ); + 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 e977d06e1a07194ed618dfa22fe5b4e9ca7809b7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 Jul 2024 15:50:47 +0200 Subject: [PATCH 204/371] Add required methods in SpotRoiUtils. We want to retrieve spots as a map from labels to corresponding spots. --- .../trackmate/detection/SpotRoiUtils.java | 191 +++++++++++------- 1 file changed, 114 insertions(+), 77 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 ) From 061da4d5e1aed12a073c5fb1f6a41a04b0c10871 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 22 Jul 2024 15:58:40 +0200 Subject: [PATCH 205/371] 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 189bde70a..ed1021592 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 3d2a41d97..abf89bbcf 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardController.java @@ -218,7 +218,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 f8a41cd6eed5bd73cd5fbf22be6de60ff69e6790 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 23 Jul 2024 11:17:56 +0200 Subject: [PATCH 206/371] 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 2c1b0d33b..341376d58 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 cde3735dd0106a7ba380385dcc0d490a698a488d Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 23 Jul 2024 11:41:37 +0200 Subject: [PATCH 207/371] 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 ++++++++++++++-- 1 file changed, 14 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 ) From 331da002ec1302d0d5db7733eb99f83911f1653b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 23 Jul 2024 17:57:25 +0200 Subject: [PATCH 208/371] 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 6c5465690..c5e76f893 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LabelImageDetector.java @@ -160,10 +160,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 ef8aa9302..1a467b580 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java @@ -50,7 +50,6 @@ import net.imglib2.type.NativeType; 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; import net.imglib2.type.numeric.real.FloatType; @@ -203,109 +202,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 Cursor< BoolType > 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 From bce24f411e8633cbfb302156d6aae498f15c6462 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 8 Apr 2025 17:09:33 +0200 Subject: [PATCH 209/371] Update devel TrackMate major version number to 9. The v9 series will track our efforts in supporting 3D segmentation results in TrackMate. The v8 is reserved for our parallel effort in supporting SOTA algorithms for bacterial dynamics and mask editing. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4d436a357..af8eba615 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ sc.fiji TrackMate - 8.1.7-SNAPSHOT + 9.0.0-SNAPSHOT TrackMate TrackMate plugin for Fiji. From dae89efd2091ea0107c74a1a417186167ff80480 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 27 Oct 2025 11:12:30 -0500 Subject: [PATCH 210/371] Protect is someone asks the nD (n>2) bounds of a 2D spots. --- src/main/java/fiji/plugin/trackmate/SpotRoi.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index 1bf39eec3..670464664 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 * . @@ -78,7 +78,7 @@ public SpotRoi( final double[] x, final double[] y ) { - super( ID ); + super( ID ); this.x = x; this.y = y; } @@ -154,6 +154,8 @@ public int nPoints() @Override public double realMin( final int d ) { + if ( d > 1 ) + return 0; final double[] arr = ( d == 0 ) ? x : y; return getDoublePosition( d ) + Util.min( arr ); } @@ -161,6 +163,8 @@ public double realMin( final int d ) @Override public double realMax( final int d ) { + if ( d > 1 ) + return 0; final double[] arr = ( d == 0 ) ? x : y; return getDoublePosition( d ) + Util.max( arr ); } @@ -422,7 +426,7 @@ public Iterator< T > iterator() /** * Iterates inside a close polygon given by X & Y in pixel coordinates. - * + * * @param * the type of pixel in the image. */ From 587fe8ec521e8f83663caebffee27d3407b794c3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 27 Oct 2025 11:13:06 -0500 Subject: [PATCH 211/371] Put back the createROIInterval method that went with the big rebase. --- .../fiji/plugin/trackmate/util/TMUtils.java | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 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 72f7443bf..c4a4b2bef 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 * . @@ -46,8 +46,10 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.detection.DetectionUtils; import ij.IJ; import ij.ImagePlus; +import ij.gui.Roi; import net.imagej.ImgPlus; import net.imagej.ImgPlusMetadata; import net.imagej.axis.Axes; @@ -72,6 +74,41 @@ public class TMUtils * STATIC METHODS */ + /** + * Returns an {@link Interval} that corresponds to the ROI in the specified + * image. + *

    + * If the image has no ROI, the interval returned is null. For + * 3D images the interval extends over all Z. The interval does not include + * the time dimension nor the channel dimension. It is 2D for 2D images, and + * 3D for 3D images regardless of the presence of C and T. + * + * @param imp + * the image. + * @return a new interval, or null if the image has no ROI. + */ + public static Interval createROIInterval( final ImagePlus imp ) + { + final Roi roi = imp.getRoi(); + if ( roi == null ) + return null; + + final boolean is3D = !DetectionUtils.is2D( imp ); + final long[] min = new long[ is3D ? 3 : 2 ]; + final long[] max = new long[ min.length ]; + + min[ 0 ] = roi.getBounds().x; + max[ 0 ] = roi.getBounds().x + roi.getBounds().width - 1; + min[ 1 ] = roi.getBounds().y; + max[ 1 ] = roi.getBounds().y + roi.getBounds().height - 1; + if ( is3D ) + { + min[ 2 ] = 0; + max[ 2 ] = imp.getNSlices(); + } + return new FinalInterval( min, max ); + } + /** * Returns a new map sorted by its values. * From 0a645c266058d9e63e015a8fdd48e76e560ee3a7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 27 Oct 2025 11:13:36 -0500 Subject: [PATCH 212/371] Fix the spot boundingBox method in the Labkit editor. --- .../editor/labkit/model/TMLabKitUtils.java | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitUtils.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitUtils.java index a8a0e1448..9af3c6e20 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitUtils.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitUtils.java @@ -27,10 +27,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.util.TMUtils; import net.imagej.ImgPlus; import net.imagej.axis.Axes; +import net.imagej.axis.AxisType; import net.imagej.axis.CalibratedAxis; import net.imglib2.RandomAccessibleInterval; import net.imglib2.img.Img; @@ -57,34 +57,22 @@ static final int timeAxis( final Labeling labeling ) static final void boundingBox( final Spot spot, final ImgPlus< UnsignedIntType > img, final long[] min, final long[] max ) { + final double[] rmin = spot.minAsDoubleArray(); + final double[] rmax = spot.maxAsDoubleArray(); final double[] calibration = TMUtils.getSpatialCalibration( img ); - 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 AxisType[] axes = new AxisType[] { Axes.X, Axes.Y, Axes.Z }; + for ( int d = 0; d < max.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.round( rmin[ d ] / calibration[ d ] ); + min[ d ] = Math.max( 0, min[ d ] ); - min[ 0 ] = Math.max( 0, min[ 0 ] ); - min[ 1 ] = Math.max( 0, min[ 1 ] ); - final long width = img.min( img.dimensionIndex( Axes.X ) ) + img.dimension( img.dimensionIndex( Axes.X ) ); - final long height = img.min( img.dimensionIndex( Axes.Y ) ) + img.dimension( img.dimensionIndex( Axes.Y ) ); - max[ 0 ] = Math.min( width, max[ 0 ] ); - max[ 1 ] = Math.min( height, max[ 1 ] ); + max[ d ] = Math.round( rmax[ d ] / calibration[ d ] ); + final AxisType axis = axes[ d ]; + final int axisDim = img.dimensionIndex( axis ); + final long imgMax = img.min( axisDim ) + img.dimension( axisDim ); + max[ d ] = Math.min( imgMax, max[ d ] ); + } } static final Img< UnsignedIntType > copy( final RandomAccessibleInterval< UnsignedIntType > in ) From e8f2857975c96618820967849b40c6ebda6d7f9c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 27 Oct 2025 11:14:29 -0500 Subject: [PATCH 213/371] Fix the spot iteration for Labkit model. --- .../trackmate/gui/editor/labkit/model/TMLabKitModel.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitModel.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitModel.java index 6ae51e8b2..cef771a46 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitModel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitModel.java @@ -35,7 +35,6 @@ import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; -import fiji.plugin.trackmate.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import ij.ImagePlus; @@ -489,7 +488,7 @@ private static void processFrame( { final Label label = labeling.addLabel( spot.getName() ); label.setColor( new ARGBType( colorGen.color( spot ).getRGB() ) ); - final Cursor< UnsignedIntType > c = SpotUtil.iterable( spot, img ).localizingCursor(); + final Cursor< UnsignedIntType > c = spot.iterable( img ).localizingCursor(); while ( c.hasNext() ) { c.fwd(); @@ -526,7 +525,7 @@ private static void processFrame( final Label label = labeling.addLabel( spot.getName() ); label.setColor( new ARGBType( colorGen.color( spot ).getRGB() ) ); - final Cursor< UnsignedIntType > c = SpotUtil.iterable( spot, img ).localizingCursor(); + final Cursor< UnsignedIntType > c = spot.iterable( img ).localizingCursor(); while ( c.hasNext() ) { c.fwd(); From a22284891bd29d2505be05c803589930feafb0cc Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 27 Oct 2025 11:14:48 -0500 Subject: [PATCH 214/371] Fix the MaskUtils.fromThresholdWithROI calls with TrackMate v9 --- .../trackmate/gui/editor/labkit/model/LabkitImporter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/LabkitImporter.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/LabkitImporter.java index c25197656..f0259bb11 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/LabkitImporter.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/LabkitImporter.java @@ -185,7 +185,8 @@ public void run() for ( final Label label : modifiedLabels ) { final IterableRegion< BitType > region = regions.get( label ); - final List< Spot > spots = MaskUtils.fromThresholdWithROI( region, region, calibration, threshold, simplifyContours, numThreads, null ); + final double smoothingScale = -1.; // no smoothing + final List< Spot > spots = MaskUtils.fromThresholdWithROI( region, region, calibration, threshold, simplifyContours, smoothingScale, numThreads, null ); map.put( label, spots ); } allModifiedSpots.put( lt, map ); @@ -200,7 +201,8 @@ public void run() for ( long t = minT; t <= maxT; t++ ) { final IntervalView< BitType > slice = Views.hyperSlice( region, timeAxis, t ); - final List< Spot > spots = MaskUtils.fromThresholdWithROI( slice, slice, calibration, threshold, simplifyContours, numThreads, null ); + final double smoothingScale = -1.; // no smoothing + final List< Spot > spots = MaskUtils.fromThresholdWithROI( slice, slice, calibration, threshold, simplifyContours, smoothingScale, numThreads, null ); Map< Label, List< Spot > > map = allModifiedSpots.get( Integer.valueOf( ( int ) t ) ); if ( map == null ) From 56742afe722777ec41361ef07ebc73d61a296e2c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 27 Oct 2025 11:36:20 -0500 Subject: [PATCH 215/371] Fix javadoc warnings and errors. --- .../trackmate/TrackMateFactoryBase.java | 8 ++--- .../trackmate/action/LabelImgExporter.java | 20 ++++++++---- .../trackmate/detection/DetectionUtils.java | 10 ++++-- .../plugin/trackmate/graph/GraphUtils.java | 8 +++-- .../plugin/trackmate/util/cli/CLIUtils.java | 22 +++---------- .../util/cli/CommonTrackMateArguments.java | 10 +++--- .../trackmate/util/cli/Configurator.java | 12 +++---- .../GenericDetectionConfigurationPanel.java | 32 +++---------------- 8 files changed, 50 insertions(+), 72 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/TrackMateFactoryBase.java b/src/main/java/fiji/plugin/trackmate/TrackMateFactoryBase.java index 5c13b0e4d..eb6365d41 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMateFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMateFactoryBase.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -53,7 +53,7 @@ public interface TrackMateFactoryBase< F extends TrackMateFactoryBase< F > > ext * {@link SpotDetectorFactory} key. * * @param settings - * the map to marshal. + * the settings map to marshal. * @param element * the JDom element to update. * @return an error message if marshaling was unsuccessful. If successful, diff --git a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java index 38add12ec..68e8d0c49 100644 --- a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.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 * . @@ -211,7 +211,7 @@ public static final ImagePlus createLabelImagePlus( * different from the track IDs and different for each spot. * @param labelIdPainting * specifies how to paint the label ID of spots. - * + * * @return a new {@link ImagePlus}. */ public static final ImagePlus createLabelImagePlus( @@ -297,7 +297,7 @@ public static final ImagePlus createLabelImagePlus( * different from the track IDs and different for each spot. * @param labelIdPainting * specifies how to paint the label ID of spots. - * + * * @return a new {@link ImagePlus}. */ public static final ImagePlus createLabelImagePlus( @@ -385,7 +385,7 @@ public static final ImagePlus createLabelImagePlus( * different from the track IDs and different for each spot. * @param labelIdPainting * specifies how to paint the label ID of spots. - * + * * @return a new {@link Img}. */ public static final Img< FloatType > createLabelImg( @@ -484,13 +484,16 @@ public static final Img< FloatType > createLabelImg( * Creates a new label {@link ImgPlus} of {@link FloatType} where the spots * are painted with an ID. All visible spots are painted, whether they are * in a track or not. - * + * * @param spots * the spots to paint. * @param dimensions * 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. If * false they will be painted with their shape. @@ -498,11 +501,14 @@ public static final Img< FloatType > createLabelImg( * specifies how to paint the label ID of spots. The * {@link LabelIdPainting#LABEL_IS_TRACK_ID} is not supported and * defaults to {@link LabelIdPainting#LABEL_IS_SPOT_ID}. + * @param outputType + * the pixel type of the output image. * @param logger * a {@link Logger} instance, to report progress of the export * process. - * * @return a new {@link ImgPlus}. + * @param + * the pixel type. */ public static < T extends RealType< T > & NativeType< T > > ImgPlus< T > createLabelImg( final SpotCollection spots, diff --git a/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java b/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java index 4c54f41fc..f10edac04 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/DetectionUtils.java @@ -353,7 +353,7 @@ public static final Interval squeeze( final Interval interval ) /** * Applies a simple 3x3 median filter to the target image. - * + * * @param * the pixel type in the image. * @param image @@ -594,6 +594,10 @@ public static final < T extends RealType< T > > void normalize( final Iterable< * * @param img * the image to wrap. + * @return a new ImagePlus. + * @param + * the type of pixels in the image. Must extend {@link RealType} + * and {@link NativeType}. */ public static < T extends RealType< T > & NativeType< T > > ImagePlus wrap( final ImgPlus< T > img ) { @@ -630,11 +634,11 @@ public static < T extends RealType< T > & NativeType< T > > ImagePlus wrap( fina * @param c * the channel to extract (0-based). If negative, all channels * are included. - * @param nameGen + * @param namegen * a generator for the name of the output ImagePlus. * @return a new list of ImagePlus. */ - public static < T extends RealType< T > & NativeType< T > > List< ImagePlus > splitSingleTimePoints( final ImgPlus< T > img, final Interval interval, final int c, final Function< Long, String > namegen2 ) + public static < T extends RealType< T > & NativeType< T > > List< ImagePlus > splitSingleTimePoints( final ImgPlus< T > img, final Interval interval, final int c, final Function< Long, String > namegen ) { final int zIndex = img.dimensionIndex( Axes.Z ); final int cIndex = img.dimensionIndex( Axes.CHANNEL ); diff --git a/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java b/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java index 5bdd82da1..5b4ba37b6 100644 --- a/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java +++ b/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java @@ -52,6 +52,10 @@ public class GraphUtils * @return a {@link SimpleWeightedGraph} with the same vertices and edges as * the input graph, but with undirected edges and the maximum weight * between any two vertices + * @param + * the vertex type. + * @param + * the edge type. */ public static < V, E > SimpleWeightedGraph< V, E > convertToSimpleWeightedGraph( final SimpleDirectedWeightedGraph< V, E > directedGraph ) { @@ -86,7 +90,7 @@ public static < V, E > SimpleWeightedGraph< V, E > convertToSimpleWeightedGraph( /** * Pretty-prints a model. - * + * * @param model * the model. * @return a pretty-print string representation of a {@link TrackModel}, as @@ -427,7 +431,7 @@ private static char[] makeChars( final int width, final char c ) /** * Returns the siblings of a spot. That is: all the spots that have the same * predecessor. - * + * * @param cache * a neighbor cache. * @param spot diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java b/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java index 37a6a27d5..71e7e84c1 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -75,15 +75,6 @@ public static final Process createProcess( final CLIConfigurator cli, final File { final List< String > cmd = CommandBuilder.build( cli ); final ProcessBuilder pb = new ProcessBuilder( cmd ); - if ( cli instanceof CondaCLIConfigurator ) - { - // Env variables. - final Map< String, String > env = new HashMap<>(); - final String condaRootPrefix = getCondaRootPrefix(); - env.put( "MAMBA_ROOT_PREFIX", condaRootPrefix ); - env.put( "CONDA_ROOT_PREFIX", condaRootPrefix ); - pb.environment().putAll( env ); - } pb.redirectOutput( ProcessBuilder.Redirect.appendTo( logFile ) ); pb.redirectError( ProcessBuilder.Redirect.appendTo( logFile ) ); return pb.start(); @@ -328,11 +319,6 @@ public static List< String > preparePythonCommand( final String envName, final S return cmd; } - public static void clearEnvMap() - { - envMap = null; - } - public static Map< String, String > getEnvMap() throws IOException { if ( envMap == null ) @@ -482,7 +468,7 @@ public static String findDefaultCondaPath() throws IllegalArgumentException * from https://stackoverflow.com/a/20280989/201698 * * @param path - * the path to delete recursively on shutdown. + * the path to delete on shutdown. */ public static void recursiveDeleteOnShutdownHook( final Path path ) { diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CommonTrackMateArguments.java b/src/main/java/fiji/plugin/trackmate/util/cli/CommonTrackMateArguments.java index 870abb2b8..a3140eb6a 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CommonTrackMateArguments.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/CommonTrackMateArguments.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -54,7 +54,7 @@ public class CommonTrackMateArguments * the configurator to which to add the argument. * @param nChannels * how many channels in the input image. - * @return the created argument. + * @return the integer target channel argument. */ public static IntArgument addTargetChannel( final Configurator config, final int nChannels ) { @@ -112,7 +112,7 @@ public static DoubleArgument addRadius( final Configurator config, final String * the config to which to add the argument. * @param units * the units of the diameter to display. - * @return the created argument. + * @return the diameter double argument. */ public static DoubleArgument addDiameter( final Configurator config, final String units ) { diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/Configurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/Configurator.java index 4c6b347fc..ae9079506 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/Configurator.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/Configurator.java @@ -2,18 +2,18 @@ * #%L * TrackMate: your buddy for everyday tracking. * %% - * Copyright (C) 2010 - 2026 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 * . @@ -116,7 +116,7 @@ public List< SelectableArguments > getSelectables() * used concurrently in the same command. This will be used when creating * UIs. * - * @return a new {@link SelectableArguments} instance. + * @return the created selectable arguments group. */ protected SelectableArguments addSelectableArguments() { @@ -790,9 +790,9 @@ protected ChoiceAdder addChoiceArgument() * * @param extraArg * the argument to add to this CLI config. - * @param - * the argument type. * @return the argument + * @param + * the type of the argument. */ protected < T extends Argument< ?, ? > > T addExtraArgument( final T extraArg ) { diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/GenericDetectionConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/util/cli/GenericDetectionConfigurationPanel.java index 30e029d23..f5c0df59b 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/GenericDetectionConfigurationPanel.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/GenericDetectionConfigurationPanel.java @@ -1,24 +1,3 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; import java.awt.BorderLayout; @@ -65,16 +44,15 @@ public GenericDetectionConfigurationPanel( } /** - * Creates a basic {@link DetectionPreview}. Can be overridden by - * subclasses. + * Creates a basic {@link DetectionPreview}. Can be overridden by subclasses * * @param model - * the model to populate the preview with. + * the model to update with the previewed spots. * @param settings - * the settings to use to configure the preview. + * the settings to use to run the detection. * @param factorySupplier - * a supplier for the detector factory to use in the preview. - * @return a new {@link DetectionPreview}. + * a supplier for the detector factory. + * @return the detection preview object. */ protected DetectionPreview getDetectionPreview( final Model model, From ab48d717d01c6a64969b131d750c36d394983f14 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 27 Oct 2025 11:57:59 -0500 Subject: [PATCH 216/371] Fix the mask, threshold and label image detector config panel layout. --- .../detector/ThresholdDetectorConfigurationPanel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3bb0c6315..8a1a3c7e1 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 @@ -135,10 +135,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, 0, 47 }; + gridBagLayout.rowHeights = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 150 }; 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, 0.0 }; + gridBagLayout.rowWeights = new double[] { 0., 1., 0., 0., 0., 0., 0., 0.1 }; setLayout( gridBagLayout ); final JLabel jLabelDetectorName = new JLabel( detectorName, ThresholdDetectorFactory.ICON, JLabel.CENTER ); From ec3863594fe446b60b597e24f961348101bc3891 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 29 Jun 2026 14:06:45 +0200 Subject: [PATCH 217/371] Put back missing clearEnvMap() method. --- src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java b/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java index 71e7e84c1..d9f0ce6a9 100644 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java @@ -597,6 +597,11 @@ public static boolean isValidPath( final String pathString ) } } + public static void clearEnvMap() + { + envMap = null; + } + public static void main( final String[] args ) throws Exception { System.out.println( "Conda path: " + getCondaPath() ); From 90c85975c9c6f9f725e52c6919750aa2587c7507 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 29 Jun 2026 14:15:41 +0200 Subject: [PATCH 218/371] Remove unused dependencies --- pom.xml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pom.xml b/pom.xml index af8eba615..09e9a2c2e 100644 --- a/pom.xml +++ b/pom.xml @@ -236,10 +236,6 @@ net.imglib2 imglib2-mesh - - net.imagej - imagej-ops - sc.fiji bigvolumeviewer @@ -263,7 +259,6 @@ ${scijava.natives.classifier.jogl} - sc.fiji @@ -309,18 +304,10 @@ - - com.google.guava - guava - com.github.vlsi.mxgraph jgraphx - com.itextpdf itextpdf From f62a522654e06311e533c3ce4c2d8401e4dd2284 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 29 Jun 2026 16:59:38 +0200 Subject: [PATCH 219/371] Update parent to pom-scijava v45.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 09e9a2c2e..42ae0d388 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.scijava pom-scijava - 43.0.0 + 45.0.0 From 10ff31386f59f5debde8ebc112f7c3aa6b9987a1 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 1 Jul 2026 18:19:10 +0200 Subject: [PATCH 220/371] Allow de/serializing enum parameters. It makes sense to have some parameters which values are Enum. Before this commit, they were serialized to XML as the toString() representation of the enum, but the deseriaization failed. Now this is supported. We serialize directly the toString() of the enum, not the name(), which makes it easier for users that do not check the TrackMate code. --- .../plugin/trackmate/TrackMateFactoryBase.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/TrackMateFactoryBase.java b/src/main/java/fiji/plugin/trackmate/TrackMateFactoryBase.java index eb6365d41..fcc5319bc 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMateFactoryBase.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMateFactoryBase.java @@ -136,6 +136,22 @@ else if ( klass == Boolean.class ) { value = att.getBooleanValue(); } + else if ( klass.isEnum() ) + { + @SuppressWarnings( "unchecked" ) + final Class< Enum< ? > > enumClass = ( Class< Enum< ? > > ) klass; + final Enum< ? >[] enums = enumClass.getEnumConstants(); + Enum< ? > tmp = null; + for ( final Enum< ? > e : enums ) + { + if ( e.toString().equals( att.getValue() ) ) + { + tmp = e; + break; + } + } + value = tmp; + } else { return "When unmarshalling: Unsupported type " + klass.getSimpleName() + " for parameter " + keyString + " in factory " + getName(); From 3722de31103d4f481e13f8abefad026195095ffd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 2 Jul 2026 16:14:28 +0200 Subject: [PATCH 221/371] Add config-ui as a dependency. --- pom.xml | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 42ae0d388..9b7c323a0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 @@ -164,7 +166,7 @@ - true + true fiji.plugin.trackmate gpl_v3 TrackMate developers. @@ -182,18 +184,26 @@ 8.0.0 10.6.7 - - 21 - [21,) - https://docs.oracle.com/en/java/javase/21/docs/api/ - + 21 + [21,) + + https://docs.oracle.com/en/java/javase/21/docs/api/ + - false + which is a depdency of Labkit.' --> + false + + + org.scijava + config-ui + 0.0.1-SNAPSHOT + + org.slf4j slf4j-simple @@ -247,7 +257,7 @@ org.jogamp.gluegen gluegen-rt - + org.jogamp.gluegen gluegen-rt From 5e4e2ab2405acfcddf1c8cb8353fc2080b7a8d7c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 2 Jul 2026 16:15:03 +0200 Subject: [PATCH 222/371] An intermediate class For TrackMate modules using Configurator. + */ --- .../util/config/TrackMateConfigurator.java | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/util/config/TrackMateConfigurator.java diff --git a/src/main/java/fiji/plugin/trackmate/util/config/TrackMateConfigurator.java b/src/main/java/fiji/plugin/trackmate/util/config/TrackMateConfigurator.java new file mode 100644 index 000000000..a0077f668 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/config/TrackMateConfigurator.java @@ -0,0 +1,167 @@ +package fiji.plugin.trackmate.util.config; + +import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_DO_MEDIAN_FILTERING; +import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_DO_SUBPIXEL_LOCALIZATION; +import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_RADIUS; +import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_TARGET_CHANNEL; +import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_THRESHOLD; +import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_DO_MEDIAN_FILTERING; +import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_DO_SUBPIXEL_LOCALIZATION; +import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_RADIUS; +import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL; +import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_THRESHOLD; +import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS; +import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_SMOOTHING_SCALE; + +import org.scijava.ui.config.Configurator; +import org.scijava.ui.config.Parameters.BooleanParam; +import org.scijava.ui.config.Parameters.DoubleParam; +import org.scijava.ui.config.Parameters.IntParam; + +/** + * An intermediate class to facilitate adding common TrackMate parameters to a + * {@link Configurator}. + */ +public abstract class TrackMateConfigurator extends Configurator +{ + + protected TrackMateConfigurator( final String name, final String help ) + { + super( name, help ); + } + + protected BooleanParam addSimplifyContour() + { + final BooleanParam param = addBooleanParameter() + .key( KEY_SIMPLIFY_CONTOURS ) + .defaultValue( true ) + .name( "Simplify contour" ) + .help( "If true contours will be simplified with fewer control points." ) + .visible( true ) + .get(); + param.set( true ); + return param; + } + + protected DoubleParam addSmoothContour( final String units ) + { + final DoubleParam param = addDoubleParameter() + .key( KEY_SMOOTHING_SCALE ) + .defaultValue( 0. ) + .min( 0. ) + .max( 20. ) + .units( units ) + .name( "Smooth contour" ) + .help( "If > 0, contours will be smoothed over this radius." ) + .visible( true ) + .get(); + param.set( 0. ); + // No need to translate: TrackMate expects physical units + return param; + } + + /** + * Creates an argument used to specify on what channel in the input image to + * operate on and add it to this configurator. + *

    + * The channel index is 1-based. + * + * @param nChannels + * how many channels in the input image. + * @return the integer target channel argument. + */ + protected IntParam addTargetChannel( final int nChannels ) + { + final IntParam param = addIntParameter() + .key( KEY_TARGET_CHANNEL ) + .defaultValue( DEFAULT_TARGET_CHANNEL ) + .name( "Target channel" ) + .help( "Index of the channel to process." ) + .visible( true ) + .min( 1 ) // 1-based + .max( Integer.valueOf( nChannels ) ) + .get(); + param.set( DEFAULT_TARGET_CHANNEL ); + return param; + } + + protected DoubleParam addRadius( final String units ) + { + final DoubleParam param = addDoubleParameter() + .key( KEY_RADIUS ) + .defaultValue( DEFAULT_RADIUS ) + .units( units ) + .name( "Radius" ) + .help( "Radius of the objects to detect, in " + units + "." ) + .visible( true ) + .get(); + param.set( DEFAULT_RADIUS ); + return param; + } + + /** + * Adds a diameter argument to this configurator. + *

    + * Here there is a gotcha: the value displayed in the UI is the diameter, + * but the value stored and returned is the radius. Therefore, we add a + * translator that divides the value by 2. + * + * @param units + * the units of the diameter to display. + * @return the diameter double argument. + */ + protected DoubleParam addDiameter( final String units ) + { + final DoubleParam param = addDoubleParameter() + .key( KEY_RADIUS ) + .defaultValue( DEFAULT_RADIUS ) + .units( units ) + .name( "Diameter" ) + .help( "Diameter of the objects to detect." ) + .visible( true ) + .get(); + param.set( DEFAULT_RADIUS ); + // Add a translator from radius (stored) to diameter (displayed). + setDisplayTranslator( param, r -> r * 2., d -> d / 2. ); + return param; + } + + protected DoubleParam addThreshold() + { + final DoubleParam param = addDoubleParameter() + .key( KEY_THRESHOLD ) + .defaultValue( DEFAULT_THRESHOLD ) + .name( "Threshold" ) + .help( "The threshold to apply to the detector." ) + .visible( true ) + .get(); + param.set( DEFAULT_THRESHOLD ); + return param; + } + + protected BooleanParam addSubpixelLocalization() + { + final BooleanParam param = addBooleanParameter() + .key( KEY_DO_SUBPIXEL_LOCALIZATION ) + .defaultValue( DEFAULT_DO_SUBPIXEL_LOCALIZATION ) + .name( "Sub-pixel localization" ) + .help( "If true, the detector will try to localize spots with sub-pixel accuracy." ) + .visible( true ) + .get(); + param.set( DEFAULT_DO_SUBPIXEL_LOCALIZATION ); + return param; + } + + protected BooleanParam addMedianFiltering() + { + final BooleanParam flag = addBooleanParameter() + .key( KEY_DO_MEDIAN_FILTERING ) + .defaultValue( DEFAULT_DO_MEDIAN_FILTERING ) + .name( "Median filtering" ) + .help( "If true, the detector will apply a median filter to the image before detection." ) + .visible( true ) + .get(); + flag.set( DEFAULT_DO_MEDIAN_FILTERING ); + return flag; + } +} From 1c4edb718e3bd5c874b387b7734a5a81936a0b65 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 2 Jul 2026 16:15:35 +0200 Subject: [PATCH 223/371] A generic detector config panel using config-ui Configurator. --- .../util/config/GenericConfigPanel.java | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java diff --git a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java new file mode 100644 index 000000000..1e9a5283e --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java @@ -0,0 +1,192 @@ +package fiji.plugin.trackmate.util.config; + +import static org.scijava.ui.config.utils.GuiUtils.isLikelyUrl; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Image; +import java.util.Map; +import java.util.function.DoubleConsumer; +import java.util.function.Supplier; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; +import javax.swing.UIManager; + +import org.scijava.ui.config.Configurator; +import org.scijava.ui.config.visitors.Maps; +import org.scijava.ui.config.visitors.gui.GuiBuilder; +import org.scijava.ui.config.visitors.gui.GuiBuilder.ConfigPanel; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.DoubleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.IntElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.StyleElement; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase; +import fiji.plugin.trackmate.gui.GuiUtils; +import fiji.plugin.trackmate.gui.components.ConfigurationPanel; +import fiji.plugin.trackmate.util.DetectionPreview; +import fiji.plugin.trackmate.util.DetectionPreview.Builder; +import fiji.plugin.trackmate.util.DetectionPreviewPanel; +import fiji.plugin.trackmate.util.cli.HasInteractivePreview; + +public class GenericConfigPanel extends ConfigurationPanel +{ + + private static final long serialVersionUID = 1L; + + public static Font FONT = UIManager.getFont( "Label.font" ); + + private final Configurator config; + + private final ConfigPanel mainPanel; + + public GenericConfigPanel( + final Settings settings, + final Model model, + final Configurator config, + final Supplier< SpotDetectorFactoryBase< ? > > factorySupplier ) + { + this.config = config; + + final BorderLayout borderLayout = new BorderLayout(); + setLayout( borderLayout ); + + /* + * HEADER + */ + + final JPanel header = new JPanel(); + header.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); + header.setLayout( new BoxLayout( header, BoxLayout.Y_AXIS ) ); + + final Image icon = config.getIcon().getScaledInstance( 64, 64, Image.SCALE_SMOOTH ); + final JLabel lblDetector = new JLabel( config.getName(), new ImageIcon( icon ), JLabel.RIGHT ); + lblDetector.setFont( FONT.deriveFont( Font.BOLD ) ); + lblDetector.setHorizontalAlignment( SwingConstants.CENTER ); + lblDetector.setAlignmentX( JLabel.CENTER_ALIGNMENT ); + header.add( lblDetector ); + + final String help = config.getHelp(); + final String text = help.trim(); + final JEditorPane infoDisplay; + if ( isLikelyUrl( text ) ) + infoDisplay = GuiUtils.infoDisplay( "" + text + "", false ); + else + infoDisplay = GuiUtils.infoDisplay( help, true ); + infoDisplay.setMaximumSize( new Dimension( 100_000, 40 ) ); + header.add( Box.createVerticalStrut( 5 ) ); + header.add( infoDisplay ); + add( header, BorderLayout.NORTH ); + + /* + * CONFIG + */ + + this.mainPanel = GuiBuilder.build( config ); + final JScrollPane scrollPane = new JScrollPane( mainPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); + scrollPane.setBorder( null ); + scrollPane.getVerticalScrollBar().setUnitIncrement( 16 ); + add( scrollPane, BorderLayout.CENTER ); + + /* + * PREVIEW + */ + + final DetectionPreview detectionPreview = getDetectionPreview( model, settings, factorySupplier ); + final DetectionPreviewPanel p = detectionPreview.getPanel(); + add( p, BorderLayout.SOUTH ); + } + + @Override + public void setSettings( final Map< String, Object > settings ) + { + Maps.fromMap( settings, config ); + mainPanel.refresh(); + } + + @Override + public Map< String, Object > getSettings() + { + return Maps.toMap( config ); + } + + @Override + public void clean() + {} + + /** + * Creates a basic {@link DetectionPreview}. Can be overridden by subclasses + * + * @param model + * the model to update with the previewed spots. + * @param settings + * the settings to use to run the detection. + * @param factorySupplier + * a supplier for the detector factory. + * @return the detection preview object. + */ + protected DetectionPreview getDetectionPreview( + final Model model, + final Settings settings, + final Supplier< SpotDetectorFactoryBase< ? > > factorySupplier ) + { + final Builder builder = DetectionPreview.create() + .model( model ) + .settings( settings ) + .detectorFactory( factorySupplier.get() ) + .detectionSettingsSupplier( () -> getSettings() ); + if ( config instanceof HasInteractivePreview ) + { + final HasInteractivePreview hasPreview = ( HasInteractivePreview ) config; + + final String key = hasPreview.getPreviewArgumentKey(); + builder.thresholdKey( key ); + + if ( key != null ) + { + final DoubleConsumer thresholdUpdater; + final StyleElement element = mainPanel.getStyleElement( key ); + if ( element instanceof DoubleElement ) + { + thresholdUpdater = t -> { + ( ( DoubleElement ) element ).set( t ); + mainPanel.refresh(); + }; + } + else if ( element instanceof BoundedDoubleElement ) + { + thresholdUpdater = t -> { + ( ( BoundedDoubleElement ) element ).set( t ); + mainPanel.refresh(); + }; + } + else if ( element instanceof IntElement ) + { + final IntElement el = ( IntElement ) element; + thresholdUpdater = t -> { + el.set( ( int ) t ); + mainPanel.refresh(); + }; + } + else + { + throw new IllegalStateException( "Cannot create interactive thresholding preview for arguments that map of an element of class: " + element.getClass().getDeclaringClass() ); + } + builder.thresholdUpdater( thresholdUpdater ); + } + builder.axisLabel( hasPreview.getPreviewAxisLabel() ); + } + return builder.get(); + } +} From 1872632f8b2ede9f12f3c3ae570782c1f04686a0 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 4 Jul 2026 17:02:57 +0200 Subject: [PATCH 224/371] Generic config panel with configurator for detectors and trackers. The etectors have an option to generate a preview for the current frame automatically. --- .../util/config/GenericConfigPanel.java | 98 +---------------- .../config/GenericConfigPanelPreview.java | 102 ++++++++++++++++++ .../util/config/HasInteractivePreview.java | 54 ++++++++++ 3 files changed, 160 insertions(+), 94 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java create mode 100644 src/main/java/fiji/plugin/trackmate/util/config/HasInteractivePreview.java diff --git a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java index 1e9a5283e..fa593d5d0 100644 --- a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java +++ b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java @@ -7,8 +7,6 @@ import java.awt.Font; import java.awt.Image; import java.util.Map; -import java.util.function.DoubleConsumer; -import java.util.function.Supplier; import javax.swing.BorderFactory; import javax.swing.Box; @@ -25,20 +23,9 @@ import org.scijava.ui.config.visitors.Maps; import org.scijava.ui.config.visitors.gui.GuiBuilder; import org.scijava.ui.config.visitors.gui.GuiBuilder.ConfigPanel; -import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; -import org.scijava.ui.config.visitors.gui.elements.StyleElements.DoubleElement; -import org.scijava.ui.config.visitors.gui.elements.StyleElements.IntElement; -import org.scijava.ui.config.visitors.gui.elements.StyleElements.StyleElement; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase; + import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.components.ConfigurationPanel; -import fiji.plugin.trackmate.util.DetectionPreview; -import fiji.plugin.trackmate.util.DetectionPreview.Builder; -import fiji.plugin.trackmate.util.DetectionPreviewPanel; -import fiji.plugin.trackmate.util.cli.HasInteractivePreview; public class GenericConfigPanel extends ConfigurationPanel { @@ -47,15 +34,11 @@ public class GenericConfigPanel extends ConfigurationPanel public static Font FONT = UIManager.getFont( "Label.font" ); - private final Configurator config; + protected final Configurator config; - private final ConfigPanel mainPanel; + protected final ConfigPanel mainPanel; - public GenericConfigPanel( - final Settings settings, - final Model model, - final Configurator config, - final Supplier< SpotDetectorFactoryBase< ? > > factorySupplier ) + public GenericConfigPanel( final Configurator config ) { this.config = config; @@ -98,14 +81,6 @@ public GenericConfigPanel( scrollPane.setBorder( null ); scrollPane.getVerticalScrollBar().setUnitIncrement( 16 ); add( scrollPane, BorderLayout.CENTER ); - - /* - * PREVIEW - */ - - final DetectionPreview detectionPreview = getDetectionPreview( model, settings, factorySupplier ); - final DetectionPreviewPanel p = detectionPreview.getPanel(); - add( p, BorderLayout.SOUTH ); } @Override @@ -124,69 +99,4 @@ public Map< String, Object > getSettings() @Override public void clean() {} - - /** - * Creates a basic {@link DetectionPreview}. Can be overridden by subclasses - * - * @param model - * the model to update with the previewed spots. - * @param settings - * the settings to use to run the detection. - * @param factorySupplier - * a supplier for the detector factory. - * @return the detection preview object. - */ - protected DetectionPreview getDetectionPreview( - final Model model, - final Settings settings, - final Supplier< SpotDetectorFactoryBase< ? > > factorySupplier ) - { - final Builder builder = DetectionPreview.create() - .model( model ) - .settings( settings ) - .detectorFactory( factorySupplier.get() ) - .detectionSettingsSupplier( () -> getSettings() ); - if ( config instanceof HasInteractivePreview ) - { - final HasInteractivePreview hasPreview = ( HasInteractivePreview ) config; - - final String key = hasPreview.getPreviewArgumentKey(); - builder.thresholdKey( key ); - - if ( key != null ) - { - final DoubleConsumer thresholdUpdater; - final StyleElement element = mainPanel.getStyleElement( key ); - if ( element instanceof DoubleElement ) - { - thresholdUpdater = t -> { - ( ( DoubleElement ) element ).set( t ); - mainPanel.refresh(); - }; - } - else if ( element instanceof BoundedDoubleElement ) - { - thresholdUpdater = t -> { - ( ( BoundedDoubleElement ) element ).set( t ); - mainPanel.refresh(); - }; - } - else if ( element instanceof IntElement ) - { - final IntElement el = ( IntElement ) element; - thresholdUpdater = t -> { - el.set( ( int ) t ); - mainPanel.refresh(); - }; - } - else - { - throw new IllegalStateException( "Cannot create interactive thresholding preview for arguments that map of an element of class: " + element.getClass().getDeclaringClass() ); - } - builder.thresholdUpdater( thresholdUpdater ); - } - builder.axisLabel( hasPreview.getPreviewAxisLabel() ); - } - return builder.get(); - } } diff --git a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java new file mode 100644 index 000000000..22975e21f --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java @@ -0,0 +1,102 @@ +package fiji.plugin.trackmate.util.config; + +import java.awt.BorderLayout; +import java.util.function.DoubleConsumer; +import java.util.function.Supplier; + +import org.scijava.ui.config.Configurator; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.DoubleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.IntElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.StyleElement; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase; +import fiji.plugin.trackmate.util.DetectionPreview; +import fiji.plugin.trackmate.util.DetectionPreview.Builder; +import fiji.plugin.trackmate.util.DetectionPreviewPanel; + +public class GenericConfigPanelPreview extends GenericConfigPanel +{ + + private static final long serialVersionUID = 1L; + + public GenericConfigPanelPreview( + final Settings settings, + final Model model, + final Configurator config, + final Supplier< SpotDetectorFactoryBase< ? > > factorySupplier ) + { + super( config ); + + final DetectionPreview detectionPreview = getDetectionPreview( model, settings, factorySupplier ); + final DetectionPreviewPanel p = detectionPreview.getPanel(); + add( p, BorderLayout.SOUTH ); + } + + /** + * Creates a basic {@link DetectionPreview}. Can be overridden by subclasses + * + * @param model + * the model to update with the previewed spots. + * @param settings + * the settings to use to run the detection. + * @param factorySupplier + * a supplier for the detector factory. + * @return the detection preview object. + */ + protected DetectionPreview getDetectionPreview( + final Model model, + final Settings settings, + final Supplier< SpotDetectorFactoryBase< ? > > factorySupplier ) + { + final Builder builder = DetectionPreview.create() + .model( model ) + .settings( settings ) + .detectorFactory( factorySupplier.get() ) + .detectionSettingsSupplier( () -> getSettings() ); + if ( config instanceof HasInteractivePreview ) + { + final HasInteractivePreview hasPreview = ( HasInteractivePreview ) config; + + final String key = hasPreview.getPreviewArgumentKey(); + builder.thresholdKey( key ); + + if ( key != null ) + { + final DoubleConsumer thresholdUpdater; + final StyleElement element = mainPanel.getStyleElement( key ); + if ( element instanceof DoubleElement ) + { + thresholdUpdater = t -> { + ( ( DoubleElement ) element ).set( t ); + mainPanel.refresh(); + }; + } + else if ( element instanceof BoundedDoubleElement ) + { + thresholdUpdater = t -> { + ( ( BoundedDoubleElement ) element ).set( t ); + mainPanel.refresh(); + }; + } + else if ( element instanceof IntElement ) + { + final IntElement el = ( IntElement ) element; + thresholdUpdater = t -> { + el.set( ( int ) t ); + mainPanel.refresh(); + }; + } + else + { + throw new IllegalStateException( "Cannot create interactive thresholding preview for arguments that map of an element of class: " + element.getClass().getDeclaringClass() ); + } + builder.thresholdUpdater( thresholdUpdater ); + } + builder.axisLabel( hasPreview.getPreviewAxisLabel() ); + } + return builder.get(); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/util/config/HasInteractivePreview.java b/src/main/java/fiji/plugin/trackmate/util/config/HasInteractivePreview.java new file mode 100644 index 000000000..8b24d352f --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/config/HasInteractivePreview.java @@ -0,0 +1,54 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2026 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.config; + +/** + * Interface for {@link Configurator}s which settings can be previewed with + * {@link fiji.plugin.trackmate.util.DetectionPreview}. + */ +public interface HasInteractivePreview +{ + + /** + * Declares the argument key and axis label to be used in the + * {@link fiji.plugin.trackmate.util.DetectionPreview} GUI. + * + * @return argumentKey the argument key. This is the key used in the + * {@link fiji.plugin.trackmate.util.cli.Configurator.Argument#getKey()}. + */ + public default String getPreviewArgumentKey() + { + return null; + } + + /** + * Declares the axis label to be used in the + * {@link fiji.plugin.trackmate.util.DetectionPreview} GUI. + * + * @return axisLabel the label to be used for the axis in the detection + * preview histogram. + */ + public default String getPreviewAxisLabel() + { + return null; + } +} From e3e0f85a98a8f500529c92bae5e855cd82097bc3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 4 Jul 2026 17:03:26 +0200 Subject: [PATCH 225/371] Base interface for detector and tracker factories using the Configurator --- .../util/config/FactoryGenericConfig.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/util/config/FactoryGenericConfig.java diff --git a/src/main/java/fiji/plugin/trackmate/util/config/FactoryGenericConfig.java b/src/main/java/fiji/plugin/trackmate/util/config/FactoryGenericConfig.java new file mode 100644 index 000000000..99341106e --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/config/FactoryGenericConfig.java @@ -0,0 +1,82 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2026 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.config; + +import org.scijava.ui.config.Configurator; + +import fiji.plugin.trackmate.TrackMateModule; +import fiji.plugin.trackmate.visualization.ViewUtils; +import ij.ImagePlus; +import net.imagej.ImgPlus; +import net.imglib2.img.display.imagej.ImageJFunctions; + +/** + * Base interface for detector and tracker factories that need to be configured + * with a {@link Configurator} instance. + * + * @author Jean-Yves Tinevez + * + * @param + * the type of {@link Configurator} used to configure the factory. + */ +public interface FactoryGenericConfig< C extends Configurator > extends TrackMateModule +{ + + /** + * Creates a new configurator for this detector factory, based on the + * specified image. + * + * @param imp + * the input image to configure the detector for. + * @return a new {@link Configurator}. + */ + public C createConfig( ImagePlus imp ); + + /** + * Creates a new configurator for this detector factory, based on the + * specified image. + * + * @param img + * the input image to configure the detector for. + * @return a new {@link Configurator}. + */ + public default C createConfig( final ImgPlus< ? > img ) + { + @SuppressWarnings( { "unchecked", "rawtypes" } ) + final ImagePlus imp = ImageJFunctions.wrap( ( ImgPlus ) img, "wrapped" ); + return createConfig( imp ); + } + + /** + * Creates a new configurator for this factory. + * + * @return a new {@link Configurator}. + */ + public default C createConfig() + { + final int nZ = 2; // Force 3D + final int nT = 2; // Force timelapse + final double[] calibration = new double[] { 1., 1., 1. }; + final ImagePlus imp = ViewUtils.makeEmptyImagePlus( 32, 32, nZ, nT, calibration ); + return createConfig( imp ); + } +} From 1c8b83bd61ec8e28aeaa9fd20895dc838b4cfb02 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 5 Jul 2026 22:03:36 +0200 Subject: [PATCH 226/371] Interfaces for detector and tracker factories that use a Configurator. --- .../detection/SpotDetectorConfigFactory.java | 69 +++++++++++++++++++ .../tracking/SpotTrackerConfigFactory.java | 59 ++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/detection/SpotDetectorConfigFactory.java create mode 100644 src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerConfigFactory.java diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorConfigFactory.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorConfigFactory.java new file mode 100644 index 000000000..9f246d88a --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorConfigFactory.java @@ -0,0 +1,69 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2021 - 2026 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.Map; + +import org.scijava.ui.config.Configurator; +import org.scijava.ui.config.visitors.Maps; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.gui.components.ConfigurationPanel; +import fiji.plugin.trackmate.util.config.FactoryGenericConfig; +import fiji.plugin.trackmate.util.config.GenericConfigPanelPreview; +import ij.ImagePlus; +import net.imglib2.type.NativeType; +import net.imglib2.type.numeric.RealType; + +/** + * Base interface for detector factories that need to be configured with a + * {@link Configurator} instance. + *

    + * Automatically generates a config panel, default settings and settings + * serialization based on the {@link Configurator} instance. Subclasses need to + * implement at least the {@link #createConfig(ImagePlus)} method that + * instantiates a config based on the input image. + *

    + * In addition they also need to implement {@link SpotDetectorFactory} or + * {@link SpotGlobalDetectorFactory} to create the actual detector. + * + * @author Jean-Yves Tinevez + * + * @param + * the type of {@link Configurator} used to configure the factory. + */ +public interface SpotDetectorConfigFactory< T extends RealType< T > & NativeType< T >, C extends Configurator > extends SpotDetectorFactoryBase< T >, FactoryGenericConfig< C > +{ + + @Override + public default ConfigurationPanel getDetectorConfigurationPanel( final Settings settings, final Model model ) + { + return new GenericConfigPanelPreview( settings, model, createConfig( settings.imp ), () -> this ); + } + + @Override + public default Map< String, Object > getDefaultSettings() + { + return Maps.toMap( createConfig() ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerConfigFactory.java b/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerConfigFactory.java new file mode 100644 index 000000000..d42678070 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerConfigFactory.java @@ -0,0 +1,59 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2026 TrackMate developers. + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +package fiji.plugin.trackmate.tracking; + +import java.util.Map; + +import org.scijava.ui.config.Configurator; +import org.scijava.ui.config.visitors.Maps; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.gui.components.ConfigurationPanel; +import fiji.plugin.trackmate.util.config.FactoryGenericConfig; +import fiji.plugin.trackmate.util.config.GenericConfigPanel; + +/** + * Interface for tracker factories that need to be configured with a + * {@link Configurator} instance. + * + * @author Jean-Yves Tinevez + * + * @param + * the type of {@link Configurator} used to configure the detector + * factory. + */ +public interface SpotTrackerConfigFactory< C extends Configurator > extends SpotTrackerFactory, FactoryGenericConfig< C > +{ + + @Override + public default ConfigurationPanel getTrackerConfigurationPanel( final Model model ) + { + final C config = createConfig(); + return new GenericConfigPanel( config ); + } + + @Override + default Map< String, Object > getDefaultSettings() + { + return Maps.toMap( createConfig() ); + } +} From dfd13935a748bb9d73437a347e7aadd590a08da4 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 5 Jul 2026 22:04:38 +0200 Subject: [PATCH 227/371] The LoG, DoG and Hessian detector now use the new Configurator. Little changes from the CLI configurator, since the new one is based on that. --- .../detection/HessianDetectorFactory.java | 13 ++++---- .../detection/LogDetectorFactory.java | 33 +++++++++---------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java index f399922ad..9ca145412 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java @@ -33,6 +33,7 @@ import java.util.Map; import org.scijava.plugin.Plugin; +import org.scijava.ui.config.Parameters.DoubleParam; import fiji.plugin.trackmate.util.TMUtils; import ij.ImagePlus; @@ -114,7 +115,7 @@ public String getName() } @Override - public HessianDetectorCLI getConfigurator( final ImagePlus imp ) + public HessianDetectorCLI createConfig( final ImagePlus imp ) { final int nChannels = ( imp == null ) ? 1 : imp.getNChannels(); final String units = ( imp == null ) ? "no image" : imp.getCalibration().getUnit(); @@ -126,14 +127,14 @@ public HessianDetectorCLI getConfigurator( final ImagePlus imp ) * * @author Jean-Yves Tinevez */ - public static class HessianDetectorCLI extends LogDetectorCLI + public static class HessianDetectorCLI extends LogDetectorConfig { public HessianDetectorCLI( final int nChannels, final String units ) { super( nChannels, units ); // Diameter in Z - final DoubleArgument diameterZ = addDoubleArgument() + final DoubleParam diameterZ = addDoubleParameter() .key( KEY_RADIUS_Z ) .name( "Diameter along Z" ) .units( units ) @@ -143,10 +144,10 @@ public HessianDetectorCLI( final int nChannels, final String units ) // Convert to diameter for display purposes. setDisplayTranslator( diameterZ, r -> r * 2., d -> d / 2. ); // Change order - arguments.remove( diameterZ ); - arguments.add( 2, diameterZ ); + orderedElements.remove( diameterZ ); + orderedElements.add( 2, diameterZ ); // Normalize quality values - addFlag() + addBooleanParameter() .key( KEY_NORMALIZE ) .name( "Normalize quality values" ) .defaultValue( DEFAULT_NORMALIZE ) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LogDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/LogDetectorFactory.java index 27bf5498f..ff4f33c1e 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LogDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LogDetectorFactory.java @@ -26,11 +26,6 @@ import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_RADIUS; import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL; import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_THRESHOLD; -import static fiji.plugin.trackmate.util.cli.CommonTrackMateArguments.addDiameter; -import static fiji.plugin.trackmate.util.cli.CommonTrackMateArguments.addMedianFiltering; -import static fiji.plugin.trackmate.util.cli.CommonTrackMateArguments.addSubpixelLocalization; -import static fiji.plugin.trackmate.util.cli.CommonTrackMateArguments.addTargetChannel; -import static fiji.plugin.trackmate.util.cli.CommonTrackMateArguments.addThreshold; import java.util.Map; @@ -38,11 +33,11 @@ import org.scijava.plugin.Plugin; -import fiji.plugin.trackmate.detection.LogDetectorFactory.LogDetectorCLI; +import fiji.plugin.trackmate.detection.LogDetectorFactory.LogDetectorConfig; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.util.cli.Configurator; -import fiji.plugin.trackmate.util.cli.HasInteractivePreview; +import fiji.plugin.trackmate.util.config.HasInteractivePreview; +import fiji.plugin.trackmate.util.config.TrackMateConfigurator; import ij.ImagePlus; import net.imagej.ImgPlus; import net.imglib2.Interval; @@ -51,7 +46,7 @@ import net.imglib2.type.numeric.RealType; @Plugin( type = SpotDetectorFactory.class ) -public class LogDetectorFactory< T extends RealType< T > & NativeType< T > > implements SpotDetectorFactory< T >, SpotDetectorFactoryGenericConfig< T, LogDetectorCLI > +public class LogDetectorFactory< T extends RealType< T > & NativeType< T > > implements SpotDetectorFactory< T >, SpotDetectorConfigFactory< T, LogDetectorConfig > { /** A string key identifying this factory. */ @@ -72,6 +67,7 @@ public class LogDetectorFactory< T extends RealType< T > & NativeType< T > > imp public static final ImageIcon ICON = new ImageIcon( Icons.class.getResource( "images/LoG-icon-64px.png" ) ); + @Override public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, Object > settings, final Interval interval, final int frame ) { @@ -113,11 +109,11 @@ public ImageIcon getIcon() } @Override - public LogDetectorCLI getConfigurator( final ImagePlus imp ) + public LogDetectorConfig createConfig( final ImagePlus imp ) { final int nChannels = ( imp == null ) ? 1 : imp.getNChannels( ); final String units = ( imp == null ) ? "no image" : imp.getCalibration().getUnit(); - return new LogDetectorCLI( nChannels, units ); + return new LogDetectorConfig( nChannels, units ); } /** @@ -126,16 +122,17 @@ public LogDetectorCLI getConfigurator( final ImagePlus imp ) * * @author Jean-Yves Tinevez */ - public static class LogDetectorCLI extends Configurator implements HasInteractivePreview + public static class LogDetectorConfig extends TrackMateConfigurator implements HasInteractivePreview { - public LogDetectorCLI( final int nChannels, final String units ) + public LogDetectorConfig( final int nChannels, final String units ) { - addTargetChannel( this, nChannels ); - addDiameter( this, units ); - addThreshold( this ); - addMedianFiltering( this ); - addSubpixelLocalization( this ); + super( NAME, INFO_TEXT ); + addTargetChannel( nChannels ); + addDiameter( units ); + addThreshold(); + addMedianFiltering(); + addSubpixelLocalization(); } @Override From 26dc9826052f919ee96d5ecc3a3a5a684f850937 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 5 Jul 2026 22:06:10 +0200 Subject: [PATCH 228/371] Remove the CLI Configorator classes of TrackMate v8. In v9, all calls to Python will be made through Appose, and we are now shutting down this framework, to ease maintanability. But also this means that we force TrackMate module creators to update to Appose as well. --- .../SpotDetectorFactoryGenericConfig.java | 71 - .../SpotTrackerFactoryGenericConfig.java | 62 - .../trackmate/util/cli/CLIConfigurator.java | 70 - .../plugin/trackmate/util/cli/CLIUtils.java | 620 ------- .../trackmate/util/cli/CommandBuilder.java | 276 --- .../util/cli/CommandCLIConfigurator.java | 109 -- .../util/cli/CommonTrackMateArguments.java | 172 -- .../util/cli/CondaCLIConfigurator.java | 194 --- .../trackmate/util/cli/ConfigGuiBuilder.java | 788 --------- .../trackmate/util/cli/Configurator.java | 1378 --------------- .../util/cli/FactoryGenericConfig.java | 80 - .../util/cli/GenericConfigurationPanel.java | 132 -- .../GenericDetectionConfigurationPanel.java | 111 -- .../util/cli/HasInteractivePreview.java | 54 - .../util/cli/TrackMateSettingsBuilder.java | 137 -- .../util/cli/condapath/CondaDetector.java | 1549 ----------------- .../cli/condapath/CondaPathConfigCommand.java | 436 ----- .../trackmate/util/cli/ExampleCommandCLI.java | 240 --- 18 files changed, 6479 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryGenericConfig.java delete mode 100644 src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactoryGenericConfig.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/CommandCLIConfigurator.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/CommonTrackMateArguments.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/CondaCLIConfigurator.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/ConfigGuiBuilder.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/Configurator.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/FactoryGenericConfig.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/GenericConfigurationPanel.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/GenericDetectionConfigurationPanel.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/HasInteractivePreview.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/TrackMateSettingsBuilder.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/condapath/CondaDetector.java delete mode 100644 src/main/java/fiji/plugin/trackmate/util/cli/condapath/CondaPathConfigCommand.java delete mode 100644 src/test/java/fiji/plugin/trackmate/util/cli/ExampleCommandCLI.java diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryGenericConfig.java b/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryGenericConfig.java deleted file mode 100644 index c0790501f..000000000 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotDetectorFactoryGenericConfig.java +++ /dev/null @@ -1,71 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.Map; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.gui.components.ConfigurationPanel; -import fiji.plugin.trackmate.util.cli.Configurator; -import fiji.plugin.trackmate.util.cli.FactoryGenericConfig; -import fiji.plugin.trackmate.util.cli.GenericDetectionConfigurationPanel; -import fiji.plugin.trackmate.util.cli.TrackMateSettingsBuilder; -import net.imglib2.type.NativeType; -import net.imglib2.type.numeric.RealType; - -/** - * Interface for detector factories that need to be configured with a - * {@link Configurator} instance. - * - * @author Jean-Yves Tinevez - * - * @param - * the type of pixels in the input image, which must implement - * {@link RealType} and {@link NativeType}. - * @param - * the type of {@link Configurator} used to configure the detector - * factory. - */ -public interface SpotDetectorFactoryGenericConfig< T extends RealType< T > & NativeType< T >, C extends Configurator > extends SpotDetectorFactoryBase< T >, FactoryGenericConfig< C > -{ - - @Override - public default ConfigurationPanel getDetectorConfigurationPanel( final Settings settings, final Model model ) - { - final C config = getConfigurator( settings.imp ); - return new GenericDetectionConfigurationPanel( - settings, - model, - config, - getName(), - getIcon(), - getUrl(), - () -> this ); - } - - @Override - default Map< String, Object > getDefaultSettings() - { - return TrackMateSettingsBuilder.getDefaultSettings( getConfigurator() ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactoryGenericConfig.java b/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactoryGenericConfig.java deleted file mode 100644 index d42d137ea..000000000 --- a/src/main/java/fiji/plugin/trackmate/tracking/SpotTrackerFactoryGenericConfig.java +++ /dev/null @@ -1,62 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 TrackMate developers. - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ -package fiji.plugin.trackmate.tracking; - -import java.util.Map; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.gui.components.ConfigurationPanel; -import fiji.plugin.trackmate.util.cli.Configurator; -import fiji.plugin.trackmate.util.cli.FactoryGenericConfig; -import fiji.plugin.trackmate.util.cli.GenericConfigurationPanel; -import fiji.plugin.trackmate.util.cli.TrackMateSettingsBuilder; - -/** - * Interface for tracker factories that need to be configured with a - * {@link Configurator} instance. - * - * @author Jean-Yves Tinevez - * - * @param - * the type of {@link Configurator} used to configure the detector - * factory. - */ -public interface SpotTrackerFactoryGenericConfig< C extends Configurator > extends SpotTrackerFactory, FactoryGenericConfig< C > -{ - - @Override - public default ConfigurationPanel getTrackerConfigurationPanel( final Model model ) - { - final C config = getConfigurator(); - return new GenericConfigurationPanel( - config, - getName(), - getIcon(), - getUrl() ); - } - - @Override - default Map< String, Object > getDefaultSettings() - { - return TrackMateSettingsBuilder.getDefaultSettings( getConfigurator() ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java deleted file mode 100644 index 3078c071c..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java +++ /dev/null @@ -1,70 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -/** - * Extends the {@link Configurator} for tools that can be run from the command - * line. - * - * @author Jean-Yves Tinevez - */ -public abstract class CLIConfigurator extends Configurator -{ - - /* - * COMMAND LINE TRANSLATOR. - */ - - protected final Map< Argument< ?, ? >, Function< Object, List< String > > > cliTranslators = new HashMap<>(); - - /** - * Decorates the specified argument with a translator, that will modify the - * value of the argument in the command line output. Because we focus on the - * command line output, the translator returns a list of tokens. - *

    - * This can be used for instance to deal with a diameter expressed in µm - * everywhere throughout TrackMate, then translate it to a pixel value in - * the command line output. - * - * @param arg - * the argument to translate. - * @param translator - * a function that takes the value of the argument and returns a - * list of strings to be used in the command line output. - */ - protected void setCommandTranslator( final Argument< ?, ? > arg, final Function< Object, List< String > > translator ) - { - cliTranslators.put( arg, translator ); - } - - /** - * Returns the command object of this command line tool. - * - * @return the command object, as an argument. - */ - public abstract Argument< ?, ? > getCommandArg(); -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java b/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java deleted file mode 100644 index d9f0ce6a9..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CLIUtils.java +++ /dev/null @@ -1,620 +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.cli; - -import java.awt.Color; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.io.input.Tailer; -import org.apache.commons.io.input.TailerListenerAdapter; -import org.scijava.prefs.PrefService; - -import fiji.plugin.trackmate.Logger; -import fiji.plugin.trackmate.util.TMUtils; -import ij.IJ; - -public class CLIUtils -{ - - public static final String CONDA_PATH_PREF_KEY = "trackmate.conda.path"; - - public static final String CONDA_ROOT_PREFIX_KEY = "trackmate.conda.root.prefix"; - - private static Map< String, String > envMap; - - /** - * Creates and start a process that runs the command specified in the CLI. - * - * @param cli - * the CLI configurator that specifies the command to run. - * @param logFile - * the file to which the process output is appended. - * @return the process that runs the command specified in the CLI. - * @throws IOException - * if there is an error creating the process. - */ - public static final Process createProcess( final CLIConfigurator cli, final File logFile ) throws IOException - { - final List< String > cmd = CommandBuilder.build( cli ); - final ProcessBuilder pb = new ProcessBuilder( cmd ); - pb.redirectOutput( ProcessBuilder.Redirect.appendTo( logFile ) ); - pb.redirectError( ProcessBuilder.Redirect.appendTo( logFile ) ); - return pb.start(); - } - - /** - * Creates and starts a process that runs the command specified in the CLI, - * and redirects the process output to the logger. - *

    - * This method handles the process output by appending it to a log file and - * redirecting it to the logger. It also catches exceptions when launching - * the process and redirect errors to the logger. - * - * @param cli - * the CLI configurator that specifies the command to run. - * @param logger - * the logger to which the process output is redirected. - * @param logFile - * the file to which the process output is appended. - * @return the process that runs the command specified in the CLI, or - * null if there was an error creating the process. - */ - public static final Process createAndHandleProcess( final CLIConfigurator cli, final Logger logger, final File logFile ) - { - // Appends process output to the log file, and redirects to the logger. - final Tailer tailer = Tailer.builder() - .setFile( logFile ) - .setTailerListener( new LoggerTailerListener( logger ) ) - .setDelayDuration( Duration.ofMillis( 200 ) ) - .setTailFromEnd( true ) - .get(); - - final String executableName = cli.getClass().getSimpleName(); - try - { - final List< String > cmd = CommandBuilder.build( cli ); - logger.setStatus( "Running " + executableName ); - logger.log( "Running " + executableName + " with args:\n" ); - cmd.forEach( t -> { - if ( t.contains( File.separator ) ) - logger.log( t + ' ' ); - else - logger.log( t + ' ', Logger.GREEN_COLOR.darker() ); - } ); - logger.log( "\n" ); - - final Process process = createProcess( cli, logFile ); - return process; - } - catch ( final IOException e ) - { - final String msg = e.getMessage(); - String errorMessage; - if ( msg.matches( ".+error=13.+" ) ) - { - errorMessage = "Problem running " + executableName + ":\n" - + "The executable does not have the file permission to run.\n"; - } - else - { - errorMessage = "Problem running " + executableName + ":\n" + e.getMessage(); - } - try - { - errorMessage = errorMessage + '\n' + new String( Files.readAllBytes( logFile.toPath() ) ); - } - catch ( final IOException e1 ) - {} - e.printStackTrace(); - logger.error( errorMessage ); - } - catch ( final Exception e ) - { - String errorMessage = "Problem running " + executableName + ":\n" + e.getMessage(); - try - { - errorMessage = errorMessage + '\n' + new String( Files.readAllBytes( logFile.toPath() ) ); - } - catch ( final IOException e1 ) - {} - e.printStackTrace(); - logger.error( errorMessage ); - } - finally - { - tailer.close(); - } - return null; - } - - /** - * Creates and executes the process that runs the command specified in the - * CLI. - * - * @param cli - * the CLI configurator that specifies the command to run. - * @param logger - * the logger to which the process output is redirected. - * @param logFile - * the file to which the process output is appended. - * @return true if the process exited with code 0, - * false otherwise. - */ - public static boolean execute( final CLIConfigurator cli, final Logger logger, final File logFile ) - { - - final Process process = createAndHandleProcess( cli, logger, logFile ); - if ( process == null ) - return false; - - try - { - final int returnValue = process.waitFor(); - return returnValue == 0; - } - catch ( final InterruptedException e ) - { - logger.error( "Process interrupted: " + e.getMessage() ); - e.printStackTrace(); - } - return false; - } - - /** - * Returns the version of a Python module. - * - * The version is returned as a string, as if the command - * - *

    -	 * python -c import moduleName;
    -	 * print(moduleName.__version__)
    -	 * 
    - * - * was run. - * - * @param envName - * the name of the conda environment in which the module is - * installed - * @param moduleName - * the name of the module - * @return the version of the module as a string, or null if - * the module name does nor exist or if the conda environment does - * not exist. - */ - public static String getModuleVersion( final String envName, final String moduleName ) - { - // We protect the space between 'import' and the command with a _ - final String cmd = "" - + "python -c import_" + moduleName + ";" - + "print(" + moduleName + ".__version__)"; - final List< String > tokens = preparePythonCommand( envName, cmd ); - if ( tokens.isEmpty() ) - return null; - - // Put back the space in the token. - final ListIterator< String > it = tokens.listIterator(); - while ( it.hasNext() ) - { - final String token = it.next(); - it.set( token.replace( "t_", "t " ) ); - } - final ProcessBuilder pb = new ProcessBuilder( tokens ); - // Env variables. - final Map< String, String > env = new HashMap<>(); - final String condaRootPrefix = getCondaRootPrefix(); - env.put( "MAMBA_ROOT_PREFIX", condaRootPrefix ); - env.put( "CONDA_ROOT_PREFIX", condaRootPrefix ); - pb.environment().putAll( env ); - pb.redirectErrorStream( true ); - - try - { - final Process process = pb.start(); - final BufferedReader reader = new BufferedReader( - new InputStreamReader( process.getInputStream() ) ); - - // Return the last line of output. - String line; - String prevLine = null; - final StringBuffer errorMsg = new StringBuffer(); - while ( ( line = reader.readLine() ) != null ) - { - prevLine = line; - errorMsg.append( '\n' + line ); - } - - final int exitCode = process.waitFor(); - if ( exitCode == 0 ) - return prevLine; - else - throw new Exception( "Error running the command '" + moduleName - + "' in environment '" + envName + "'" + errorMsg ); - } - catch ( final Exception e ) - { - e.printStackTrace(); - return null; - } - } - - /** - * Generates the list of tokens to be used with {@link ProcessBuilder} to - * run a Python command. - * - * @param envName - * the name of the conda environment in which the command is - * installed. - * @param cmdName - * the name of the command to run. - * @return a list of tokens to use with {@link ProcessBuilder}. The list is - * empty if there is an error with the conda environment or with the - * command. - */ - public static List< String > preparePythonCommand( final String envName, final String cmdName ) - { - final List< String > cmd = new ArrayList<>(); - // Conda and executable stuff. - try - { - final String pythonPath = CLIUtils.getEnvMap().get( envName ); - if ( pythonPath == null ) - throw new Exception( "Unknown conda environment: " + envName ); - - final int i = pythonPath.lastIndexOf( "python" ); - final String binPath = pythonPath.substring( 0, i ); - final String executablePath = binPath + cmdName; - final String[] split = executablePath.split( " " ); - cmd.addAll( Arrays.asList( split ) ); - return cmd; - } - catch ( final IOException e ) - { - System.err.println( "Could not find the conda executable or change the conda environment.\n" - + "Please configure the path to your conda executable in Edit > Options > Configure TrackMate Conda path..." ); - e.printStackTrace(); - } - catch ( final Exception e ) - { - System.err.println( "Error running the conda executable:" ); - e.printStackTrace(); - } - return cmd; - } - - public static Map< String, String > getEnvMap() throws IOException - { - if ( envMap == null ) - { - synchronized ( CLIUtils.class ) - { - if ( envMap == null ) - { - // Prepare the command and environment variables. - // Command - final ProcessBuilder pb = new ProcessBuilder( Arrays.asList( getCondaPath(), "env", "list" ) ); - // Env variables. - final Map< String, String > env = new HashMap<>(); - final String condaRootPrefix = getCondaRootPrefix(); - env.put( "MAMBA_ROOT_PREFIX", condaRootPrefix ); - env.put( "CONDA_ROOT_PREFIX", condaRootPrefix ); - pb.environment().putAll( env ); - // Run and collect output. - final Process process = pb.start(); - final BufferedReader stdOutput = new BufferedReader( new InputStreamReader( process.getInputStream() ) ); - final BufferedReader stdError = new BufferedReader( new InputStreamReader( process.getErrorStream() ) ); - - /* - * Did we have an error? Read the error from the command - */ - String s; - String errorOutput = ""; - while ( ( s = stdError.readLine() ) != null ) - errorOutput += ( s + '\n' ); - if ( !errorOutput.isEmpty() ) - throw new IOException( "Could not retrieve environment map properly:\n" + errorOutput ); - - String line; - envMap = new HashMap<>(); - while ( ( line = stdOutput.readLine() ) != null ) - { - line = line.trim(); - line = line.replaceAll( "\\*", "" ); - if ( line.isEmpty() || line.startsWith( "#" ) || line.startsWith( "Name" ) || line.startsWith( "──────" ) ) - continue; - - final String[] parts = line.split( "\\s+" ); - if ( parts.length >= 2 ) - { - final String envName = parts[ 0 ]; - final String envPath = parts[ 1 ] + "/bin/python"; - envMap.put( envName, envPath ); - } - else if ( parts.length == 1 ) - { - /* - * When we don't have the right configuration, - * sometimes the list returns the path to the envs - * but not the name. We try then to extract the name - * from the path. - */ - - final String envRoot = parts[ 0 ]; - if ( !isValidPath( envRoot ) ) - { - continue; - } - final Path path = Paths.get( envRoot ); - final String envName = path.getFileName().toString(); - final String envPath = envRoot + "/bin/python"; - envMap.put( envName, envPath ); - } - } - } - } - } - return envMap; - } - - public static List< String > getEnvList() throws IOException - { - final List< String > l = new ArrayList<>( getEnvMap().keySet() ); - l.sort( null ); - return l; - } - - public static String getCondaPath() - { - final PrefService prefs = TMUtils.getContext().getService( PrefService.class ); - String findPath; - try - { - findPath = CLIUtils.findDefaultCondaPath(); - } - catch ( final IllegalArgumentException e ) - { - findPath = "/usr/local/opt/micromamba/bin/micromamba"; - } - return prefs.get( CLIUtils.class, CLIUtils.CONDA_PATH_PREF_KEY, findPath ); - } - - public static String getCondaRootPrefix() - { - final PrefService prefs = TMUtils.getContext().getService( PrefService.class ); - final String findPath = "/usr/local/opt/micromamba"; - return prefs.get( CLIUtils.class, CLIUtils.CONDA_ROOT_PREFIX_KEY, findPath ); - } - - public static String findDefaultCondaPath() throws IllegalArgumentException - { - final String username = System.getProperty( "user.name" ); - final String prefix = IJ.isMacOSX() - ? "/Users/" - : "/home/"; - final String anaconda1 = prefix + username + "/anaconda3/bin/conda"; - final String anaconda2 = "/opt/anaconda3/bin/conda"; - final String miniconda1 = prefix + username + "/miniconda3/bin/conda"; - final String miniconda2 = "/opt/miniconda3/bin/conda"; - final String mamba1 = prefix + username + "/mamba/bin/mamba"; - final String mamba2 = "/opt/mamba/bin/mamba"; - final String micromamba1 = prefix + username + ( IJ.isMacOSX() - ? "/Library/micromamba/bin/micromamba" - : "/.local/share/micromamba/bin/micromamba" ); - final String micromamba2 = "/usr/local/micromamba/bin/micromamba"; - final String micromamba3 = "/usr/local/opt/micromamba/bin/micromamba"; - final String micromamba4 = "/opt/micromamba/bin/micromamba"; - final String micromamba5 = prefix + username + "/mambaforge/condabin/mamba"; - final String[] toTest = new String[] { - anaconda1, - anaconda2, - miniconda1, - miniconda2, - mamba1, - mamba2, - micromamba1, - micromamba2, - micromamba3, - micromamba4, - micromamba5 - }; - for ( final String str : toTest ) - { - final Path path = Paths.get( str ); - if ( Files.isExecutable( path ) ) - return str; - } - throw new IllegalArgumentException( "Could not find a conda executable I know of, within: " + Arrays.asList( toTest ) ); - } - - /** - * Add a hook to delete the content of given path when Fiji quits. Taken - * from https://stackoverflow.com/a/20280989/201698 - * - * @param path - * the path to delete on shutdown. - */ - public static void recursiveDeleteOnShutdownHook( final Path path ) - { - Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() - { - @Override - public void run() - { - try - { - Files.walkFileTree( path, new SimpleFileVisitor< Path >() - { - @Override - public FileVisitResult visitFile( final Path file, final BasicFileAttributes attrs ) throws IOException - { - Files.delete( file ); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory( final Path dir, final IOException e ) throws IOException - { - if ( e == null ) - { - Files.delete( dir ); - return FileVisitResult.CONTINUE; - } - throw e; - } - } ); - } - catch ( final IOException e ) - { - throw new RuntimeException( "Failed to delete " + path, e ); - } - } - } ) ); - } - - public static class LoggerTailerListener extends TailerListenerAdapter - { - - protected final Logger logger; - - public Color COLOR = Logger.BLUE_COLOR.darker(); - - private final static Pattern PERCENTAGE_PATTERN = Pattern.compile( ".+\\D(\\d+(?:\\.\\d+)?)%.+" ); - - private final static Pattern INFO_PATTERN = Pattern.compile( "(.+\\[INFO\\]\\s+(.+)|^INFO:.*$)" ); - - public LoggerTailerListener( final Logger logger ) - { - this.logger = logger; - } - - @Override - public void handle( final String rawLine ) - { - final String line = cleanLine( rawLine ); - - // Do we have percentage? - final Matcher matcher = PERCENTAGE_PATTERN.matcher( line ); - if ( matcher.matches() ) - { - final String percent = matcher.group( 1 ); - logger.setProgress( Double.valueOf( percent ) / 100. ); - } - else - { - final Matcher matcher2 = INFO_PATTERN.matcher( line ); - if ( matcher2.matches() ) - { - final String str = matcher2.group( 1 ).trim(); - if ( str.length() > 2 ) - logger.setStatus( str - .replaceAll( "\\[INFO\\]", "" ) - .replaceAll( "INFO:", "" ) - .replaceAll( "INFO", "" ) ); - } - else if ( !line.trim().isEmpty() ) - { - logger.log( " - " + line + '\n', COLOR ); - } - } - } - - protected String cleanLine( final String line ) - { - // Remove ANSI escape sequences - String cleaned = line.replaceAll( "\u001B\\[[;\\d]*[A-Za-z]", "" ); - // Remove carriage returns and other control characters - cleaned = cleaned.replaceAll( "[\r\n\t]", " " ); - // Remove non-printable ASCII characters - cleaned = cleaned.replaceAll( "[^\\x20-\\x7E]", "" ); - // Collapse multiple spaces - cleaned = cleaned.replaceAll( "\\s+", " " ); - // Trim whitespace - return cleaned.trim(); - } - } - - public static boolean isValidPath( final String pathString ) - { - try - { - // Convert the string to a Path object - final Path path = Paths.get( pathString ); - - // Check for basic path characteristics - if ( !pathString.contains( "/" ) && !pathString.contains( "\\" ) ) - return false; - - // Check for illegal characters (Windows-specific example) - if ( System.getProperty( "os.name" ).toLowerCase().contains( "win" ) ) - { - final Pattern illegalCharsPattern = Pattern.compile( "[<>:*?\"|]" ); - if ( illegalCharsPattern.matcher( pathString ).find() ) - return false; - } - - return Files.exists( path ); - } - catch ( final InvalidPathException e ) - { - return false; - } - } - - public static void clearEnvMap() - { - envMap = null; - } - - public static void main( final String[] args ) throws Exception - { - System.out.println( "Conda path: " + getCondaPath() ); - System.out.println( "Known environments: " + getEnvList() ); - System.out.println( "Paths:" ); - getEnvMap().forEach( ( k, v ) -> System.out.println( k + " -> " + v ) ); - - System.out.println(); - System.out.println( "Testing versions" ); - - System.out.println( "1 - " + getModuleVersion( "trackastra", "trackastra" ) ); - System.out.println( "2 - " + getModuleVersion( "cellpose", "cellpose" ) ); - System.out.println( "3 - " + getModuleVersion( "cellpose", "cellposebloat" ) ); - System.out.println( "4 - " + getModuleVersion( "cellposebarf", "cellpose" ) ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java b/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java deleted file mode 100644 index ab006ef5c..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java +++ /dev/null @@ -1,276 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import org.apache.commons.lang3.StringUtils; - -import fiji.plugin.trackmate.util.cli.CommandCLIConfigurator.ExecutablePath; -import fiji.plugin.trackmate.util.cli.CondaCLIConfigurator.CondaEnvironmentCommand; -import fiji.plugin.trackmate.util.cli.Configurator.AbstractStringArgument; -import fiji.plugin.trackmate.util.cli.Configurator.Argument; -import fiji.plugin.trackmate.util.cli.Configurator.ArgumentVisitor; -import fiji.plugin.trackmate.util.cli.Configurator.ChoiceArgument; -import fiji.plugin.trackmate.util.cli.Configurator.DoubleArgument; -import fiji.plugin.trackmate.util.cli.Configurator.Flag; -import fiji.plugin.trackmate.util.cli.Configurator.IntArgument; -import fiji.plugin.trackmate.util.cli.Configurator.PathArgument; -import fiji.plugin.trackmate.util.cli.Configurator.StringArgument; - -public class CommandBuilder implements ArgumentVisitor -{ - - private final List< String > tokens = new ArrayList<>(); - - private final Map< Argument< ?, ? >, Function< Object, List< String > > > translators; - - protected CommandBuilder( final Map< Argument< ?, ? >, Function< Object, List< String > > > translators ) - { - this.translators = translators; - } - - @Override - public String toString() - { - return StringUtils.join( tokens, " " ); - } - - private void check( final Argument< ?, ? > arg ) - { - if ( arg.isInCLI() && arg.getArgument() == null ) - throw new IllegalArgumentException( "Incorrect configuration for argument '" + arg.getName() - + "'. The command argument is not set." ); - } - - @Override - public void visit( final ExecutablePath executablePath ) - { - if ( executablePath.getValue() == null ) - throw new IllegalArgumentException( "Executable path is not set." ); - tokens.addAll( translators.getOrDefault( executablePath, v -> Collections.singletonList( "" + v ) ).apply( executablePath.getValue() ) ); - } - - @Override - public void visit( final CondaEnvironmentCommand condaEnv ) - { - if ( condaEnv.getValue() == null ) - throw new IllegalArgumentException( "Conda environment is not set." ); - tokens.addAll( translators.getOrDefault( condaEnv, v -> Collections.singletonList( "" + v ) ).apply( condaEnv.getValue() ) ); - } - - @Override - public void visit( final Flag flag ) - { - check( flag ); - final boolean val; - if ( flag.isSet() ) - val = flag.getValue(); - else - val = flag.getDefaultValue(); - - // Deal with flag that have a '=' vs switches. - final String a = flag.getArgument(); - final List< String > vals = translators.getOrDefault( flag, v -> Collections.singletonList( "" + v ) ).apply( val ); - if ( a.endsWith( "=" ) ) - { - tokens.add( a + String.join( ",", vals ) ); - } - else - { - if ( val ) - { - tokens.add( a ); - /* - * Only add values after token if they have been explicitely - * translated. - */ - if ( translators.containsKey( flag ) ) - tokens.addAll( vals ); - } - } - } - - @Override - public void visit( final IntArgument arg ) - { - check( arg ); - // Is it required and we have not set it? -> error - if ( arg.isRequired() && !arg.isSet() ) - throw new IllegalArgumentException( "Required argument '" + arg.getName() + "' is not set." ); - - // Is not set and we don't have a default value? -> skip - if ( !arg.isSet() && !arg.hasDefaultValue() ) - return; - - // We have a default or a value. - final int val = ( !arg.isSet() ) - ? arg.getDefaultValue() - : arg.getValue(); - - // Test for min & max - if ( arg.hasMin() && ( val < arg.getMin() ) ) - throw new IllegalArgumentException( "Value " + val + " for argument '" + arg.getName() + "' is smaller than the min: " + arg.getMin() ); - if ( arg.hasMax() && arg.getMax() != Integer.MAX_VALUE && ( val > arg.getMax() ) ) - throw new IllegalArgumentException( "Value " + val + " for argument '" + arg.getName() + "' is larger than the max: " + arg.getMax() ); - - final String a = arg.getArgument(); - final List< String > vals = translators.getOrDefault( arg, v -> Collections.singletonList( "" + v ) ).apply( val ); - // Does the switch ends in '='? - if ( a.endsWith( "=" ) ) - { - // Concatenante with no space. - tokens.add( a + String.join( ",", vals ) ); - } - else - { - tokens.add( a ); - tokens.addAll( vals ); - } - } - - @Override - public void visit( final DoubleArgument arg ) - { - check( arg ); - // Is it required and we have not set it? -> error - if ( arg.isRequired() && !arg.isSet() ) - throw new IllegalArgumentException( "Required argument '" + arg.getName() + "' is not set." ); - - // Is not set and we don't have a default value? -> skip - if ( !arg.isSet() && !arg.hasDefaultValue() ) - return; - - // We have a default or a value. - final double val = ( !arg.isSet() ) - ? arg.getDefaultValue() - : arg.getValue(); - - // Test for min & max - if ( arg.hasMin() && ( val < arg.getMin() ) ) - throw new IllegalArgumentException( "Value " + val + " for argument '" + arg.getName() - + "' is smaller than the min: " + arg.getMin() ); - if ( arg.hasMax() && ( val > arg.getMax() ) ) - throw new IllegalArgumentException( "Value " + val + " for argument '" + arg.getName() - + "' is larger than the max: " + arg.getMax() ); - - final String a = arg.getArgument(); - final List< String > vals = translators.getOrDefault( arg, v -> Collections.singletonList( "" + v ) ).apply( val ); - // Does the switch ends in '='? - if ( a.endsWith( "=" ) ) - { - // Concatenante with no space. - tokens.add( a + String.join( ",", vals ) ); - } - else - { - tokens.add( a ); - tokens.addAll( vals ); - } - } - - private void visitString( final AbstractStringArgument< ? > arg ) - { - check( arg ); - // Is it required and we have not set it? -> error - if ( arg.isRequired() && !arg.isSet() ) - throw new IllegalArgumentException( "Required argument '" + arg.getName() + "' is not set." ); - - // Is not set and we don't have a default value? -> skip - if ( !arg.isSet() && !arg.hasDefaultValue() ) - return; - - // We have a default or a value. - final String val = ( !arg.isSet() ) - ? arg.getDefaultValue() - : arg.getValue(); - - final String a = arg.getArgument(); - final List< String > vals = translators.getOrDefault( arg, v -> Collections.singletonList( "" + v ) ).apply( val ); - // Does the switch ends in '='? - if ( a.endsWith( "=" ) ) - { - // Concatenante with no space. - tokens.add( a + String.join( ",", vals ) ); - } - else - { - if ( !a.isEmpty() ) - tokens.add( a ); - tokens.addAll( vals ); - } - } - - @Override - public void visit( final StringArgument stringArgument ) - { - visitString( stringArgument ); - } - - @Override - public void visit( final PathArgument pathArgument ) - { - visitString( pathArgument ); - } - - @Override - public void visit( final ChoiceArgument arg ) - { - check( arg ); - // Is it required and we have not set it? -> error - if ( arg.isRequired() && !arg.isSet() ) - throw new IllegalArgumentException( "Required argument '" + arg.getName() + "' is not set." ); - - // Is not set? -> skip - if ( !arg.isSet() ) - return; - - final String a = arg.getArgument(); - final List vals = translators.getOrDefault( arg, v -> Collections.singletonList( "" + v ) ).apply( arg.getValue() ); - // Does the switch ends in '='? - if ( a.endsWith( "=" ) ) - { - // Concatenante with no space. - tokens.add( a + String.join( ",", vals ) ); - } - else - { - tokens.add( a ); - tokens.addAll( vals ); - } - } - - public static List< String > build( final CLIConfigurator cli ) - { - final CommandBuilder cb = new CommandBuilder( cli.cliTranslators ); - cli.getCommandArg().accept( cb ); - cli.getSelectedArguments() - .stream() - .filter( a -> a.isInCLI() ) - .forEach( arg -> arg.accept( cb ) ); - return cb.tokens; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CommandCLIConfigurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/CommandCLIConfigurator.java deleted file mode 100644 index 1603280af..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CommandCLIConfigurator.java +++ /dev/null @@ -1,109 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import java.io.File; - -/** - * Base class for CLI config that are based on an executable, reachable by a - * path. - */ -public abstract class CommandCLIConfigurator extends CLIConfigurator -{ - - protected final ExecutablePath executable; - - public static class ExecutablePath extends AbstractStringArgument< ExecutablePath > - { - - @Override - public ExecutablePath name( final String name ) - { - return super.name( name ); - } - - @Override - public ExecutablePath help( final String help ) - { - return super.help( help ); - } - - @Override - public ExecutablePath key( final String key ) - { - return super.key( key ); - } - - @Override - public void accept( final ArgumentVisitor visitor ) - { - visitor.visit( this ); - } - } - - protected CommandCLIConfigurator() - { - this.executable = new ExecutablePath(); - } - - @Override - public ExecutablePath getCommandArg() - { - return executable; - } - - protected String checkExecutable() - { - if ( !executable.isSet() ) - { - return "Executable path is not set.\n"; - } - else - { - final String path = executable.getValue(); - final File file = new File( path ); - if ( !file.exists() ) - return "Executable path " + path + " does not exist.\n"; - if ( !file.canExecute() ) - return "Executable " + path + " cannot be run.\n"; - } - return null; - } - - @Override - public String check() - { - final String out = checkExecutable(); - if ( out != null ) - return out; - return super.check(); - } - - @Override - public String toString() - { - final StringBuilder str = new StringBuilder(); - str.append( executable.toString() ); - str.append( super.toString() + "\n" ); - return str.toString(); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CommonTrackMateArguments.java b/src/main/java/fiji/plugin/trackmate/util/cli/CommonTrackMateArguments.java deleted file mode 100644 index a3140eb6a..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CommonTrackMateArguments.java +++ /dev/null @@ -1,172 +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.cli; - -import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_DO_MEDIAN_FILTERING; -import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_DO_SUBPIXEL_LOCALIZATION; -import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_RADIUS; -import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_TARGET_CHANNEL; -import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_THRESHOLD; -import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_DO_MEDIAN_FILTERING; -import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_DO_SUBPIXEL_LOCALIZATION; -import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_RADIUS; -import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL; -import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_THRESHOLD; -import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS; - -import fiji.plugin.trackmate.util.cli.Configurator.DoubleArgument; -import fiji.plugin.trackmate.util.cli.Configurator.Flag; -import fiji.plugin.trackmate.util.cli.Configurator.IntArgument; - -/** - * Arguments that are commonly used by TrackMate, to add to custom - * {@link fiji.plugin.trackmate.util.cli.CLIConfigurator}. - */ -public class CommonTrackMateArguments -{ - - /** - * Creates an argument used to specify on what channel in the input image to - * operate on and add it to the given configurator. - *

    - * The channel index is 1-based. - * - * @param config - * the configurator to which to add the argument. - * @param nChannels - * how many channels in the input image. - * @return the integer target channel argument. - */ - public static IntArgument addTargetChannel( final Configurator config, final int nChannels ) - { - final IntArgument arg = config.addIntArgument() - .key( KEY_TARGET_CHANNEL ) - .defaultValue( DEFAULT_TARGET_CHANNEL ) - .name( "Target channel" ) - .help( "Index of the channel to process." ) - .inCLI( false ) - .visible( true ) - .min( 1 ) // 1-based - .max( Integer.valueOf( nChannels ) ) - .get(); - arg.set( DEFAULT_TARGET_CHANNEL ); - return arg; - } - - public static Flag addSimplifyContour( final Configurator config ) - { - final Flag arg = config.addFlag() - .key( KEY_SIMPLIFY_CONTOURS ) - .defaultValue( true ) - .name( "Simplify contour" ) - .help( "If true contours will be simplified with fewer control points." ) - .inCLI( false ) - .visible( true ) - .get(); - arg.set( true ); - return arg; - } - - public static DoubleArgument addRadius( final Configurator config, final String units ) - { - final DoubleArgument arg = config.addDoubleArgument() - .key( KEY_RADIUS ) - .argument( "--radius" ) - .defaultValue( DEFAULT_RADIUS ) - .units( units ) - .name( "Radius" ) - .help( "Radius of the objects to detect, in " + units + "." ) - .visible( true ) - .get(); - arg.set( DEFAULT_RADIUS ); - return arg; - } - - /** - * Adds a diameter argument to the given configurator. - *

    - * Here there is a gotcha: the value displayed in the UI is the diameter, - * but the value stored and returned is the radius. Therefore, we add a - * translator that divides the value by 2. - * - * @param config - * the config to which to add the argument. - * @param units - * the units of the diameter to display. - * @return the diameter double argument. - */ - public static DoubleArgument addDiameter( final Configurator config, final String units ) - { - final DoubleArgument arg = config.addDoubleArgument() - .key( KEY_RADIUS ) - .argument( "--radius" ) - .defaultValue( DEFAULT_RADIUS ) - .units( units ) - .name( "Diameter" ) - .help( "Diameter of the objects to detect." ) - .visible( true ) - .get(); - arg.set( DEFAULT_RADIUS ); - // Add a translator from radius (stored) to diameter (displayed). - config.setDisplayTranslator( arg, r -> r * 2., d -> d / 2. ); - return arg; - } - - public static DoubleArgument addThreshold( final Configurator config ) - { - final DoubleArgument arg = config.addDoubleArgument() - .key( KEY_THRESHOLD ) - .defaultValue( DEFAULT_THRESHOLD ) - .name( "Threshold" ) - .help( "The threshold to apply to the detector." ) - .visible( true ) - .get(); - arg.set( DEFAULT_THRESHOLD ); - return arg; - } - - public static Flag addSubpixelLocalization( final Configurator config ) - { - final Flag flag = config.addFlag() - .key( KEY_DO_SUBPIXEL_LOCALIZATION ) - .defaultValue( DEFAULT_DO_SUBPIXEL_LOCALIZATION ) - .name( "Sub-pixel localization" ) - .help( "If true, the detector will try to localize spots with sub-pixel accuracy." ) - .visible( true ) - .get(); - flag.set( DEFAULT_DO_SUBPIXEL_LOCALIZATION ); - return flag; - } - - public static Flag addMedianFiltering( final Configurator config ) - { - final Flag flag = config.addFlag() - .key( KEY_DO_MEDIAN_FILTERING ) - .defaultValue( DEFAULT_DO_MEDIAN_FILTERING ) - .name( "Median filtering" ) - .help( "If true, the detector will apply a median filter to the image before detection." ) - .visible( true ) - .get(); - flag.set( DEFAULT_DO_MEDIAN_FILTERING ); - return flag; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CondaCLIConfigurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/CondaCLIConfigurator.java deleted file mode 100644 index d2adc2b52..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/CondaCLIConfigurator.java +++ /dev/null @@ -1,194 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public abstract class CondaCLIConfigurator extends CLIConfigurator -{ - - public static final String KEY_CONDA_ENV = "CONDA_ENV"; - - protected final CondaEnvironmentCommand condaEnv; - - public static class CondaEnvironmentCommand extends AbstractStringArgument< CondaEnvironmentCommand > - { - - private final List< String > envs = new ArrayList<>(); - - protected CondaEnvironmentCommand() - { - name( "Conda environment" ); - help( "The conda environment in which the tool is configured." ); - key( KEY_CONDA_ENV ); - defaultValue( "base" ); - required( true ); - } - - protected CondaEnvironmentCommand addEnvironment( final String env ) - { - if ( !envs.contains( env ) ) - envs.add( env ); - return this; - } - - @Override - public void set( final String env ) - { - if ( envs.isEmpty() ) - { - System.err.println( "The list of conda environments is empty." ); - return; - } - final int sel = envs.indexOf( env ); - if ( sel < 0 ) - { - super.set( envs.get( 0 ) ); - return; - } - super.set( env ); - } - - public void set( final int selected ) - { - if ( envs.isEmpty() ) - { - System.err.println( "The list of conda environments is empty." ); - return; - } - - if ( selected < 0 || selected >= envs.size() ) - set( envs.get( 0 ) ); - else - set( envs.get( selected ) ); - } - - public List< String > getEnvironments() - { - return envs; - } - - @Override - public void accept( final ArgumentVisitor visitor ) - { - visitor.visit( this ); - } - } - - protected CondaCLIConfigurator() - { - super(); - - // Make a UI-only arg configuring the conda env. - // Default is last one (base is not interesting as a default). - final List< String > envList = new ArrayList<>(); - try - { - final List< String > l = CLIUtils.getEnvList(); - envList.addAll( l ); - } - catch ( final Exception e ) - { - // Do nothing. The list of envs will be empty. - System.err.println( "There was an error retrieving the list of conda environments.\n" - + "Did you configure Conda for TrackMate? (Edit > Options > Configure TrackMate Conda path...)" ); - e.printStackTrace(); - } - this.condaEnv = new CondaEnvironmentCommand(); - envList.forEach( condaEnv::addEnvironment ); - condaEnv.key( KEY_CONDA_ENV ); - condaEnv.set( 0 ); - - // Add the translator to make a proper cmd line calling conda first. - setCommandTranslator( condaEnv, s -> { - final List< String > cmd = new ArrayList<>(); - final String condaPath = CLIUtils.getCondaPath(); - final String os = System.getProperty( "os.name" ).toLowerCase(); - if ( os.contains( "win" ) ) - { - // In Windows: Launch a cmd.exe shell. - cmd.addAll( Arrays.asList( "cmd.exe", "/c" ) ); - } - else - { - // On Mac or Linux. - } - // Call conda run. - cmd.add( condaPath ); - cmd.add( "run" ); - cmd.add( "-n" ); - // The executable stuff. - final String envname = ( String ) s; - cmd.add( envname ); - - // The rest of the command, split by spaces. - final String executableCommand = getCommand(); - final String[] split = executableCommand.split( " " ); - cmd.addAll( Arrays.asList( split ) ); - return cmd; - } ); - } - - @Override - public CondaEnvironmentCommand getCommandArg() - { - return condaEnv; - } - - /** - * Returns the command that must be run in the configured conda environment. - * In case the command is made of several tokens, they can be returned - * separated by space (as in a normal command line). - * - * @return the command for this tool. - */ - protected abstract String getCommand(); - - /** - * Returns the version of the Python tool that this configurator is - * configured for. This method assumes that the module has the same name - * that the CLI command used to run it. - * - * @return the version string or null if the version could not - * be determined or if the command does not run on Python. - */ - public String getVersion() - { - return getVersion( getCommandArg().getValue() ); - } - - /** - * Returns the version of the Python tool that this configurator is - * configured for. - * - * @param moduleName - * the name of the module to get the version for. - * @return the version string or null if the version could not - * be determined or if the command does not run on Python. - */ - public String getVersion( final String moduleName ) - { - return CLIUtils.getModuleVersion( condaEnv.getValue(), moduleName ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/ConfigGuiBuilder.java b/src/main/java/fiji/plugin/trackmate/util/cli/ConfigGuiBuilder.java deleted file mode 100644 index 1a7d30b72..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/ConfigGuiBuilder.java +++ /dev/null @@ -1,788 +0,0 @@ -/*- -f * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.booleanElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.boundedDoubleElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.doubleElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.intElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedCheckBox; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedComboBoxSelector; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedFormattedTextField; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedSliderPanel; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedTextField; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.listElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.stringElement; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.io.File; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.DoubleSupplier; -import java.util.function.Function; -import java.util.function.IntSupplier; -import java.util.function.Supplier; - -import javax.swing.AbstractButton; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JSeparator; -import javax.swing.JTextField; -import javax.swing.SwingConstants; - -import fiji.plugin.trackmate.gui.Fonts; -import fiji.plugin.trackmate.gui.displaysettings.BoundedValue; -import fiji.plugin.trackmate.gui.displaysettings.BoundedValue.UpdateListener; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BooleanElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.DoubleElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.IntElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.ListElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StringElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StyleElement; -import fiji.plugin.trackmate.util.FileChooser; -import fiji.plugin.trackmate.util.FileChooser.DialogType; -import fiji.plugin.trackmate.util.cli.CommandCLIConfigurator.ExecutablePath; -import fiji.plugin.trackmate.util.cli.CondaCLIConfigurator.CondaEnvironmentCommand; -import fiji.plugin.trackmate.util.cli.Configurator.Argument; -import fiji.plugin.trackmate.util.cli.Configurator.ArgumentVisitor; -import fiji.plugin.trackmate.util.cli.Configurator.ChoiceArgument; -import fiji.plugin.trackmate.util.cli.Configurator.DoubleArgument; -import fiji.plugin.trackmate.util.cli.Configurator.Flag; -import fiji.plugin.trackmate.util.cli.Configurator.IntArgument; -import fiji.plugin.trackmate.util.cli.Configurator.PathArgument; -import fiji.plugin.trackmate.util.cli.Configurator.SelectableArguments; -import fiji.plugin.trackmate.util.cli.Configurator.StringArgument; - -public class ConfigGuiBuilder implements ArgumentVisitor -{ - - private static final int tfCols = 4; - - private final ConfigPanel panel; - - private final GridBagConstraints c; - - private int topInset = 5; - - private int bottomInset = 5; - - private final Map< Argument< ?, ? >, Function< ?, ? > > forwardUITranslators; - - private final Map< Argument< ?, ? >, Function< ?, ? > > backwardUITranslators; - - private ConfigGuiBuilder( - final Map< Argument< ?, ? >, Function< ?, ? > > forwardUITranslators, - final Map< Argument< ?, ? >, Function< ?, ? > > backwardUITranslators ) - { - this.forwardUITranslators = forwardUITranslators; - this.backwardUITranslators = backwardUITranslators; - this.panel = new ConfigPanel(); - final GridBagLayout layout = new GridBagLayout(); - layout.columnWeights = new double[] { 0., 1., 0. }; - panel.setLayout( layout ); - panel.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); - this.c = new GridBagConstraints(); - c.fill = GridBagConstraints.HORIZONTAL; - c.gridwidth = 1; - c.gridx = 0; - c.gridy = 0; - } - - - private void setCurrentRadioButton( final JRadioButton radioButton ) - { - if ( radioButton == null || ( panel.rdbtn != radioButton ) ) - { - topInset = 5; - bottomInset = 5; - } - else - { - topInset = 0; - bottomInset = 0; - } - panel.rdbtn = radioButton; - } - - /* - * ARGUMENT VISITOR. - */ - - @Override - public void visit( final ExecutablePath arg ) - { - if ( !arg.isSet() ) - { - if ( !arg.hasDefaultValue() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); - arg.set( arg.getDefaultValue() ); - } - - final StringElement element = stringElement( arg.getName(), arg::getValue, arg::set ); - panel.elements.put( arg.getKey(), element ); - addPathToLayout( - arg.getHelp(), - new JLabel( element.getLabel() ), - linkedTextField( element ), - null ); - - panel.add( Box.createVerticalStrut( 10 ), c ); - final JSeparator separator = new JSeparator( JSeparator.HORIZONTAL ); - separator.setMinimumSize( new Dimension( 10, 10 ) ); - addToLayout( null, separator ); - } - - @Override - public void visit( final Flag flag ) - { - if ( !flag.isSet() ) - { - if ( !flag.hasDefaultValue() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value or a default value. The argument '" + flag.getName() + "' misses both." ); - flag.set( flag.getDefaultValue() ); - } - - final BooleanElement element = booleanElement( flag.getName(), flag::getValue, flag::set ); - panel.elements.put( flag.getKey(), element ); - final JCheckBox checkbox = linkedCheckBox( element, "" ); - checkbox.setHorizontalAlignment( SwingConstants.LEADING ); - addToLayout( - flag.getHelp(), - new JLabel( element.getLabel() ), - checkbox, - flag ); - } - - @Override - public void visit( final IntArgument arg ) - { - if ( !arg.isSet() ) - { - if ( !arg.hasDefaultValue() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); - arg.set( arg.getDefaultValue() ); - } - - // Translate - @SuppressWarnings( "unchecked" ) - final Function< Integer, Integer > forward = ( Function< Integer, Integer > ) forwardUITranslators.getOrDefault( arg, v -> v ); - @SuppressWarnings( "unchecked" ) - final Function< Integer, Integer > backward = ( Function< Integer, Integer > ) backwardUITranslators.getOrDefault( arg, v -> v ); - final IntSupplier valueGetter = () -> { - final int value = arg.getValue(); - return forward.apply( value ); - }; - final Consumer< Integer > valueSetter = ( v ) -> { - final int value = backward.apply( v ); - arg.set( value ); - }; - final int min = forward.apply( arg.getMin() ); - final int max = forward.apply( arg.getMax() ); - - final IntElement element = intElement( arg.getName(), min, max, valueGetter, valueSetter ); - panel.elements.put( arg.getKey(), element ); - - final int numberOfColumns; - if ( arg.hasMin() && arg.hasMin() ) - { - final int largest = Math.max( Math.abs( min ), Math.abs( max ) ); - final String numberString = String.valueOf( largest ); - numberOfColumns = numberString.length() + 1; - } - else - { - numberOfColumns = tfCols; - } - addToLayout( - arg.getHelp(), - new JLabel( element.getLabel() ), - linkedSliderPanel( element, numberOfColumns ), - arg.getUnits(), - arg ); - } - - @Override - public void visit( final DoubleArgument arg ) - { - if ( !arg.isSet() ) - { - if ( !arg.hasDefaultValue() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); - arg.set( arg.getDefaultValue() ); - } - - // Translate - @SuppressWarnings( "unchecked" ) - final Function< Double, Double > forward = ( Function< Double, Double > ) forwardUITranslators.getOrDefault( arg, v -> v ); - @SuppressWarnings( "unchecked" ) - final Function< Double, Double > backward = ( Function< Double, Double > ) backwardUITranslators.getOrDefault( arg, v -> v ); - final DoubleSupplier valueGetter = () -> { - final double value = arg.getValue(); - return forward.apply( value ); - }; - final Consumer< Double > valueSetter = ( v ) -> { - final double value = backward.apply( v ); - arg.set( value ); - }; - - if ( arg.hasMin() && arg.hasMax() ) - { - final double min = forward.apply( arg.getMin() ); - final double max = forward.apply( arg.getMax() ); - final BoundedDoubleElement element = boundedDoubleElement( arg.getName(), - min, max, valueGetter, valueSetter ); - panel.elements.put( arg.getKey(), element ); - addToLayout( - arg.getHelp(), - new JLabel( element.getLabel() ), - linkedSliderPanel( element, tfCols, arg.getMax() / 50 ), - arg.getUnits(), - arg ); - } - else - { - final DoubleElement element = doubleElement( arg.getName(), valueGetter, valueSetter ); - panel.elements.put( arg.getKey(), element ); - if ( arg.isSet() ) - element.set( forward.apply( arg.getValue() ) ); - else if ( arg.hasDefaultValue() ) - element.set( forward.apply( arg.getDefaultValue() ) ); - addToLayout( - arg.getHelp(), - new JLabel( element.getLabel() ), - linkedFormattedTextField( element ), - arg.getUnits(), - arg ); - } - } - - @Override - public void visit( final StringArgument arg ) - { - if ( !arg.isSet() ) - { - if ( !arg.hasDefaultValue() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); - arg.set( arg.getDefaultValue() ); - } - - // Translate - @SuppressWarnings( "unchecked" ) - final Function< String, String > forward = ( Function< String, String > ) forwardUITranslators.getOrDefault( arg, v -> v ); - @SuppressWarnings( "unchecked" ) - final Function< String, String > backward = ( Function< String, String > ) backwardUITranslators.getOrDefault( arg, v -> v ); - final Supplier< String > valueGetter = () -> { - final String value = arg.getValue(); - return forward.apply( value ); - }; - final Consumer< String > valueSetter = ( v ) -> { - final String value = backward.apply( v ); - arg.set( value ); - }; - - final StringElement element = stringElement( arg.getName(), valueGetter, valueSetter ); - panel.elements.put( arg.getKey(), element ); - addToLayoutTwoLines( - arg.getHelp(), - new JLabel( element.getLabel() ), - linkedTextField( element ), - arg ); - } - - @Override - public void visit( final PathArgument arg ) - { - if ( !arg.isSet() ) - { - if ( !arg.hasDefaultValue() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); - arg.set( arg.getDefaultValue() ); - } - - // Translate - @SuppressWarnings( "unchecked" ) - final Function< String, String > forward = ( Function< String, String > ) forwardUITranslators.getOrDefault( arg, v -> v ); - @SuppressWarnings( "unchecked" ) - final Function< String, String > backward = ( Function< String, String > ) backwardUITranslators.getOrDefault( arg, v -> v ); - final Supplier< String > valueGetter = () -> { - final String value = arg.getValue(); - return forward.apply( value ); - }; - final Consumer< String > valueSetter = ( v ) -> { - final String value = backward.apply( v ); - arg.set( value ); - }; - - final StringElement element = stringElement( arg.getName(), valueGetter, valueSetter ); - panel.elements.put( arg.getKey(), element ); - addPathToLayout( - arg.getHelp(), - new JLabel( element.getLabel() ), - linkedTextField( element ), - arg ); - } - - @Override - public void visit( final ChoiceArgument arg ) - { - if ( !arg.isSet() ) - { - if ( !arg.hasDefaultValue() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); - arg.set( arg.getDefaultValue() ); - } - - final List< String > displays = arg.getDisplays(); - final Supplier< String > supplier = () -> { - return displays.get( arg.getSelectedIndex() ); - }; - final Consumer< String > consumer = ( s ) -> arg.set( displays.indexOf( s ) ); - - final ListElement< String > element = listElement( arg.getName(), displays, supplier, consumer ); - panel.elements.put( arg.getKey(), element ); - final JComboBox< String > comboBox = linkedComboBoxSelector( element ); - comboBox.setSelectedIndex( arg.getSelectedIndex() ); - addToLayout( - arg.getHelp(), - new JLabel( element.getLabel() ), - comboBox, - arg.getUnits(), - arg ); - } - - @Override - public void visit( final CondaEnvironmentCommand arg ) - { - if ( arg.getEnvironments().isEmpty() ) - { - // No environment found. Tell the user. - final JLabel lbl = new JLabel( "There was an error retrieving the " - + "list of conda environments. " - + "

    " - + "Did you configure Conda for TrackMate? " - + "

    " - + "(Edit > Options > Configure TrackMate Conda path...)" ); - lbl.setFont( Fonts.SMALL_FONT ); - lbl.setForeground( Color.RED ); - lbl.setPreferredSize( new Dimension( 200, 40 ) ); - addToLayout( arg.getHelp(), lbl ); - return; - } - - if ( !arg.isSet() ) - { - if ( !arg.hasDefaultValue() ) - throw new IllegalArgumentException( "The GUI builder requires all arguments and commands " - + "to have a value or a default value. The argument '" + arg.getName() + "' misses both." ); - arg.set( arg.getDefaultValue() ); - } - - final ListElement< String > element = listElement( arg.getName(), arg.getEnvironments(), arg::getValue, arg::set ); - panel.elements.put( arg.getKey(), element ); - final JComboBox< String > comboBox = linkedComboBoxSelector( element ); - comboBox.setSelectedItem( arg.getValue() ); - addToLayout( - arg.getHelp(), - new JLabel( element.getLabel() ), - comboBox, - null ); - } - - /* - * UI STUFF. - */ - - private void addToLayoutTwoLines( final String help, final JLabel lbl, final JComponent comp, final Argument< ?, ? > arg ) - { - lbl.setText( lbl.getText() + " " ); - lbl.setFont( Fonts.SMALL_FONT ); - comp.setFont( Fonts.SMALL_FONT ); - final JComponent item; - if ( panel.rdbtn != null ) - { - final JRadioButton btn = panel.rdbtn; - btn.addItemListener( e -> comp.setEnabled( btn.isSelected() ) ); - comp.setEnabled( btn.isSelected() ); - item = new JPanel(); - item.setLayout( new BoxLayout( item, BoxLayout.LINE_AXIS ) ); - item.add( btn ); - item.add( Box.createHorizontalGlue() ); - item.add( lbl ); - } - else - { - item = lbl; - } - c.insets = new Insets( 5, 0, 0, 0 ); - c.gridwidth = 3; - panel.add( item, c ); - c.gridy++; - c.anchor = GridBagConstraints.LINE_START; - c.insets = new Insets( 0, 0, 5, 0 ); - panel.add( comp, c ); - c.gridy++; - - if ( help != null ) - { - lbl.setToolTipText( help ); - comp.setToolTipText( help ); - } - } - - private void addPathToLayout( final String help, final JLabel lbl, final JTextField tf, final Argument< ?, ? > arg ) - { - final JPanel p = new JPanel(); - final BoxLayout bl = new BoxLayout( p, BoxLayout.LINE_AXIS ); - p.setLayout( bl ); - - tf.setColumns( 10 ); // Avoid long paths deforming new panels. - - lbl.setText( lbl.getText() + " " ); - lbl.setFont( Fonts.SMALL_FONT ); - tf.setFont( Fonts.SMALL_FONT ); - final JButton browseButton = new JButton( "browse" ); - browseButton.setFont( Fonts.SMALL_FONT ); - browseButton.addActionListener( e -> { - final File file = FileChooser.chooseFile( p, tf.getText(), DialogType.LOAD ); - if ( file == null ) - return; - tf.setText( file.getAbsolutePath() ); - tf.postActionEvent(); - } ); - - if ( panel.rdbtn != null ) - { - final JRadioButton btn = panel.rdbtn; - btn.addItemListener( e -> { - tf.setEnabled( btn.isSelected() ); - browseButton.setEnabled( btn.isSelected() ); - } ); - - tf.setEnabled( btn.isSelected() ); - browseButton.setEnabled( btn.isSelected() ); - p.add( btn ); - } - p.add( lbl ); - p.add( Box.createHorizontalGlue() ); - p.add( browseButton ); - - c.insets = new Insets( topInset, 0, 0, 0 ); - c.gridwidth = 3; - panel.add( p, c ); - c.gridy++; - c.anchor = GridBagConstraints.LINE_START; - c.insets = new Insets( 0, 0, bottomInset, 0 ); - panel.add( tf, c ); - c.gridy++; - - if ( help != null ) - { - lbl.setToolTipText( help ); - tf.setToolTipText( help ); - browseButton.setToolTipText( help ); - } - } - - private void addToLayout( final String help, final JLabel lbl, final JComponent comp, final Argument< ?, ? > arg ) - { - lbl.setText( lbl.getText() + " " ); - lbl.setFont( Fonts.SMALL_FONT ); - lbl.setHorizontalAlignment( JLabel.RIGHT ); - comp.setFont( Fonts.SMALL_FONT ); - - final JComponent header; - if ( arg != null && panel.rdbtn != null ) - { - final JRadioButton btn = panel.rdbtn; - btn.addItemListener( e -> comp.setEnabled( btn.isSelected() ) ); - comp.setEnabled( btn.isSelected() ); - header = new JPanel(); - header.setLayout( new BoxLayout( header, BoxLayout.LINE_AXIS ) ); - header.add( btn ); - header.add( Box.createHorizontalGlue() ); - header.add( lbl ); - } - else - { - header = lbl; - } - - c.gridwidth = 1; - c.anchor = GridBagConstraints.LINE_END; - panel.add( header, c ); - - c.gridx++; - c.gridwidth = 2; - c.anchor = GridBagConstraints.LINE_START; - panel.add( comp, c ); - - c.gridx = 0; - c.gridy++; - c.insets = new Insets( topInset, 0, bottomInset, 0 ); - - if ( help != null ) - { - lbl.setToolTipText( help ); - comp.setToolTipText( help ); - } - } - - private void addToLayout( final String help, final JLabel lbl, final JComponent comp, final String units, final Argument< ?, ? > arg ) - { - if ( units == null ) - { - addToLayout( help, lbl, comp, arg ); - return; - } - - lbl.setText( lbl.getText() + " " ); - lbl.setFont( Fonts.SMALL_FONT ); - lbl.setHorizontalAlignment( JLabel.RIGHT ); - comp.setFont( Fonts.SMALL_FONT ); - - final JComponent header; - if ( panel.rdbtn != null ) - { - final JRadioButton btn = panel.rdbtn; - btn.addItemListener( e -> comp.setEnabled( btn.isSelected() ) ); - header = new JPanel(); - header.setLayout( new BoxLayout( header, BoxLayout.LINE_AXIS ) ); - header.add( btn ); - header.add( Box.createHorizontalGlue() ); - header.add( lbl ); - } - else - { - header = lbl; - } - - c.gridwidth = 1; - c.anchor = GridBagConstraints.LINE_END; - panel.add( header, c ); - - c.gridx++; - c.anchor = GridBagConstraints.LINE_START; - panel.add( comp, c ); - - final JLabel lblUnits = new JLabel( " " + units ); - lblUnits.setFont( Fonts.SMALL_FONT ); - c.gridx++; - c.insets = new Insets( topInset, 0, bottomInset, 0 ); - panel.add( lblUnits, c ); - - c.gridx = 0; - c.gridy++; - - if ( help != null ) - { - lbl.setToolTipText( help ); - comp.setToolTipText( help ); - lblUnits.setToolTipText( help ); - } - } - - private void addToLayout( final String help, final JComponent comp ) - { - final JComponent header; - if ( panel.rdbtn != null ) - { - final JRadioButton btn = panel.rdbtn; - btn.addItemListener( e -> comp.setEnabled( btn.isSelected() ) ); - header = new JPanel(); - header.setLayout( new BoxLayout( header, BoxLayout.LINE_AXIS ) ); - header.add( btn ); - header.add( Box.createHorizontalGlue() ); - header.add( comp ); - } - else - { - header = comp; - } - - c.gridx = 0; - c.gridwidth = 3; - c.fill = GridBagConstraints.HORIZONTAL; - c.insets = new Insets( topInset, 0, bottomInset, 0 ); - panel.add( header, c ); - - c.gridx = 0; - c.gridy++; - - if ( help != null ) - comp.setToolTipText( help ); - } - - private void addLastRow() - { - c.gridx = 0; - c.gridy++; - c.weighty = 1.; - panel.add( new JLabel(), c ); - } - - public static ConfigPanel build( final Configurator config ) - { - final ConfigGuiBuilder builder = createBuilder( config ); - // Could we make something more elegant than this? - if ( config instanceof CLIConfigurator ) - ( ( CLIConfigurator ) config ).getCommandArg().accept( builder ); - return build( config, builder ); - } - - private static ConfigGuiBuilder createBuilder( final Configurator config ) - { - return new ConfigGuiBuilder( - config.forwardUITranslators, - config.backwardUITranslators ); - } - - private static ConfigPanel build( final Configurator config, final ConfigGuiBuilder builder ) - { - /* - * Iterate over arguments. - */ - - // Map a selectable group to a button group in the GUI - final Map< Argument< ?, ? >, JRadioButton > buttons = new HashMap<>(); - for ( final SelectableArguments selectable : config.getSelectables() ) - { - final List< Argument< ?, ? > > args = selectable.getArguments(); - final int nItems = args.size(); - final String label = selectable.getKey(); - final IntSupplier get = selectable::getSelected; - final Consumer< Integer > set = selectable::select; - final IntElement element = intElement( label, 0, nItems - 1, get, set ); - builder.panel.elements.put( selectable.getKey(), element ); - final ButtonGroup buttonGroup = linkedButtonGroup( element ); - // Link radio buttons to arguments. - final Enumeration< AbstractButton > enumeration = buttonGroup.getElements(); - final Iterator< Argument< ?, ? > > it = args.iterator(); - while ( enumeration.hasMoreElements() ) - { - final JRadioButton btn = ( JRadioButton ) enumeration.nextElement(); - final Argument< ?, ? > arg = it.next(); - buttons.put( arg, btn ); - btn.setSelected( selectable.getSelection().equals( arg ) ); - } - } - - // Iterate over arguments, taking care of selectable group. - for ( final Argument< ?, ? > arg : config.getArguments() ) - { - if ( !arg.isVisible() ) - continue; - - builder.setCurrentRadioButton( buttons.get( arg ) ); - arg.accept( builder ); - } - - /* - * Last row. - */ - - builder.addLastRow(); - return builder.panel; - } - - private static ButtonGroup linkedButtonGroup( final IntElement element ) - { - final BoundedValue value = element.getValue(); - final ButtonGroup buttonGroup = new ButtonGroup(); - final List< JRadioButton > buttons = new ArrayList<>(); - for ( int i = 0; i <= value.getRangeMax(); i++ ) - { - final JRadioButton btn = new JRadioButton(); - buttons.add( btn ); - final int selected = i; - btn.addItemListener( new ItemListener() - { - - @Override - public void itemStateChanged( final ItemEvent e ) - { - if (btn.isSelected()) - element.set( selected ); - } - } ); - buttonGroup.add( btn ); - } - element.getValue().setUpdateListener( new UpdateListener() - { - - @Override - public void update() - { - final int selected = element.get(); - buttons.get( selected ).setSelected( true ); - } - } ); - return buttonGroup; - } - - public class ConfigPanel extends JPanel - { - - /** - * Map of StyleElements that are created by this builder. The keys are - * the corresponding argument keys. - */ - final Map< String, StyleElement > elements = new LinkedHashMap<>(); - - private JRadioButton rdbtn; - - private static final long serialVersionUID = 1L; - - public void refresh() - { - elements.values().forEach( e -> e.update() ); - } - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/Configurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/Configurator.java deleted file mode 100644 index ae9079506..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/Configurator.java +++ /dev/null @@ -1,1378 +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.cli; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import org.apache.commons.lang3.StringUtils; - -import fiji.plugin.trackmate.util.cli.CommandCLIConfigurator.ExecutablePath; -import fiji.plugin.trackmate.util.cli.CondaCLIConfigurator.CondaEnvironmentCommand; - -/** - * Base class for CLI configurator tools. The implementation of a CLI - * configurator is made by subclassing this class (or one of its specialization) - * and specifying arguments for the CLI component with the addXYX() - * builders. - * - * @author Jean-Yves Tinevez - */ -public abstract class Configurator -{ - - protected final List< Argument< ?, ? > > arguments = new ArrayList<>(); - - protected final List< SelectableArguments > selectables = new ArrayList<>(); - - /** - * The translators that will be applied to the value before displaying it in - * the UI. - */ - protected final Map< Argument< ?, ? >, Function< ?, ? > > forwardUITranslators = new HashMap<>(); - - /** - * The translators that will be applied to a value read from the UI, before - * storing it in the Argument. - */ - protected final Map< Argument< ?, ? >, Function< ?, ? > > backwardUITranslators = new HashMap<>(); - - /* - * GETTERS - */ - - /** - * Returns the list of arguments (plus the command) in this CLI config. All - * arguments are present, regardless of whether they are in - * {@link SelectableArguments}, {@link Argument#visible} or not, - * {@link Argument#inCLI} or not. - * - * @return the list of arguments. - */ - public List< Argument< ?, ? > > getArguments() - { - return Collections.unmodifiableList( arguments ); - } - - /** - * Returns the list of {@link SelectableArguments} in this CLI config. - * - * @return the list of {@link SelectableArguments}. - */ - public List< SelectableArguments > getSelectables() - { - return Collections.unmodifiableList( selectables ); - } - - /** - * Returns the list of arguments set in this CLI config. The list contains - * only the arguments that are selected if they are in a - * {@link SelectableArguments}, and those who are not in a - * {@link SelectableArguments}. - * - * @return the selected arguments. - */ - public List< Argument< ?, ? > > getSelectedArguments() - { - final List< Argument< ?, ? > > selectedArguments = new ArrayList<>( arguments ); - for ( final SelectableArguments selectable : selectables ) - selectable.filter( selectedArguments ); - return selectedArguments; - } - - /* - * SELECTABLE ARGUMENT GROUPS. - */ - - /** - * Creates a 'one or the other' relationships. The arguments that will be - * passed to the {@link SelectableArguments} will be flagged as as not to be - * used concurrently in the same command. This will be used when creating - * UIs. - * - * @return the created selectable arguments group. - */ - protected SelectableArguments addSelectableArguments() - { - final SelectableArguments sa = new SelectableArguments(); - selectables.add( sa ); - return sa; - } - - public static class SelectableArguments - { - - private final List< Argument< ?, ? > > args = new ArrayList<>(); - - private String key; - - private int selected = 0; - - public SelectableArguments add( final Argument< ?, ? > arg ) - { - if ( !args.contains( arg ) ) - args.add( arg ); - return this; - } - - public SelectableArguments key( final String key ) - { - this.key = key; - return this; - } - - public String getKey() - { - return key; - } - - private void filter( final List< Argument< ?, ? > > arguments ) - { - final Set< Argument< ?, ? > > toRemove = new HashSet<>(); - for ( final Argument< ?, ? > arg : arguments ) - { - if ( !args.contains( arg ) ) - continue; // Unknown of this selectable, keep it. - - if ( arg.equals( getSelection() ) ) - continue; // The one selected, keep it. - - // Not selected, remove it. - toRemove.add( arg ); - } - - arguments.removeAll( toRemove ); - } - - public void select( final int selection ) - { - this.selected = Math.max( 0, Math.min( args.size() - 1, selection ) ); - } - - public void select( final Argument< ?, ? > arg ) - { - final int sel = args.indexOf( arg ); - if ( sel < 0 ) - { - this.selected = 0; - return; - } - this.selected = sel; - } - - public void select( final String key ) - { - for ( int i = 0; i < args.size(); i++ ) - { - if ( key.equals( args.get( i ).getKey() ) ) - { - this.selected = i; - return; - } - } - this.selected = 0; - } - - public Argument< ?, ? > getSelection() - { - return args.get( selected ); - } - - public int getSelected() - { - return selected; - } - - /** - * Exposes all members of the selectable. - * - * @return the arguments in this selectable. - */ - public List< Argument< ?, ? > > getArguments() - { - return args; - } - } - - /* - * VISITOR INTERFACE. - */ - - public interface ArgumentVisitor - { - public default void visit( final Flag flag ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final StringArgument stringArgument ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final DoubleArgument doubleArgument ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final IntArgument intArgument ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final ChoiceArgument choiceArgument ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final PathArgument pathArgument ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final ExecutablePath executablePath ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final CondaEnvironmentCommand condaEnvironmentCommand ) - { - throw new UnsupportedOperationException(); - } - } - - /* - * ADDER CLASSES. - */ - - @SuppressWarnings( "unchecked" ) - abstract class Adder< A extends Argument< A, O >, T extends Adder< A, T, O >, O > - { - - protected String name; - - protected String help; - - protected String key; - - protected boolean required; - - protected String units; - - protected O defaultValue; - - protected String argument; - - protected boolean visible = true; // by default - - protected boolean inCLI = true; // by default - - /** - * Specifies the argument to use in the CLI. - * - * @param argument - * the command line argument. - * @return this adder. - */ - public T argument( final String argument ) - { - this.argument = argument; - return ( T ) this; - } - - /** - * Specifies whether this argument will be visible in user interfaces - * generated from the configurator. - * - * @param visible - * UI visibility. - * @return this adder. - */ - public T visible( final boolean visible ) - { - this.visible = visible; - return ( T ) this; - } - - /** - * Specifies a user-friendly name for the argument. - * - * @param name - * the argument name. - * @return this adder. - */ - public T name( final String name ) - { - this.name = name; - return ( T ) this; - } - - /** - * Specifies a help text for the argument. - * - * @param help - * the help text. - * @return this adder. - */ - public T help( final String help ) - { - this.help = help; - return ( T ) this; - } - - /** - * Specifies the key to use to serialize this argument in TrackMate XML - * file. If null, the argument will not be serialized. - * - * @param key - * the argument key. - * @return this adder. - */ - public T key( final String key ) - { - this.key = key; - return ( T ) this; - } - - /** - * Specifies whether this argument is required in the CLI. If - * true, if not set and if there are no default value, an - * error will be thrown. - * - * @param required - * whether this argument is required. - * @return this adder. - */ - public T required( final boolean required ) - { - this.required = required; - return ( T ) this; - } - - /** - * Specifies units for values accepted by this argument. - * - * @param units - * argument value units. - * @return this adder. - */ - public T units( final String units ) - { - this.units = units; - return ( T ) this; - } - - /** - * Specifies a default value for this argument. If the argument is not - * set, it will appear in the command line with this default value. - * - * @param defaultValue - * the argument default value. - * @return this adder. - */ - public T defaultValue( final O defaultValue ) - { - this.defaultValue = defaultValue; - return ( T ) this; - } - - /** - * Specifies whether this argument will appear in the command line. - * - * @param inCLI - * appears in the command line. - * @return this adder. - */ - public T inCLI( final boolean inCLI ) - { - this.inCLI = inCLI; - return ( T ) this; - } - - /** - * Returns the argument created by this builder. - * - * @return the argument. - */ - public abstract A get(); - } - - @SuppressWarnings( "unchecked" ) - private abstract class BoundedAdder< A extends BoundedValueArgument< A, O >, T extends BoundedAdder< A, T, O >, O > extends Adder< A, T, O > - { - protected O min; - - protected O max; - - /** - * Specifies a min for the values accepted by this argument. If the user - * sets a value below this min value, an error is thrown. - * - * @param min - * the min value. - * @return this adder. - */ - public T min( final O min ) - { - this.min = min; - return ( T ) this; - } - - /** - * Specifies a max for the values accepted by this argument. If the user - * sets a value above this max value, an error is thrown. - * - * @param max - * the max value. - * @return this adder. - */ - public T max( final O max ) - { - this.max = max; - return ( T ) this; - } - } - - protected class IntAdder extends BoundedAdder< IntArgument, IntAdder, Integer > - { - @Override - public IntArgument get() - { - final IntArgument arg = new IntArgument() - .name( name ) - .help( help ) - .argument( argument ) - .defaultValue( defaultValue ) - .max( max ) - .min( min ) - .required( required ) - .units( units ) - .visible( visible ) - .inCLI( inCLI ) - .key( key ); - Configurator.this.arguments.add( arg ); - return arg; - } - } - - protected class DoubleAdder extends BoundedAdder< DoubleArgument, DoubleAdder, Double > - { - - private DoubleAdder() - {} - - @Override - public DoubleArgument get() - { - final DoubleArgument arg = new DoubleArgument() - .name( name ) - .help( help ) - .argument( argument ) - .defaultValue( defaultValue ) - .max( max ) - .min( min ) - .required( required ) - .units( units ) - .visible( visible ) - .inCLI( inCLI ) - .key( key ); - Configurator.this.arguments.add( arg ); - return arg; - } - } - - protected class FlagAdder extends Adder< Flag, FlagAdder, Boolean > - { - - private FlagAdder() - {} - - @Override - public Flag get() - { - final Flag arg = new Flag() - .name( name ) - .help( help ) - .argument( argument ) - .defaultValue( defaultValue ) - .required( required ) - .units( units ) - .visible( visible ) - .inCLI( inCLI ) - .key( key ); - Configurator.this.arguments.add( arg ); - return arg; - } - } - - protected class StringAdder extends Adder< StringArgument, StringAdder, String > - { - - private StringAdder() - {} - - @Override - public StringArgument get() - { - final StringArgument arg = new StringArgument() - .name( name ) - .help( help ) - .argument( argument ) - .defaultValue( defaultValue ) - .required( required ) - .units( units ) - .visible( visible ) - .inCLI( inCLI ) - .key( key ); - Configurator.this.arguments.add( arg ); - return arg; - } - } - - protected class PathAdder extends Adder< PathArgument, PathAdder, String > - { - - private PathAdder() - {} - - @Override - public PathArgument get() - { - final PathArgument arg = new PathArgument() - .name( name ) - .help( help ) - .argument( argument ) - .defaultValue( defaultValue ) - .required( required ) - .units( units ) - .visible( visible ) - .inCLI( inCLI ) - .key( key ); - Configurator.this.arguments.add( arg ); - return arg; - } - } - - protected class ChoiceAdder extends Adder< ChoiceArgument, ChoiceAdder, String > - { - - private ChoiceAdder() - {} - - private final List< String > choices = new ArrayList<>(); - - private final List< String > mappeds = new ArrayList<>(); - - /** - * Adds a selectable item for this choice argument. The user will be - * able to select from the list of choices added by this method. - * - * @param choice - * the choice to add. - * @return this adder. - */ - public ChoiceAdder addChoice( final String choice ) - { - return addChoice( choice, choice ); - } - - /** - * Adds a selectable item for this choice argument. The user will be - * able to select from the list of choices added by this method. In the - * command line, this choice will be mapped to the specified string. - *

    - * Example: - * - *

    -		 * addChoice( "Bacteria phase contrast", "bact_phase_omni" )
    -		 * 
    - * - * will display Bacteria phase contrast in the UI and results in - * using bact_phase_omni in the generated command line. - * - * @param choice - * the choice to add. - * @param mapped - * the string the choice will be mapped in the command line - * argument. - * @return this adder. - */ - public ChoiceAdder addChoice( final String choice, final String mapped ) - { - if ( !choices.contains( choice ) ) - { - choices.add( choice ); - mappeds.add( mapped ); - } - return this; - } - - /** - * Adds the specified items for this choice argument. The user will be - * able to select from the list of choices added by this method. - * - * @param c - * the choices to add. - * @return this adder. - */ - public Adder< ChoiceArgument, ChoiceAdder, String > addChoiceAll( final Collection< String > c ) - { - for ( final String in : c ) - addChoice( in ); - return this; - } - - /** - * Specifies a default value for this argument. If the argument is not - * set, it will appear in the command line with this default value. - *

    - * The value specified must belong to the list of choices set with - * {@link #addChoice(String)} or {@link #addChoiceAll(Collection)}. - * - * @param defaultChoice - * the argument default value. - * @return this adder. - */ - @Override - public ChoiceAdder defaultValue( final String defaultChoice ) - { - final int sel = choices.indexOf( defaultChoice ); - if ( sel < 0 ) - throw new IllegalArgumentException( "Unknown selection '" + defaultChoice + "' for parameter '" - + name + "'. Must be one of " + StringUtils.join( choices, ", " ) + "." ); - return super.defaultValue( defaultChoice ); - } - - /** - * Specifies a default value for this argument, by selecting the - * possible value in order or addition. If the argument is not set, it - * will appear in the command line with this default value. - * - * @param selected - * the index of the default value in the list of possible - * choices. - * @return this adder. - */ - public ChoiceAdder defaultValue( final int selected ) - { - if ( selected < 0 || selected >= choices.size() ) - throw new IllegalArgumentException( "Invalid index for selection of parameter '" - + name + "'. Must be in scale " + 0 + " to " + ( choices.size() - 1 ) + " in " - + StringUtils.join( choices, ", " ) + "." ); - return defaultValue( choices.get( selected ) ); - } - - @Override - public ChoiceArgument get() - { - final ChoiceArgument arg = new ChoiceArgument() - .name( name ) - .help( help ) - .argument( argument ) - .required( required ) - .units( units ) - .visible( visible ) - .inCLI( inCLI ) - .key( key ); - for ( int i = 0; i < choices.size(); i++ ) - arg.addChoice( choices.get( i ), mappeds.get( i ) ); - - arg.defaultValue( defaultValue ); - Configurator.this.arguments.add( arg ); - return arg; - } - } - - /* - * ADDER METHODS. - */ - - /** - * Adds a boolean flag argument to the CLI, via a builder. - *

    - * In the CLI tools we have been trying to implement, they exist in two - * flavors, that are both supported. - * - * If the argument starts with a double-dash --, as in Python - * argparse syntax, then it is understood that setting this flag to true - * makes it appear in the CLI. For instance: --use-gpu. - * - * If the arguments ends with a '=' sign (e.g. "save_txt="), - * then it expects to receive a 'true' or 'false' value. - * - * @return a new flag argument builder. - */ - protected FlagAdder addFlag() - { - return new FlagAdder(); - } - - /** - * Adds a string argument to the CLI, via a builder. - * - * @return new string argument builder. - */ - protected StringAdder addStringArgument() - { - return new StringAdder(); - } - - /** - * Adds a path argument to the CLI, via a builder. - * - * @return new path argument builder. - */ - protected PathAdder addPathArgument() - { - return new PathAdder(); - } - - /** - * Adds a integer argument to the CLI, via a builder. - * - * @return new integer argument builder. - */ - protected IntAdder addIntArgument() - { - return new IntAdder(); - } - - /** - * Adds a double argument to the CLI, via a builder. - * - * @return new double argument builder. - */ - protected DoubleAdder addDoubleArgument() - { - return new DoubleAdder(); - } - - /** - * Adds a choice argument to the CLI, via a builder. Such arguments can - * accept a series of discrete values (specified by addChoice() method in - * the builder). - * - * @return new choice argument builder. - */ - protected ChoiceAdder addChoiceArgument() - { - return new ChoiceAdder(); - } - - /** - * Adds an extra argument, defined by other means than the adder methods. - * - * @param extraArg - * the argument to add to this CLI config. - * @return the argument - * @param - * the type of the argument. - */ - protected < T extends Argument< ?, ? > > T addExtraArgument( final T extraArg ) - { - this.arguments.add( extraArg ); - return extraArg; - } - - /* - * ARGUMENT CLASSES. - */ - - public static class Flag extends Argument< Flag, Boolean > - { - Flag() - {} - - /** - * Sets this flag argument to true. - */ - public void set() - { - set( true ); - } - - @Override - public void accept( final ArgumentVisitor visitor ) - { - visitor.visit( this ); - } - - @Override - public void setValueObject( final Object val ) - { - if ( !Boolean.class.isInstance( val ) ) - throw new IllegalArgumentException( "Argument '" + name + "' expects Boolean. Got " + val.getClass().getSimpleName() ); - - final Boolean v = ( ( Boolean ) val ); - set( v ); - } - } - - /** - * Specialization of {@link StringArgument} to be used in a GUI. - */ - public static class PathArgument extends AbstractStringArgument< PathArgument > - { - private PathArgument() - {} - - @Override - public void accept( final ArgumentVisitor visitor ) - { - visitor.visit( this ); - } - } - - public static class StringArgument extends AbstractStringArgument< StringArgument > - { - private StringArgument() - {} - - @Override - public void accept( final ArgumentVisitor visitor ) - { - visitor.visit( this ); - } - } - - public static abstract class AbstractStringArgument< T extends AbstractStringArgument< T > > extends Argument< T, String > - { - - @Override - public void setValueObject( final Object val ) - { - if ( !String.class.isInstance( val ) ) - throw new IllegalArgumentException( "Argument '" + name + "' expects String. Got " + val.getClass().getSimpleName() ); - - final String v = ( ( String ) val ); - set( v ); - } - } - - public static class IntArgument extends BoundedValueArgument< IntArgument, Integer > - { - IntArgument() - {} - - @Override - public void accept( final ArgumentVisitor visitor ) - { - visitor.visit( this ); - } - - @Override - public void setValueObject( final Object val ) - { - if ( !Integer.class.isInstance( val ) ) - throw new IllegalArgumentException( "Argument '" + name + "' expects Integer. Got " + val.getClass().getSimpleName() ); - - final Integer v = ( ( Integer ) val ); - set( v ); - } - } - - public static class DoubleArgument extends BoundedValueArgument< DoubleArgument, Double > - { - @Override - public void accept( final ArgumentVisitor visitor ) - { - visitor.visit( this ); - } - - @Override - public void setValueObject( final Object val ) - { - if ( !Double.class.isInstance( val ) ) - throw new IllegalArgumentException( "Argument '" + name + "' expects Double. Got " + val.getClass().getSimpleName() ); - - final Double v = ( ( Double ) val ); - set( v ); - } - } - - public static class ChoiceArgument extends Argument< ChoiceArgument, String > - { - - private final List< String > choices = new ArrayList<>(); - - private final List< String > displays = new ArrayList<>(); - - private int selected = -1; // -1 means no selection.; - - private ChoiceArgument() - {} - - private ChoiceArgument addChoice( final String choice, final String displayed ) - { - if ( !choices.contains( choice ) ) - { - choices.add( choice ); - displays.add( displayed ); - } - return this; - } - - /** - * The list of the display strings corresponding to the possible - * choices. - * - * @return The list of the display strings. - */ - public List< String > getDisplays() - { - return displays; - } - - @Override - public void accept( final ArgumentVisitor visitor ) - { - visitor.visit( this ); - } - - @Override - public boolean isSet() - { - return selected >= 0; - } - - @Override - public String getValue() - { - return choices.get( selected ); - } - - public int getSelectedIndex() - { - return selected; - } - - @Override - public void set( final String choice ) - { - final int sel = choices.indexOf( choice ); - if ( sel < 0 ) - throw new IllegalArgumentException( "Unknown selection '" + choice + "' for parameter '" - + name + "'. Must be one of: [ " + StringUtils.join( choices, ", " ) + " ]." ); - this.selected = sel; - } - - public void set( final int selected ) - { - if ( selected < 0 || selected >= choices.size() ) - throw new IllegalArgumentException( "Invalid index for selection of parameter '" - + name + "'. Must be in scale " + 0 + " to " + ( choices.size() - 1 ) + " in " - + StringUtils.join( choices, ", " ) + "." ); - this.selected = selected; - } - - @Override - public void setValueObject( final Object val ) - { - if ( !String.class.isInstance( val ) ) - throw new IllegalArgumentException( "Argument '" + name + "' expects String. Got " + val.getClass().getSimpleName() ); - - final String v = ( ( String ) val ); - set( v ); - } - - @Override - ChoiceArgument defaultValue( final String defaultChoice ) - { - final int sel = choices.indexOf( defaultChoice ); - if ( sel < 0 ) - throw new IllegalArgumentException( "Unknown selection '" + defaultChoice + "' for parameter '" - + name + "'. Must be one of " + StringUtils.join( choices, ", " ) + "." ); - super.defaultValue( defaultChoice ); - return this; - } - - ChoiceArgument defaultValue( final int selected ) - { - if ( selected < 0 || selected >= choices.size() ) - throw new IllegalArgumentException( "Invalid index for selection of parameter '" - + name + "'. Must be in scale " + 0 + " to " + ( choices.size() - 1 ) + " in " - + StringUtils.join( choices, ", " ) + "." ); - super.defaultValue( choices.get( selected ) ); - return this; - } - - @Override - public String toString() - { - final String str = super.toString(); - return str - + " - choices: " + choices + "\n" - + " - display strings: " + displays + "\n"; - } - - - } - - @SuppressWarnings( "unchecked" ) - public static abstract class BoundedValueArgument< T extends BoundedValueArgument< T, O >, O > extends Argument< T, O > - { - - private BoundedValueArgument() - {} - - private O min; - - private O max; - - T min( final O min ) - { - this.min = min; - return ( T ) this; - } - - public O getMax() - { - return max; - } - - T max( final O max ) - { - this.max = max; - return ( T ) this; - } - - public O getMin() - { - return min; - } - - public boolean hasMin() - { - return min != null; - } - - public boolean hasMax() - { - return max != null; - } - - @Override - public String toString() - { - final String str = super.toString(); - return str - + " - has min: " + hasMin() + "\n" - + ( hasMin() - ? " - min: " + getMin() + "\n" - : "" ) - + " - has max: " + hasMax() + "\n" - + ( hasMax() - ? " - max: " + getMax() + "\n" - : "" ); - } - } - - /** - * Mother class for command arguments. Typically in the command line they - * appear after the executable name with '--something'. - * - * @param - * the implementing type of the argument. - * @param - * the type of value this argument accepts. - */ - @SuppressWarnings( "unchecked" ) - public static abstract class Argument< T extends Argument< T, O >, O > - { - - protected boolean visible = true; - - protected String name; - - protected String help; - - private String key; - - private String argument; - - private boolean inCLI = true; - - T argument( final String argument ) - { - this.argument = argument; - return ( T ) this; - } - - public String getArgument() - { - return argument; - } - - private O value; - - private O defaultValue; - - /** - * Arguments flagged as not required, but without default value, will be - * prompted to the user. - */ - private boolean required = false; - - private String units; - - T required( final boolean required ) - { - this.required = required; - return ( T ) this; - } - - public boolean isRequired() - { - return required; - } - - T units( final String units ) - { - this.units = units; - return ( T ) this; - } - - public String getUnits() - { - return units; - } - - T defaultValue( final O defaultValue ) - { - this.defaultValue = defaultValue; - return ( T ) this; - } - - public O getDefaultValue() - { - return defaultValue; - } - - public boolean hasDefaultValue() - { - return defaultValue != null; - } - - public void set( final O value ) - { - this.value = value; - } - - public O getValue() - { - return value; - } - - public boolean isSet() - { - return value != null; - } - - public Object getValueObject() - { - return getValue(); - } - - - /** - * Sets the value of this argument via the specified object. This is - * used when deserializing TrackMate settings map. - * - * @param val - * the object to set the value from - * @see TrackMateSettingsBuilder - */ - public abstract void setValueObject( Object val ); - - /** - * If false, this argument won't be used in the command - * line generator. This is useful to add extra parameters to the GUI - * that are required by TrackMate but not by the CLI tool. - * - * @param inCLI - * whether this argument should be used when generating - * commands. By default: true. - * @see CommandBuilder - * @return the argument. - */ - T inCLI( final boolean inCLI ) - { - this.inCLI = inCLI; - return ( T ) this; - } - - public boolean isInCLI() - { - return inCLI; - } - - /** - * If false, this argument won't be shown in UIs. It will - * be used for the command line builder nonetheless. - * - * @param visible - * whether this argument should be visible in the UI or not. - * By default: true. - * @see CliGuiBuilder - * @return the argument. - */ - T visible( final boolean visible ) - { - this.visible = visible; - return ( T ) this; - } - - public boolean isVisible() - { - return visible; - } - - T name( final String name ) - { - this.name = name; - return ( T ) this; - } - - T help( final String help ) - { - this.help = help; - return ( T ) this; - } - - /** - * Sets the String key to use in TrackMate settings map - * de/serialization. - * - * @param key - * the key to use. By default: the {@link #name} of this - * argument. - * @return the argument. - * @see TrackMateSettingsBuilder - */ - T key( final String key ) - { - - this.key = key; - return ( T ) this; - } - - public String getName() - { - return name; - } - - public String getHelp() - { - return help; - } - - public String getKey() - { - return key; - } - - public abstract void accept( final ArgumentVisitor visitor ); - - @Override - public String toString() - { - return this.getClass().getSimpleName() - + " (" + getName() + ")\n" - + " - help: " + getHelp() + "\n" - + " - key: " + getKey() + "\n" - + " - argument: " + getArgument() + "\n" - + " - visible: " + isVisible() + "\n" - + " - is set: " + isSet() + "\n" - + ( isSet() - ? " - value: " + getValue() + "\n" - : "" ) - + " - has default value: " + hasDefaultValue() + "\n" - + ( hasDefaultValue() - ? " - default value: " + getDefaultValue() + "\n" - : "" ) - + " - required: " + isRequired() + "\n" - + " - units: " + getUnits() + "\n"; - } - } - - @Override - public String toString() - { - final StringBuilder str = new StringBuilder(); - str.append( super.toString() + "\n" ); - arguments.forEach( str::append ); - return str.toString(); - } - - public String check() - { - final StringBuilder str = new StringBuilder(); - for ( final Argument< ?, ? > arg : getSelectedArguments() ) - { - if ( arg.isInCLI() && arg.getArgument() == null ) - str.append( "Argument '" + arg.getName() + "' does not define the argument switch.\n" ); - - if ( arg.isRequired() && !arg.isSet() && !arg.hasDefaultValue() ) - str.append( "Argument '" + arg.getName() + "' is required but is not set and does not define a default value.\n" ); - } - return str.length() == 0 ? null : str.toString(); - } - - /** - * Decorates the specified argument with a translator, that will modify the - * value displayed in the user-interfaces built with this - * configurator. - *

    - * This can be used to translate the value of an argument into a more - * user-friendly value, for instance to translate a radius into a diameter. - *

    - * Warning: display translation is not supported for {@link Flag} and - * {@link ChoiceArgument}. - * - * @param arg - * the argument to decorate. - * @param forward - * the function to apply to the value to display it in the UI. - * @param backward - * the function to apply to the value to get the value back from - * the UI. - * @param - * the type of value the argument accepts. - */ - protected < O > void setDisplayTranslator( final Argument< ?, O > arg, final Function< O, O > forward, final Function< O, O > backward ) - { - if ( arg instanceof ChoiceArgument ) - throw new IllegalArgumentException( "ChoiceArgument does not support display translators." ); - if ( arg instanceof Flag ) - throw new IllegalArgumentException( "Flag does not support display translators." ); - - forwardUITranslators.put( arg, forward ); - backwardUITranslators.put( arg, backward ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/FactoryGenericConfig.java b/src/main/java/fiji/plugin/trackmate/util/cli/FactoryGenericConfig.java deleted file mode 100644 index 00c31a88d..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/FactoryGenericConfig.java +++ /dev/null @@ -1,80 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import fiji.plugin.trackmate.TrackMateModule; -import fiji.plugin.trackmate.visualization.ViewUtils; -import ij.ImagePlus; -import net.imagej.ImgPlus; -import net.imglib2.img.display.imagej.ImageJFunctions; - -/** - * Base interface for detector and tracker factories that need to be configured - * with a {@link Configurator} instance. - * - * @author Jean-Yves Tinevez - * - * @param - * the type of {@link Configurator} used to configure the factory. - */ -public interface FactoryGenericConfig< C extends Configurator > extends TrackMateModule -{ - - /** - * Creates a new configurator for this detector factory, based on the - * specified image. - * - * @param imp - * the input image to configure the detector for. - * @return a new {@link Configurator}. - */ - public C getConfigurator( ImagePlus imp ); - - /** - * Creates a new configurator for this detector factory, based on the - * specified image. - * - * @param img - * the input image to configure the detector for. - * @return a new {@link Configurator}. - */ - public default C getConfigurator( final ImgPlus< ? > img ) - { - @SuppressWarnings( { "unchecked", "rawtypes" } ) - final ImagePlus imp = ImageJFunctions.wrap( ( ImgPlus ) img, "wrapped" ); - return getConfigurator( imp ); - } - - /** - * Creates a new configurator for this detector factory. - * - * @return a new {@link Configurator}. - */ - public default C getConfigurator() - { - final int nZ = 2; // Force 3D - final int nT = 2; // Force timelapse - final double[] calibration = new double[] { 1., 1., 1. }; - final ImagePlus imp = ViewUtils.makeEmptyImagePlus( 32, 32, nZ, nT, calibration ); - return getConfigurator( imp ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/GenericConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/util/cli/GenericConfigurationPanel.java deleted file mode 100644 index fba18e4b3..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/GenericConfigurationPanel.java +++ /dev/null @@ -1,132 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import static fiji.plugin.trackmate.gui.Fonts.BIG_FONT; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.util.HashMap; -import java.util.Map; - -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.Icon; -import javax.swing.JEditorPane; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.SwingConstants; - -import fiji.plugin.trackmate.gui.GuiUtils; -import fiji.plugin.trackmate.gui.components.ConfigurationPanel; -import fiji.plugin.trackmate.util.cli.ConfigGuiBuilder.ConfigPanel; - -public class GenericConfigurationPanel extends ConfigurationPanel -{ - - private static final long serialVersionUID = 1L; - - protected final ConfigPanel mainPanel; - - protected final Configurator config; - - public GenericConfigurationPanel( - final Configurator config, - final String title, - final Icon icon, - final String docURL ) - { - this.config = config; - - final BorderLayout borderLayout = new BorderLayout(); - setLayout( borderLayout ); - - /* - * HEADER - */ - - final JPanel header = new JPanel(); - header.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); - header.setLayout( new BoxLayout( header, BoxLayout.Y_AXIS ) ); - - final JLabel lblDetector = new JLabel( title, icon, JLabel.RIGHT ); - lblDetector.setFont( BIG_FONT ); - lblDetector.setHorizontalAlignment( SwingConstants.CENTER ); - lblDetector.setAlignmentX( JLabel.CENTER_ALIGNMENT ); - header.add( lblDetector ); - if ( docURL != null ) - { - final JEditorPane infoDisplay = GuiUtils.infoDisplay( "" + "Documentation for this module " - + "on the ImageJ Wiki." - + "", false ); - infoDisplay.setMaximumSize( new Dimension( 100_000, 40 ) ); - header.add( Box.createVerticalStrut( 5 ) ); - header.add( infoDisplay ); - } - add( header, BorderLayout.NORTH ); - - /* - * CONFIG - */ - - this.mainPanel = ConfigGuiBuilder.build( config ); - final JScrollPane scrollPane = new JScrollPane( mainPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); - scrollPane.setBorder( null ); - scrollPane.getVerticalScrollBar().setUnitIncrement( 16 ); - add( scrollPane, BorderLayout.CENTER ); - } - - @Override - public void setSettings( final Map< String, Object > settings ) - { - try - { - TrackMateSettingsBuilder.fromTrackMateSettings( settings, config ); - } - catch ( final IllegalArgumentException e ) - { - // Incompatible settings, we keep the defaults. - } - catch ( final Exception e ) - { - e.printStackTrace(); - } - finally - { - mainPanel.refresh(); - } - } - - @Override - public Map< String, Object > getSettings() - { - final Map< String, Object > map = new HashMap<>(); - TrackMateSettingsBuilder.toTrackMateSettings( map, config ); - return map; - } - - @Override - public void clean() - {} -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/GenericDetectionConfigurationPanel.java b/src/main/java/fiji/plugin/trackmate/util/cli/GenericDetectionConfigurationPanel.java deleted file mode 100644 index f5c0df59b..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/GenericDetectionConfigurationPanel.java +++ /dev/null @@ -1,111 +0,0 @@ -package fiji.plugin.trackmate.util.cli; - -import java.awt.BorderLayout; -import java.util.function.DoubleConsumer; -import java.util.function.Supplier; - -import javax.swing.Icon; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.DoubleElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.IntElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StyleElement; -import fiji.plugin.trackmate.util.DetectionPreview; -import fiji.plugin.trackmate.util.DetectionPreview.Builder; -import fiji.plugin.trackmate.util.DetectionPreviewPanel; - -/** - * Specialization of {@link GenericConfigurationPanel} for - * {@link SpotDetectorFactoryBase}. It adds a {@link DetectionPreview} at the - * bottom of the panel. - */ -public class GenericDetectionConfigurationPanel extends GenericConfigurationPanel -{ - - private static final long serialVersionUID = 1L; - - public GenericDetectionConfigurationPanel( - final Settings settings, - final Model model, - final Configurator config, - final String title, - final Icon icon, - final String docURL, - final Supplier< SpotDetectorFactoryBase< ? > > factorySupplier ) - { - super( config, title, icon, docURL ); - - final DetectionPreview detectionPreview = getDetectionPreview( model, settings, factorySupplier ); - final DetectionPreviewPanel p = detectionPreview.getPanel(); - add( p, BorderLayout.SOUTH ); - } - - /** - * Creates a basic {@link DetectionPreview}. Can be overridden by subclasses - * - * @param model - * the model to update with the previewed spots. - * @param settings - * the settings to use to run the detection. - * @param factorySupplier - * a supplier for the detector factory. - * @return the detection preview object. - */ - protected DetectionPreview getDetectionPreview( - final Model model, - final Settings settings, - final Supplier< SpotDetectorFactoryBase< ? > > factorySupplier ) - { - final Builder builder = DetectionPreview.create() - .model( model ) - .settings( settings ) - .detectorFactory( factorySupplier.get() ) - .detectionSettingsSupplier( () -> getSettings() ); - if ( config instanceof HasInteractivePreview ) - { - final HasInteractivePreview hasPreview = ( HasInteractivePreview ) config; - - final String key = hasPreview.getPreviewArgumentKey(); - builder.thresholdKey( key ); - - if ( key != null ) - { - final DoubleConsumer thresholdUpdater; - final StyleElement element = mainPanel.elements.get( key ); - if ( element instanceof DoubleElement ) - { - thresholdUpdater = t -> { - ( ( DoubleElement ) element ).set( t ); - mainPanel.refresh(); - }; - } - else if ( element instanceof BoundedDoubleElement ) - { - thresholdUpdater = t -> { - ( ( BoundedDoubleElement ) element ).set( t ); - mainPanel.refresh(); - }; - } - else if ( element instanceof IntElement ) - { - final IntElement el = ( IntElement ) element ; - thresholdUpdater = t -> { - el.set( ( int ) t ); - mainPanel.refresh(); - }; - } - else - { - throw new IllegalStateException( "Cannot create interactive thresholding preview for arguments that map of an element of class: " + element.getClass().getDeclaringClass() ); - } - builder.thresholdUpdater( thresholdUpdater ); - } - - builder.axisLabel( hasPreview.getPreviewAxisLabel() ); - } - return builder.get(); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/HasInteractivePreview.java b/src/main/java/fiji/plugin/trackmate/util/cli/HasInteractivePreview.java deleted file mode 100644 index cb7ff2921..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/HasInteractivePreview.java +++ /dev/null @@ -1,54 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -/** - * Interface for {@link Configurator}s which settings can be previewed with - * {@link fiji.plugin.trackmate.util.DetectionPreview}. - */ -public interface HasInteractivePreview -{ - - /** - * Declares the argument key and axis label to be used in the - * {@link fiji.plugin.trackmate.util.DetectionPreview} GUI. - * - * @return argumentKey the argument key. This is the key used in the - * {@link fiji.plugin.trackmate.util.cli.Configurator.Argument#getKey()}. - */ - public default String getPreviewArgumentKey() - { - return null; - } - - /** - * Declares the axis label to be used in the - * {@link fiji.plugin.trackmate.util.DetectionPreview} GUI. - * - * @return axisLabel the label to be used for the axis in the detection - * preview histogram. - */ - public default String getPreviewAxisLabel() - { - return null; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/TrackMateSettingsBuilder.java b/src/main/java/fiji/plugin/trackmate/util/cli/TrackMateSettingsBuilder.java deleted file mode 100644 index b6956ccb7..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/TrackMateSettingsBuilder.java +++ /dev/null @@ -1,137 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import java.util.LinkedHashMap; -import java.util.Map; - -import fiji.plugin.trackmate.util.cli.Configurator.Argument; -import fiji.plugin.trackmate.util.cli.Configurator.SelectableArguments; - -public class TrackMateSettingsBuilder -{ - - private TrackMateSettingsBuilder() - {} - - private static void toMap( final Argument< ?, ? > arg, final Map< String, Object > settings ) - { - if ( arg.getKey() != null ) - settings.put( arg.getKey(), arg.getValueObject() ); - } - - private static void toMap( final SelectableArguments selectable, final Map< String, Object > settings ) - { - if ( selectable.getKey() != null ) - settings.put( selectable.getKey(), selectable.getSelection().getKey() ); - } - - private static void fromMap( final Map< String, Object > settings, final Argument< ?, ? > arg ) - { - final Object val = settings.get( arg.getKey() ); - if ( val != null ) - arg.setValueObject( val ); - } - - private static void fromMap( final Map< String, Object > settings, final SelectableArguments selectable ) - { - final Object val = settings.get( selectable.getKey() ); - if ( val != null ) - selectable.select( ( String ) val ); - } - - /** - * Serializes the specified config and extra parameters to a TrackMate - * settings map. - * - * @param settings - * the map to serialize settings to. - * @param config - * the config. - */ - public static void toTrackMateSettings( final Map< String, Object > settings, final Configurator config ) - { - if ( config instanceof CLIConfigurator ) - toMap( ( ( CLIConfigurator ) config ).getCommandArg(), settings ); - config.getArguments().forEach( arg -> toMap( arg, settings ) ); - config.getSelectables().forEach( selectable -> toMap( selectable, settings ) ); - } - - /** - * Serializes the parameters stored in a TrackMate map to a config object. - * The parameters in the map unknown to the config are simply ignored. - * - * @param settings - * the map to read parameters from. - * @param config - * the config to write parameters into. - */ - public static final void fromTrackMateSettings( final Map< String, Object > settings, final Configurator config ) - { - if ( config instanceof CLIConfigurator ) - fromMap( settings, ( ( CLIConfigurator ) config ).getCommandArg() ); - config.getArguments().forEach( arg -> fromMap( settings, arg ) ); - config.getSelectables().forEach( selectable -> fromMap( settings, selectable ) ); - } - - /** - * Returns a map containing the default settings of the specified - * Configurator. The map will contain the keys of all arguments and - * selectable arguments, and their default values. - * - * @param config - * the configurator. - * @return a new map containing the default settings. - */ - public static final Map< String, Object > getDefaultSettings( final Configurator config ) - { - final Map< String, Object > settings = new LinkedHashMap< String, Object >(); - - if ( config instanceof CLIConfigurator ) - { - final Argument< ?, ? > commandArg = ( ( CLIConfigurator ) config ).getCommandArg(); - if ( commandArg.getKey() != null && commandArg.hasDefaultValue() ) - settings.put( commandArg.getKey(), commandArg.getDefaultValue() ); - } - - config.arguments.forEach( arg -> { - final String key = arg.getKey(); - if ( key == null ) - return; - - if ( !arg.hasDefaultValue() ) - throw new IllegalArgumentException( "The argument '" + key + "' in the configurator " + config.getClass().getSimpleName() + " has no default value, which is required." ); - settings.put( key, arg.getDefaultValue() ); - } ); - config.selectables.forEach( sel -> { - final String selKey = sel.getKey(); - if ( selKey == null ) - return; - - final String argKey = sel.getSelection().getKey(); - if ( argKey == null ) - throw new IllegalArgumentException( "The selectable argument '" + selKey + "' in the configurator " + config + " has no key, which is required." ); - settings.put( selKey, argKey ); - } ); - return settings; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/condapath/CondaDetector.java b/src/main/java/fiji/plugin/trackmate/util/cli/condapath/CondaDetector.java deleted file mode 100644 index 06a04586c..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/condapath/CondaDetector.java +++ /dev/null @@ -1,1549 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli.condapath; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import ij.IJ; - -/** - * Detects conda installation and provides information needed to run conda - * commands. Returns both the conda executable path and CONDA_ROOT_PREFIX. - */ -public class CondaDetector -{ - - private static CondaInfo cachedInfo = null; - - private static long cacheTimestamp = 0; - - private static final long CACHE_TIMEOUT_MS = 60000; // 1 minute - - /** - * Container for conda installation information - */ - public static class CondaInfo - { - private final String condaExecutable; - - private final String rootPrefix; - - private final String version; - - public CondaInfo( final String condaExecutable, final String rootPrefix, final String version ) - { - this.condaExecutable = condaExecutable; - this.rootPrefix = rootPrefix; - this.version = version; - } - - /** - * Path to conda executable (for running `conda run`) - */ - public String getCondaExecutable() - { - return condaExecutable; - } - - /** - * Conda root prefix (for CONDA_ROOT_PREFIX environment variable) - */ - public String getRootPrefix() - { - return rootPrefix; - } - - /** - * Conda version (informational) - */ - public String getVersion() - { - return version; - } - - /** - * Check if this is micromamba (for command flag compatibility) - */ - public boolean isMicromamba() - { - return isMicromambaExecutable( condaExecutable ); - } - - @Override - public String toString() - { - return String.format( "CondaInfo{executable='%s', rootPrefix='%s', version='%s'}", - condaExecutable, rootPrefix, version ); - } - } - - /** - * Container for conda environment information - */ - public static class CondaEnvironment - { - private final String name; - - private final String path; - - public CondaEnvironment( final String name, final String path ) - { - this.name = name; - this.path = path; - } - - public String getName() - { - return name; - } - - public String getPath() - { - return path; - } - - @Override - public String toString() - { - return name + " (" + path + ")"; - } - } - - /** - * Custom exception for when conda cannot be found - */ - public static class CondaNotFoundException extends Exception - { - private static final long serialVersionUID = 1L; - - public CondaNotFoundException( final String message ) - { - super( message ); - } - } - - // ========== Main Detection Methods ========== - - /** - * Main entry point - detects conda and returns all necessary information - */ - public static CondaInfo detect() throws CondaNotFoundException - { - // Check cache - final long now = System.currentTimeMillis(); - if ( cachedInfo != null && ( now - cacheTimestamp ) < CACHE_TIMEOUT_MS ) - return cachedInfo; - - // Try detection methods in order of reliability - final CondaInfo info = detectCondaInfo(); - - if ( info != null ) - { - cachedInfo = info; - cacheTimestamp = now; - return info; - } - - throw new CondaNotFoundException( - "Could not find conda installation. Please ensure conda is installed and initialized.\n" + - "Suggestions:\n" + - " 1. Install Anaconda or Miniconda\n" + - " 2. Run 'conda init' in your terminal\n" + - " 3. Restart your application" ); - } - - /** - * Clear the cache (useful for testing or after conda installation changes) - */ - public static void clearCache() - { - cachedInfo = null; - cacheTimestamp = 0; - } - - // ========== Detection Logic ========== - - private static CondaInfo detectCondaInfo() - { - IJ.log( "Starting conda detection..." ); - - // Method 1: Use CONDA_EXE environment variable (fastest, most reliable) - CondaInfo info = detectFromEnvironment(); - if ( info != null ) - return info; - - // Method 2: Parse shell config files (critical for macOS/Linux GUI apps) - IJ.log( "" ); - if ( isMac() || isLinux() ) - { - info = detectFromShellConfig(); - if ( info != null ) - return info; - } - else - { - IJ.log( "Method 2: Parsing shell configuration files..." ); - IJ.log( " Skipped (only applicable on macOS/Linux)" ); - } - - // Method 3: Search in PATH - IJ.log( "" ); - info = detectFromPath(); - if ( info != null ) - return info; - - // Method 4: Check common installation locations (fallback) - IJ.log( "" ); - info = detectFromCommonLocations(); - if ( info != null ) - return info; - - IJ.log( "" ); - IJ.log( "Failed to detect conda installation" ); - return null; - } - - /** - * Method 1: Detect from CONDA_EXE environment variable - */ - private static CondaInfo detectFromEnvironment() - { - IJ.log( "Method 1: Checking CONDA_EXE environment variable..." ); - - // Check CONDA_EXE first - String condaExe = System.getenv( "CONDA_EXE" ); - - // Also check for _CONDA_EXE (sometimes set by conda) - if ( condaExe == null || condaExe.isEmpty() ) - condaExe = System.getenv( "_CONDA_EXE" ); - - if ( condaExe != null && new File( condaExe ).exists() ) - { - // Resolve symlinks/aliases - condaExe = resolveRealExecutable( condaExe ); - - if ( condaExe != null ) - { - final String rootPrefix = deriveRootPrefix( condaExe ); - final String version = getCondaVersion( condaExe ); - - if ( rootPrefix != null && version != null ) - { - IJ.log( " Found conda via CONDA_EXE: " + condaExe ); - return new CondaInfo( condaExe, rootPrefix, version ); - } - } - } - - IJ.log( " CONDA_EXE not set or invalid" ); - return null; - } - - /** - * Method 2: Parse shell config files to find conda - * Essential for macOS/Linux GUI applications that don't inherit shell - * environment - */ - private static CondaInfo detectFromShellConfig() - { - IJ.log( "Method 2: Parsing shell configuration files..." ); - final String home = System.getProperty( "user.home" ); - - // Check shell config files in order of likelihood - final String[] configFiles = { - ".zshrc", // macOS default since Catalina - ".bash_profile", // macOS bash - ".bashrc", // Linux bash - ".profile", // Generic - ".config/fish/config.fish" // Fish shell - }; - - for ( final String configFile : configFiles ) - { - final Path configPath = Paths.get( home, configFile ); - - if ( Files.exists( configPath ) ) - { - try - { - final String content = new String( Files.readAllBytes( configPath ) ); - - // Look for conda/mamba/micromamba references - // Check for explicit strings OR any conda-related content - if ( content.contains( "conda.sh" ) || - content.contains( "mamba.sh" ) || - content.contains( "conda initialize" ) || - content.contains( "conda" ) || - content.contains( "mamba" ) || - content.contains( "micromamba" ) ) - { - IJ.log( " Checking: " + configFile ); - String condaExe = extractCondaExeFromConfig( content, configPath ); - - if ( condaExe != null && new File( condaExe ).exists() ) - { - // Resolve symlinks/aliases - condaExe = resolveRealExecutable( condaExe ); - - final String rootPrefix = deriveRootPrefix( condaExe ); - final String version = getCondaVersion( condaExe ); - - if ( rootPrefix != null && version != null ) - { - IJ.log( " Found conda by parsing: " + configFile ); - return new CondaInfo( condaExe, rootPrefix, version ); - } - else - { - IJ.log( " Found executable but failed to validate: " + condaExe ); - } - } - } - } - catch ( final IOException e ) - { - // Can't read this config file, try next - IJ.log( " Could not read: " + configFile ); - } - } - } - - IJ.log( " No conda found in shell config files" ); - return null; - } - - /** - * Extract conda/mamba/micromamba executable path from shell config content - * Handles conda, mamba, mambaforge, miniforge, micromamba installations - */ - private static String extractCondaExeFromConfig( final String content, final Path configPath ) - { - // Method 1: Look for __conda_setup or __mamba_setup pattern - // Pattern: __conda_setup="$('/Users/username/mambaforge/bin/conda' - // 'shell.zsh' 'hook' 2> /dev/null)" - Pattern pattern = Pattern.compile( - "__(conda|mamba)_setup=[\"']\\$\\([\"']([^\"']+/(conda|mamba|micromamba))[\"']" ); - Matcher matcher = pattern.matcher( content ); - if ( matcher.find() ) - { - final String condaExe = matcher.group( 2 ); - if ( new File( condaExe ).exists() ) - { - IJ.log( " Found via setup variable: " + condaExe ); - return condaExe; - } - } - - // Method 2: Look for explicit CONDA_EXE, MAMBA_EXE, or - // MAMBA_ROOT_PREFIX export - pattern = Pattern.compile( - "export (CONDA_EXE|MAMBA_EXE|MAMBA_ROOT_PREFIX)=['\"]?([^'\"\\s]+)['\"]?" ); - matcher = pattern.matcher( content ); - while ( matcher.find() ) - { - final String varName = matcher.group( 1 ); - String exe = matcher.group( 2 ); - - // If MAMBA_ROOT_PREFIX, append /bin/micromamba - if ( varName.equals( "MAMBA_ROOT_PREFIX" ) ) - exe = exe + "/bin/micromamba"; - - if ( new File( exe ).exists() ) - { - IJ.log( " Found via " + varName + ": " + exe ); - return exe; - } - } - - // Method 3: Look for conda.sh, mamba.sh, or micromamba.sh source with - // path - pattern = Pattern.compile( - "[.\\s][\\s\"']*([^\"']+)/(etc/profile\\.d/(conda|mamba|micromamba)\\.sh)[\"']" ); - matcher = pattern.matcher( content ); - if ( matcher.find() ) - { - final String rootPrefix = matcher.group( 1 ); - // Try conda first, then mamba, then micromamba - final String condaExe = rootPrefix + "/bin/conda"; - if ( new File( condaExe ).exists() ) - { - IJ.log( " Found via profile.d: " + condaExe ); - return condaExe; - } - final String mambaExe = rootPrefix + "/bin/mamba"; - if ( new File( mambaExe ).exists() ) - { - IJ.log( " Found via profile.d: " + mambaExe ); - return mambaExe; - } - final String microExe = rootPrefix + "/bin/micromamba"; - if ( new File( microExe ).exists() ) - { - IJ.log( " Found via profile.d: " + microExe ); - return microExe; - } - } - - // Method 4: Look for PATH export with conda/mamba/micromamba - pattern = Pattern.compile( - "export PATH=[\"']([^\"':]+/(mambaforge|miniconda|anaconda|miniforge|micromamba)[^\"':]*)[\"':]" ); - matcher = pattern.matcher( content ); - if ( matcher.find() ) - { - final String binDir = matcher.group( 1 ); - // Try conda, mamba, then micromamba - final String condaExe = binDir + "/conda"; - if ( new File( condaExe ).exists() ) - { - IJ.log( " Found via PATH export: " + condaExe ); - return condaExe; - } - final String mambaExe = binDir + "/mamba"; - if ( new File( mambaExe ).exists() ) - { - IJ.log( " Found via PATH export: " + mambaExe ); - return mambaExe; - } - final String microExe = binDir + "/micromamba"; - if ( new File( microExe ).exists() ) - { - IJ.log( " Found via PATH export: " + microExe ); - return microExe; - } - } - - // Method 5: Look for any mamba/conda/micromamba bin directory in path - // assignments - // Pattern: path+=('/Users/username/mambaforge/bin') - // Pattern: export PATH="/Users/username/Applications/bin:$PATH" (where - // bin contains micromamba) - pattern = Pattern.compile( - "(?:path[+=]+|export PATH=)[^\"']*[\"']([^\"':]+/bin)[\"':]" ); - matcher = pattern.matcher( content ); - while ( matcher.find() ) - { - final String binDir = matcher.group( 1 ); - - // Check for conda, mamba, or micromamba in this bin directory - final String condaExe = binDir + "/conda"; - if ( new File( condaExe ).exists() ) - { - IJ.log( " Found via path assignment: " + condaExe ); - return condaExe; - } - final String mambaExe = binDir + "/mamba"; - if ( new File( mambaExe ).exists() ) - { - IJ.log( " Found via path assignment: " + mambaExe ); - return mambaExe; - } - final String microExe = binDir + "/micromamba"; - if ( new File( microExe ).exists() ) - { - IJ.log( " Found via path assignment: " + microExe ); - return microExe; - } - } - - return null; - } - - /** - * Method 3: Search for conda in system PATH - */ - private static CondaInfo detectFromPath() - { - IJ.log( "Method 3: Searching system PATH..." ); - String condaExe = findInPath( "conda" ); - if ( condaExe != null ) - { - // Resolve symlinks/aliases - condaExe = resolveRealExecutable( condaExe ); - - final String rootPrefix = deriveRootPrefix( condaExe ); - final String version = getCondaVersion( condaExe ); - - if ( rootPrefix != null && version != null ) - { - IJ.log( " Found conda in PATH: " + condaExe ); - return new CondaInfo( condaExe, rootPrefix, version ); - } - } - IJ.log( " Conda not found in PATH" ); - return null; - } - - /** - * Method 4: Check common installation directories - */ - private static CondaInfo detectFromCommonLocations() - { - IJ.log( "Method 4: Checking common installation locations..." ); - final List< String > locations = getCommonCondaLocations(); - - for ( final String location : locations ) - { - final File dir = new File( location ); - if ( !dir.exists() || !dir.isDirectory() ) - continue; - - String condaExe = buildCondaExecutablePath( location ); - if ( condaExe != null && new File( condaExe ).exists() ) - { - // Resolve symlinks/aliases - condaExe = resolveRealExecutable( condaExe ); - - final String version = getCondaVersion( condaExe ); - if ( version != null ) - { - IJ.log( " Found conda at: " + location ); - return new CondaInfo( condaExe, location, version ); - } - } - } - - IJ.log( " No conda found in common locations" ); - return null; - } - - // ========== Helper Methods ========== - - /** - * Resolve symlinks and get the actual executable path This ensures we know - * what conda/mamba/micromamba we're really using - */ - private static String resolveRealExecutable( final String executablePath ) - { - if ( executablePath == null ) - return null; - - try - { - final File file = new File( executablePath ); - - // Resolve symlinks using canonical path - String realPath = file.getCanonicalPath(); - - // On Unix, also try readlink to be thorough - if ( !isWindows() && Files.isSymbolicLink( Paths.get( executablePath ) ) ) - { - try - { - Path resolved = Files.readSymbolicLink( Paths.get( executablePath ) ); - if ( !resolved.isAbsolute() ) - { - // Resolve relative symlink - final Path parent = Paths.get( executablePath ).getParent(); - resolved = parent.resolve( resolved ).normalize(); - } - realPath = resolved.toString(); - - // Log symlink resolution - if ( !executablePath.equals( realPath ) ) - IJ.log( " Resolved symlink: " + executablePath + " -> " + realPath ); - } - catch ( final IOException e ) - { - // Fall back to canonical path - } - } - - return realPath; - } - catch ( final IOException e ) - { - IJ.log( " Could not resolve path: " + executablePath ); - return executablePath; // Return original if resolution fails - } - } - - /** - * Check if the executable is micromamba (even if aliased as conda) This is - * used to determine command compatibility - */ - private static boolean isMicromambaExecutable( final String condaExePath ) - { - // Quick check first - if path contains micromamba, it definitely is - if ( condaExePath != null && condaExePath.toLowerCase().contains( "micromamba" ) ) - return true; - - // Otherwise, run --version to check what it actually is - try - { - final List< String > command = buildSimpleCommand( condaExePath, "--version" ); - - final ProcessBuilder pb = new ProcessBuilder( command ); - pb.redirectErrorStream( true ); - final Process process = pb.start(); - - final String output = readProcessOutput( process ); - final boolean completed = process.waitFor( 5, TimeUnit.SECONDS ); - - if ( completed && process.exitValue() == 0 && output != null ) - { - // Check if output contains "micromamba" - return output.toLowerCase().contains( "micromamba" ); - } - } - catch ( final Exception e ) - { - // If we can't determine, assume it's not micromamba - } - - return false; - } - - /** - * Derive conda root prefix from executable path For micromamba, tries to - * get actual root prefix from micromamba info - */ - private static String deriveRootPrefix( final String condaExePath ) - { - try - { - final Path path = Paths.get( condaExePath ).toAbsolutePath().normalize(); - final String exeName = path.getFileName().toString(); - - // Special handling for micromamba - query it directly - if ( exeName.contains( "micromamba" ) ) - { - final String microRootPrefix = getMicromambaRootPrefix( condaExePath ); - if ( microRootPrefix != null ) - { - IJ.log( " Micromamba root prefix from 'micromamba info': " + microRootPrefix ); - return microRootPrefix; - } - } - - if ( isWindows() ) - { - // Windows logic (unchanged) - final String pathStr = path.toString(); - if ( pathStr.contains( "\\Library\\bin\\conda" ) ) - { - Path parent = path.getParent(); - if ( parent != null ) - { - parent = parent.getParent(); - if ( parent != null ) - { - final Path rootPrefix = parent.getParent(); - if ( rootPrefix != null ) - return rootPrefix.toString(); - } - } - } - else - { - final Path parent = path.getParent(); - if ( parent != null ) - { - final Path rootPrefix = parent.getParent(); - if ( rootPrefix != null ) - return rootPrefix.toString(); - } - } - } - else - { - // Unix: typically /path/to/installation/bin/conda - final Path parent = path.getParent(); // bin - if ( parent != null ) - { - // installation root - final Path rootPrefix = parent.getParent(); - if ( rootPrefix != null ) - { - // For micromamba in generic bin (like /usr/local/bin) - // Check if this looks like a proper conda root - if ( exeName.contains( "micromamba" ) ) - { - final File envsDir = new File( rootPrefix.toFile(), "envs" ); - final File pkgsDir = new File( rootPrefix.toFile(), "pkgs" ); - - if ( !envsDir.exists() && !pkgsDir.exists() ) - { - // Not a proper conda root, try default - // micromamba locations - final String home = System.getProperty( "user.home" ); - final String[] defaultMicroRoots = { - home + "/micromamba", - home + "/.mamba", - home + "/.local/share/mamba" - }; - - for ( final String defaultRoot : defaultMicroRoots ) - { - final File defaultEnvs = new File( defaultRoot, "envs" ); - if ( defaultEnvs.exists() ) - { - IJ.log( " Using default micromamba root: " + defaultRoot ); - return defaultRoot; - } - } - - // Fall back to ~/micromamba even if it doesn't - // exist yet - final String fallback = home + "/micromamba"; - IJ.log( " Using fallback micromamba root: " + fallback ); - return fallback; - } - } - - return rootPrefix.toString(); - } - } - } - } - catch ( final Exception e ) - { - IJ.log( "Failed to derive root prefix from " + condaExePath + ": " + e.getMessage() ); - } - - return null; - } - - /** - * Get micromamba root prefix by running 'micromamba info' - */ - private static String getMicromambaRootPrefix( final String micromambaPath ) - { - try - { - final List< String > command = buildSimpleCommand( micromambaPath, "info" ); - - final ProcessBuilder pb = new ProcessBuilder( command ); - pb.redirectErrorStream( true ); - final Process process = pb.start(); - - final String output = readProcessOutput( process ); - final boolean completed = process.waitFor( 10, TimeUnit.SECONDS ); - - if ( completed && process.exitValue() == 0 && output != null ) - { - // Look for "base environment : /path/to/root" - final Pattern pattern = Pattern.compile( "base environment\\s*:\\s*([^\\s]+)" ); - final Matcher matcher = pattern.matcher( output ); - if ( matcher.find() ) - { - final String rootPrefix = matcher.group( 1 ); - if ( new File( rootPrefix ).exists() ) - return rootPrefix; - } - - // Alternative: look for "root prefix : /path/to/root" - final Pattern pattern2 = Pattern.compile( "root prefix\\s*:\\s*([^\\s]+)" ); - final Matcher matcher2 = pattern2.matcher( output ); - if ( matcher2.find() ) - { - final String rootPrefix = matcher2.group( 1 ); - if ( new File( rootPrefix ).exists() ) - return rootPrefix; - } - } - } - catch ( final Exception e ) - { - // Failed to query micromamba, will fall back to other methods - } - - return null; - } - - /** - * Get conda version by running `conda --version` or `micromamba --version` - */ - private static String getCondaVersion( final String condaExePath ) - { - try - { - final List< String > command = buildSimpleCommand( condaExePath, "--version" ); - - final ProcessBuilder pb = new ProcessBuilder( command ); - pb.redirectErrorStream( true ); - final Process process = pb.start(); - - final String output = readProcessOutput( process ); - final boolean completed = process.waitFor( 10, TimeUnit.SECONDS ); - - if ( completed && process.exitValue() == 0 && output != null ) - { - // Output format: "conda 23.7.4", "mamba 1.5.8", or "micromamba - // 1.5.8" - final String version = output.trim() - .replace( "conda", "" ) - .replace( "mamba", "" ) - .replace( "micromamba", "" ) - .trim(); - return version; - } - } - catch ( final Exception e ) - { - IJ.log( "Failed to get conda version from " + condaExePath + ": " + e.getMessage() ); - } - - return null; - } - - /** - * Find executable in system PATH - */ - private static String findInPath( final String executable ) - { - final String[] command = isWindows() - ? new String[] { "where", executable } - : new String[] { "which", executable }; - - try - { - final ProcessBuilder pb = new ProcessBuilder( command ); - pb.redirectErrorStream( true ); - final Process process = pb.start(); - - final String output = readProcessOutput( process ); - final boolean completed = process.waitFor( 5, TimeUnit.SECONDS ); - - // Check exit code - 'where' returns non-zero when not found - if ( !completed ) - { - IJ.log( " Command timed out searching for '" + executable + "'" ); - return null; - } - - if ( process.exitValue() != 0 ) - { - IJ.log( " '" + executable + "' not found in PATH" ); - return null; - } - - if ( output != null && !output.isEmpty() ) - { - // 'where' on Windows may return multiple results - final String[] lines = output.split( "\n" ); - - for ( final String line : lines ) - { - String path = line.trim(); - - // Skip empty lines - if ( path.isEmpty() ) - continue; - - // Skip error messages from Windows 'where' command - if ( path.startsWith( "INFO:" ) || - path.startsWith( "ERROR:" ) || - path.startsWith( "WARNING:" ) || - path.contains( "Could not find" ) ) - { - IJ.log( " Skipping error message: " + path ); - continue; - } - - // Validate that this looks like a real path - if ( !isValidPath( path ) ) - { - IJ.log( " Skipping invalid path format: " + path ); - continue; - } - - // On Windows, skip conda in Library\bin (it's a helper - // script) - if ( isWindows() && path.contains( "\\Library\\bin\\conda" ) ) - { - IJ.log( " Skipping helper script: " + path ); - continue; - } - - // Verify the file actually exists - if ( !new File( path ).exists() ) - { - IJ.log( " Skipping non-existent path: " + path ); - continue; - } - - // Resolve symlinks on Unix - if ( !isWindows() ) - { - try - { - path = new File( path ).getCanonicalPath(); - } - catch ( final IOException e ) - { - // Keep original path - } - } - - return path; - } - } - } - catch ( final Exception e ) - { - IJ.log( " Error searching PATH: " + e.getMessage() ); - } - - return null; - } - - /** - * Validate that a string looks like a valid file path - */ - private static boolean isValidPath( final String path ) - { - if ( path == null || path.isEmpty() ) - return false; - - // Check for obvious non-path patterns (error messages) - if ( path.startsWith( "INFO:" ) || - path.startsWith( "ERROR:" ) || - path.startsWith( "WARNING:" ) || - path.toLowerCase().contains( "could not find" ) || - path.toLowerCase().contains( "not recognized" ) ) - return false; - - if ( isWindows() ) - { - // Windows paths should contain : (drive letter) or start with \\ - // (UNC) - // or at least contain \ (subdirectory) - if ( !path.contains( ":" ) && !path.startsWith( "\\\\" ) && !path.contains( "\\" ) ) - { - // Might be a relative path - check if file exists - if ( !new File( path ).exists() ) - return false; - } - - // Check for invalid Windows path characters that indicate error - // messages - final String invalidChars = "<>|\u0000"; - for ( int i = 0; i < invalidChars.length(); i++ ) - { - if ( path.indexOf( invalidChars.charAt( i ) ) >= 0 ) - return false; - } - } - else - { - // Unix paths should start with / (absolute) or contain / (relative) - // Simple heuristic: if it's long, has spaces, but no /, probably an - // error message - if ( !path.contains( "/" ) && path.contains( " " ) && path.length() > 30 ) - return false; - } - - return true; - } - - /** - * Build path to conda/mamba/micromamba executable from installation - * directory - */ - private static String buildCondaExecutablePath( final String installDir ) - { - if ( isWindows() ) - { - // Try Scripts\conda.exe first (most common for miniconda/anaconda) - String scriptsPath = Paths.get( installDir, "Scripts", "conda.exe" ).toString(); - if ( new File( scriptsPath ).exists() ) - return scriptsPath; - - // Try Scripts\mamba.exe - scriptsPath = Paths.get( installDir, "Scripts", "mamba.exe" ).toString(); - if ( new File( scriptsPath ).exists() ) - return scriptsPath; - - // Try Scripts\micromamba.exe - scriptsPath = Paths.get( installDir, "Scripts", "micromamba.exe" ).toString(); - if ( new File( scriptsPath ).exists() ) - return scriptsPath; - - // Try condabin\conda.bat (alternative for some installations) - final String condabinPath = Paths.get( installDir, "condabin", "conda.bat" ).toString(); - if ( new File( condabinPath ).exists() ) - return condabinPath; - - // Try miniforge/mambaforge location: Scripts\conda.bat - scriptsPath = Paths.get( installDir, "Scripts", "conda.bat" ).toString(); - if ( new File( scriptsPath ).exists() ) - return scriptsPath; - - // DO NOT check Library\bin - that's a helper script, not main conda - } - else - { - // Unix: bin/conda, bin/mamba, or bin/micromamba - String binPath = Paths.get( installDir, "bin", "conda" ).toString(); - if ( new File( binPath ).exists() ) - return binPath; - - binPath = Paths.get( installDir, "bin", "mamba" ).toString(); - if ( new File( binPath ).exists() ) - return binPath; - - binPath = Paths.get( installDir, "bin", "micromamba" ).toString(); - if ( new File( binPath ).exists() ) - return binPath; - } - - return null; // No valid conda executable found - } - - /** - * Get common conda installation locations based on platform - */ - private static List< String > getCommonCondaLocations() - { - final List< String > paths = new ArrayList<>(); - final String home = System.getProperty( "user.home" ); - - if ( isWindows() ) - { - // Windows locations - User installations - paths.add( home + "\\miniconda3" ); - paths.add( home + "\\anaconda3" ); - paths.add( home + "\\Miniconda3" ); - paths.add( home + "\\Anaconda3" ); - paths.add( home + "\\miniforge3" ); - paths.add( home + "\\mambaforge" ); - paths.add( home + "\\micromamba" ); - - // AppData\Local installations (common for miniforge) - paths.add( home + "\\AppData\\Local\\miniforge3" ); - paths.add( home + "\\AppData\\Local\\mambaforge" ); - paths.add( home + "\\AppData\\Local\\miniconda3" ); - paths.add( home + "\\AppData\\Local\\anaconda3" ); - paths.add( home + "\\AppData\\Local\\micromamba" ); - - // System-wide installations - paths.add( "C:\\ProgramData\\miniconda3" ); - paths.add( "C:\\ProgramData\\anaconda3" ); - paths.add( "C:\\ProgramData\\miniforge3" ); - paths.add( "C:\\ProgramData\\mambaforge" ); - paths.add( "C:\\ProgramData\\micromamba" ); - paths.add( "C:\\tools\\miniconda3" ); - paths.add( "C:\\tools\\anaconda3" ); - paths.add( "C:\\tools\\micromamba" ); - - // Check other drives - final String[] drives = { "D:", "E:", "F:" }; - for ( final String drive : drives ) - { - paths.add( drive + "\\ProgramData\\miniconda3" ); - paths.add( drive + "\\ProgramData\\anaconda3" ); - paths.add( drive + "\\ProgramData\\miniforge3" ); - paths.add( drive + "\\ProgramData\\micromamba" ); - } - - } - else if ( isMac() ) - { - // macOS locations - paths.add( home + "/miniconda3" ); - paths.add( home + "/anaconda3" ); - paths.add( home + "/miniforge3" ); - paths.add( home + "/mambaforge" ); - paths.add( home + "/micromamba" ); - paths.add( home + "/Applications" ); - paths.add( "/opt/miniconda3" ); - paths.add( "/opt/anaconda3" ); - paths.add( "/opt/conda" ); - paths.add( "/opt/micromamba" ); - paths.add( "/usr/local/miniconda3" ); - paths.add( "/usr/local/anaconda3" ); - paths.add( "/usr/local/micromamba" ); - paths.add( "/usr/local/bin" ); - - } - else - { - // Linux locations - paths.add( home + "/miniconda3" ); - paths.add( home + "/anaconda3" ); - paths.add( home + "/miniforge3" ); - paths.add( home + "/mambaforge" ); - paths.add( home + "/micromamba" ); - paths.add( home + "/.local" ); - paths.add( "/opt/conda" ); - paths.add( "/opt/miniconda3" ); - paths.add( "/opt/anaconda3" ); - paths.add( "/opt/micromamba" ); - paths.add( "/usr/local/miniconda3" ); - paths.add( "/usr/local/anaconda3" ); - paths.add( "/usr/local/micromamba" ); - } - - return paths; - } - - /** - * Build command list for executing conda - */ - private static List< String > buildSimpleCommand( final String condaExePath, final String... args ) - { - final List< String > command = new ArrayList<>(); - - if ( isWindows() && !condaExePath.endsWith( ".exe" ) ) - { - command.add( "cmd.exe" ); - command.add( "/c" ); - } - - command.add( condaExePath ); - command.addAll( Arrays.asList( args ) ); - - return command; - } - - /** - * Read all output from a process - */ - private static String readProcessOutput( final Process process ) throws IOException - { - final StringBuilder output = new StringBuilder(); - try (final BufferedReader reader = new BufferedReader( - new InputStreamReader( process.getInputStream() ) )) - { - String line; - while ( ( line = reader.readLine() ) != null ) - { - if ( output.length() > 0 ) - output.append( "\n" ); - output.append( line ); - } - } - return output.toString(); - } - - // ========== Platform Detection ========== - - private static boolean isWindows() - { - return System.getProperty( "os.name" ).toLowerCase().contains( "win" ); - } - - private static boolean isMac() - { - final String os = System.getProperty( "os.name" ).toLowerCase(); - return os.contains( "mac" ) || os.contains( "darwin" ); - } - - private static boolean isLinux() - { - return System.getProperty( "os.name" ).toLowerCase().contains( "linux" ); - } - - // ========== Environment Discovery ========== - - /** - * Simple JSON parser for conda env list output - *

    - * Format: {"envs": ["/path/to/env1", "/path/to/env2"]} - */ - private static List< CondaEnvironment > parseEnvironmentsJson( final String json ) - { - final List< CondaEnvironment > environments = new ArrayList<>(); - - // Extract paths from JSON (simple regex-based parsing) - final int envsStart = json.indexOf( "\"envs\":" ); - if ( envsStart != -1 ) - { - final int arrayStart = json.indexOf( "[", envsStart ); - final int arrayEnd = json.indexOf( "]", arrayStart ); - - if ( arrayStart != -1 && arrayEnd != -1 ) - { - final String envsArray = json.substring( arrayStart + 1, arrayEnd ); - final String[] paths = envsArray.split( "," ); - - for ( String path : paths ) - { - path = path.trim() - .replace( "\"", "" ) - .replace( "\\\\", "\\" ); - - if ( !path.isEmpty() ) - { - final File envDir = new File( path ); - String name = envDir.getName(); - - // Base environment has special name - if ( name.equals( "miniconda3" ) || name.equals( "anaconda3" ) || - name.equals( "mambaforge" ) || name.equals( "miniforge3" ) ) - name = "base"; - - environments.add( new CondaEnvironment( name, path ) ); - } - } - } - } - return environments; - } - - /** - * Find all conda environments - * - * @return List of conda environments - * @throws CondaNotFoundException if conda is not found - */ - public static List< CondaEnvironment > findAllEnvironments() throws CondaNotFoundException - { - final String condaPath = detect().getCondaExecutable(); - List< CondaEnvironment > environments = new ArrayList<>(); - try - { - final List< String > command = new ArrayList<>(); - - if ( isWindows() && !condaPath.endsWith( ".exe" ) ) - { - command.add( "cmd.exe" ); - command.add( "/c" ); - } - - command.add( condaPath ); - command.add( "env" ); - command.add( "list" ); - command.add( "--json" ); - - final ProcessBuilder pb = new ProcessBuilder( command ); - pb.redirectErrorStream( true ); - final Process process = pb.start(); - - // Read JSON output - final StringBuilder json = new StringBuilder(); - try (final BufferedReader reader = new BufferedReader( - new InputStreamReader( process.getInputStream() ) )) - { - String line; - while ( ( line = reader.readLine() ) != null ) - json.append( line ); - } - process.waitFor( 30, TimeUnit.SECONDS ); - - // Parse JSON (simple parsing - for production use a JSON library) - environments = parseEnvironmentsJson( json.toString() ); - - } - catch ( final IOException | InterruptedException e ) - { - IJ.log( "Failed to list conda environments: " + e.getMessage() ); - } - - return environments; - } - - /** - * Check if a specific environment exists - * - * @param envName Name of the environment to check - * @return true if the environment exists - * @throws CondaNotFoundException if conda is not found - */ - public static boolean environmentExists( final String envName ) throws CondaNotFoundException - { - return findAllEnvironments().stream() - .anyMatch( env -> env.getName().equals( envName ) ); - } - - /** - * Get path to a specific environment - * - * @param envName Name of the environment - * @return Path to the environment, or null if not found - * @throws CondaNotFoundException if conda is not found - */ - public static String getEnvironmentPath( final String envName ) throws CondaNotFoundException - { - return findAllEnvironments().stream() - .filter( env -> env.getName().equals( envName ) ) - .map( CondaEnvironment::getPath ) - .findFirst() - .orElse( null ); - } - - // ========== Demo Main Method ========== - - public static void main( final String[] args ) - { - diagnose(); - } - - public static void diagnose() - { - - IJ.log( "╔════════════════════════════════════════╗" ); - IJ.log( "║ Conda Detection Diagnosis System ║" ); - IJ.log( "╚════════════════════════════════════════╝" ); - IJ.log( "" ); - - IJ.log( "Starting conda detection process..." ); - IJ.log( "" ); - - // Show system information - IJ.log( "System Information:" ); - IJ.log( " OS: " + System.getProperty( "os.name" ) ); - IJ.log( " Architecture: " + System.getProperty( "os.arch" ) ); - IJ.log( " User Home: " + System.getProperty( "user.home" ) ); - IJ.log( "" ); - - // Try each detection method explicitly for the demo - IJ.log( "Attempting Detection Methods:" ); - IJ.log( "" ); - - // Method 1: Environment variable - IJ.log( "Method 1: Checking CONDA_EXE environment variable..." ); - final String condaEnvVar = System.getenv( "CONDA_EXE" ); - if ( condaEnvVar != null ) - { - IJ.log( " Found: " + condaEnvVar ); - if ( new File( condaEnvVar ).exists() ) - IJ.log( " ✓ File exists and is accessible" ); - else - IJ.log( " ✗ File does not exist" ); - } - else - { - IJ.log( " CONDA_EXE not set" ); - } - IJ.log( "" ); - - // Method 2: Shell config (only on Mac/Linux) - if ( isMac() || isLinux() ) - { - IJ.log( "Method 2: Parsing shell configuration files..." ); - final String home = System.getProperty( "user.home" ); - final String[] configFiles = { - ".zshrc", - ".bash_profile", - ".bashrc", - ".profile", - ".config/fish/config.fish" - }; - - boolean foundInConfig = false; - for ( final String configFile : configFiles ) - { - final Path configPath = Paths.get( home, configFile ); - if ( Files.exists( configPath ) ) - { - IJ.log( " Checking: " + configFile ); - try - { - final String content = new String( Files.readAllBytes( configPath ) ); - if ( content.contains( "conda" ) || content.contains( "mamba" ) ) - { - IJ.log( " → Contains conda/mamba references" ); - final String extracted = extractCondaExeFromConfig( content, configPath ); - if ( extracted != null ) - { - IJ.log( " ✓ Extracted path: " + extracted ); - foundInConfig = true; - } - else - { - IJ.log( " ✗ Could not extract conda path" ); - } - } - else - { - IJ.log( " No conda references found" ); - } - } - catch ( final IOException e ) - { - IJ.log( " ✗ Could not read file" ); - } - } - } - if ( !foundInConfig ) - IJ.log( " No conda installation found in shell configs" ); - IJ.log( "" ); - } - else - { - IJ.log( "Method 2: Parsing shell configuration files..." ); - IJ.log( " Skipped (only applicable on macOS/Linux)" ); - IJ.log( "" ); - } - - // Method 3: PATH search - IJ.log( "Method 3: Searching system PATH..." ); - final String pathResult = findInPath( "conda" ); - if ( pathResult != null ) - IJ.log( " ✓ Found in PATH: " + pathResult ); - else - IJ.log( " Conda not found in PATH" ); - IJ.log( "" ); - - // Method 4: Common locations - IJ.log( "Method 4: Checking common installation locations..." ); - final List< String > locations = getCommonCondaLocations(); - IJ.log( " Checking " + locations.size() + " potential locations:" ); - - boolean foundInCommon = false; - for ( final String location : locations ) - { - final String condaExe = buildCondaExecutablePath( location ); - if ( condaExe != null && new File( condaExe ).exists() ) - { - IJ.log( " ✓ Found: " + condaExe ); - foundInCommon = true; - } - } - if ( !foundInCommon ) - IJ.log( " No conda installation found in common locations" ); - IJ.log( "" ); - - // Now run the actual detection - IJ.log( "═══════════════════════════════════════════" ); - IJ.log( "" ); - IJ.log( "Running integrated detection..." ); - IJ.log( "" ); - - try - { - final CondaInfo info = detect(); - - IJ.log( "" ); - IJ.log( "✅ Conda detected successfully!" ); - IJ.log( "" ); - IJ.log( "Detection Results:" ); - IJ.log( " Conda Executable: " + info.getCondaExecutable() ); - IJ.log( " Root Prefix: " + info.getRootPrefix() ); - IJ.log( " Version: " + info.getVersion() ); - IJ.log( " Is Micromamba: " + info.isMicromamba() ); - - // Validate the installation - IJ.log( "" ); - IJ.log( "Validating installation..." ); - final File exeFile = new File( info.getCondaExecutable() ); - IJ.log( " Executable exists: " + exeFile.exists() ); - IJ.log( " Executable size: " + exeFile.length() + " bytes" ); - IJ.log( " Can execute: " + exeFile.canExecute() ); - - final File rootDir = new File( info.getRootPrefix() ); - IJ.log( " Root prefix exists: " + rootDir.exists() ); - IJ.log( " Root prefix is dir: " + rootDir.isDirectory() ); - - // List all environments - IJ.log( "" ); - IJ.log( "═══════════════════════════════════════════" ); - IJ.log( "" ); - IJ.log( "Discovering conda environments..." ); - - final List< CondaEnvironment > envs = findAllEnvironments(); - - if ( envs.isEmpty() ) - { - IJ.log( "" ); - IJ.log( "⚠ No environments found" ); - } - else - { - IJ.log( "" ); - IJ.log( "📦 Found " + envs.size() + " environment(s):" ); - IJ.log( "" ); - for ( int i = 0; i < envs.size(); i++ ) - { - final CondaEnvironment env = envs.get( i ); - IJ.log( " " + ( i + 1 ) + ". " + env.getName() ); - IJ.log( " Path: " + env.getPath() ); - - // Check if environment is valid - final File envDir = new File( env.getPath() ); - if ( envDir.exists() ) - { - final File envBin = new File( env.getPath(), - isWindows() ? "python.exe" : "bin/python" ); - if ( envBin.exists() ) - IJ.log( " Status: ✓ Valid" ); - else - IJ.log( " Status: ⚠ Missing Python" ); - } - else - { - IJ.log( " Status: ✗ Path does not exist" ); - } - } - - // Test environment existence check - if ( !envs.isEmpty() ) - { - final int testIndex = new Random().nextInt( envs.size() ); - final String testEnv = envs.get( testIndex ).getName(); - - IJ.log( "" ); - IJ.log( "═══════════════════════════════════════════" ); - IJ.log( "" ); - IJ.log( "Testing environment lookup for: '" + testEnv + "'" ); - - if ( environmentExists( testEnv ) ) - { - final String envPath = getEnvironmentPath( testEnv ); - IJ.log( " ✓ Environment found" ); - IJ.log( " Path: " + envPath ); - } - else - { - IJ.log( " ✗ Environment not found (unexpected)" ); - } - } - } - - IJ.log( "" ); - IJ.log( "═══════════════════════════════════════════" ); - IJ.log( "" ); - IJ.log( "✅ All checks completed successfully!" ); - - } - catch ( final CondaNotFoundException e ) - { - IJ.log( "" ); - IJ.log( "❌ Conda Detection Failed" ); - IJ.log( "" ); - IJ.log( e.getMessage() ); - IJ.log( "" ); - IJ.log( "═══════════════════════════════════════════" ); - IJ.log( "" ); - IJ.log( "Troubleshooting Tips:" ); - IJ.log( " • Ensure conda/miniconda/anaconda is installed" ); - IJ.log( " • Run 'conda init' in your terminal" ); - IJ.log( " • Check that conda is in your PATH" ); - IJ.log( " • Try running 'conda --version' in terminal" ); - IJ.log( " • If using mambaforge, ensure it's properly installed" ); - } - } -} diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/condapath/CondaPathConfigCommand.java b/src/main/java/fiji/plugin/trackmate/util/cli/condapath/CondaPathConfigCommand.java deleted file mode 100644 index a412a14a5..000000000 --- a/src/main/java/fiji/plugin/trackmate/util/cli/condapath/CondaPathConfigCommand.java +++ /dev/null @@ -1,436 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli.condapath; - -import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Font; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Map; - -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JFileChooser; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.JTextField; -import javax.swing.SwingUtilities; -import javax.swing.WindowConstants; -import javax.swing.border.EmptyBorder; - -import org.scijava.command.Command; -import org.scijava.command.CommandService; -import org.scijava.plugin.Plugin; -import org.scijava.prefs.PrefService; - -import fiji.plugin.trackmate.gui.Fonts; -import fiji.plugin.trackmate.gui.GuiUtils; -import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.util.cli.CLIUtils; -import fiji.plugin.trackmate.util.cli.condapath.CondaDetector.CondaInfo; -import fiji.plugin.trackmate.util.cli.condapath.CondaDetector.CondaNotFoundException; -import ij.IJ; -import ij.ImageJ; - -@Plugin( type = Command.class, - label = "Configure the path to the Conda executable used in TrackMate...", - iconPath = "/icons/commands/information.png", - menuPath = "Edit > Options > Configure TrackMate Conda path..." ) -public class CondaPathConfigCommand implements Command -{ - - @Override - public void run() - { - SwingUtilities.invokeLater( () -> createAndShowDialog() ); - } - - private void createAndShowDialog() - { - final PrefService prefs = TMUtils.getContext().getService( PrefService.class ); - - // Get or detect default paths - String findPath; - try - { - findPath = CLIUtils.findDefaultCondaPath(); - } - catch ( final IllegalArgumentException e ) - { - findPath = "/usr/local/opt/micromamba/bin/micromamba"; - } - - final String condaPath = prefs.get( CLIUtils.class, CLIUtils.CONDA_PATH_PREF_KEY, findPath ); - final Path path = Paths.get( condaPath ); - final Path parent = path.getParent(); - final Path parentOfParent = ( parent != null ) ? parent.getParent() : null; - final String defaultValue = "/usr/local/opt/micromamba/"; - - String condaRootPrefix = ( parentOfParent != null ) - ? parentOfParent.toString() - : defaultValue; - condaRootPrefix = prefs.get( CLIUtils.class, CLIUtils.CONDA_ROOT_PREFIX_KEY, condaRootPrefix ); - - // Create non-modal dialog - final JDialog dialog = new JDialog( IJ.getInstance(), "TrackMate Conda Configuration", false ); - dialog.setIconImage( TRACKMATE_ICON.getImage() ); - dialog.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); - - // Main panel with padding - final JPanel mainPanel = new JPanel( new BorderLayout( 10, 10 ) ); - mainPanel.setBorder( new EmptyBorder( 15, 15, 15, 15 ) ); - mainPanel.setBackground( Color.WHITE ); - - // ========== Header Panel ========== - final JPanel headerPanel = createHeaderPanel(); - mainPanel.add( headerPanel, BorderLayout.NORTH ); - - // ========== Center Panel (Form) ========== - final JPanel centerPanel = new JPanel(); - centerPanel.setLayout( new BoxLayout( centerPanel, BoxLayout.Y_AXIS ) ); - centerPanel.setBackground( Color.WHITE ); - - // Status label for feedback - final JTextArea statusArea = new JTextArea( 2, 50 ); - statusArea.setEditable( false ); - statusArea.setLineWrap( true ); - statusArea.setWrapStyleWord( true ); - statusArea.setFont( new Font( "SansSerif", Font.PLAIN, 11 ) ); - statusArea.setForeground( new Color( 60, 120, 180 ) ); - statusArea.setBackground( new Color( 240, 248, 255 ) ); - statusArea.setBorder( BorderFactory.createCompoundBorder( - BorderFactory.createLineBorder( new Color( 180, 200, 220 ) ), - new EmptyBorder( 5, 8, 5, 8 ) ) ); - statusArea.setText( "Configure conda paths or use auto-detection" ); - - final JScrollPane statusScrollPane = new JScrollPane( statusArea ); - statusScrollPane.setBorder( BorderFactory.createEmptyBorder() ); - statusScrollPane.setMaximumSize( new Dimension( Integer.MAX_VALUE, 60 ) ); - centerPanel.add( statusScrollPane ); - centerPanel.add( Box.createVerticalStrut( 15 ) ); - - // Conda executable path - final JPanel execPanel = createPathPanel( - "Conda Executable Path", - "Path to the conda, mamba, or micromamba executable", - condaPath ); - final JTextField execField = ( JTextField ) execPanel.getClientProperty( "textfield" ); - final JButton execBrowseButton = ( JButton ) execPanel.getClientProperty( "browse" ); - centerPanel.add( execPanel ); - centerPanel.add( Box.createVerticalStrut( 10 ) ); - - // Conda root prefix - final JPanel rootPanel = createPathPanel( - "Conda Root Prefix", - "Root directory of conda installation (CONDA_ROOT_PREFIX)", - condaRootPrefix ); - final JTextField rootField = ( JTextField ) rootPanel.getClientProperty( "textfield" ); - final JButton rootBrowseButton = ( JButton ) rootPanel.getClientProperty( "browse" ); - centerPanel.add( rootPanel ); - centerPanel.add( Box.createVerticalStrut( 15 ) ); - - // Browse button actions - execBrowseButton.addActionListener( e -> browseForFile( execField, dialog ) ); - rootBrowseButton.addActionListener( e -> browseForDirectory( rootField, dialog ) ); - - mainPanel.add( centerPanel, BorderLayout.CENTER ); - - // ========== Button Panel ========== - final JPanel buttonPanel = new JPanel( new FlowLayout( FlowLayout.RIGHT, 10, 0 ) ); - buttonPanel.setBackground( Color.WHITE ); - - final JButton autoDetectButton = new JButton( "Auto-detect" ); - autoDetectButton.setIcon( Icons.PREVIEW_ICON ); - autoDetectButton.addActionListener( e -> autoDetect( execField, rootField, statusArea ) ); - - final JButton diagnoseButton = new JButton( "Diagnose" ); - diagnoseButton.setIcon( Icons.COG_ICON ); - diagnoseButton.addActionListener( e -> diagnose() ); - - final JButton testButton = new JButton( "Test" ); - testButton.setIcon( Icons.EXECUTE_ICON ); - testButton.addActionListener( e -> test( execField.getText(), rootField.getText(), statusArea ) ); - - final JButton okButton = new JButton( "OK" ); - okButton.addActionListener( e -> saveAndClose( execField.getText(), rootField.getText(), prefs, dialog ) ); - - final JButton cancelButton = new JButton( "Cancel" ); - cancelButton.addActionListener( e -> dialog.dispose() ); - - buttonPanel.add( autoDetectButton ); - buttonPanel.add( diagnoseButton ); - buttonPanel.add( testButton ); - buttonPanel.add( Box.createHorizontalStrut( 20 ) ); - buttonPanel.add( okButton ); - buttonPanel.add( cancelButton ); - - mainPanel.add( buttonPanel, BorderLayout.SOUTH ); - - // ========== Finalize Dialog ========== - dialog.add( mainPanel ); - dialog.pack(); - dialog.setLocationRelativeTo( IJ.getInstance() ); - dialog.setVisible( true ); - } - - // ========== UI Component Factories ========== - - private JPanel createHeaderPanel() - { - final JPanel headerPanel = new JPanel( new BorderLayout( 10, 5 ) ); - headerPanel.setBackground( Color.WHITE ); - - final JLabel iconLabel = new JLabel( GuiUtils.scaleImage( Icons.TRACKMATE_ICON, 48, 48 ) ); - headerPanel.add( iconLabel, BorderLayout.WEST ); - - final JPanel textPanel = new JPanel(); - textPanel.setLayout( new BoxLayout( textPanel, BoxLayout.Y_AXIS ) ); - textPanel.setBackground( Color.WHITE ); - - final JLabel titleLabel = new JLabel( "Conda Configuration" ); - titleLabel.setFont( Fonts.BIG_FONT ); - titleLabel.setAlignmentX( Component.LEFT_ALIGNMENT ); - - final JLabel subtitleLabel = new JLabel( - "Configure conda executable for TrackMate modules" ); - subtitleLabel.setFont( Fonts.SMALL_FONT ); - subtitleLabel.setForeground( Color.GRAY ); - subtitleLabel.setAlignmentX( Component.LEFT_ALIGNMENT ); - - textPanel.add( titleLabel ); - textPanel.add( Box.createVerticalStrut( 3 ) ); - textPanel.add( subtitleLabel ); - - headerPanel.add( textPanel, BorderLayout.CENTER ); - headerPanel.add( Box.createVerticalStrut( 10 ), BorderLayout.SOUTH ); - - return headerPanel; - } - - private JPanel createPathPanel( final String title, final String description, final String defaultPath ) - { - final JPanel panel = new JPanel( new BorderLayout( 5, 5 ) ); - panel.setBackground( Color.WHITE ); - panel.setBorder( BorderFactory.createCompoundBorder( - BorderFactory.createTitledBorder( title ), - new EmptyBorder( 5, 5, 5, 5 ) ) ); - - // Description - final JLabel descLabel = new JLabel( description ); - descLabel.setFont( Fonts.SMALL_FONT ); - descLabel.setForeground( Color.GRAY ); - panel.add( descLabel, BorderLayout.NORTH ); - - // Path input panel - final JPanel inputPanel = new JPanel( new BorderLayout( 5, 0 ) ); - inputPanel.setBackground( Color.WHITE ); - - final JTextField textField = new JTextField( defaultPath, 40 ); - textField.setFont( new Font( "Monospaced", Font.PLAIN, 12 ) ); - - final JButton browseButton = new JButton( "Browse..." ); - browseButton.setFocusable( false ); - - inputPanel.add( textField, BorderLayout.CENTER ); - inputPanel.add( browseButton, BorderLayout.EAST ); - - panel.add( inputPanel, BorderLayout.CENTER ); - - // Store components for later access - panel.putClientProperty( "textfield", textField ); - panel.putClientProperty( "browse", browseButton ); - - return panel; - } - - // ========== Action Handlers ========== - - private void browseForFile( final JTextField textField, final JDialog parent ) - { - final JFileChooser chooser = new JFileChooser(); - chooser.setDialogTitle( "Select Conda Executable" ); - chooser.setFileSelectionMode( JFileChooser.FILES_ONLY ); - - final String currentPath = textField.getText(); - if ( !currentPath.isEmpty() ) - { - final File currentFile = new File( currentPath ); - if ( currentFile.getParentFile() != null && currentFile.getParentFile().exists() ) - chooser.setCurrentDirectory( currentFile.getParentFile() ); - } - - if ( chooser.showOpenDialog( parent ) == JFileChooser.APPROVE_OPTION ) - { - final File selected = chooser.getSelectedFile(); - textField.setText( selected.getAbsolutePath() ); - } - } - - private void browseForDirectory( final JTextField textField, final JDialog parent ) - { - final JFileChooser chooser = new JFileChooser(); - chooser.setDialogTitle( "Select Conda Root Directory" ); - chooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); - - final String currentPath = textField.getText(); - if ( !currentPath.isEmpty() ) - { - final File currentDir = new File( currentPath ); - if ( currentDir.exists() ) - chooser.setCurrentDirectory( currentDir ); - } - - if ( chooser.showOpenDialog( parent ) == JFileChooser.APPROVE_OPTION ) - { - final File selected = chooser.getSelectedFile(); - textField.setText( selected.getAbsolutePath() ); - } - } - - private void autoDetect( final JTextField execField, final JTextField rootField, final JTextArea statusArea ) - { - statusArea.setForeground( new Color( 60, 120, 180 ) ); - statusArea.setText( "Auto-detecting conda installation..." ); - - new Thread( () -> { - try - { - final CondaInfo condaInfo = CondaDetector.detect(); - SwingUtilities.invokeLater( () -> { - execField.setText( condaInfo.getCondaExecutable() ); - rootField.setText( condaInfo.getRootPrefix() ); - statusArea.setForeground( new Color( 0, 128, 0 ) ); - statusArea.setText( String.format( - "✓ Auto-detection successful!\nFound conda %s at: %s", - condaInfo.getVersion(), - condaInfo.getCondaExecutable() ) ); - } ); - } - catch ( final CondaNotFoundException e ) - { - SwingUtilities.invokeLater( () -> { - statusArea.setForeground( new Color( 180, 0, 0 ) ); - statusArea.setText( "✗ Auto-detection failed:\n" + e.getMessage() ); - } ); - } - }, "Conda-AutoDetect" ).start(); - } - - private void diagnose() - { - new Thread( () -> { - IJ.log( "\n========== Conda Diagnostics ==========\n" ); - CondaDetector.diagnose(); - }, "Conda-Diagnose" ).start(); - } - - private void test( final String execPath, final String rootPath, final JTextArea statusArea ) - { - statusArea.setForeground( new Color( 60, 120, 180 ) ); - statusArea.setText( "Testing conda configuration..." ); - - new Thread( () -> { - try - { - // Temporarily set the paths for testing - final PrefService prefs = TMUtils.getContext().getService( PrefService.class ); - final String oldExec = prefs.get( CLIUtils.class, CLIUtils.CONDA_PATH_PREF_KEY, "" ); - final String oldRoot = prefs.get( CLIUtils.class, CLIUtils.CONDA_ROOT_PREFIX_KEY, "" ); - - prefs.put( CLIUtils.class, CLIUtils.CONDA_PATH_PREF_KEY, execPath ); - prefs.put( CLIUtils.class, CLIUtils.CONDA_ROOT_PREFIX_KEY, rootPath ); - CLIUtils.clearEnvMap(); - - final Map< String, String > map = CLIUtils.getEnvMap(); - final StringBuilder str = new StringBuilder(); - str.append( "✓ Test successful! Found " + map.size() + " environment(s):\n" ); - map.forEach( ( k, v ) -> str.append( String.format( " • %s\n", k ) ) ); - - SwingUtilities.invokeLater( () -> { - statusArea.setForeground( new Color( 0, 128, 0 ) ); - statusArea.setText( str.toString() ); - } ); - - IJ.log( "\n========== Conda Test Results ==========\n" + str.toString() ); - - // Restore old values - prefs.put( CLIUtils.class, CLIUtils.CONDA_PATH_PREF_KEY, oldExec ); - prefs.put( CLIUtils.class, CLIUtils.CONDA_ROOT_PREFIX_KEY, oldRoot ); - CLIUtils.clearEnvMap(); - } - catch ( final IOException e ) - { - SwingUtilities.invokeLater( () -> { - statusArea.setForeground( new Color( 180, 0, 0 ) ); - statusArea.setText( "✗ Test failed:\nConda executable path seems incorrect.\n" + e.getMessage() ); - } ); - IJ.error( "Conda Test Failed", - "Conda executable path seems to be incorrect.\nError: " + e.getMessage() ); - } - catch ( final Exception e ) - { - SwingUtilities.invokeLater( () -> { - statusArea.setForeground( new Color( 180, 0, 0 ) ); - statusArea.setText( "✗ Test failed:\n" + e.getMessage() ); - } ); - e.printStackTrace(); - IJ.error( "Conda Test Failed", - "Error when running conda.\nError: " + e.getMessage() ); - } - }, "Conda-Test" ).start(); - } - - private void saveAndClose( final String execPath, final String rootPath, final PrefService prefs, final JDialog dialog ) - { - prefs.put( CLIUtils.class, CLIUtils.CONDA_PATH_PREF_KEY, execPath ); - prefs.put( CLIUtils.class, CLIUtils.CONDA_ROOT_PREFIX_KEY, rootPath ); - CLIUtils.clearEnvMap(); - - IJ.log( "Conda configuration saved:" ); - IJ.log( " Executable: " + execPath ); - IJ.log( " Root Prefix: " + rootPath ); - - dialog.dispose(); - } - - // ========== Main for Testing ========== - - public static void main( final String[] args ) - { - ImageJ.main( args ); - TMUtils.getContext().getService( CommandService.class ).run( CondaPathConfigCommand.class, false ); - } -} diff --git a/src/test/java/fiji/plugin/trackmate/util/cli/ExampleCommandCLI.java b/src/test/java/fiji/plugin/trackmate/util/cli/ExampleCommandCLI.java deleted file mode 100644 index 18ea2e60b..000000000 --- a/src/test/java/fiji/plugin/trackmate/util/cli/ExampleCommandCLI.java +++ /dev/null @@ -1,240 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.cli; - -import java.awt.BorderLayout; -import java.util.List; - -import javax.swing.JButton; -import javax.swing.JFrame; - -import fiji.plugin.trackmate.util.cli.ConfigGuiBuilder.ConfigPanel; - -public class ExampleCommandCLI extends CommandCLIConfigurator -{ - - private final IntArgument nThreads; - - private final DoubleArgument radius; - - private final DoubleArgument time; - - private final DoubleArgument weight; - - public ExampleCommandCLI() - { - /* - * The executable that will be called by the TrackMate module. - * - * CLI configs that inherit from 'CommandCLIConfigurator' are all based - * on an actual executable, that is a file with exec rights somewhere on - * the user computer. They need to set it themselves, so the config part - * only specifies the name, the help and the key of this command. - * - * The help and name will be used in the UI. - */ - executable - .name( "Path to the executable." ) - .help( "Browse to the executable location on your computer." ) - .key( "PATH_TO_EXECUTABLE" ); - - /* - * In this example we will assume that the tool we want to run accepts a - * few arguments. - * - * The first one is the --nThreads argument, that accept - * integer larger than 1 and smaller than 24. - * - * The 'argument()' part must be something the tool can understand. This - * is what is passed to it before the value. - * - * This example argument is not required, but has a default value of 1. - * The default value is used only in the command line. If an argument is - * not required, is not set, but has a default value, then the argument - * will appear in the command line with this default value. - * - * Adding arguments is done via 'adder' methods, that are only visible - * in inhering classes. The 'get()' method of the adder returns the - * created argument. It also adds it to the inner parts of the mother - * class, so that it is handled automatically when creating a GUI or a - * command line. But it is a good idea to expose it in this concrete - * class so that you can expose it to the user and let them set it. - */ - this.nThreads = addIntArgument() - .argument( "--nThreads" ) // arg in the command line - .name( "N threads" ) // convenient name - .help( "Sets the number of threads to use for computation." ) // help - .defaultValue( 1 ) - .min( 1 ) - .max( 24 ) // will be used to create an adequate UI widget - .key( "N_THREADS" ) // use to serialize to Map - .get(); - - /* - * The second argument is a double. It also has a unit, which is only - * used in the UI. - * - * Additionally, the value is stored as a radius, but displayed as a - * diameter, with values being converted on the fly. - * - * Because it does not specify a min or a max, any numerical value can - * be entered in the GUI. The implementation will have to add an extra - * check to verify consistency of values. - * - * With this, the radius will have the default value of 5, and a - * diameter of 10 µm will be displayed in the UI. - */ - this.radius = CommonTrackMateArguments.addDiameter( this, "µm" ); - - /* - * The third argument is a double. It is required, which means that an - * error will be thrown when making a command line from this config if - * the user forgot to set a value. - */ - this.weight = addDoubleArgument() - .argument( "--weight" ) - .name( "Weight" ) - .key( "WEIGHT" ) - .defaultValue( 70. ) - .units( "kg" ) - .required( true ) // required flag - .get(); - - /* - * A double argument that has a min and a max will generate another - * widget in the UI: a slider. - */ - this.time = addDoubleArgument() - .argument( "--time" ) - .name( "Time" ) - .help( "Time to wait after processing." ) - .key( "TIME" ) - .min( 1. ) - .max( 100. ) - .units( "seconds" ) - .get(); - } - - public IntArgument nThreads() - { - return nThreads; - } - - public DoubleArgument radius() - { - return radius; - } - - public DoubleArgument weight() - { - return weight; - } - - public DoubleArgument time() - { - return time; - } - - public static void main( final String[] args ) - { - final ExampleCommandCLI cli = new ExampleCommandCLI(); - - /* - * Configure the CLI. - */ - - cli.getCommandArg().set( "/path/to/my/executable" ); - - /* - * Play with the command line. - */ - - /* - * Will generate an error because 'weight' is required and not set. - * - * The argument 'nThreads' is not set, but it has a default value and is - * not required -> no error, the command line will use the default - * value. - * - * The argument 'time' is not set either, does not have a default, but - * it is not required -> no error, the command line will just miss the - * 'time' argument. - */ - System.out.println( "First attempt with the command line:" ); - try - { - final List< String > cmd = CommandBuilder.build( cli ); - System.out.println( "To run: " + cmd ); // error - } - catch ( final IllegalArgumentException e ) - { - System.err.println( "Could not generate the command line:" ); - System.err.println( e.getMessage() ); - } - - // Set the weight. Now it should be ok. - System.out.println( "\nSecond attempt with the command line, after setting the weight:" ); - cli.weight().set( 50. ); - try - { - final List< String > cmd = CommandBuilder.build( cli ); - System.out.println( "To run: " + cmd ); - } - catch ( final IllegalArgumentException e ) - { - System.err.println( "Could not generate the command line:" ); - System.err.println( e.getMessage() ); - } - - /* - * Make a UI that configures the CLI. - */ - - // The UI cannot be created wit arguments that do not have a value. This - // will generate an error: - System.out.println( "\nFirst attempt with the UI generator:" ); - try - { - ConfigGuiBuilder.build( cli ); - } - catch ( final IllegalArgumentException e ) - { - System.err.println( "Could not generate the UI:" ); - System.err.println( e.getMessage() ); - } - - System.out.println( "\nSecond attempt with the UI generator:" ); - cli.time().set( 5. ); - cli.nThreads().set( 2 ); - // This should be ok now. - final ConfigPanel panel = ConfigGuiBuilder.build( cli ); - final JFrame frame = new JFrame( "Demo CLI tool" ); - frame.getContentPane().add( panel, BorderLayout.CENTER ); - final JButton btn = new JButton( "echo" ); - btn.addActionListener( e -> System.out.println( CommandBuilder.build( cli ) ) ); - frame.getContentPane().add( btn, BorderLayout.SOUTH ); - frame.setLocationRelativeTo( null ); - frame.pack(); - frame.setVisible( true ); - frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); - } -} From a5fdda016fe5c7111329b19f203ab9f19edfc1b1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 5 Jul 2026 22:28:13 +0200 Subject: [PATCH 229/371] Don't crash the generic config panel if there are no icon. --- .../plugin/trackmate/util/config/GenericConfigPanel.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java index fa593d5d0..82a04c555 100644 --- a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java +++ b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java @@ -53,8 +53,10 @@ public GenericConfigPanel( final Configurator config ) header.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); header.setLayout( new BoxLayout( header, BoxLayout.Y_AXIS ) ); - final Image icon = config.getIcon().getScaledInstance( 64, 64, Image.SCALE_SMOOTH ); - final JLabel lblDetector = new JLabel( config.getName(), new ImageIcon( icon ), JLabel.RIGHT ); + final ImageIcon icon = ( config.getIcon() != null ) + ? new ImageIcon( config.getIcon().getScaledInstance( 64, 64, Image.SCALE_SMOOTH ) ) + : null; + final JLabel lblDetector = new JLabel( config.getName(), icon, JLabel.RIGHT ); lblDetector.setFont( FONT.deriveFont( Font.BOLD ) ); lblDetector.setHorizontalAlignment( SwingConstants.CENTER ); lblDetector.setAlignmentX( JLabel.CENTER_ALIGNMENT ); From 297d9318183942b650b7ab0cc62106ff77bdf32e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 5 Jul 2026 22:28:37 +0200 Subject: [PATCH 230/371] Use reorder in the Hessian detector Configurator. --- .../plugin/trackmate/detection/HessianDetectorFactory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java index 9ca145412..eceee4a71 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java @@ -144,8 +144,7 @@ public HessianDetectorCLI( final int nChannels, final String units ) // Convert to diameter for display purposes. setDisplayTranslator( diameterZ, r -> r * 2., d -> d / 2. ); // Change order - orderedElements.remove( diameterZ ); - orderedElements.add( 2, diameterZ ); + reorder( diameterZ, 2 ); // Normalize quality values addBooleanParameter() .key( KEY_NORMALIZE ) From cdd0279a318e7be8ee1cc40b83b008a0f34963f5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 5 Jul 2026 22:32:18 +0200 Subject: [PATCH 231/371] Don't limit the size of the info display in generic config panel. --- .../fiji/plugin/trackmate/util/config/GenericConfigPanel.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java index 82a04c555..351aad7b8 100644 --- a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java +++ b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanel.java @@ -3,7 +3,6 @@ import static org.scijava.ui.config.utils.GuiUtils.isLikelyUrl; import java.awt.BorderLayout; -import java.awt.Dimension; import java.awt.Font; import java.awt.Image; import java.util.Map; @@ -69,7 +68,6 @@ public GenericConfigPanel( final Configurator config ) infoDisplay = GuiUtils.infoDisplay( "" + text + "", false ); else infoDisplay = GuiUtils.infoDisplay( help, true ); - infoDisplay.setMaximumSize( new Dimension( 100_000, 40 ) ); header.add( Box.createVerticalStrut( 5 ) ); header.add( infoDisplay ); add( header, BorderLayout.NORTH ); From bd2abb66f19326d84b06d24da5fa98a429e10cb8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 5 Jul 2026 22:39:36 +0200 Subject: [PATCH 232/371] Fix th configs for the LoG, DoG and Hessian detector. - Again with an icon. - Displaying the proper name and help. --- .../detection/DogDetectorFactory.java | 21 +++++++++++++++++++ .../detection/HessianDetectorFactory.java | 4 ++-- .../detection/LogDetectorFactory.java | 11 +++++----- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/DogDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/DogDetectorFactory.java index 3c0d9726e..d55073727 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/DogDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/DogDetectorFactory.java @@ -32,6 +32,7 @@ import org.scijava.plugin.Plugin; import fiji.plugin.trackmate.util.TMUtils; +import ij.ImagePlus; import net.imagej.ImgPlus; import net.imglib2.Interval; import net.imglib2.RandomAccessible; @@ -77,6 +78,26 @@ public SpotDetector< T > getDetector( final ImgPlus< T > img, final Map< String, return detector; } + @Override + public DogDetectorConfig createConfig( final ImagePlus imp ) + { + final int nChannels = ( imp == null ) ? 1 : imp.getNChannels(); + final String units = ( imp == null ) ? "no image" : imp.getCalibration().getUnit(); + return new DogDetectorConfig( nChannels, units ); + } + + /** + * Specifies what are the parameters of the DoG detector. + */ + public static class DogDetectorConfig extends LogDetectorConfig + { + + public DogDetectorConfig( final int nChannels, final String units ) + { + super( THIS_NAME, THIS_INFO_TEXT, nChannels, units ); + } + } + @Override public String getKey() { diff --git a/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java index eceee4a71..1baef2dab 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/HessianDetectorFactory.java @@ -123,7 +123,7 @@ public HessianDetectorCLI createConfig( final ImagePlus imp ) } /** - * Specifies what are the parameters of the {@link LogDetector}. + * Specifies what are the parameters of the Hessian detector. * * @author Jean-Yves Tinevez */ @@ -132,7 +132,7 @@ public static class HessianDetectorCLI extends LogDetectorConfig public HessianDetectorCLI( final int nChannels, final String units ) { - super( nChannels, units ); + super( NAME, INFO_TEXT, nChannels, units ); // Diameter in Z final DoubleParam diameterZ = addDoubleParameter() .key( KEY_RADIUS_Z ) diff --git a/src/main/java/fiji/plugin/trackmate/detection/LogDetectorFactory.java b/src/main/java/fiji/plugin/trackmate/detection/LogDetectorFactory.java index ff4f33c1e..dd1ded507 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/LogDetectorFactory.java +++ b/src/main/java/fiji/plugin/trackmate/detection/LogDetectorFactory.java @@ -113,26 +113,27 @@ public LogDetectorConfig createConfig( final ImagePlus imp ) { final int nChannels = ( imp == null ) ? 1 : imp.getNChannels( ); final String units = ( imp == null ) ? "no image" : imp.getCalibration().getUnit(); - return new LogDetectorConfig( nChannels, units ); + return new LogDetectorConfig( NAME, INFO_TEXT, nChannels, units ); } /** - * Specifies what are the parameters of the {@link LogDetector} and - * DogDetector. + * Specifies what are the parameters of the LogDetector. * * @author Jean-Yves Tinevez */ public static class LogDetectorConfig extends TrackMateConfigurator implements HasInteractivePreview { - public LogDetectorConfig( final int nChannels, final String units ) + public LogDetectorConfig( final String name, final String infoText, final int nChannels, final String units ) { - super( NAME, INFO_TEXT ); + super( name, infoText ); addTargetChannel( nChannels ); addDiameter( units ); addThreshold(); addMedianFiltering(); addSubpixelLocalization(); + + addIcon( new ImageIcon( Icons.class.getResource( "images/LoG-icon-64px.png" ) ).getImage() ); } @Override From 0a035c77397cfb312a5e025a4e05a19c1c197676 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 7 Jul 2026 13:20:59 +0200 Subject: [PATCH 233/371] Fix smoothing scale being ignored for 2D images. We emulate what we do for 3D, that is: smooth the binary mask with a gaussian. --- .../trackmate/detection/SpotRoiUtils.java | 59 +++++++++++++++++-- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java index 5e3078861..453578718 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.java +++ b/src/main/java/fiji/plugin/trackmate/detection/SpotRoiUtils.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.awt.Polygon; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -35,23 +36,32 @@ 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.RandomAccessibleInterval; +import net.imglib2.algorithm.gauss3.Gauss3; +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.Intervals; +import net.imglib2.util.Util; +import net.imglib2.view.IntervalView; 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 @@ -131,7 +141,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot final List< Spot > spots = new ArrayList<>(); for ( final List< Spot > s : map.values() ) spots.addAll( s ); - + return spots; } @@ -144,7 +154,7 @@ public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot * 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 @@ -195,7 +205,13 @@ public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integ { final LabelRegion< Integer > region = iterator.next(); // Analyze in zero-min region. - final List< Polygon > pp = maskToPolygons( Views.zeroMin( region ) ); + final RandomAccessibleInterval< BoolType > mask = Views.zeroMin( region ); + final List< Polygon > pp; + // Smooth if requested. + if ( smoothingScale > 0 ) + pp = maskToPolygons( smoothMask( mask, smoothingScale, calibration ) ); + else + pp = maskToPolygons( mask ); // Translate back to interval coords. for ( final Polygon polygon : pp ) polygon.translate( ( int ) region.min( 0 ), ( int ) region.min( 1 ) ); @@ -389,6 +405,37 @@ public static final PolygonRoi simplify( final PolygonRoi roi, final double smoo return fRoi; } + /** + * Smooths a 2D binary mask using a Gaussian filter. + * + * @param mask + * the binary mask to smooth. + * @param smoothingScale + * the Gaussian sigma in physical units. + * @param calibration + * the pixel calibration. + * @return a new smoothed mask as a binary image. + */ + private static RandomAccessibleInterval< BoolType > smoothMask( + final RandomAccessibleInterval< BoolType > mask, + final double smoothingScale, + final double[] calibration ) + { + final double[] sigmas = new double[ 2 ]; + for ( int d = 0; d < 2; d++ ) + sigmas[ d ] = smoothingScale / calibration[ d ]; + + final int[] halfkernelsizes = Gauss3.halfkernelsizes( sigmas ); + final long[] borders = Arrays.stream( halfkernelsizes ).asLongStream().toArray(); + final Interval outputSize = Intervals.expand( mask, borders ); + final Img< FloatType > img = Util.getArrayOrCellImgFactory( outputSize, new FloatType() ).create( outputSize ); + final RandomAccessibleInterval< FloatType > filtered = Views.translateInverse( img, borders ); + Gauss3.gauss( sigmas, Views.extendZero( mask ), filtered ); + final IntervalView< FloatType > crop = Views.interval( filtered, mask ); + final RandomAccessibleInterval< BoolType > smoothedMask = Converters.convert( crop, ( i, o ) -> o.set( i.get() > 0.5f ), new BoolType() ); + return smoothedMask; + } + /** * Parse a 2D mask and return a list of polygons for the external contours * of white objects. From ed46c9fa233370b76ecdf0472a3cc3263d340296 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 19 Jul 2026 16:38:15 +0200 Subject: [PATCH 234/371] Move the units() method to the Dimension class. --- .../java/fiji/plugin/trackmate/Dimension.java | 48 +++++++++++++++++ .../features/AbstractFeatureGrapher.java | 5 +- .../fiji/plugin/trackmate/io/CSVExporter.java | 3 +- .../fiji/plugin/trackmate/util/TMUtils.java | 51 +------------------ .../table/AllSpotsTableView.java | 3 +- .../visualization/table/BranchTableView.java | 3 +- .../visualization/table/TrackTableView.java | 7 ++- 7 files changed, 57 insertions(+), 63 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Dimension.java b/src/main/java/fiji/plugin/trackmate/Dimension.java index a1c6cd3b9..e7c8280b4 100644 --- a/src/main/java/fiji/plugin/trackmate/Dimension.java +++ b/src/main/java/fiji/plugin/trackmate/Dimension.java @@ -43,4 +43,52 @@ public enum Dimension * We separated length and position so that x,y,z are plotted on a different * graph from spot sizes. */ + + /** + * 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 spaceUnits + * the space units. + * @param timeUnits + * the time units. + * @return the units for the specified dimension. + */ + public String units( final String spaceUnits, final String timeUnits ) + { + switch ( this ) + { + case ANGLE: + return "radians"; + case INTENSITY: + return "counts"; + case INTENSITY_SQUARED: + return "counts^2"; + case NONE: + return ""; + case POSITION: + case LENGTH: + return spaceUnits; + case AREA: + return spaceUnits + "^2"; + case VOLUME: + return spaceUnits + "^3"; + case QUALITY: + return "quality"; + case COST: + return "cost"; + case TIME: + return timeUnits; + case VELOCITY: + return spaceUnits + "/" + timeUnits; + case RATE: + return "/" + timeUnits; + case ANGLE_RATE: + return "rad/" + timeUnits; + default: + case STRING: + return null; + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/features/AbstractFeatureGrapher.java b/src/main/java/fiji/plugin/trackmate/features/AbstractFeatureGrapher.java index 5ed7a9c4e..f2dfd6aa8 100644 --- a/src/main/java/fiji/plugin/trackmate/features/AbstractFeatureGrapher.java +++ b/src/main/java/fiji/plugin/trackmate/features/AbstractFeatureGrapher.java @@ -49,7 +49,6 @@ import fiji.plugin.trackmate.Dimension; import fiji.plugin.trackmate.util.ExportableChartPanel; -import fiji.plugin.trackmate.util.TMUtils; public abstract class AbstractFeatureGrapher { @@ -96,7 +95,7 @@ public AbstractFeatureGrapher( public JFrame render() { // X label - final String xAxisLabel = featureNames.get( xFeature ) + " (" + TMUtils.getUnitsFor( xDimension, spaceUnits, timeUnits ) + ")"; + final String xAxisLabel = featureNames.get( xFeature ) + " (" + xDimension.units( spaceUnits, timeUnits ) + ")"; // Find how many different dimensions final Set< Dimension > dimensions = getUniqueValues( yFeatures, yDimensions ); @@ -107,7 +106,7 @@ public JFrame render() { // Y label - final String yAxisLabel = TMUtils.getUnitsFor( dimension, spaceUnits, timeUnits ); + final String yAxisLabel = dimension.units( spaceUnits, timeUnits ); // Collect suitable feature for this dimension final List< String > featuresThisDimension = getCommonKeys( dimension, yFeatures, yDimensions ); diff --git a/src/main/java/fiji/plugin/trackmate/io/CSVExporter.java b/src/main/java/fiji/plugin/trackmate/io/CSVExporter.java index 11504c93a..bcb5f51fd 100644 --- a/src/main/java/fiji/plugin/trackmate/io/CSVExporter.java +++ b/src/main/java/fiji/plugin/trackmate/io/CSVExporter.java @@ -41,7 +41,6 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.TrackModel; -import fiji.plugin.trackmate.util.TMUtils; public class CSVExporter { @@ -188,7 +187,7 @@ private static void writeHeaderSpots( final CSVWriter writer, final Model model, for ( final String feature : features ) { final Dimension dimension = model.getFeatureModel().getSpotFeatureDimensions().get( feature ); - final String units = TMUtils.getUnitsFor( dimension, model.getSpaceUnits(), model.getTimeUnits() ); + final String units = dimension.units( model.getSpaceUnits(), model.getTimeUnits() ); featureUnits.put( feature, units ); } writeHeader( writer, features, featureNames, featureShortNames, featureUnits, extra ); diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java index c4a4b2bef..477281160 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java +++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java @@ -42,7 +42,6 @@ import org.scijava.Context; import org.scijava.util.DoubleArray; -import fiji.plugin.trackmate.Dimension; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; @@ -621,55 +620,7 @@ private static final int otsuThresholdIndex( final int[] hist, final int nPoints return threshold; } - /** - * 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 ) - { - switch ( dimension ) - { - case ANGLE: - return "radians"; - case INTENSITY: - return "counts"; - case INTENSITY_SQUARED: - return "counts^2"; - case NONE: - return ""; - case POSITION: - case LENGTH: - return spaceUnits; - case AREA: - return spaceUnits + "^2"; - case VOLUME: - return spaceUnits + "^3"; - case QUALITY: - return "quality"; - case COST: - return "cost"; - case TIME: - return timeUnits; - case VELOCITY: - return spaceUnits + "/" + timeUnits; - case RATE: - return "/" + timeUnits; - case ANGLE_RATE: - return "rad/" + timeUnits; - default: - case STRING: - return null; - } - } + public static final String getCurrentTimeString() { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java index 169c78b50..1969372c8 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java @@ -66,7 +66,6 @@ import fiji.plugin.trackmate.util.FileChooser; import fiji.plugin.trackmate.util.FileChooser.DialogType; import fiji.plugin.trackmate.util.FileChooser.SelectionMode; -import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import fiji.plugin.trackmate.visualization.TrackMateModelView; import fiji.plugin.trackmate.visualization.trackscheme.utils.SearchBar; @@ -192,7 +191,7 @@ public static final TablePanel< Spot > createSpotTable( final Model model, final for ( final String feature : features ) { final Dimension dimension = model.getFeatureModel().getSpotFeatureDimensions().get( feature ); - final String units = TMUtils.getUnitsFor( dimension, model.getSpaceUnits(), model.getTimeUnits() ); + final String units = dimension.units( model.getSpaceUnits(), model.getTimeUnits() ); featureUnits.put( feature, units ); } final Map< String, Boolean > isInts = model.getFeatureModel().getSpotFeatureIsInt(); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java index 69468ffa2..05ebfd767 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java @@ -66,7 +66,6 @@ import fiji.plugin.trackmate.util.FileChooser; import fiji.plugin.trackmate.util.FileChooser.DialogType; import fiji.plugin.trackmate.util.FileChooser.SelectionMode; -import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import fiji.plugin.trackmate.visualization.TrackMateModelView; @@ -294,7 +293,7 @@ public static final TablePanel< Branch > createBranchTable( final Model model, f final BiFunction< Branch, String, Double > featureFun = ( br, feature ) -> br.getFeature( feature ); final Map< String, String > featureUnits = new HashMap<>(); BRANCH_FEATURES_DIMENSIONS.forEach( - ( f, d ) -> featureUnits.put( f, TMUtils.getUnitsFor( d, model.getSpaceUnits(), model.getTimeUnits() ) ) ); + ( f, d ) -> featureUnits.put( f, d.units( model.getSpaceUnits(), model.getTimeUnits() ) ) ); final Map< String, String > infoTexts = new HashMap<>(); final Function< Branch, String > labelGenerator = b -> b.toString(); final BiConsumer< Branch, String > labelSetter = null; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java index 7b8ccdc1d..9f2db4faa 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java @@ -71,7 +71,6 @@ import fiji.plugin.trackmate.util.FileChooser; import fiji.plugin.trackmate.util.FileChooser.DialogType; import fiji.plugin.trackmate.util.FileChooser.SelectionMode; -import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import fiji.plugin.trackmate.visualization.TrackMateModelView; import fiji.plugin.trackmate.visualization.trackscheme.utils.SearchBar; @@ -238,7 +237,7 @@ public static final TablePanel< Integer > createTrackTable( final Model model, f for ( final String feature : features ) { final Dimension dimension = model.getFeatureModel().getTrackFeatureDimensions().get( feature ); - final String units = TMUtils.getUnitsFor( dimension, model.getSpaceUnits(), model.getTimeUnits() ); + final String units = dimension.units( model.getSpaceUnits(), model.getTimeUnits() ); featureUnits.put( feature, units ); } final Map< String, Boolean > isInts = model.getFeatureModel().getTrackFeatureIsInt(); @@ -277,7 +276,7 @@ public static final TablePanel< DefaultWeightedEdge > createEdgeTable( final Mod for ( final String feature : features ) { final Dimension dimension = model.getFeatureModel().getEdgeFeatureDimensions().get( feature ); - final String units = TMUtils.getUnitsFor( dimension, model.getSpaceUnits(), model.getTimeUnits() ); + final String units = dimension.units( model.getSpaceUnits(), model.getTimeUnits() ); featureUnits.put( feature, units ); } final Map< String, Boolean > isInts = model.getFeatureModel().getEdgeFeatureIsInt(); @@ -342,7 +341,7 @@ public static final TablePanel< Spot > createSpotTable( final Model model, final for ( final String feature : features ) { final Dimension dimension = model.getFeatureModel().getSpotFeatureDimensions().get( feature ); - final String units = TMUtils.getUnitsFor( dimension, model.getSpaceUnits(), model.getTimeUnits() ); + final String units = dimension.units( model.getSpaceUnits(), model.getTimeUnits() ); featureUnits.put( feature, units ); } final Map< String, Boolean > isInts = model.getFeatureModel().getSpotFeatureIsInt(); From a6b3e82cc542a458000214c940e31a06c162b09e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 19 Jul 2026 16:39:50 +0200 Subject: [PATCH 235/371] Try a visitor pattern with the Spot classes. Now that we have an interface with 3 different implementations, it might be worth facilitating writing code that behaves differently for each implementation. --- src/main/java/fiji/plugin/trackmate/Spot.java | 22 ++++++++++++++++++- .../java/fiji/plugin/trackmate/SpotBase.java | 6 +++++ .../java/fiji/plugin/trackmate/SpotMesh.java | 6 +++++ .../java/fiji/plugin/trackmate/SpotRoi.java | 6 +++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/Spot.java b/src/main/java/fiji/plugin/trackmate/Spot.java index d0b95ac22..70ae40247 100644 --- a/src/main/java/fiji/plugin/trackmate/Spot.java +++ b/src/main/java/fiji/plugin/trackmate/Spot.java @@ -73,13 +73,14 @@ public interface Spot extends RealLocalizable, RealPositionable, RealInterval, C * PUBLIC METHODS */ + public void accept( SpotVisitor v ); + @Override public default int compareTo( final Spot o ) { return ID() - o.ID(); } - /** * Returns a copy of this spot. The class and all fields will be identical, * except for the {@link #ID()}. @@ -639,4 +640,23 @@ public int compare( final Spot o1, final Spot o2 ) return comparator.compare( o1.getName(), o2.getName() ); } }; + + public static interface SpotVisitor + { + + default void visit( final SpotBase spot ) + { + throw new UnsupportedOperationException( "SpotBase not supported." ); + } + + default void visit( final SpotRoi spot ) + { + throw new UnsupportedOperationException( "SpotRoi not supported." ); + } + + default void visit( final SpotMesh spot ) + { + throw new UnsupportedOperationException( "SpotMesh not supported." ); + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/SpotBase.java b/src/main/java/fiji/plugin/trackmate/SpotBase.java index b97961d74..c92adceaa 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotBase.java +++ b/src/main/java/fiji/plugin/trackmate/SpotBase.java @@ -205,6 +205,12 @@ public SpotBase( final int ID ) * PUBLIC METHODS */ + @Override + public void accept( final SpotVisitor v ) + { + v.visit( this ); + } + @Override public SpotBase copy() { diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 2da0821b2..a850d03c5 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -85,6 +85,12 @@ public SpotMesh( setMesh( m ); } + @Override + public void accept( final SpotVisitor v ) + { + v.visit( this ); + } + public void setMesh( final Mesh m ) { // Store a copy in a Buffer mesh. diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index 670464664..19d1796d2 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -83,6 +83,12 @@ public SpotRoi( this.y = y; } + @Override + public void accept( final SpotVisitor v ) + { + v.visit( this ); + } + @Override public SpotRoi copy() { From 63f22160c4da3e9a7a734521530f77b1464b0d24 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 19 Jul 2026 16:40:00 +0200 Subject: [PATCH 236/371] Add Geff-Java as a dep. --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index 9b7c323a0..838eff3ab 100644 --- a/pom.xml +++ b/pom.xml @@ -204,11 +204,19 @@ 0.0.1-SNAPSHOT + org.slf4j slf4j-simple + + + org.litt + geff + 1.1.1-SNAPSHOT + + sc.fiji From b9a5dcf6dc5a059c73446cf4b1eab147a303c8ba Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 19 Jul 2026 16:40:18 +0200 Subject: [PATCH 237/371] WIP: A code to play with a GEFF writer for TrackMate file. --- .../plugin/trackmate/io/TmGeffWriter.java | 256 ++++++++++++++++++ .../trackmate/io/TmGeffWriterTestDrive.java | 28 ++ 2 files changed, 284 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/io/TmGeffWriter.java create mode 100644 src/test/java/fiji/plugin/trackmate/io/TmGeffWriterTestDrive.java diff --git a/src/main/java/fiji/plugin/trackmate/io/TmGeffWriter.java b/src/main/java/fiji/plugin/trackmate/io/TmGeffWriter.java new file mode 100644 index 000000000..98fd0909a --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/io/TmGeffWriter.java @@ -0,0 +1,256 @@ +package fiji.plugin.trackmate.io; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.mastodon.geff.GeffAxis; +import org.mastodon.geff.GeffEdge; +import org.mastodon.geff.GeffMetadata; +import org.mastodon.geff.GeffNode; +import org.mastodon.geff.PropMetadata; + +import fiji.plugin.trackmate.Dimension; +import fiji.plugin.trackmate.FeatureModel; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.Spot.SpotVisitor; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.SpotCollection; +import fiji.plugin.trackmate.SpotRoi; +import fiji.plugin.trackmate.TrackModel; +import gnu.trove.map.TObjectIntMap; +import gnu.trove.map.hash.TObjectIntHashMap; + +public class TmGeffWriter +{ + + public static void write( final Model model, final String zarrPath ) throws IOException + { + + /* + * Serialize spots + */ + final SpotCollection spots = model.getSpots(); + final Map< String, Boolean > isInt = model.getFeatureModel().getSpotFeatureIsInt(); + final GeffSpotVisitor visitor = new GeffSpotVisitor( isInt ); + spots.iterable( true ).forEach( spot -> spot.accept( visitor ) ); + + /* + * Serialize edges + */ + final TrackModel trackModel = model.getTrackModel(); + final Set< DefaultWeightedEdge > edges = trackModel.edgeSet(); + final FeatureModel fm = model.getFeatureModel(); + + final List< GeffEdge > geffEdges = new ArrayList<>(); + int edgeId = 0; + for ( final DefaultWeightedEdge edge : edges ) + { + final Spot source = trackModel.getEdgeSource( edge ); + final Integer srcId = visitor.spotToId.get( source ); + final Spot target = trackModel.getEdgeTarget( edge ); + final Integer tgtId = visitor.spotToId.get( target ); + if ( srcId == null || tgtId == null ) + continue; + + final boolean swap = source.getFeature( Spot.FRAME ) > target.getFeature( Spot.FRAME ); + final GeffEdge edgeNode = new GeffEdge.Builder() + .setId( edgeId++ ) + .setSourceNodeId( swap ? tgtId : srcId ) + .setTargetNodeId( swap ? srcId : tgtId ) + .setScore( trackModel.getEdgeWeight( edge ) ) + .setDistance( Math.sqrt( source.squareDistanceTo( target ) ) ) + .build(); + + // Feature + for ( final String edgeFeature : fm.getEdgeFeatures() ) + { + final Double ef = fm.getEdgeFeature( edge, edgeFeature ); + if ( ef == null ) + continue; + final Object val = ( fm.getEdgeFeatureIsInt().get( edgeFeature ) + ? ef.intValue() + : ef.doubleValue() ); + edgeNode.setProp( edgeFeature, val ); + } + geffEdges.add( edgeNode ); + } + + /* + * Serialize tracks + */ + // TODO -> nodes with a specific path. + + /* + * Metadata + */ + + // Axes + final List< GeffAxis > axes = buildAxes( model.getTimeUnits(), model.getSpaceUnits() ); + final GeffMetadata metadata = new GeffMetadata( "1.0.0", true, axes ); + + // Spot features + final Map< String, PropMetadata > nodePropsMetadata = new HashMap<>(); + for ( final String spotFeature : fm.getSpotFeatures() ) + { + final String dType = isInt.get( spotFeature ) ? "int32" : "float64"; + final Dimension dimension = fm.getSpotFeatureDimensions().get( spotFeature ); + final String unit = dimension.units( model.getSpaceUnits(), model.getTimeUnits() ); + final String name = fm.getSpotFeatureNames().get( spotFeature ); + // Description stores short names. + final String description = fm.getSpotFeatureShortNames().get( spotFeature ); + final PropMetadata propMetadata = new PropMetadata( spotFeature, dType, false, unit, name, description ); + nodePropsMetadata.put( spotFeature, propMetadata ); + } + metadata.setNodePropsMetadata( nodePropsMetadata ); + + // Edge features + final Map< String, PropMetadata > edgePropsMetadata = new HashMap<>(); + for ( final String edgeFeature : fm.getEdgeFeatures() ) + { + final String dType = fm.getEdgeFeatureIsInt().get( edgeFeature ) ? "int32" : "float64"; + final Dimension dimension = fm.getEdgeFeatureDimensions().get( edgeFeature ); + final String unit = dimension.units( model.getSpaceUnits(), model.getTimeUnits() ); + final String name = fm.getEdgeFeatureNames().get( edgeFeature ); + final String description = fm.getEdgeFeatureShortNames().get( edgeFeature ); + final PropMetadata propMetadata = new PropMetadata( edgeFeature, dType, false, unit, name, description ); + edgePropsMetadata.put( edgeFeature, propMetadata ); + } + metadata.setEdgePropsMetadata( edgePropsMetadata ); + + /* + * Write to disk + */ + + GeffNode.writeToZarr( visitor.nodes, zarrPath, metadata ); + GeffEdge.writeToZarr( geffEdges, zarrPath, metadata ); + GeffMetadata.writeToZarr( metadata, zarrPath ); + } + + private static List< GeffAxis > buildAxes( final String timeUnit, final String spaceUnit ) + { + return Arrays.asList( + GeffAxis.createTimeAxis( GeffAxis.NAME_TIME, timeUnit, null, null ), + GeffAxis.createSpaceAxis( GeffAxis.NAME_SPACE_X, normalizeSpaceUnit( spaceUnit ), null, null ), + GeffAxis.createSpaceAxis( GeffAxis.NAME_SPACE_Y, normalizeSpaceUnit( spaceUnit ), null, null ), + GeffAxis.createSpaceAxis( GeffAxis.NAME_SPACE_Z, normalizeSpaceUnit( spaceUnit ), null, null ) ); + } + + /** + * Maps common unit abbreviations to OME-Zarr compliant names. See + * https://ngff.openmicroscopy.org/latest/#axes-md for the valid set. + */ + static String normalizeSpaceUnit( final String unit ) + { + if ( unit == null || unit.isEmpty() ) + return "pixel"; + switch ( unit.trim() ) + { + case "um": + case "µm": + case "μm": + case "micron": + case "microns": + return "micrometer"; + case "nm": + return "nanometer"; + case "mm": + return "millimeter"; + case "cm": + return "centimeter"; + case "m": + return "meter"; + case "km": + return "kilometer"; + case "pm": + return "picometer"; + case "Å": + return "angstrom"; + default: + return unit; + } + } + + public static class GeffSpotVisitor implements SpotVisitor + { + + /** Features not in the general prop, because they are the core node. */ + private static final Set< String > SLOP_PROPS = Set.of( "FRAME", "POSITION_X", "POSITION_Y", "POSITION_Z", "RADIUS" ); + + private int geffId = 0; + + private final TObjectIntMap< Spot > spotToId = new TObjectIntHashMap< Spot >(); + + private final List< GeffNode > nodes = new ArrayList<>(); + + private final Map< String, Boolean > isInt; + + public GeffSpotVisitor( final Map< String, Boolean > isInt ) + { + this.isInt = isInt; + } + + private void serializeFeatures( final Spot spot, final GeffNode node ) + { + final Map< String, Double > features = spot.getFeatures(); + for ( final Map.Entry< String, Double > entry : features.entrySet() ) + { + final String name = entry.getKey(); + if ( SLOP_PROPS.contains( name ) ) + continue; + + final Object val = ( isInt.get( name ) ? entry.getValue().intValue() : entry.getValue() ); + node.setProp( name, val ); + } + } + + @Override + public void visit( final SpotBase spot ) + { + final GeffNode node = new GeffNode.Builder() + .id( geffId ) + .timepoint( spot.getFeature( Spot.FRAME ).intValue() ) + .x( spot.getDoublePosition( 0 ) ) + .y( spot.getDoublePosition( 1 ) ) + .z( spot.getDoublePosition( 2 ) ) + .radius( spot.getFeature( Spot.RADIUS ).doubleValue() ) + .build(); + serializeFeatures( spot, node ); + nodes.add( node ); + spotToId.put( spot, geffId++ ); + } + + @Override + public void visit( final SpotRoi spot ) + { + final int nPoints = spot.nPoints(); + final double[] polygonX = new double[ nPoints ]; + final double[] polygonY = new double[ nPoints ]; + for ( int i = 0; i < nPoints; i++ ) + { + polygonX[ i ] = spot.x( i ); + polygonY[ i ] = spot.y( i ); + } + + final GeffNode node = new GeffNode.Builder() + .id( geffId ) + .timepoint( spot.getFeature( Spot.FRAME ).intValue() ) + .x( spot.getDoublePosition( 0 ) ) + .y( spot.getDoublePosition( 1 ) ) + .z( spot.getDoublePosition( 2 ) ) + .radius( spot.getFeature( Spot.RADIUS ).doubleValue() ) + .polygonX( polygonX ) + .polygonY( polygonY ) + .build(); + serializeFeatures( spot, node ); + nodes.add( node ); + spotToId.put( spot, geffId++ ); + } + } +} diff --git a/src/test/java/fiji/plugin/trackmate/io/TmGeffWriterTestDrive.java b/src/test/java/fiji/plugin/trackmate/io/TmGeffWriterTestDrive.java new file mode 100644 index 000000000..579a6c8a0 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/io/TmGeffWriterTestDrive.java @@ -0,0 +1,28 @@ +package fiji.plugin.trackmate.io; + +import java.io.File; +import java.io.IOException; + +import fiji.plugin.trackmate.Model; + +public class TmGeffWriterTestDrive +{ + + public static void main( final String[] args ) throws IOException + { + final String path = "samples/FakeTracks.xml"; + final TmXmlReader reader = new TmXmlReader( new File( path ) ); + if ( !reader.isReadingOk() ) + { + System.err.println( reader.getErrorMessage() ); + return; + } + final String savePath = path.replace( ".xml", ".geff" ); + + final Model model = reader.getModel(); + + System.out.println( "Writing to " + savePath ); + TmGeffWriter.write( model, savePath ); + System.out.println( "Done." ); + } +} From 5f6b6d73a94973e7fea1a8420c69736aee9539cf Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 12:32:58 +0200 Subject: [PATCH 238/371] Don't add a MouseAndKeyHandler twice. It is added elsewhere by BDV, and the one we were adding is messing with the beautiful zoom prodedure in BDV (properly keeping the zoom on the mouse location). --- .../trackmate/gui/editor/labkit/component/TMLabKitFrame.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java index 2a178f23c..b124907b3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java @@ -52,7 +52,6 @@ import javax.swing.SwingUtilities; import javax.swing.UIManager; -import org.scijava.ui.behaviour.MouseAndKeyHandler; import org.scijava.ui.behaviour.io.InputTriggerConfig; import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider.Scope; import org.scijava.ui.behaviour.util.Actions; @@ -153,10 +152,6 @@ public TMLabKitFrame( final TMLabKitModel model ) final TriggerBehaviourBindings triggerbindings = new TriggerBehaviourBindings(); SwingUtilities.replaceUIActionMap( getRootPane(), keybindings.getConcatenatedActionMap() ); SwingUtilities.replaceUIInputMap( getRootPane(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); - final MouseAndKeyHandler mouseAndKeyHandler = new MouseAndKeyHandler(); - mouseAndKeyHandler.setInputMap( triggerbindings.getConcatenatedInputTriggerMap() ); - mouseAndKeyHandler.setBehaviourMap( triggerbindings.getConcatenatedBehaviourMap() ); - addHandler( mouseAndKeyHandler ); final TMKeymapManager keymapManager = new TMKeymapManager(); final InputTriggerConfig inputTriggerConfig = keymapManager.getForwardSelectedKeymap().getConfig(); From f9505b07bb69bb9ac0828bb621587a87330ed48e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 13:06:29 +0200 Subject: [PATCH 239/371] Interface for commands with undo / redo --- .../gui/editor/labkit/model/EditCommand.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/EditCommand.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/EditCommand.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/EditCommand.java new file mode 100644 index 000000000..5c8628e1e --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/EditCommand.java @@ -0,0 +1,27 @@ +package fiji.plugin.trackmate.gui.editor.labkit.model; + +import net.imglib2.Interval; + +/** + * Command interface for undo/redo operations in the label editor. + */ +public interface EditCommand +{ + /** + * Revert the edit, restoring the state before the edit was applied. + */ + void undo(); + + /** + * Re-apply the edit, restoring the state after the edit was applied. + */ + void redo(); + + /** + * Returns the interval affected by this edit. + * Used to trigger repaints of the affected region. + * + * @return the affected interval, or {@code null} if the entire image is affected. + */ + Interval getRegion(); +} From 14d18398c27b69916de879df44157c393f48caa7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 15:10:13 +0200 Subject: [PATCH 240/371] Code style changes. --- .../component/TMLabelBrushController.java | 76 ++++++------------- 1 file changed, 23 insertions(+), 53 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java index 5ad21f4d7..ff591cfd5 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java @@ -286,9 +286,7 @@ public void setPlanarMode( final boolean planarMode ) private class PaintBehavior implements DragBehaviour { - /** - * If true we paint. If false we erase. - */ + /** If true we paint. If false we erase. */ private final boolean paint; private RealPoint before; @@ -305,8 +303,7 @@ private void paint( final RealLocalizable screenCoordinates ) RandomAccessible< LabelingType< Label > > extended = extendLabelingType( getFrame() ); final double radius = Math.max( 0, ( brushDiameter - 1 ) * 0.5 ); final AffineTransform3D m = displayToImageTransformation(); - final double[] screen = { screenCoordinates.getDoublePosition( 0 ), screenCoordinates - .getDoublePosition( 1 ), 0 }; + final double[] screen = { screenCoordinates.getDoublePosition( 0 ), screenCoordinates.getDoublePosition( 1 ), 0 }; double[] center = new double[ 3 ]; m.apply( screen, center ); if ( extended.numDimensions() == 3 && planarMode ) @@ -315,8 +312,7 @@ private void paint( final RealLocalizable screenCoordinates ) final double pixelWidth = RealPoints.length( labelTransform.d( 0 ) ); final double pixelHeight = RealPoints.length( labelTransform.d( 1 ) ); final double pixelDepth = RealPoints.length( labelTransform.d( 2 ) ); - double[] axes = { radius, radius * pixelWidth / pixelHeight, radius * pixelWidth / - pixelDepth }; + double[] axes = { radius, radius * pixelWidth / pixelHeight, radius * pixelWidth / pixelDepth }; if ( extended.numDimensions() == 2 ) { center = Arrays.copyOf( center, 2 ); @@ -325,7 +321,6 @@ private void paint( final RealLocalizable screenCoordinates ) final IterableRegion< BitType > region = Ellipsoid.asIterableRegion( center, axes ); Regions.sample( region, extended ).forEach( pixelOperation() ); } - } private Consumer< LabelingType< Label > > pixelOperation() @@ -383,11 +378,9 @@ private List< Label > getVisibleLabels() return visibleLabels; } - private RandomAccessible< LabelingType< Label > > extendLabelingType( - final RandomAccessibleInterval< LabelingType< Label > > slice ) + private static final RandomAccessible< LabelingType< Label > > extendLabelingType( final RandomAccessibleInterval< LabelingType< Label > > slice ) { - final LabelingType< Label > variable = slice.randomAccess() - .setPositionAndGet( Intervals.minAsLongArray( slice ) ).createVariable(); + final LabelingType< Label > variable = slice.randomAccess().setPositionAndGet( Intervals.minAsLongArray( slice ) ).createVariable(); variable.clear(); @SuppressWarnings( "deprecation" ) final RandomAccessible< LabelingType< Label > > extended = Views.extendValue( slice, variable ); @@ -411,32 +404,19 @@ private AffineTransform3D viewerTransformation() private void paint( final RealLocalizable a, final RealLocalizable b ) { - final long distance = ( long ) ( 4 * ( distance( a, b ) + 1 ) ); + final double dist = LinAlgHelpers.distance( a.positionAsDoubleArray(), b.positionAsDoubleArray() ); + final long distance = ( long ) ( 4 * ( dist + 1 ) ); final long step = ( long ) Math.max( brushDiameter, 1.0 ); - for ( long i = 0; i < distance; i += step ) - paint( interpolate( ( double ) i / ( double ) distance, a, b ) ); - } - - RealLocalizable interpolate( final double ratio, final RealLocalizable a, - final RealLocalizable b ) - { - final RealPoint result = new RealPoint( a.numDimensions() ); - for ( int d = 0; d < result.numDimensions(); d++ ) - result.setPosition( ratio * a.getDoublePosition( d ) + ( 1 - ratio ) * b - .getDoublePosition( d ), d ); - return result; - } - double distance( final RealLocalizable a, final RealLocalizable b ) - { - return LinAlgHelpers.distance( asArray( a ), asArray( b ) ); - } + final RealPoint location = new RealPoint( a.numDimensions() ); + for ( long i = 0; i < distance; i += step ) + { + final double ratio = ( double ) i / ( double ) distance; + for ( int d = 0; d < location.numDimensions(); d++ ) + location.setPosition( ratio * a.getDoublePosition( d ) + ( 1 - ratio ) * b.getDoublePosition( d ), d ); - private double[] asArray( final RealLocalizable a ) - { - final double[] result = new double[ a.numDimensions() ]; - a.localize( result ); - return result; + paint( location ); + } } @Override @@ -485,23 +465,16 @@ private void makeLabelVisible() private double getBrushDisplayRadius() { - return brushDiameter * 0.5 * getScale( model.labelTransformation() ) * - getScale( paintBehaviour.viewerTransformation() ); - } - - // TODO: find a good place - private double getScale( final AffineTransform3D transformation ) - { - return Affine3DHelpers.extractScale( transformation, 0 ); + final double labelScale = Affine3DHelpers.extractScale( model.labelTransformation(), 0 ); + final double viewScale = Affine3DHelpers.extractScale( paintBehaviour.viewerTransformation(), 0 ); + return brushDiameter * 0.5 * labelScale * viewScale; } private RandomAccessibleInterval< LabelingType< Label > > getFrame() { - final RandomAccessibleInterval< LabelingType< Label > > frame = model.labeling() - .get(); + final RandomAccessibleInterval< LabelingType< Label > > frame = model.labeling().get(); if ( this.model.isTimeSeries() ) - return Views.hyperSlice( frame, frame - .numDimensions() - 1, viewer.state().getCurrentTimepoint() ); + return Views.hyperSlice( frame, frame.numDimensions() - 1, viewer.state().getCurrentTimepoint() ); return frame; } @@ -512,10 +485,8 @@ private void fireBitmapChanged( final RealPoint a, final RealPoint b, double rad final long[] max = new long[ 2 ]; for ( int d = 0; d < 2; d++ ) { - min[ d ] = ( long ) ( Math.min( a.getDoublePosition( d ), b.getDoublePosition( - d ) ) - radius ); - max[ d ] = ( long ) ( Math.ceil( Math.max( a.getDoublePosition( d ), b - .getDoublePosition( d ) ) ) + radius ); + min[ d ] = ( long ) ( Math.min( a.getDoublePosition( d ), b.getDoublePosition( d ) ) - radius ); + max[ d ] = ( long ) ( Math.ceil( Math.max( a.getDoublePosition( d ), b.getDoublePosition( d ) ) ) + radius ); } model.dataChangedNotifier().notifyListeners( new FinalInterval( min, max ) ); } @@ -524,8 +495,7 @@ private class ChangeBrushRadius implements ScrollBehaviour { @Override - public void scroll( final double wheelRotation, final boolean isHorizontal, - final int x, final int y ) + public void scroll( final double wheelRotation, final boolean isHorizontal, final int x, final int y ) { if ( !isHorizontal ) { From 8db8fa86e85337b6443a69ecffc220c72c99c890 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 17:16:56 +0200 Subject: [PATCH 241/371] Use TMImageLabelingModel when we can. --- .../editor/labkit/component/TMBasicLabelingComponent.java | 6 +++--- .../gui/editor/labkit/component/TMLabKitFrame.java | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMBasicLabelingComponent.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMBasicLabelingComponent.java index d3a153ea7..9b9646494 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMBasicLabelingComponent.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMBasicLabelingComponent.java @@ -58,13 +58,13 @@ import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.gui.editor.labkit.component.TMFloodFillController.FloodEraseMode; import fiji.plugin.trackmate.gui.editor.labkit.component.TMFloodFillController.FloodFillMode; +import fiji.plugin.trackmate.gui.editor.labkit.model.TMImageLabelingModel; import net.miginfocom.swing.MigLayout; import sc.fiji.labkit.ui.bdv.BdvAutoContrast; import sc.fiji.labkit.ui.bdv.BdvLayer; import sc.fiji.labkit.ui.labeling.Label; import sc.fiji.labkit.ui.labeling.LabelsLayer; import sc.fiji.labkit.ui.models.Holder; -import sc.fiji.labkit.ui.models.ImageLabelingModel; import sc.fiji.labkit.ui.models.LabelingModel; /** @@ -81,7 +81,7 @@ public class TMBasicLabelingComponent extends JPanel implements AutoCloseable private final JFrame dialogBoxOwner; - private final ImageLabelingModel model; + private final TMImageLabelingModel model; private JSlider zSlider; @@ -95,7 +95,7 @@ public class TMBasicLabelingComponent extends JPanel implements AutoCloseable public TMBasicLabelingComponent( final JFrame dialogBoxOwner, - final ImageLabelingModel model, + final TMImageLabelingModel model, final BdvOptions options ) { this.model = model; diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java index b124907b3..5be823cd7 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java @@ -64,6 +64,7 @@ import bdv.ui.keymap.KeymapManager; import bdv.util.BdvOptions; import fiji.plugin.trackmate.gui.Icons; +import fiji.plugin.trackmate.gui.editor.labkit.model.TMImageLabelingModel; import fiji.plugin.trackmate.gui.editor.labkit.model.TMLabKitModel; import net.imglib2.Dimensions; import net.imglib2.util.Intervals; @@ -94,7 +95,7 @@ public class TMLabKitFrame extends JFrame public TMLabKitFrame( final TMLabKitModel model ) { - final ImageLabelingModel imageLabelingModel = model.imageLabelingModel(); + final TMImageLabelingModel imageLabelingModel = model.imageLabelingModel(); /* * Here we create a specific config for BDV, so that we can use a custom From e968b0c68a6ef28599078124311c86bea97a245e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 18:13:03 +0200 Subject: [PATCH 242/371] Undo / redo for the edits made with the brush. --- .../labkit/component/TMLabKitActions.java | 16 ++ .../component/TMLabelBrushController.java | 129 +++++++++--- .../gui/editor/labkit/model/EditCommand.java | 27 --- .../labkit/model/TMImageLabelingModel.java | 15 +- .../editor/labkit/model/UndoRedoStack.java | 188 ++++++++++++++++++ .../editor/labkit/model/UndoableCommand.java | 102 ++++++++++ 6 files changed, 423 insertions(+), 54 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/EditCommand.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoableCommand.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java index 0de248f0c..cc99c73ac 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java @@ -42,6 +42,7 @@ import bdv.ui.keymap.KeymapSettingsPage; import fiji.plugin.trackmate.gui.editor.labkit.model.TMLabKitModel; import fiji.plugin.trackmate.gui.editor.labkit.model.TMTransformationModel; +import fiji.plugin.trackmate.gui.editor.labkit.model.UndoRedoStack; public class TMLabKitActions { @@ -75,12 +76,25 @@ public static void install( final TMTransformationModel transformationModel = ( TMTransformationModel ) model.imageLabelingModel().transformationModel(); actions.runnableAction( () -> transformationModel.resetView(), RESET_VIEW, RESET_VIEW_KEYS ); + + /* + * Undo / redo actions + */ + + final UndoRedoStack undoRedo = model.imageLabelingModel().undoRedo(); + actions.runnableAction( () -> undoRedo.undo(), UNDO, UNDO_KEYS ); + actions.runnableAction( () -> undoRedo.redo(), REDO, REDO_KEYS ); } private static final String RESET_VIEW = "reset view"; private static final String[] RESET_VIEW_KEYS = new String[] { "shift R " }; + private static final String UNDO = "undo"; + private static final String[] UNDO_KEYS = new String[] { "ctrl Z", "meta Z" }; + private static final String REDO = "redo"; + private static final String[] REDO_KEYS = new String[] { "ctrl shift Z", "meta shift Z" }; + @Plugin( type = CommandDescriptionProvider.class ) public static class Descriptions extends CommandDescriptionProvider { @@ -95,6 +109,8 @@ public void getCommandDescriptions( final CommandDescriptions descriptions ) descriptions.add( RESET_VIEW, RESET_VIEW_KEYS, "Reset the view." ); descriptions.add( BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS, "Show the Preferences dialog." ); descriptions.add( CloseWindowActions.CLOSE_DIALOG, CloseWindowActions.CLOSE_DIALOG_KEYS, "Close the active dialog." ); + descriptions.add( UNDO, UNDO_KEYS, "Undo the last edit." ); + descriptions.add( REDO, REDO_KEYS, "Redo the last undone edit." ); } } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java index ff591cfd5..919582439 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java @@ -27,7 +27,6 @@ import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; -import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -47,6 +46,8 @@ import bdv.util.Affine3DHelpers; import bdv.util.BdvHandle; import bdv.viewer.ViewerPanel; +import fiji.plugin.trackmate.gui.editor.labkit.model.TMImageLabelingModel; +import fiji.plugin.trackmate.gui.editor.labkit.model.UndoRedoStack; import fiji.plugin.trackmate.util.TMUtils; import net.imglib2.FinalInterval; import net.imglib2.RandomAccessible; @@ -66,7 +67,6 @@ import sc.fiji.labkit.ui.brush.neighborhood.Ellipsoid; import sc.fiji.labkit.ui.brush.neighborhood.RealPoints; import sc.fiji.labkit.ui.labeling.Label; -import sc.fiji.labkit.ui.models.LabelingModel; import sc.fiji.labkit.ui.panel.GuiUtils; import sc.fiji.labkit.ui.utils.Notifier; @@ -85,7 +85,9 @@ public enum PaintBrushMode REPLACE( "Replace", "Paint over existing labels." ), /** Add the painted label to existing labels. */ ADD( "Add", "Add selected label to existing ones." ), - /** Only paint over the background. If a label exists, don't change it.*/ + /** + * Only paint over the background. If a label exists, don't change it. + */ DONT_OVERWRITE( "Preserve", "Don't overwrite existing labels, only paint on background." ); private final String name; @@ -152,7 +154,7 @@ public String getTooltip() private final ViewerPanel viewer; - private final LabelingModel model; + private final TMImageLabelingModel model; private final BrushCursor brushCursor; @@ -160,9 +162,9 @@ public String getTooltip() private final MouseAdapter moveBrushAdapter = GuiUtils.toMouseListener( moveBrushBehaviour ); - private final PaintBehavior paintBehaviour = new PaintBehavior( true ); + private final PaintBehavior paintBehaviour; - private final PaintBehavior eraseBehaviour = new PaintBehavior( false ); + private final PaintBehavior eraseBehaviour; private PaintBrushMode paintBrushMode; @@ -176,8 +178,13 @@ public String getTooltip() private boolean planarMode = false; - public TMLabelBrushController( final BdvHandle bdv, final LabelingModel model ) + public TMLabelBrushController( final BdvHandle bdv, final TMImageLabelingModel model ) { + // Behaviors + final boolean is2D = model.labeling().get().numDimensions() == 2; + this.paintBehaviour = new PaintBehavior( true, is2D ); + this.eraseBehaviour = new PaintBehavior( false, is2D ); + this.bdv = bdv; this.viewer = bdv.getViewerPanel(); this.brushCursor = new BrushCursor( model ); @@ -186,6 +193,7 @@ public TMLabelBrushController( final BdvHandle bdv, final LabelingModel model ) viewer.getDisplay().overlays().add( brushCursor ); viewer.transformListeners().add( affineTransform3D -> updateBrushOverlayRadius() ); + // Load defaults from prefs. final PrefService prefs = TMUtils.getContext().getService( PrefService.class ); brushDiameter = prefs.getDouble( TMLabelBrushController.class, PREF_KEY_BRUSH_DIAMETER, 1. ); @@ -291,38 +299,59 @@ private class PaintBehavior implements DragBehaviour private RealPoint before; - public PaintBehavior( final boolean paint ) + /** + * The bounding box of the current stroke, used for undo/redo region. + */ + private FinalInterval strokeRegion; + + private final boolean is2D; + + public PaintBehavior( final boolean paint, final boolean is2D ) { this.paint = paint; + this.is2D = is2D; } private void paint( final RealLocalizable screenCoordinates ) { synchronized ( viewer ) { - RandomAccessible< LabelingType< Label > > extended = extendLabelingType( getFrame() ); final double radius = Math.max( 0, ( brushDiameter - 1 ) * 0.5 ); - final AffineTransform3D m = displayToImageTransformation(); - final double[] screen = { screenCoordinates.getDoublePosition( 0 ), screenCoordinates.getDoublePosition( 1 ), 0 }; - double[] center = new double[ 3 ]; - m.apply( screen, center ); - if ( extended.numDimensions() == 3 && planarMode ) + final double[] center = posToImageCoords( screenCoordinates ); + final double[] axes = radiusToImageCoords( radius ); + + RandomAccessible< LabelingType< Label > > extended = extendLabelingType( getFrameLabeling() ); + if ( !is2D && planarMode ) extended = Views.hyperSlice( extended, 2, Math.round( center[ 2 ] ) ); - final AffineTransform3D labelTransform = model.labelTransformation(); - final double pixelWidth = RealPoints.length( labelTransform.d( 0 ) ); - final double pixelHeight = RealPoints.length( labelTransform.d( 1 ) ); - final double pixelDepth = RealPoints.length( labelTransform.d( 2 ) ); - double[] axes = { radius, radius * pixelWidth / pixelHeight, radius * pixelWidth / pixelDepth }; - if ( extended.numDimensions() == 2 ) - { - center = Arrays.copyOf( center, 2 ); - axes = Arrays.copyOf( axes, 2 ); - } + final IterableRegion< BitType > region = Ellipsoid.asIterableRegion( center, axes ); Regions.sample( region, extended ).forEach( pixelOperation() ); } } + private double[] radiusToImageCoords( final double radius ) + { + final AffineTransform3D labelTransform = model.labelTransformation(); + final double pixelWidth = RealPoints.length( labelTransform.d( 0 ) ); + final double pixelHeight = RealPoints.length( labelTransform.d( 1 ) ); + if ( is2D ) + return new double[] { radius, radius * pixelWidth / pixelHeight }; + + final double pixelDepth = RealPoints.length( labelTransform.d( 2 ) ); + return new double[] { radius, radius * pixelWidth / pixelHeight, radius * pixelWidth / pixelDepth }; + } + + private double[] posToImageCoords( final RealLocalizable screenCoordinates ) + { + final AffineTransform3D m = displayToImageTransformation(); + final double[] screen = { screenCoordinates.getDoublePosition( 0 ), screenCoordinates.getDoublePosition( 1 ), 0 }; + final double[] center = new double[ 3 ]; + m.apply( screen, center ); + if ( is2D ) + return new double[] { center[ 0 ], center[ 1 ] }; + return center; + } + private Consumer< LabelingType< Label > > pixelOperation() { final Label label = model.selectedLabel().get(); @@ -427,8 +456,15 @@ public void init( final int x, final int y ) makeLabelVisible(); final RealPoint coords = new RealPoint( x, y ); this.before = coords; - paint( coords ); + + // Initialize stroke region final double radius = getBrushDisplayRadius(); + strokeRegion = createStrokeRegion( posToImageCoords( coords ), radiusToImageCoords( radius ) ); + + // Snapshot the current state for undo/redo + model.undoRedo().startUndo( viewer.state().getCurrentTimepoint() ); + + paint( coords ); fireBitmapChanged( coords, coords, radius ); } @@ -440,6 +476,13 @@ public void drag( final int x, final int y ) paint( before, coords ); final double radius = getBrushDisplayRadius(); fireBitmapChanged( before, coords, radius ); + + // Expand stroke region + strokeRegion = expandStrokeRegion( + strokeRegion, + posToImageCoords( before ), + posToImageCoords( coords ), + radiusToImageCoords( radius ) ); this.before = coords; } @@ -448,6 +491,35 @@ public void end( final int x, final int y ) { brushCursor.setPosition( x, y ); brushCursor.setFontVisible( true ); + + final UndoRedoStack undo = model.undoRedo(); + undo.setUndoPoint( viewer.state().getCurrentTimepoint(), strokeRegion ); + } + + /** Creates an interval representing the initial brush stroke region. */ + private static final FinalInterval createStrokeRegion( final double[] center, final double[] radius ) + { + final long[] min = new long[ 2 ]; + final long[] max = new long[ 2 ]; + for ( int d = 0; d < 2; d++ ) + { + min[ d ] = ( long ) Math.floor( center[ d ] - radius[ d ] ); + max[ d ] = ( long ) Math.ceil( center[ d ] + radius[ d ] ); + } + return new FinalInterval( min, max ); + } + + /** Expands the stroke region to include a new brush position. */ + private static final FinalInterval expandStrokeRegion( final FinalInterval current, final double[] centerA, final double[] centerB, final double[] radius ) + { + final long[] min = new long[ current.numDimensions() ]; + final long[] max = new long[ current.numDimensions() ]; + for ( int d = 0; d < current.numDimensions(); d++ ) + { + min[ d ] = Math.min( current.min( d ), ( long ) Math.floor( Math.min( centerA[ d ], centerB[ d ] ) - radius[ d ] ) ); + max[ d ] = Math.max( current.max( d ), ( long ) Math.ceil( Math.max( centerA[ d ], centerB[ d ] ) + radius[ d ] ) ); + } + return new FinalInterval( min, max ); } } @@ -470,7 +542,12 @@ private double getBrushDisplayRadius() return brushDiameter * 0.5 * labelScale * viewScale; } - private RandomAccessibleInterval< LabelingType< Label > > getFrame() + /** + * Returns the labeling of the current frame. + * + * @return the labeling of the current frame + */ + private RandomAccessibleInterval< LabelingType< Label > > getFrameLabeling() { final RandomAccessibleInterval< LabelingType< Label > > frame = model.labeling().get(); if ( this.model.isTimeSeries() ) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/EditCommand.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/EditCommand.java deleted file mode 100644 index 5c8628e1e..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/EditCommand.java +++ /dev/null @@ -1,27 +0,0 @@ -package fiji.plugin.trackmate.gui.editor.labkit.model; - -import net.imglib2.Interval; - -/** - * Command interface for undo/redo operations in the label editor. - */ -public interface EditCommand -{ - /** - * Revert the edit, restoring the state before the edit was applied. - */ - void undo(); - - /** - * Re-apply the edit, restoring the state after the edit was applied. - */ - void redo(); - - /** - * Returns the interval affected by this edit. - * Used to trigger repaints of the affected region. - * - * @return the affected interval, or {@code null} if the entire image is affected. - */ - Interval getRegion(); -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMImageLabelingModel.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMImageLabelingModel.java index 06fb80b0f..a850048bf 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMImageLabelingModel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMImageLabelingModel.java @@ -44,12 +44,15 @@ public class TMImageLabelingModel extends ImageLabelingModel private RandomAccessibleInterval< UnsignedIntType > initialIndexImg; + private final UndoRedoStack undoStack; + public TMImageLabelingModel( final InputImage inputImage ) { super( inputImage ); final ImgPlus< ? > image = inputImage.imageForSegmentation(); + this.undoStack = new UndoRedoStack( this ); final boolean isTimeSeries = ImgPlusViewsOld.hasAxis( image, Axes.TIME ); - tmTranslationModel = new TMTransformationModel( isTimeSeries ); + this.tmTranslationModel = new TMTransformationModel( isTimeSeries ); } @Override @@ -73,4 +76,14 @@ void setInitialState( final Map< Label, Spot > initialMapping, final RandomAcces this.initialMapping = initialMapping; this.initialIndexImg = initialIndexImg; } + + /** + * Returns the undo/redo stack associated with this model. + * + * @return the undo/redo stack. + */ + public UndoRedoStack undoRedo() + { + return undoStack; + } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java new file mode 100644 index 000000000..3c6cfaed4 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java @@ -0,0 +1,188 @@ +package fiji.plugin.trackmate.gui.editor.labkit.model; + +import java.util.ArrayDeque; +import java.util.Deque; + +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.type.numeric.integer.UnsignedIntType; +import net.imglib2.util.ImgUtil; +import net.imglib2.util.Util; +import net.imglib2.view.Views; + +/** + * Manages undo/redo history for label editing operations. + */ +public class UndoRedoStack +{ + private final Deque< UndoableCommand > undoStack = new ArrayDeque<>(); + + private final Deque< UndoableCommand > redoStack = new ArrayDeque<>(); + + private final int maxSize; + + private final TMImageLabelingModel model; + + /** + * Snapshot of the index image before painting, used to store state of the + * labeling before an edit. + */ + private RandomAccessibleInterval< UnsignedIntType > snapshot; + + /** + * Creates a new UndoRedoStack, set to operate on the specified model, with + * the specified maximum size. + * + * @param model + * the model to operate on. + * @param maxSize + * the maximum number of commands to keep in history. + */ + public UndoRedoStack( final TMImageLabelingModel model, final int maxSize ) + { + this.model = model; + this.maxSize = maxSize; + } + + /** + * Creates a new UndoRedoStack for the specified model, with a default + * maximum size of 50 commands. + * + * @param model + * the model to operate on. + */ + public UndoRedoStack( final TMImageLabelingModel model ) + { + this( model, 50 ); + } + + /** + * Starts an undo operation. + *

    + * This method must be called before any edit operation is performed, + * to capture the state of the labeling before the edit. After the edit is + * performed, call {@link #setUndoPoint(int, FinalInterval)} to record the + * edit operation. + * + * @param frame + * the time point of the labeling on which the edit operation is + * performed. + */ + public void startUndo( final int frame ) + { + snapshot( frame ); + } + + /** + * Records an undo point after an edit operation is performed. + *

    + * This method must be called after an edit operation is performed, + * to record the state of the labeling after the edit. The region parameter + * specifies the region of the labeling that was affected by the edit. + * + * @param frame + * the time point of the labeling on which the edit operation was + * performed. + * @param region + * the region of the labeling that was affected by the edit. + */ + public void setUndoPoint( final int frame, final FinalInterval region ) + { + final UndoableCommand current = new UndoableCommand( getFrame( frame ), region, frame ); + current.captureBefore( snapshot ); + current.captureAfter( getFrame( frame ) ); + push( current ); + } + + private void push( final UndoableCommand command ) + { + redoStack.clear(); + if ( undoStack.size() >= maxSize ) + undoStack.removeFirst(); + + undoStack.addLast( command ); + } + + /** + * Undo the last operation. + * + * @return the region affected by the undo, or null if nothing + * was undone + */ + public Interval undo() + { + if ( undoStack.isEmpty() ) + return null; + + final UndoableCommand command = undoStack.removeLast(); + command.restoreBefore( getFrame( command.frame ) ); + redoStack.addLast( command ); + model.dataChangedNotifier().notifyListeners( null ); + return command.region; + } + + /** + * Redo the last undone operation. + * + * @return the region affected by the redo, or null if nothing + * was redone + */ + public Interval redo() + { + if ( redoStack.isEmpty() ) + return null; + + final UndoableCommand command = redoStack.removeLast(); + command.restoreAfter( getFrame( command.frame ) ); + undoStack.addLast( command ); + model.dataChangedNotifier().notifyListeners( null ); + return command.region; + } + + /** + * Returns whether undo is possible. + * + * @return {@code true} if there are commands to undo + */ + public boolean canUndo() + { + return !undoStack.isEmpty(); + } + + /** + * Returns whether redo is possible. + * + * @return {@code true} if there are commands to redo + */ + public boolean canRedo() + { + return !redoStack.isEmpty(); + } + + /** + * Clears all history. + */ + public void clear() + { + undoStack.clear(); + redoStack.clear(); + } + + private void snapshot( final int frame ) + { + final RandomAccessibleInterval< UnsignedIntType > current = getFrame( frame ); + if ( snapshot == null ) + this.snapshot = Util.getArrayOrCellImgFactory( current, new UnsignedIntType() ).create( current ); + ImgUtil.copy( current, snapshot ); + } + + private RandomAccessibleInterval< UnsignedIntType > getFrame( final int frame ) + { + @SuppressWarnings( "unchecked" ) + final RandomAccessibleInterval< UnsignedIntType > indexImg = ( RandomAccessibleInterval< UnsignedIntType > ) model.labeling().get().getIndexImg(); + if ( model.isTimeSeries() ) + return Views.hyperSlice( indexImg, indexImg.numDimensions() - 1, frame ); + return indexImg; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoableCommand.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoableCommand.java new file mode 100644 index 000000000..3b3391bc5 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoableCommand.java @@ -0,0 +1,102 @@ +package fiji.plugin.trackmate.gui.editor.labkit.model; + +import net.imglib2.Cursor; +import net.imglib2.Interval; +import net.imglib2.RandomAccessible; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.type.numeric.integer.UnsignedIntType; +import net.imglib2.util.Intervals; +import net.imglib2.view.Views; + +/** + * An {@link EditCommand} that stores before/after snapshots of label indices. + *

    + * The command captures the pixel values in a region before and after an edit + * operation (brush stroke or flood fill). Undo restores the before state, redo + * restores the after state. + */ +public class UndoableCommand +{ + final Interval region; + + final int frame; + + private final int[] beforeValues; + + private final int[] afterValues; + + /** + * Creates a new LabelEditCommand for the specified region. + * + * @param indexImg + * the live index image that will be modified by undo/redo + * @param region + * the interval affected by the edit + * @param frame + */ + UndoableCommand( final RandomAccessible< UnsignedIntType > indexImg, final Interval region, final int frame ) + { + this.region = region; + this.frame = frame; + this.beforeValues = new int[ ( int ) Intervals.numElements( region ) ]; + this.afterValues = new int[ beforeValues.length ]; + } + + /** + * Captures the current state of the index image as the "before" state. Must + * be called BEFORE applying the edit operation. + */ + void captureBefore( final RandomAccessible< UnsignedIntType > before ) + { + captureState( before, beforeValues ); + } + + /** + * Captures the current state of the index image as the "after" state. Must + * be called AFTER applying the edit operation. + */ + void captureAfter( final RandomAccessible< UnsignedIntType > after ) + { + captureState( after, afterValues ); + } + + /** + * Restores the before state to the specified index image. + * + * @param to + * the index image to restore to + */ + void restoreBefore( final RandomAccessible< UnsignedIntType > to ) + { + restoreState( to, beforeValues ); + } + + /** + * Restores the after state to the specified index image. + * + * @param to + * the index image to restore to + */ + void restoreAfter( final RandomAccessible< UnsignedIntType > to ) + { + restoreState( to, afterValues ); + } + + private final void captureState( final RandomAccessible< UnsignedIntType > from, final int[] values ) + { + final RandomAccessibleInterval< UnsignedIntType > view = Views.interval( from, region ); + final Cursor< UnsignedIntType > cursor = Views.flatIterable( view ).cursor(); + int i = 0; + while ( cursor.hasNext() ) + values[ i++ ] = cursor.next().getInteger(); + } + + private final void restoreState( final RandomAccessible< UnsignedIntType > to, final int[] values ) + { + final RandomAccessibleInterval< UnsignedIntType > view = Views.interval( to, region ); + final Cursor< UnsignedIntType > cursor = Views.flatIterable( view ).cursor(); + int i = 0; + while ( cursor.hasNext() ) + cursor.next().setInt( values[ i++ ] ); + } +} From ec6b63f3cae85a99b1fb1ab52b9c64823a099f56 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 18:26:05 +0200 Subject: [PATCH 243/371] Use the TMImageLabelingModel type in flood fill controller --- .../component/TMFloodFillController.java | 73 ++++++++++--------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java index f2602ebe1..bf655661a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java @@ -46,6 +46,7 @@ import bdv.util.BdvHandle; import bdv.viewer.ViewerPanel; +import fiji.plugin.trackmate.gui.editor.labkit.model.TMImageLabelingModel; import fiji.plugin.trackmate.util.TMUtils; import gnu.trove.map.TIntIntMap; import gnu.trove.map.hash.TIntIntHashMap; @@ -65,7 +66,6 @@ import sc.fiji.labkit.ui.brush.BdvMouseBehaviourUtils; import sc.fiji.labkit.ui.brush.FloodFillController; import sc.fiji.labkit.ui.labeling.Label; -import sc.fiji.labkit.ui.models.LabelingModel; /** * Copied from FloodFillController @@ -143,7 +143,7 @@ public String getTooltip() private final ViewerPanel viewer; - private final LabelingModel model; + private final TMImageLabelingModel model; private final BdvHandle bdv; @@ -155,8 +155,7 @@ public String getTooltip() private Collection< Label > visibleLabels() { - return model.labeling().get().getLabels().stream().filter( Label::isVisible ) - .collect( Collectors.toList() ); + return model.labeling().get().getLabels().stream().filter( Label::isVisible ).collect( Collectors.toList() ); } private final FloodFillClick floodFillBehaviour = new FloodFillClick( () -> { @@ -191,7 +190,7 @@ private Collection< Label > visibleLabels() } } ); - public TMFloodFillController( final BdvHandle bdv, final LabelingModel model ) + public TMFloodFillController( final BdvHandle bdv, final TMImageLabelingModel model ) { this.bdv = bdv; this.viewer = bdv.getViewerPanel(); @@ -286,7 +285,9 @@ protected void floodFill( final RealLocalizable imageCoordinates ) final Point seed = roundAndReduceDimension( imageCoordinates, frame.numDimensions() ); final Consumer< Set< Label > > operation = operationFactory.get(); if ( askUser( frame, seed, operation ) ) + { FloodFill.doFloodFillOnActiveLabels( frame, seed, operation ); + } } } @@ -297,8 +298,12 @@ private boolean askUser( final RandomAccessibleInterval< LabelingType< Label > > { final String message = "Are you sure to flood fill the background of this 3d image?" + "\n(This may take a while to compute.)"; - final int result = JOptionPane.showConfirmDialog( viewer, message, "Flood Fill 3D Image", - JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE ); + final int result = JOptionPane.showConfirmDialog( + viewer, + message, + "Flood Fill 3D Image", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE ); return result == JOptionPane.OK_OPTION; } return true; @@ -323,11 +328,9 @@ public void click( final int x, final int y ) private RandomAccessibleInterval< LabelingType< Label > > labeling() { - final RandomAccessibleInterval< LabelingType< Label > > label = model.labeling() - .get(); + final RandomAccessibleInterval< LabelingType< Label > > label = model.labeling().get(); if ( model.isTimeSeries() ) - return Views.hyperSlice( label, label - .numDimensions() - 1, viewer.state().getCurrentTimepoint() ); + return Views.hyperSlice( label, label.numDimensions() - 1, viewer.state().getCurrentTimepoint() ); return label; } @@ -348,22 +351,24 @@ private static class FloodFill * @param seed * Seed point. * @param operation - * Operation that es performed for the flood filled pixels. + * Operation that is performed for the flood filled pixels. */ public static void doFloodFillOnActiveLabels( - final RandomAccessibleInterval< LabelingType< Label > > labeling, final Point seed, + final RandomAccessibleInterval< LabelingType< Label > > labeling, + final Point seed, final Consumer< ? super LabelingType< Label > > operation ) { final Set< Label > seedValue = getPixel( labeling, seed ).copy(); - final Predicate< LabelingType< Label > > visit = value -> activeLabelsAreEquals( value, - seedValue ); + final Predicate< LabelingType< Label > > visit = value -> activeLabelsAreEquals( value, seedValue ); cachedFloodFill( labeling, seed, visit, operation ); } // package-private to allow testing static < T > void cachedFloodFill( - final RandomAccessibleInterval< LabelingType< T > > image, final Localizable seed, - final Predicate< ? super LabelingType< T > > visit, final Consumer< ? super LabelingType< T > > operation ) + final RandomAccessibleInterval< LabelingType< T > > image, + final Localizable seed, + final Predicate< ? super LabelingType< T > > visit, + final Consumer< ? super LabelingType< T > > operation ) { final Predicate< LabelingType< T > > cachedVisit = new CacheForPredicateLabelingType<>( visit ); final Consumer< LabelingType< T > > cachedOperation = new CacheForOperationLabelingType<>( operation ); @@ -371,7 +376,9 @@ static < T > void cachedFloodFill( } private static < T extends Type< T > > void doFloodFill( - final RandomAccessibleInterval< T > image, final Localizable seed, final Predicate< T > visit, + final RandomAccessibleInterval< T > image, + final Localizable seed, + final Predicate< T > visit, final Consumer< T > operation ) { final RandomAccess< T > ra = image.randomAccess(); @@ -383,33 +390,29 @@ private static < T extends Type< T > > void doFloodFill( return; final BiPredicate< T, T > filter = ( f, s ) -> visit.test( f ); @SuppressWarnings( "deprecation" ) - final ExtendedRandomAccessibleInterval< T, RandomAccessibleInterval< T > > target = - Views.extendValue( image, seedValueChanged ); + final ExtendedRandomAccessibleInterval< T, RandomAccessibleInterval< T > > target = Views.extendValue( image, seedValueChanged ); final DiamondShape shape = new DiamondShape( 1 ); - net.imglib2.algorithm.fill.FloodFill.fill( target, target, seed, shape, - filter, operation ); + net.imglib2.algorithm.fill.FloodFill.fill( target, target, seed, shape, filter, operation ); } - private static boolean activeLabelsAreEquals( final LabelingType< Label > a, - final Set< Label > b ) + private static boolean activeLabelsAreEquals( final LabelingType< Label > a, final Set< Label > b ) { - final boolean bIsSubSetOfA = b.stream().filter( Label::isVisible ).allMatch( - a::contains ); - final boolean aIsSubSetOfB = a.stream().filter( Label::isVisible ).allMatch( - b::contains ); + final boolean bIsSubSetOfA = b.stream().filter( Label::isVisible ).allMatch( a::contains ); + final boolean aIsSubSetOfB = a.stream().filter( Label::isVisible ).allMatch( b::contains ); return aIsSubSetOfB && bIsSubSetOfA; } - private static < T > T getPixel( final RandomAccessible< T > image, - final Localizable position ) + private static < T > T getPixel( final RandomAccessible< T > image, final Localizable position ) { final RandomAccess< T > ra = image.randomAccess(); ra.setPosition( position ); return ra.get(); } - public static boolean isBackgroundFloodFill( final RandomAccessibleInterval< LabelingType< Label > > frame, - final Point seed, final Consumer< Set< Label > > operation ) + public static boolean isBackgroundFloodFill( + final RandomAccessibleInterval< LabelingType< Label > > frame, + final Point seed, + final Consumer< Set< Label > > operation ) { final LabelingType< Label > seedValue = frame.randomAccess().setPositionAndGet( seed ); final LabelingType< Label > changedSeedValue = seedValue.copy(); @@ -421,8 +424,7 @@ public static boolean isBackgroundFloodFill( final RandomAccessibleInterval< Lab return isBackgroundFill && operationHasEffect; } - private static class CacheForPredicateLabelingType< T > implements - Predicate< LabelingType< T > > + private static class CacheForPredicateLabelingType< T > implements Predicate< LabelingType< T > > { private final Predicate< ? super LabelingType< T > > predicate; @@ -451,8 +453,7 @@ public boolean test( final LabelingType< T > ts ) } } - private static class CacheForOperationLabelingType< T > implements - Consumer< LabelingType< T > > + private static class CacheForOperationLabelingType< T > implements Consumer< LabelingType< T > > { private final Consumer< ? super LabelingType< T > > operation; From d18d119da5c150c02e57bd2f95620d72e7f61baa Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 18:42:02 +0200 Subject: [PATCH 244/371] D'oh! Method signature should be for Interval not FinalInterval. --- .../gui/editor/labkit/component/TMLabelBrushController.java | 5 +++-- .../trackmate/gui/editor/labkit/model/UndoRedoStack.java | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java index 919582439..b0f491ba8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java @@ -50,6 +50,7 @@ import fiji.plugin.trackmate.gui.editor.labkit.model.UndoRedoStack; import fiji.plugin.trackmate.util.TMUtils; import net.imglib2.FinalInterval; +import net.imglib2.Interval; import net.imglib2.RandomAccessible; import net.imglib2.RandomAccessibleInterval; import net.imglib2.RealLocalizable; @@ -302,7 +303,7 @@ private class PaintBehavior implements DragBehaviour /** * The bounding box of the current stroke, used for undo/redo region. */ - private FinalInterval strokeRegion; + private Interval strokeRegion; private final boolean is2D; @@ -510,7 +511,7 @@ private static final FinalInterval createStrokeRegion( final double[] center, fi } /** Expands the stroke region to include a new brush position. */ - private static final FinalInterval expandStrokeRegion( final FinalInterval current, final double[] centerA, final double[] centerB, final double[] radius ) + private static final Interval expandStrokeRegion( final Interval current, final double[] centerA, final double[] centerB, final double[] radius ) { final long[] min = new long[ current.numDimensions() ]; final long[] max = new long[ current.numDimensions() ]; diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java index 3c6cfaed4..57f163ee6 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java @@ -3,7 +3,6 @@ import java.util.ArrayDeque; import java.util.Deque; -import net.imglib2.FinalInterval; import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; import net.imglib2.type.numeric.integer.UnsignedIntType; @@ -62,7 +61,7 @@ public UndoRedoStack( final TMImageLabelingModel model ) *

    * This method must be called before any edit operation is performed, * to capture the state of the labeling before the edit. After the edit is - * performed, call {@link #setUndoPoint(int, FinalInterval)} to record the + * performed, call {@link #setUndoPoint(int, Interval)} to record the * edit operation. * * @param frame @@ -87,7 +86,7 @@ public void startUndo( final int frame ) * @param region * the region of the labeling that was affected by the edit. */ - public void setUndoPoint( final int frame, final FinalInterval region ) + public void setUndoPoint( final int frame, final Interval region ) { final UndoableCommand current = new UndoableCommand( getFrame( frame ), region, frame ); current.captureBefore( snapshot ); From 11d9ab22c38bdd23e2482a60d86a2ed80d11ca8f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 19:05:53 +0200 Subject: [PATCH 245/371] A modification of imglib2 FloodFill to return the the bounding-box of the filled region. --- .../gui/editor/labkit/util/FloodFill.java | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/editor/labkit/util/FloodFill.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/util/FloodFill.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/util/FloodFill.java new file mode 100644 index 000000000..5e9027673 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/util/FloodFill.java @@ -0,0 +1,233 @@ +package fiji.plugin.trackmate.gui.editor.labkit.util; + +import java.util.function.BiPredicate; +import java.util.function.Consumer; + +import gnu.trove.list.TLongList; +import gnu.trove.list.array.TLongArrayList; +import net.imglib2.Cursor; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.Localizable; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessible; +import net.imglib2.algorithm.neighborhood.Neighborhood; +import net.imglib2.algorithm.neighborhood.Shape; +import net.imglib2.type.Type; +import net.imglib2.util.Pair; +import net.imglib2.view.Views; + +/** + * Iterative n-dimensional flood fill for arbitrary neighborhoods. + *

    + * This class is modifed from the original + * {@link net.imglib2.algorithm.floodfill.FloodFill} class to be able to return + * the bounding-box (as interval) of the filled region. + * + * @author Philipp Hanslovsky + * @author Stephan Saalfeld + * @author Jean-Yves Tinevez + */ +public class FloodFill +{ + // int or long? current TLongList cannot store more than Integer.MAX_VALUE + private static final int CLEANUP_THRESHOLD = ( int ) 1e5; + + /** + * Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting + * at seed location, write fillLabel into target at current location and + * continue for each pixel in neighborhood defined by shape if neighborhood + * pixel is in the same connected component and fillLabel has not been + * written into that location yet. + * + * Convenience call to + * {@link #fill(RandomAccessible, RandomAccessible, Localizable, Type, Shape, BiPredicate)}. + * seedLabel is extracted from source at seed location. + * + * @param source + * input + * @param target + * {@link RandomAccessible} to be written into. May be the same + * as input. + * @param seed + * Start flood fill at this location. + * @param fillLabel + * Immutable. Value to be written into valid flood fill + * locations. + * @param shape + * Defines neighborhood that is considered for connected + * components, e.g. + * {@link net.imglib2.algorithm.neighborhood.DiamondShape} + * @param + * input pixel type + * @param + * fill label type + * @return the bounding box of the filled region as a {@link Interval}. + */ + public static < T extends Type< T >, U extends Type< U > > Interval fill( + final RandomAccessible< T > source, + final RandomAccessible< U > target, + final Localizable seed, + final U fillLabel, + final Shape shape ) + { + final RandomAccess< T > access = source.randomAccess(); + access.setPosition( seed ); + final T seedValue = access.get().copy(); + final BiPredicate< T, U > filter = ( t, u ) -> t.valueEquals( seedValue ) && !u.valueEquals( fillLabel ); + return fill( source, target, seed, fillLabel, shape, filter ); + } + + /** + * Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting + * at seed location, write fillLabel into target at current location and + * continue for each pixel in neighborhood defined by shape if neighborhood + * pixel is in the same connected component and fillLabel has not been + * written into that location yet. + * + * Convenience call to + * {@link FloodFill#fill(RandomAccessible, RandomAccessible, Localizable, Shape, BiPredicate, Consumer)} + * with {@link Type#set} as writer. + * + * @param source + * input + * @param target + * {@link RandomAccessible} to be written into. May be the same + * as input. + * @param seed + * Start flood fill at this location. + * @param fillLabel + * Immutable. Value to be written into valid flood fill + * locations. + * @param shape + * Defines neighborhood that is considered for connected + * components, e.g. + * {@link net.imglib2.algorithm.neighborhood.DiamondShape} + * @param filter + * Returns true if pixel has not been visited yet and should be + * written into. Returns false if target pixel has been visited + * or source pixel is not part of the same connected component. + * @param + * input pixel type + * @param + * fill label type + * @return the bounding box of the filled region as a {@link Interval}. + */ + public static < T, U extends Type< U > > Interval fill( + final RandomAccessible< T > source, + final RandomAccessible< U > target, + final Localizable seed, + final U fillLabel, + final Shape shape, + final BiPredicate< T, U > filter ) + { + return fill( source, target, seed, shape, filter, targetPixel -> targetPixel.set( fillLabel ) ); + } + + /** + * + * Iterative n-dimensional flood fill for arbitrary neighborhoods: Starting + * at seed location, write fillLabel into target at current location and + * continue for each pixel in neighborhood defined by shape if neighborhood + * pixel is in the same connected component and fillLabel has not been + * written into that location yet. + * + * @param source + * input + * @param target + * {@link RandomAccessible} to be written into. May be the same + * as input. + * @param seed + * Start flood fill at this location. + * @param shape + * Defines neighborhood that is considered for connected + * components, e.g. + * {@link net.imglib2.algorithm.neighborhood.DiamondShape} + * @param filter + * Returns true if pixel has not been visited yet and should be + * written into. Returns false if target pixel has been visited + * or source pixel is not part of the same connected component. + * @param writer + * Defines how fill label is written into target at current + * location. + * @param + * input pixel type + * @param + * fill label type + * @return the bounding box of the filled region as a {@link Interval}. + */ + public static < T, U > Interval fill( + final RandomAccessible< T > source, + final RandomAccessible< U > target, + final Localizable seed, + final Shape shape, + final BiPredicate< T, U > filter, + final Consumer< U > writer ) + { + final int n = source.numDimensions(); + + final RandomAccessible< Pair< T, U > > paired = Views.pair( source, target ); + + TLongList coordinates = new TLongArrayList(); + for ( int d = 0; d < n; ++d ) + { + coordinates.add( seed.getLongPosition( d ) ); + } + + // Initialize bounding box at seed position + final long[] min = new long[ n ]; + final long[] max = new long[ n ]; + for ( int d = 0; d < n; ++d ) + { + final long seedPos = seed.getLongPosition( d ); + coordinates.add( seedPos ); + min[ d ] = seedPos; + max[ d ] = seedPos; + } + + final int cleanupThreshold = n * CLEANUP_THRESHOLD; + + final RandomAccessible< Neighborhood< Pair< T, U > > > neighborhood = shape.neighborhoodsRandomAccessible( paired ); + final RandomAccess< Neighborhood< Pair< T, U > > > neighborhoodAccess = neighborhood.randomAccess(); + + final RandomAccess< U > targetAccess = target.randomAccess(); + targetAccess.setPosition( seed ); + writer.accept( targetAccess.get() ); + + for ( int i = 0; i < coordinates.size(); i += n ) + { + for ( int d = 0; d < n; ++d ) + neighborhoodAccess.setPosition( coordinates.get( i + d ), d ); + + final Cursor< Pair< T, U > > neighborhoodCursor = neighborhoodAccess.get().cursor(); + + while ( neighborhoodCursor.hasNext() ) + { + final Pair< T, U > p = neighborhoodCursor.next(); + if ( filter.test( p.getA(), p.getB() ) ) + { + writer.accept( p.getB() ); + for ( int d = 0; d < n; ++d ) + { + final long pos = neighborhoodCursor.getLongPosition( d ); + coordinates.add( pos ); + // Expand bounding box + if ( pos < min[ d ] ) + min[ d ] = pos; + if ( pos > max[ d ] ) + max[ d ] = pos; + } + } + } + + if ( i > cleanupThreshold ) + { + // TODO should it start from i + n? + coordinates = coordinates.subList( i, coordinates.size() ); + i = 0; + } + + } + return new FinalInterval( min, max ); + } +} \ No newline at end of file From f7ba04d1940edb65fb8a7068c12b345e7631e234 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 19:07:12 +0200 Subject: [PATCH 246/371] Undo / redo for the flood fill operations. This is still a WIP: plenty of things to debug. Among them: - if the user paints above or below the image, there is a crash - if the user drew a ROI before calling the editor, the origin of the label image is not (0,0) and the undo redo has coords confused. --- .../component/TMFloodFillController.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java index bf655661a..27ebb0bfc 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java @@ -50,6 +50,7 @@ import fiji.plugin.trackmate.util.TMUtils; import gnu.trove.map.TIntIntMap; import gnu.trove.map.hash.TIntIntHashMap; +import net.imglib2.Interval; import net.imglib2.Localizable; import net.imglib2.Point; import net.imglib2.RandomAccess; @@ -286,7 +287,15 @@ protected void floodFill( final RealLocalizable imageCoordinates ) final Consumer< Set< Label > > operation = operationFactory.get(); if ( askUser( frame, seed, operation ) ) { - FloodFill.doFloodFillOnActiveLabels( frame, seed, operation ); + // Start undo before modifying + final int frameIndex = viewer.state().getCurrentTimepoint(); + model.undoRedo().startUndo( frameIndex ); + + // Execute flood fill and get the affected region + final Interval region = FloodFill.doFloodFillOnActiveLabels( frame, seed, operation ); + + // Set undo point after modifying + model.undoRedo().setUndoPoint( frameIndex, region ); } } } @@ -352,19 +361,20 @@ private static class FloodFill * Seed point. * @param operation * Operation that is performed for the flood filled pixels. + * @return the bounding box of the filled region as a {@link Interval}. */ - public static void doFloodFillOnActiveLabels( + public static Interval doFloodFillOnActiveLabels( final RandomAccessibleInterval< LabelingType< Label > > labeling, final Point seed, final Consumer< ? super LabelingType< Label > > operation ) { final Set< Label > seedValue = getPixel( labeling, seed ).copy(); final Predicate< LabelingType< Label > > visit = value -> activeLabelsAreEquals( value, seedValue ); - cachedFloodFill( labeling, seed, visit, operation ); + return cachedFloodFill( labeling, seed, visit, operation ); } // package-private to allow testing - static < T > void cachedFloodFill( + static < T > Interval cachedFloodFill( final RandomAccessibleInterval< LabelingType< T > > image, final Localizable seed, final Predicate< ? super LabelingType< T > > visit, @@ -372,10 +382,10 @@ static < T > void cachedFloodFill( { final Predicate< LabelingType< T > > cachedVisit = new CacheForPredicateLabelingType<>( visit ); final Consumer< LabelingType< T > > cachedOperation = new CacheForOperationLabelingType<>( operation ); - doFloodFill( image, seed, cachedVisit, cachedOperation ); + return doFloodFill( image, seed, cachedVisit, cachedOperation ); } - private static < T extends Type< T > > void doFloodFill( + private static < T extends Type< T > > Interval doFloodFill( final RandomAccessibleInterval< T > image, final Localizable seed, final Predicate< T > visit, @@ -387,12 +397,13 @@ private static < T extends Type< T > > void doFloodFill( final T seedValueChanged = seedValue.copy(); operation.accept( seedValueChanged ); if ( visit.test( seedValueChanged ) ) - return; + return null; final BiPredicate< T, T > filter = ( f, s ) -> visit.test( f ); @SuppressWarnings( "deprecation" ) final ExtendedRandomAccessibleInterval< T, RandomAccessibleInterval< T > > target = Views.extendValue( image, seedValueChanged ); final DiamondShape shape = new DiamondShape( 1 ); - net.imglib2.algorithm.fill.FloodFill.fill( target, target, seed, shape, filter, operation ); + + return fiji.plugin.trackmate.gui.editor.labkit.util.FloodFill.fill( target, target, seed, shape, filter, operation ); } private static boolean activeLabelsAreEquals( final LabelingType< Label > a, final Set< Label > b ) From 77b01c13912e3a58a2f4641327494b8cbf65a1aa Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 26 Jul 2026 20:44:19 +0200 Subject: [PATCH 247/371] Fix editor bugs with ROI editing and editing outside the image. --- .../labkit/component/TMLabelBrushController.java | 12 +++++++----- .../gui/editor/labkit/model/UndoRedoStack.java | 6 +++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java index b0f491ba8..0678098f2 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java @@ -460,7 +460,7 @@ public void init( final int x, final int y ) // Initialize stroke region final double radius = getBrushDisplayRadius(); - strokeRegion = createStrokeRegion( posToImageCoords( coords ), radiusToImageCoords( radius ) ); + strokeRegion = createStrokeRegion( posToImageCoords( coords ), ( int ) Math.ceil( brushDiameter / 2. ) ); // Snapshot the current state for undo/redo model.undoRedo().startUndo( viewer.state().getCurrentTimepoint() ); @@ -494,18 +494,20 @@ public void end( final int x, final int y ) brushCursor.setFontVisible( true ); final UndoRedoStack undo = model.undoRedo(); - undo.setUndoPoint( viewer.state().getCurrentTimepoint(), strokeRegion ); + undo.setUndoPoint( + viewer.state().getCurrentTimepoint(), + Intervals.intersect( getFrameLabeling(), strokeRegion ) ); } /** Creates an interval representing the initial brush stroke region. */ - private static final FinalInterval createStrokeRegion( final double[] center, final double[] radius ) + private static final FinalInterval createStrokeRegion( final double[] center, final int radius ) { final long[] min = new long[ 2 ]; final long[] max = new long[ 2 ]; for ( int d = 0; d < 2; d++ ) { - min[ d ] = ( long ) Math.floor( center[ d ] - radius[ d ] ); - max[ d ] = ( long ) Math.ceil( center[ d ] + radius[ d ] ); + min[ d ] = ( long ) Math.floor( center[ d ] - radius ); + max[ d ] = ( long ) Math.ceil( center[ d ] + radius ); } return new FinalInterval( min, max ); } diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java index 57f163ee6..8735fc51b 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java @@ -5,6 +5,7 @@ import net.imglib2.Interval; import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; import net.imglib2.type.numeric.integer.UnsignedIntType; import net.imglib2.util.ImgUtil; import net.imglib2.util.Util; @@ -172,7 +173,10 @@ private void snapshot( final int frame ) { final RandomAccessibleInterval< UnsignedIntType > current = getFrame( frame ); if ( snapshot == null ) - this.snapshot = Util.getArrayOrCellImgFactory( current, new UnsignedIntType() ).create( current ); + { + final Img< UnsignedIntType > img = Util.getArrayOrCellImgFactory( current, new UnsignedIntType() ).create( current ); + this.snapshot = img.view().translate( current.minAsLongArray() ); + } ImgUtil.copy( current, snapshot ); } From 723b2f58d43816e6c5f1304f8b5fbdda519077e9 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 27 Jul 2026 10:03:25 +0200 Subject: [PATCH 248/371] Ensure undo process consistency. We throw an exception if the user tries to start an undo step before finishing the previous one. --- .../labkit/component/TMFloodFillController.java | 6 +++--- .../labkit/component/TMLabelBrushController.java | 10 ++++------ .../gui/editor/labkit/model/UndoRedoStack.java | 16 +++++++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java index 27ebb0bfc..dda63f2c0 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMFloodFillController.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 * . @@ -295,7 +295,7 @@ protected void floodFill( final RealLocalizable imageCoordinates ) final Interval region = FloodFill.doFloodFillOnActiveLabels( frame, seed, operation ); // Set undo point after modifying - model.undoRedo().setUndoPoint( frameIndex, region ); + model.undoRedo().setUndoPoint( region ); } } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java index 0678098f2..ac39322f4 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.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 * . @@ -494,9 +494,7 @@ public void end( final int x, final int y ) brushCursor.setFontVisible( true ); final UndoRedoStack undo = model.undoRedo(); - undo.setUndoPoint( - viewer.state().getCurrentTimepoint(), - Intervals.intersect( getFrameLabeling(), strokeRegion ) ); + undo.setUndoPoint( Intervals.intersect( getFrameLabeling(), strokeRegion ) ); } /** Creates an interval representing the initial brush stroke region. */ @@ -547,7 +545,7 @@ private double getBrushDisplayRadius() /** * Returns the labeling of the current frame. - * + * * @return the labeling of the current frame */ private RandomAccessibleInterval< LabelingType< Label > > getFrameLabeling() diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java index 8735fc51b..8c7a9de10 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java @@ -30,6 +30,8 @@ public class UndoRedoStack */ private RandomAccessibleInterval< UnsignedIntType > snapshot; + private int currentFrame = -1; + /** * Creates a new UndoRedoStack, set to operate on the specified model, with * the specified maximum size. @@ -48,7 +50,7 @@ public UndoRedoStack( final TMImageLabelingModel model, final int maxSize ) /** * Creates a new UndoRedoStack for the specified model, with a default * maximum size of 50 commands. - * + * * @param model * the model to operate on. */ @@ -64,13 +66,15 @@ public UndoRedoStack( final TMImageLabelingModel model ) * to capture the state of the labeling before the edit. After the edit is * performed, call {@link #setUndoPoint(int, Interval)} to record the * edit operation. - * + * * @param frame * the time point of the labeling on which the edit operation is * performed. */ public void startUndo( final int frame ) { + if ( currentFrame >= 0 ) + throw new IllegalStateException( "UndoRedoStack: startUndo called before previous undo operation was completed. Call setUndoPoint after performing the edit operation." ); snapshot( frame ); } @@ -87,12 +91,13 @@ public void startUndo( final int frame ) * @param region * the region of the labeling that was affected by the edit. */ - public void setUndoPoint( final int frame, final Interval region ) + public void setUndoPoint( final Interval region ) { - final UndoableCommand current = new UndoableCommand( getFrame( frame ), region, frame ); + final UndoableCommand current = new UndoableCommand( getFrame( currentFrame ), region, currentFrame ); current.captureBefore( snapshot ); - current.captureAfter( getFrame( frame ) ); + current.captureAfter( getFrame( currentFrame ) ); push( current ); + currentFrame = -1; } private void push( final UndoableCommand command ) @@ -171,6 +176,7 @@ public void clear() private void snapshot( final int frame ) { + this.currentFrame = frame; final RandomAccessibleInterval< UnsignedIntType > current = getFrame( frame ); if ( snapshot == null ) { From 2cbe92319bd1d06f562a57f9652223e2a7e59109 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 27 Jul 2026 10:27:56 +0200 Subject: [PATCH 249/371] Message the user about undo / redo. --- .../labkit/component/TMLabKitActions.java | 53 +++++++++++++++++-- .../labkit/component/TMLabKitFrame.java | 7 ++- .../editor/labkit/model/UndoRedoStack.java | 13 +++-- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java index cc99c73ac..7a637c350 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.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,6 +24,8 @@ import static fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame.KEY_CONFIG_CONTEXT; import static fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame.KEY_CONFIG_SCOPE; +import java.awt.Font; + import javax.swing.SwingUtilities; import org.scijava.plugin.Plugin; @@ -40,9 +42,12 @@ import bdv.ui.keymap.Keymap; import bdv.ui.keymap.KeymapManager; import bdv.ui.keymap.KeymapSettingsPage; +import bdv.viewer.ViewerPanel; +import bdv.viewer.animate.MessageOverlayAnimator; import fiji.plugin.trackmate.gui.editor.labkit.model.TMLabKitModel; import fiji.plugin.trackmate.gui.editor.labkit.model.TMTransformationModel; import fiji.plugin.trackmate.gui.editor.labkit.model.UndoRedoStack; +import net.imglib2.Interval; public class TMLabKitActions { @@ -51,6 +56,7 @@ public static void install( final Actions actions, final TMLabKitModel model, final TMLabKitFrame frame, + final ViewerPanel viewerPanel, final InputActionBindings keybindings, final KeymapManager keymapManager, final AppearanceManager appearanceManager ) @@ -81,20 +87,57 @@ public static void install( * Undo / redo actions */ + final boolean hasTime = model.imageLabelingModel().isTimeSeries(); + final MessageOverlayAnimator messages = new MessageOverlayAnimator( 2000, 0.01, 0.2, new Font( "Arial", Font.PLAIN, 12 ) ); + viewerPanel.addOverlayAnimator( messages ); final UndoRedoStack undoRedo = model.imageLabelingModel().undoRedo(); - actions.runnableAction( () -> undoRedo.undo(), UNDO, UNDO_KEYS ); - actions.runnableAction( () -> undoRedo.redo(), REDO, REDO_KEYS ); + actions.runnableAction( () -> undo( undoRedo, messages, hasTime ), UNDO, UNDO_KEYS ); + actions.runnableAction( () -> redo( undoRedo, messages, hasTime ), REDO, REDO_KEYS ); + } + + private static final void undo( final UndoRedoStack undoRedo, final MessageOverlayAnimator messages, final boolean hasTime ) + { + final Interval interval = undoRedo.undo(); + if ( interval == null ) + return; + messages.add( "Undo" + undoRedoMsg( interval, hasTime ) ); + } + + private static final void redo( final UndoRedoStack undoRedo, final MessageOverlayAnimator messages, final boolean hasTime ) + { + final Interval interval = undoRedo.redo(); + if ( interval == null ) + return; + messages.add( "Redo" + undoRedoMsg( interval, hasTime ) ); } + private static final String undoRedoMsg( final Interval interval, final boolean hasTime ) + { + String out = ( hasTime ) + ? " at frame " + interval.dimension( interval.numDimensions() - 1 ) + " @ " + : " @ "; + out += "[" + interval.min( 0 ); + for ( int i = 1; i < interval.numDimensions() - 1; i++ ) + out += ", " + interval.min( i ); + out += "] → [" + interval.max( 0 ); + for ( int i = 1; i < interval.numDimensions() - 1; i++ ) + out += ", " + interval.max( i ); + out += "]"; + return out; + } private static final String RESET_VIEW = "reset view"; + private static final String[] RESET_VIEW_KEYS = new String[] { "shift R " }; private static final String UNDO = "undo"; + private static final String[] UNDO_KEYS = new String[] { "ctrl Z", "meta Z" }; + private static final String REDO = "redo"; + private static final String[] REDO_KEYS = new String[] { "ctrl shift Z", "meta shift Z" }; - + @Plugin( type = CommandDescriptionProvider.class ) public static class Descriptions extends CommandDescriptionProvider { diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java index 5be823cd7..409954a04 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.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 * . @@ -63,6 +63,7 @@ import bdv.ui.keymap.Keymap; import bdv.ui.keymap.KeymapManager; import bdv.util.BdvOptions; +import bdv.viewer.ViewerPanel; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.editor.labkit.model.TMImageLabelingModel; import fiji.plugin.trackmate.gui.editor.labkit.model.TMLabKitModel; @@ -120,6 +121,7 @@ public TMLabKitFrame( final TMLabKitModel model ) // Main central panel, config specific for BDV. final TMBasicLabelingComponent mainPanel = new TMBasicLabelingComponent( this, imageLabelingModel, options ); + final ViewerPanel viewerPanel = mainPanel.getBdvHandle().getViewerPanel(); // Left side bar. final JPanel leftPanel = new JPanel(); @@ -180,6 +182,7 @@ public TMLabKitFrame( final TMLabKitModel model ) myActions, model, this, + viewerPanel, keybindings, keymapManager, appearanceManager ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java index 8c7a9de10..43ac768be 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/UndoRedoStack.java @@ -8,6 +8,7 @@ import net.imglib2.img.Img; import net.imglib2.type.numeric.integer.UnsignedIntType; import net.imglib2.util.ImgUtil; +import net.imglib2.util.Intervals; import net.imglib2.util.Util; import net.imglib2.view.Views; @@ -113,7 +114,9 @@ private void push( final UndoableCommand command ) * Undo the last operation. * * @return the region affected by the undo, or null if nothing - * was undone + * was undone. The last dimension of the returned interval is the + * time point of the labeling on which the undo operation was + * performed. */ public Interval undo() { @@ -124,14 +127,16 @@ public Interval undo() command.restoreBefore( getFrame( command.frame ) ); redoStack.addLast( command ); model.dataChangedNotifier().notifyListeners( null ); - return command.region; + return Intervals.addDimension( command.region, command.frame, command.frame ); } /** * Redo the last undone operation. * * @return the region affected by the redo, or null if nothing - * was redone + * was redone. The last dimension of the returned interval is the + * time point of the labeling on which the redo operation was + * performed. */ public Interval redo() { @@ -142,7 +147,7 @@ public Interval redo() command.restoreAfter( getFrame( command.frame ) ); undoStack.addLast( command ); model.dataChangedNotifier().notifyListeners( null ); - return command.region; + return Intervals.addDimension( command.region, command.frame, command.frame ); } /** From bdf07f60672f29dd0bc7a4726280776595bb92dc Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 27 Jul 2026 11:16:58 +0200 Subject: [PATCH 250/371] D'oh! --- .../trackmate/gui/editor/labkit/component/TMLabKitActions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java index 7a637c350..79cee3302 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java @@ -114,7 +114,7 @@ private static final void redo( final UndoRedoStack undoRedo, final MessageOverl private static final String undoRedoMsg( final Interval interval, final boolean hasTime ) { String out = ( hasTime ) - ? " at frame " + interval.dimension( interval.numDimensions() - 1 ) + " @ " + ? " at frame " + interval.min( interval.numDimensions() - 1 ) + " @ " : " @ "; out += "[" + interval.min( 0 ); for ( int i = 1; i < interval.numDimensions() - 1; i++ ) From b0be5c61eb933bfddb8b3bd27cd1a04ed802b36a Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Mon, 27 Jul 2026 13:30:42 +0200 Subject: [PATCH 251/371] Fix performance issue when removing with the brush. When there is many labels, the previous method was taking a lot of time to remove the list of labels under a pixel. Simply clearing the pixel is much much much faster. --- .../labkit/component/TMLabelBrushController.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java index ac39322f4..dcf012d61 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabelBrushController.java @@ -27,9 +27,7 @@ import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; -import java.util.List; import java.util.function.Consumer; -import java.util.stream.Collectors; import javax.swing.Timer; @@ -391,23 +389,13 @@ private Consumer< LabelingType< Label > > pixelOperation() if ( label != null ) // otherwise fall through return pixel -> pixel.remove( label ); case REMOVE_ALL: - final List< Label > visibleLabels = getVisibleLabels(); - return pixel -> pixel.removeAll( visibleLabels ); + return pixel -> pixel.clear(); default: throw new IllegalArgumentException( "Unknown erase brush mode: " + eraseBrushMode ); } } } - private List< Label > getVisibleLabels() - { - final List< Label > visibleLabels = - model.labeling().get().getLabels().stream() - .filter( Label::isVisible ) - .collect( Collectors.toList() ); - return visibleLabels; - } - private static final RandomAccessible< LabelingType< Label > > extendLabelingType( final RandomAccessibleInterval< LabelingType< Label > > slice ) { final LabelingType< Label > variable = slice.randomAccess().setPositionAndGet( Intervals.minAsLongArray( slice ) ).createVariable(); From a1dbc03653e18ba36a4cccbf7b1106a453ecca5e Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 28 Jul 2026 13:48:41 +0200 Subject: [PATCH 252/371] WIP: Testing undo / redo for the main Model. --- .../java/fiji/plugin/trackmate/Model.java | 36 +++- .../plugin/trackmate/undo/UndoRedoStack.java | 200 ++++++++++++++++++ 2 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java index 5eedf49d7..854bf6774 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 * . @@ -32,6 +32,7 @@ import org.jgrapht.graph.SimpleWeightedGraph; import fiji.plugin.trackmate.features.FeatureFilter; +import fiji.plugin.trackmate.undo.UndoRedoStack; /** * The model for the data managed by TrackMate. @@ -43,7 +44,7 @@ * built in coherent sets. *

    * - * @author Jean-Yves Tinevez <tinevez@pasteur.fr> - 2010-2013 + * @author Jean-Yves Tinevez - 2010-2026 */ public class Model { @@ -122,14 +123,17 @@ public class Model */ Set< ModelChangeListener > modelChangeListeners = new LinkedHashSet<>(); + private final UndoRedoStack undoRedoStack; + /* * CONSTRUCTOR */ public Model() { - featureModel = createFeatureModel(); - trackModel = createTrackModel(); + this.featureModel = createFeatureModel(); + this.trackModel = createTrackModel(); + this.undoRedoStack = new UndoRedoStack( this ); // TODO addModelChangeListener( new SpotMeshSliceCacheInvalidator() ); } @@ -831,7 +835,7 @@ private void flushUpdate() { System.out.println( "[TrackMateModel] #flushUpdate()." ); System.out.println( "[TrackMateModel] #flushUpdate(): Event cache is :" + eventCache ); - System.out.println( "[TrackMateModel] #flushUpdate(): Track content is:\n" + trackModel.echo() ); +// System.out.println( "[TrackMateModel] #flushUpdate(): Track content is:\n" + trackModel.echo() ); } /* @@ -976,4 +980,24 @@ public void modelChanged( final ModelChangeEvent event ) .forEach( s -> ( ( SpotMesh ) s ).resetZSliceCache() ); } } + + public void pauseUndo() + { + undoRedoStack.pauseUndo(); + } + + public void resumeUndo() + { + undoRedoStack.resumeUndo(); + } + + public void undo() + { + undoRedoStack.undo(); + } + + public void redo() + { + undoRedoStack.redo(); + } } diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java new file mode 100644 index 000000000..f6912b5d6 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -0,0 +1,200 @@ +package fiji.plugin.trackmate.undo; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +import org.jgrapht.graph.DefaultWeightedEdge; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.ModelChangeListener; +import fiji.plugin.trackmate.Spot; + +public class UndoRedoStack implements ModelChangeListener +{ + + private final Model model; + + private boolean paused = false; + + private final Deque< ModelUndoableCommand > undoStack = new ArrayDeque<>(); + + private final Deque< ModelUndoableCommand > redoStack = new ArrayDeque<>(); + + private final int maxSize; + + public UndoRedoStack( final Model model ) + { + this( model, 50 ); + } + + public UndoRedoStack( final Model model, final int maxSize ) + { + this.model = model; + this.maxSize = maxSize; + model.addModelChangeListener( this ); + } + + public void pauseUndo() + { + this.paused = true; + } + + public void resumeUndo() + { + this.paused = false; + } + + public void undo() + { + if ( undoStack.isEmpty() ) + return; + + final ModelUndoableCommand command = undoStack.removeLast(); + redoStack.addLast( command ); + command.restoreBefore( model ); + } + + public void redo() + { + if ( redoStack.isEmpty() ) + return; + + final ModelUndoableCommand command = redoStack.removeLast(); + undoStack.addLast( command ); + command.restoreAfter( model ); + } + + @Override + public void modelChanged( final ModelChangeEvent event ) + { + if ( paused ) + return; + + System.out.println(); // DEBUG + System.out.println( "UndoRedoStack: model changed" ); // DEBUG + if ( event.getEventID() == ModelChangeEvent.MODEL_MODIFIED ) + { + System.out.println( "Model modified" ); // DEBUG + System.out.println( event ); // DEBUG + final ModelUndoableCommand command = toCommand( event ); + + redoStack.clear(); + if ( undoStack.size() >= maxSize ) + undoStack.removeFirst(); + + undoStack.addLast( command ); + } + } + + private ModelUndoableCommand toCommand( final ModelChangeEvent event ) + { + final ModelUndoableCommand command = new ModelUndoableCommand(); + for ( final Spot spot : event.getSpots() ) + { + if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_ADDED ) + { + command.spotsAdded.add( spot ); + } + else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_REMOVED ) + { + command.spotsRemoved.add( spot ); + } + else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_MODIFIED ) + { + // TODO: Store feature values BEFORE + System.out.println( "Spot modified: " + spot ); // DEBUG + } + } + for ( final DefaultWeightedEdge edge : event.getEdges() ) + { + if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_ADDED ) + { + command.edgesAdded.add( new fiji.plugin.trackmate.undo.UndoRedoStack.ModelUndoableCommand.EdgeRep( + model.getTrackModel().getEdgeSource( edge ), + model.getTrackModel().getEdgeTarget( edge ), + model.getTrackModel().getEdgeWeight( edge ) ) ); + } + else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_REMOVED ) + { + command.edgesRemoved.add( new fiji.plugin.trackmate.undo.UndoRedoStack.ModelUndoableCommand.EdgeRep( + model.getTrackModel().getEdgeSource( edge ), + model.getTrackModel().getEdgeTarget( edge ), + model.getTrackModel().getEdgeWeight( edge ) ) ); + } + else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) + { + // TODO: Store feature values BEFORE + System.out.println( "Edge modified: " + edge ); // DEBUG + } + } + return command; + } + + private static class ModelUndoableCommand + { + + static record EdgeRep( Spot source, Spot target, double weight ) + {} + + public final List< EdgeRep > edgesRemoved = new ArrayList<>(); + + public final List< EdgeRep > edgesAdded = new ArrayList<>(); + + private final List< Spot > spotsAdded = new ArrayList<>(); + + private final List< Spot > spotsRemoved = new ArrayList<>(); + + public void restoreBefore( final Model model ) + { + model.pauseUndo(); + model.beginUpdate(); + try + { + for ( final EdgeRep edge : edgesAdded ) + model.removeEdge( edge.source, edge.target ); + + for ( final Spot spot : spotsAdded ) + model.removeSpot( spot ); + + for ( final Spot spot : spotsRemoved ) + model.addSpotTo( spot, spot.getFeature( Spot.FRAME ).intValue() ); + + for ( final EdgeRep edge : edgesRemoved ) + model.addEdge( edge.source, edge.target, edge.weight ); + } + finally + { + model.endUpdate(); + } + model.resumeUndo(); + } + + public void restoreAfter( final Model model ) + { + model.pauseUndo(); + model.beginUpdate(); + try + { + for ( final EdgeRep edge : edgesRemoved ) + model.removeEdge( edge.source, edge.target ); + + for ( final Spot spot : spotsRemoved ) + model.removeSpot( spot ); + + for ( final Spot spot : spotsAdded ) + model.addSpotTo( spot, spot.getFeature( Spot.FRAME ).intValue() ); + + for ( final EdgeRep edge : edgesAdded ) + model.addEdge( edge.source, edge.target, edge.weight ); + } + finally + { + model.endUpdate(); + } + model.resumeUndo(); + } + } +} From d09349aeba6bd1767da2c4a585b6de05368e0f80 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 28 Jul 2026 13:49:00 +0200 Subject: [PATCH 253/371] Don't record undo when loading a model. --- .../fiji/plugin/trackmate/features/FeatureUtils.java | 1 + .../java/fiji/plugin/trackmate/io/TmXmlReader.java | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java b/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java index 3622346e8..70972ef24 100644 --- a/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java +++ b/src/main/java/fiji/plugin/trackmate/features/FeatureUtils.java @@ -387,6 +387,7 @@ public static final FeatureColorGenerator< Integer > createWholeTrackColorGenera public static final Model DUMMY_MODEL = new Model(); static { + DUMMY_MODEL.pauseUndo(); final Random ran = new Random(); DUMMY_MODEL.beginUpdate(); try diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index 1582e531f..a2aa7b087 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 * . @@ -197,7 +197,7 @@ public class TmXmlReader /** * Initializes this reader to read the file given in argument. - * + * * @param file * the file to read. */ @@ -233,7 +233,7 @@ 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() @@ -370,6 +370,7 @@ public Model getModel() return null; final Model model = createModel(); + model.pauseUndo(); // TODO // Physical units final String spaceUnits = modelElement.getAttributeValue( SPATIAL_UNITS_ATTRIBUTE_NAME ); @@ -407,6 +408,7 @@ public Model getModel() } // That's it + model.resumeUndo(); return model; } @@ -540,7 +542,7 @@ public Settings readSettings( /** * Returns the version string stored in the file. - * + * * @return the version string stored in the file. */ public String getVersion() From 64c1eb1540952bbd0e0241a805aaa10fcf1a0c98 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 28 Jul 2026 13:49:33 +0200 Subject: [PATCH 254/371] Plug undo / redo into the ImageJ view. --- .../hyperstack/ModelEditActions.java | 10 +++++++ .../hyperstack/SpotEditTool.java | 27 ++++++++++++++----- 2 files changed, 31 insertions(+), 6 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 45cb65ebb..5a779cf59 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java @@ -546,4 +546,14 @@ public void navigateToPreviousTrack() { trackNavigator.previousTrack(); } + + public void undo() + { + model.undo(); + } + + public void redo() + { + model.redo(); + } } 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 4ee2de83d..253c32df3 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 * . @@ -118,7 +118,7 @@ public void imageClosed( final ImagePlus imp ) /** * Returns the singleton instance for this tool. If it was not previously * instantiated, this calls instantiates it. - * + * * @return the instance. */ public static SpotEditTool getInstance() @@ -132,7 +132,7 @@ public static SpotEditTool getInstance() /** * Returns true if the tool is currently present in ImageJ * toolbar. - * + * * @return true if the tool is currently present in ImageJ * toolbar. */ @@ -195,7 +195,7 @@ protected void registerTool( final ImageCanvas canvas ) /** * Registers the given {@link HyperStackDisplayer}. If this method is not * called, the tool will not respond. - * + * * @param displayer * the displayer to register. */ @@ -289,6 +289,21 @@ public void keyPressed( final KeyEvent e ) switch ( e.getKeyCode() ) { + // Undo / redo + case KeyEvent.VK_Z: + { + + if ( e.isControlDown() || e.isMetaDown() ) + { + if ( e.isShiftDown() ) + actions.redo(); + else + actions.undo(); + e.consume(); + } + break; + } + // Track navigation actions. case KeyEvent.VK_UP: { @@ -326,7 +341,7 @@ public void keyPressed( final KeyEvent e ) e.consume(); break; } - + // Delete currently edited spot case KeyEvent.VK_DELETE: { From 1ad5272a67fb73076a4d32947a043bb4279f42e9 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 28 Jul 2026 14:37:04 +0200 Subject: [PATCH 255/371] Allow modifying the polygon of a SpotRoi. For undo purposes. --- .../java/fiji/plugin/trackmate/SpotRoi.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index 19d1796d2..cc59260b7 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -139,6 +139,21 @@ public double xr( final int i ) return x[ i ]; } + /** + * Sets the X coordinates of the ith vertex of the polygon, relative to + * the spot center, in physical coordinates. This method is meant to be + * used for undo / redo operations, and should not be used otherwise. + * + * @param i + * the index of the vertex. + * @param x + * the vertex X position. + */ + public void setXr( final int i, final double x ) + { + this.x[ i ] = x; + } + /** * Returns the Y coordinates of the ith vertex of the polygon, relative * to the spot center, in physical coordinates. @@ -152,6 +167,21 @@ public double yr( final int i ) return y[ i ]; } + /** + * Sets the Y coordinates of the ith vertex of the polygon, relative to + * the spot center, in physical coordinates. This method is meant to be + * used for undo / redo operations, and should not be used otherwise. + * + * @param i + * the index of the vertex. + * @param y + * the vertex Y position. + */ + public void setYr( final int i, final double y ) + { + this.y[ i ] = y; + } + public int nPoints() { return x.length; From 60044a4c2707236091863809d7e9e80e91769557 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 28 Jul 2026 14:38:00 +0200 Subject: [PATCH 256/371] Undo / redo can restore spot position and radius. --- .../java/fiji/plugin/trackmate/Model.java | 31 +++++ .../plugin/trackmate/undo/UndoRedoStack.java | 112 ++++++++++++++++-- .../hyperstack/ModelEditActions.java | 4 + 3 files changed, 138 insertions(+), 9 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java index 854bf6774..263e907f6 100644 --- a/src/main/java/fiji/plugin/trackmate/Model.java +++ b/src/main/java/fiji/plugin/trackmate/Model.java @@ -981,23 +981,54 @@ public void modelChanged( final ModelChangeEvent event ) } } + /** + * Pauses the undo recording. + *

    + * This is useful when a process is going to make a lot of changes to the + * model that we don't want to be recorded in the undo stack. + */ public void pauseUndo() { undoRedoStack.pauseUndo(); } + /** + * Resumes the undo recording. + */ public void resumeUndo() { undoRedoStack.resumeUndo(); } + /** + * Undo the last action. + */ public void undo() { undoRedoStack.undo(); } + /** + * Redo the last undone action. + */ public void redo() { undoRedoStack.redo(); } + + /** + * Flags the specified spot for undo. + *

    + * This method must be called only when the spot is going to be modified + * (change position, radius, features, etc.) and before the + * modification is done. This will store the current state of the spot in + * the undo stack, so that it can be restored later if the user calls undo. + * + * @param spot + * the spot to flag for undo. + */ + public void flagForUndo( final Spot spot ) + { + undoRedoStack.flagForUndo( spot ); + } } diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index f6912b5d6..5fc4e0823 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -3,7 +3,9 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.jgrapht.graph.DefaultWeightedEdge; @@ -11,6 +13,9 @@ import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.ModelChangeListener; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.Spot.SpotVisitor; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.SpotRoi; public class UndoRedoStack implements ModelChangeListener { @@ -23,8 +28,13 @@ public class UndoRedoStack implements ModelChangeListener private final Deque< ModelUndoableCommand > redoStack = new ArrayDeque<>(); + private final Map< Spot, Map< String, Double > > spotFeatureValuesBefore = new HashMap<>(); + + private final Map< SpotRoi, double[][] > spotPolygonValuesBefore = new HashMap<>(); + private final int maxSize; + public UndoRedoStack( final Model model ) { this( model, 50 ); @@ -73,14 +83,9 @@ public void modelChanged( final ModelChangeEvent event ) if ( paused ) return; - System.out.println(); // DEBUG - System.out.println( "UndoRedoStack: model changed" ); // DEBUG if ( event.getEventID() == ModelChangeEvent.MODEL_MODIFIED ) { - System.out.println( "Model modified" ); // DEBUG - System.out.println( event ); // DEBUG final ModelUndoableCommand command = toCommand( event ); - redoStack.clear(); if ( undoStack.size() >= maxSize ) undoStack.removeFirst(); @@ -105,7 +110,15 @@ else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_REMOVED ) else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_MODIFIED ) { // TODO: Store feature values BEFORE - System.out.println( "Spot modified: " + spot ); // DEBUG + final Map< String, Double > previousFeatureValues = spotFeatureValuesBefore.get( spot ); + command.spotFeatureValuesBefore.put( spot, previousFeatureValues ); + command.spotFeatureValuesAfter.put( spot, new HashMap<>( spot.getFeatures() ) ); + if ( spot instanceof SpotRoi ) + { + final SpotRoi spotRoi = ( SpotRoi ) spot; + command.spotPolygonValuesBefore.put( spotRoi, spotPolygonValuesBefore.get( spotRoi ) ); + command.spotPolygonValuesAfter.put( spotRoi, toPolygon( spotRoi ) ); + } } } for ( final DefaultWeightedEdge edge : event.getEdges() ) @@ -130,23 +143,33 @@ else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) System.out.println( "Edge modified: " + edge ); // DEBUG } } + spotFeatureValuesBefore.clear(); return command; } private static class ModelUndoableCommand { - static record EdgeRep( Spot source, Spot target, double weight ) + + private static record EdgeRep( Spot source, Spot target, double weight ) {} - public final List< EdgeRep > edgesRemoved = new ArrayList<>(); + private final List< EdgeRep > edgesRemoved = new ArrayList<>(); - public final List< EdgeRep > edgesAdded = new ArrayList<>(); + private final List< EdgeRep > edgesAdded = new ArrayList<>(); private final List< Spot > spotsAdded = new ArrayList<>(); private final List< Spot > spotsRemoved = new ArrayList<>(); + private final Map< Spot, Map< String, Double > > spotFeatureValuesBefore = new HashMap<>(); + + private final Map< Spot, Map< String, Double > > spotFeatureValuesAfter = new HashMap<>(); + + public Map< SpotRoi, double[][] > spotPolygonValuesBefore = new HashMap<>(); + + public Map< SpotRoi, double[][] > spotPolygonValuesAfter = new HashMap<>(); + public void restoreBefore( final Model model ) { model.pauseUndo(); @@ -164,6 +187,18 @@ public void restoreBefore( final Model model ) for ( final EdgeRep edge : edgesRemoved ) model.addEdge( edge.source, edge.target, edge.weight ); + + for ( final Spot spot : spotFeatureValuesBefore.keySet() ) + { + spotFeatureValuesBefore.get( spot ).forEach( ( key, value ) -> spot.putFeature( key, value ) ); + if ( spot instanceof SpotRoi ) + { + final SpotRoi spotRoi = ( SpotRoi ) spot; + final double[][] polygonBefore = spotPolygonValuesBefore.get( spotRoi ); + updatePolygon( spotRoi, polygonBefore ); + } + model.updateFeatures( spot ); + } } finally { @@ -189,6 +224,18 @@ public void restoreAfter( final Model model ) for ( final EdgeRep edge : edgesAdded ) model.addEdge( edge.source, edge.target, edge.weight ); + + for ( final Spot spot : spotFeatureValuesAfter.keySet() ) + { + spotFeatureValuesAfter.get( spot ).forEach( ( key, value ) -> spot.putFeature( key, value ) ); + if ( spot instanceof SpotRoi ) + { + final SpotRoi spotRoi = ( SpotRoi ) spot; + final double[][] polygonAfter = spotPolygonValuesAfter.get( spotRoi ); + updatePolygon( spotRoi, polygonAfter ); + } + model.updateFeatures( spot ); + } } finally { @@ -197,4 +244,51 @@ public void restoreAfter( final Model model ) model.resumeUndo(); } } + + private class UndoStorer implements SpotVisitor + { + + @Override + public void visit( final SpotBase spot ) + { + spotFeatureValuesBefore.put( spot, new HashMap<>( spot.getFeatures() ) ); + } + + @Override + public void visit( final SpotRoi spot ) + { + visit( ( SpotBase ) spot ); + spotPolygonValuesBefore.put( spot, toPolygon( spot ) ); + } + } + + private final UndoStorer undoStorer = new UndoStorer(); + + private static final double[][] toPolygon( final SpotRoi spot ) + { + final int nPoints = spot.nPoints(); + final double[] x = new double[ nPoints ]; + final double[] y = new double[ nPoints ]; + for ( int i = 0; i < nPoints; i++ ) + { + x[ i ] = spot.xr( i ); + y[ i ] = spot.yr( i ); + } + return new double[][] { x, y }; + } + + private static final void updatePolygon( final SpotRoi spot, final double[][] polygon ) + { + final int nPoints = spot.nPoints(); + for ( int i = 0; i < nPoints; i++ ) + { + spot.setXr( i, polygon[ 0 ][ i ] ); + spot.setYr( i, polygon[ 1 ][ i ] ); + } + } + + public void flagForUndo( final Spot spot ) + { + spot.accept( undoStorer ); + } } 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 5a779cf59..1c678fb26 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java @@ -236,7 +236,10 @@ public void deleteSpot() public void startMoveSpot() { if ( null == quickEditedSpot ) + { quickEditedSpot = getSpotAtMouseLocation(); + model.flagForUndo( quickEditedSpot ); + } } public void moveSpot( final Point mouseLocation ) @@ -278,6 +281,7 @@ public void changeSpotRadius( final boolean increase, final boolean fast ) if ( null == target ) return; + model.flagForUndo( target ); final double radius = target.getFeature( Spot.RADIUS ); final int factor = ( increase ) ? -1 : 1; final double dx = imp.getCalibration().pixelWidth; From faa068a8f6af7263e4557a574a3a807e1514d8ae Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 28 Jul 2026 14:51:19 +0200 Subject: [PATCH 257/371] Undo / redo also restores feature of edges that were modified ... when a spot was modified. --- .../plugin/trackmate/undo/UndoRedoStack.java | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index 5fc4e0823..d4bc122aa 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -2,13 +2,16 @@ import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collection; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.jgrapht.graph.DefaultWeightedEdge; +import fiji.plugin.trackmate.FeatureModel; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.ModelChangeListener; @@ -16,6 +19,7 @@ import fiji.plugin.trackmate.Spot.SpotVisitor; import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.SpotRoi; +import fiji.plugin.trackmate.TrackModel; public class UndoRedoStack implements ModelChangeListener { @@ -32,8 +36,9 @@ public class UndoRedoStack implements ModelChangeListener private final Map< SpotRoi, double[][] > spotPolygonValuesBefore = new HashMap<>(); - private final int maxSize; + private final Map< DefaultWeightedEdge, Map< String, Double > > edgeFeatureValuesBefore = new HashMap<>(); + private final int maxSize; public UndoRedoStack( final Model model ) { @@ -139,8 +144,8 @@ else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_REMOVED ) } else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) { - // TODO: Store feature values BEFORE - System.out.println( "Edge modified: " + edge ); // DEBUG + command.edgeFeatureValuesBefore.put( edge, edgeFeatureValuesBefore.get( edge ) ); + command.edgeFeatureValuesAfter.put( edge, copyEdgeFeatures( edge ) ); } } spotFeatureValuesBefore.clear(); @@ -150,7 +155,6 @@ else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) private static class ModelUndoableCommand { - private static record EdgeRep( Spot source, Spot target, double weight ) {} @@ -166,9 +170,13 @@ private static record EdgeRep( Spot source, Spot target, double weight ) private final Map< Spot, Map< String, Double > > spotFeatureValuesAfter = new HashMap<>(); - public Map< SpotRoi, double[][] > spotPolygonValuesBefore = new HashMap<>(); + private final Map< SpotRoi, double[][] > spotPolygonValuesBefore = new HashMap<>(); + + private final Map< SpotRoi, double[][] > spotPolygonValuesAfter = new HashMap<>(); + + private final Map< DefaultWeightedEdge, Map< String, Double > > edgeFeatureValuesBefore = new HashMap<>(); - public Map< SpotRoi, double[][] > spotPolygonValuesAfter = new HashMap<>(); + private final Map< DefaultWeightedEdge, Map< String, Double > > edgeFeatureValuesAfter = new HashMap<>(); public void restoreBefore( final Model model ) { @@ -199,6 +207,9 @@ public void restoreBefore( final Model model ) } model.updateFeatures( spot ); } + + for ( final DefaultWeightedEdge edge : edgeFeatureValuesBefore.keySet() ) + edgeFeatureValuesBefore.get( edge ).forEach( ( key, value ) -> model.getFeatureModel().putEdgeFeature( edge, key, value ) ); } finally { @@ -236,6 +247,9 @@ public void restoreAfter( final Model model ) } model.updateFeatures( spot ); } + + for ( final DefaultWeightedEdge edge : edgeFeatureValuesAfter.keySet() ) + edgeFeatureValuesAfter.get( edge ).forEach( ( key, value ) -> model.getFeatureModel().putEdgeFeature( edge, key, value ) ); } finally { @@ -251,7 +265,13 @@ private class UndoStorer implements SpotVisitor @Override public void visit( final SpotBase spot ) { + // Spot features. spotFeatureValuesBefore.put( spot, new HashMap<>( spot.getFeatures() ) ); + // Touching edge features. + final TrackModel trackModel = model.getTrackModel(); + final Set< DefaultWeightedEdge > touchingEdges = trackModel.edgesOf( spot ); + for ( final DefaultWeightedEdge edge : touchingEdges ) + edgeFeatureValuesBefore.put( edge, copyEdgeFeatures( edge ) ); } @Override @@ -262,8 +282,26 @@ public void visit( final SpotRoi spot ) } } + private final Map< String, Double > copyEdgeFeatures( final DefaultWeightedEdge edge ) + { + final FeatureModel featureModel = model.getFeatureModel(); + final Collection< String > edgeFeatures = featureModel.getEdgeFeatures(); + final Map< String, Double > featureValues = new HashMap<>(); + for ( final String feature : edgeFeatures ) + { + final Double value = featureModel.getEdgeFeature( edge, feature ); + featureValues.put( feature, value ); + } + return featureValues; + } + private final UndoStorer undoStorer = new UndoStorer(); + public void flagForUndo( final Spot spot ) + { + spot.accept( undoStorer ); + } + private static final double[][] toPolygon( final SpotRoi spot ) { final int nPoints = spot.nPoints(); @@ -287,8 +325,4 @@ private static final void updatePolygon( final SpotRoi spot, final double[][] po } } - public void flagForUndo( final Spot spot ) - { - spot.accept( undoStorer ); - } } From a1138c2df2edce5af29d695274a8afa7c35e8d72 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Tue, 28 Jul 2026 17:21:46 +0200 Subject: [PATCH 258/371] Implement undo / redo in the TrackScheme JFrame. --- .../trackscheme/TrackSchemeActions.java | 17 ++++++++++++-- .../trackscheme/TrackSchemeFrame.java | 6 ++--- .../TrackSchemeKeyboardHandler.java | 22 ++++++++++++++----- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java index 0a3fb5bd0..436b29d9e 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.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 * . @@ -47,6 +47,8 @@ import javax.swing.Action; import javax.swing.Icon; +import org.scijava.ui.behaviour.util.RunnableAction; + import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; import com.mxgraph.swing.util.mxGraphActions; @@ -55,6 +57,7 @@ import com.mxgraph.util.mxEventSource.mxIEventListener; import com.mxgraph.view.mxGraph; +import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; public class TrackSchemeActions @@ -84,6 +87,16 @@ public class TrackSchemeActions private TrackSchemeActions() {} + public static Action getUndoAction( final Model model ) + { + return new RunnableAction( "undo", () -> model.undo() ); + } + + public static Action getRedoAction( final Model model ) + { + return new RunnableAction( "redo", () -> model.redo() ); + } + public static Action getEditAction( final TrackSchemeGraphComponent graphComponent ) { return new EditAction( "edit", EDIT_ICON, graphComponent ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java index 96843636c..8e2e2d350 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.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 * . @@ -114,7 +114,7 @@ public void init( final JGraphXAdapter lGraph ) splitPane.setDividerLocation( 170 ); getContentPane().add( splitPane, BorderLayout.CENTER ); - final TrackSchemeKeyboardHandler keyboardHandler = new TrackSchemeKeyboardHandler( graphComponent, new TrackNavigator( trackScheme.getModel(), trackScheme.getSelectionModel() ) ); + final TrackSchemeKeyboardHandler keyboardHandler = new TrackSchemeKeyboardHandler( trackScheme.getModel(), graphComponent, new TrackNavigator( trackScheme.getModel(), trackScheme.getSelectionModel() ) ); keyboardHandler.installKeyboardActions( graphComponent ); keyboardHandler.installKeyboardActions( infoPane ); } 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 7d11d151f..a4758ac20 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.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 * . @@ -34,6 +34,7 @@ import com.mxgraph.swing.util.mxGraphActions; +import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.util.TrackNavigator; public class TrackSchemeKeyboardHandler @@ -43,8 +44,11 @@ public class TrackSchemeKeyboardHandler private final TrackSchemeGraphComponent graphComponent; - public TrackSchemeKeyboardHandler( final TrackSchemeGraphComponent graphComponent, final TrackNavigator navigator ) + private final Model model; + + public TrackSchemeKeyboardHandler( final Model model, final TrackSchemeGraphComponent graphComponent, final TrackNavigator navigator ) { + this.model = model; this.graphComponent = graphComponent; this.navigator = navigator; } @@ -63,7 +67,6 @@ protected InputMap getInputMap( final int condition ) map = ( InputMap ) UIManager.get( "ScrollPane.ancestorInputMap" ); else map = new InputMap(); - map.put( KeyStroke.getKeyStroke( "F2" ), "edit" ); map.put( KeyStroke.getKeyStroke( "DELETE" ), "delete" ); @@ -95,12 +98,17 @@ protected InputMap getInputMap( final int condition ) map.put( KeyStroke.getKeyStroke( "PAGE_DOWN" ), "selectNextTrack" ); map.put( KeyStroke.getKeyStroke( "PAGE_UP" ), "selectPreviousTrack" ); + map.put( KeyStroke.getKeyStroke( "control Z" ), "undo" ); + map.put( KeyStroke.getKeyStroke( "meta Z" ), "undo" ); + map.put( KeyStroke.getKeyStroke( "control shift Z" ), "redo" ); + map.put( KeyStroke.getKeyStroke( "meta shift Z" ), "redo" ); + return map; } /** * Returns the mapping between JTree's input map and JGraph's actions. - * + * * @return the action map. */ protected ActionMap createActionMap() @@ -190,6 +198,10 @@ public void actionPerformed( final ActionEvent arg0 ) } } ); + map.put( "undo", TrackSchemeActions.getUndoAction( model ) ); + map.put( "redo", TrackSchemeActions.getRedoAction( model ) ); + return map; } + } From fe6243021871b8f396584e98cff1cf68bed68748 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Wed, 29 Jul 2026 18:01:14 +0200 Subject: [PATCH 259/371] Undo / redo spot name changes. --- .../action/autonaming/AutoNamingAction.java | 6 +-- .../autonaming/AutoNamingPerformer.java | 41 ++++++++++++++----- .../plugin/trackmate/undo/UndoRedoStack.java | 33 ++++++++++++--- .../table/AllSpotsTableView.java | 18 ++++++-- .../visualization/table/TrackTableView.java | 18 ++++++-- .../trackscheme/JGraphXAdapter.java | 6 ++- .../trackscheme/TrackSchemeActions.java | 30 ++++++++++---- .../TrackSchemeKeyboardHandler.java | 2 +- .../trackscheme/TrackSchemePopupMenu.java | 27 ++++++++---- 9 files changed, 138 insertions(+), 43 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingAction.java b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingAction.java index 5ca105d2b..64621a1b4 100644 --- a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingAction.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 * . @@ -38,7 +38,7 @@ public class AutoNamingAction extends AbstractTMAction public static final String INFO_TEXT = "" + "Rename individual spots based on auto-naming rules. " - + "All spot names are changed. There is no undo."; + + "All spot names are changed. Can be undone."; @Override public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final java.awt.Frame parent ) diff --git a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingPerformer.java b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingPerformer.java index a9e6537a9..b6f6971f4 100644 --- a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingPerformer.java +++ b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingPerformer.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 * . @@ -44,20 +44,29 @@ public class AutoNamingPerformer public static void autoNameSpots( final Model model, final AutoNamingRule rule ) { - final TimeDirectedNeighborIndex neighborIndex = model.getTrackModel().getDirectedNeighborIndex(); - for ( final Integer trackID : model.getTrackModel().unsortedTrackIDs( true ) ) + model.beginUpdate(); + try { - final TrackBranchDecomposition branchDecomposition = ConvexBranchesDecomposition.processTrack( trackID, model.getTrackModel(), neighborIndex, true, false ); - final SimpleDirectedGraph< List< Spot >, DefaultEdge > branchGraph = ConvexBranchesDecomposition.buildBranchGraph( branchDecomposition ); - processTrack( rule, model.getTrackModel(), branchGraph ); + final TimeDirectedNeighborIndex neighborIndex = model.getTrackModel().getDirectedNeighborIndex(); + for ( final Integer trackID : model.getTrackModel().unsortedTrackIDs( true ) ) + { + final TrackBranchDecomposition branchDecomposition = ConvexBranchesDecomposition.processTrack( trackID, model.getTrackModel(), neighborIndex, true, false ); + final SimpleDirectedGraph< List< Spot >, DefaultEdge > branchGraph = ConvexBranchesDecomposition.buildBranchGraph( branchDecomposition ); + processTrack( rule, model, branchGraph ); + } + } + finally + { + model.endUpdate(); } } private static void processTrack( - final AutoNamingRule rule, - final TrackModel model, + final AutoNamingRule rule, + final Model model, final SimpleDirectedGraph< List< Spot >, DefaultEdge > graph ) { + final TrackModel trackModel = model.getTrackModel(); // Find the roots. Might be several. final List< List< Spot > > roots = graph.vertexSet().stream() .filter( key -> graph.incomingEdgesOf( key ).size() == 0 ) @@ -67,14 +76,18 @@ private static void processTrack( { // Name the spots in the root branch. final Spot first = root.get( 0 ); - rule.nameRoot( first, model ); + model.flagForUndo( first ); // undo name changes. + rule.nameRoot( first, trackModel ); + model.updateFeatures( first ); // Other spots in the root branch. Spot predecessor = first; for ( int i = 1; i < root.size(); i++ ) { final Spot current = root.get( i ); + model.flagForUndo( current ); // undo name changes. rule.nameSpot( current, predecessor ); + model.updateFeatures( current ); predecessor = current; } @@ -102,8 +115,12 @@ private static void processTrack( final Spot mother = currentBranch.get( currentBranch.size() - 1 ); // Name the branch first spots. + for ( final Spot sibling : siblings ) + model.flagForUndo( sibling ); // undo name changes. rule.nameBranches( mother, siblings ); - + for ( final Spot sibling : siblings ) + model.updateFeatures( sibling ); + // Name the spots inside each branch. for ( final List< Spot > cb : childrenBranches ) { @@ -111,7 +128,9 @@ private static void processTrack( for ( int i = 1; i < cb.size(); i++ ) { final Spot current = cb.get( i ); + model.flagForUndo( current ); // undo name changes. rule.nameSpot( current, parent ); + model.updateFeatures( current ); parent = current; } } diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index d4bc122aa..4df71f340 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -38,6 +38,8 @@ public class UndoRedoStack implements ModelChangeListener private final Map< DefaultWeightedEdge, Map< String, Double > > edgeFeatureValuesBefore = new HashMap<>(); + private final Map< Spot, String > spotNameBefore = new HashMap<>(); + private final int maxSize; public UndoRedoStack( final Model model ) @@ -114,10 +116,12 @@ else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_REMOVED ) } else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_MODIFIED ) { - // TODO: Store feature values BEFORE final Map< String, Double > previousFeatureValues = spotFeatureValuesBefore.get( spot ); command.spotFeatureValuesBefore.put( spot, previousFeatureValues ); command.spotFeatureValuesAfter.put( spot, new HashMap<>( spot.getFeatures() ) ); + final String previousName = spotNameBefore.get( spot ); + command.spotNameBefore.put( spot, previousName ); + command.spotNameAfter.put( spot, spot.getName() ); if ( spot instanceof SpotRoi ) { final SpotRoi spotRoi = ( SpotRoi ) spot; @@ -149,6 +153,9 @@ else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) } } spotFeatureValuesBefore.clear(); + edgeFeatureValuesBefore.clear(); + spotPolygonValuesBefore.clear(); + spotNameBefore.clear(); return command; } @@ -178,6 +185,10 @@ private static record EdgeRep( Spot source, Spot target, double weight ) private final Map< DefaultWeightedEdge, Map< String, Double > > edgeFeatureValuesAfter = new HashMap<>(); + private final Map< Spot, String > spotNameAfter = new HashMap<>(); + + private final Map< Spot, String > spotNameBefore = new HashMap<>(); + public void restoreBefore( final Model model ) { model.pauseUndo(); @@ -198,6 +209,7 @@ public void restoreBefore( final Model model ) for ( final Spot spot : spotFeatureValuesBefore.keySet() ) { + spot.setName( spotNameBefore.get( spot ) ); spotFeatureValuesBefore.get( spot ).forEach( ( key, value ) -> spot.putFeature( key, value ) ); if ( spot instanceof SpotRoi ) { @@ -207,9 +219,13 @@ public void restoreBefore( final Model model ) } model.updateFeatures( spot ); } - for ( final DefaultWeightedEdge edge : edgeFeatureValuesBefore.keySet() ) - edgeFeatureValuesBefore.get( edge ).forEach( ( key, value ) -> model.getFeatureModel().putEdgeFeature( edge, key, value ) ); + edgeFeatureValuesBefore.get( edge ).forEach( ( key, value ) -> { + if ( value == null ) + model.getFeatureModel().removeEdgeFeature( edge, key ); + else + model.getFeatureModel().putEdgeFeature( edge, key, value ); + } ); } finally { @@ -238,6 +254,7 @@ public void restoreAfter( final Model model ) for ( final Spot spot : spotFeatureValuesAfter.keySet() ) { + spot.setName( spotNameAfter.get( spot ) ); spotFeatureValuesAfter.get( spot ).forEach( ( key, value ) -> spot.putFeature( key, value ) ); if ( spot instanceof SpotRoi ) { @@ -247,9 +264,13 @@ public void restoreAfter( final Model model ) } model.updateFeatures( spot ); } - for ( final DefaultWeightedEdge edge : edgeFeatureValuesAfter.keySet() ) - edgeFeatureValuesAfter.get( edge ).forEach( ( key, value ) -> model.getFeatureModel().putEdgeFeature( edge, key, value ) ); + edgeFeatureValuesAfter.get( edge ).forEach( ( key, value ) -> { + if ( value == null ) + model.getFeatureModel().removeEdgeFeature( edge, key ); + else + model.getFeatureModel().putEdgeFeature( edge, key, value ); + } ); } finally { @@ -272,6 +293,8 @@ public void visit( final SpotBase spot ) final Set< DefaultWeightedEdge > touchingEdges = trackModel.edgesOf( spot ); for ( final DefaultWeightedEdge edge : touchingEdges ) edgeFeatureValuesBefore.put( edge, copyEdgeFeatures( edge ) ); + // Spot name. + spotNameBefore.put( spot, spot.getName() ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java index 1969372c8..aa56efc2b 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.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 * . @@ -197,7 +197,19 @@ public static final TablePanel< Spot > createSpotTable( final Model model, final final Map< String, Boolean > isInts = model.getFeatureModel().getSpotFeatureIsInt(); final Map< String, String > infoTexts = new HashMap<>(); final Function< Spot, String > labelGenerator = spot -> spot.getName(); - final BiConsumer< Spot, String > labelSetter = ( spot, label ) -> spot.setName( label ); + final BiConsumer< Spot, String > labelSetter = ( spot, label ) -> { + model.beginUpdate(); + try + { + model.flagForUndo( spot ); // to make name change undoable + spot.setName( label ); + model.updateFeatures( spot ); + } + finally + { + model.endUpdate(); + } + }; /* * Feature provider. We add a fake one to show the spot ID. diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java index 9f2db4faa..8fbdd71b7 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.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 * . @@ -347,7 +347,19 @@ public static final TablePanel< Spot > createSpotTable( final Model model, final final Map< String, Boolean > isInts = model.getFeatureModel().getSpotFeatureIsInt(); final Map< String, String > infoTexts = new HashMap<>(); final Function< Spot, String > labelGenerator = spot -> spot.getName(); - final BiConsumer< Spot, String > labelSetter = ( spot, label ) -> spot.setName( label ); + final BiConsumer< Spot, String > labelSetter = ( spot, label ) -> { + model.beginUpdate(); + try + { + model.flagForUndo( spot ); // to make name change undoable + spot.setName( label ); + model.updateFeatures( spot ); + } + finally + { + model.endUpdate(); + } + }; /* * Feature provider. We add a fake one to show the spot ID. diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/JGraphXAdapter.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/JGraphXAdapter.java index 60636e3c3..5c3127165 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/JGraphXAdapter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/JGraphXAdapter.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 * . @@ -79,7 +79,9 @@ public void cellLabelChanged( final Object cell, final Object value, final boole if ( null == spot ) return; final String str = ( String ) value; + tmm.flagForUndo( spot ); // to make name change undoable spot.setName( str ); + tmm.updateFeatures( spot ); getModel().setValue( cell, str ); if ( autoSize ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java index 436b29d9e..0cfadc5fb 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java @@ -97,9 +97,9 @@ public static Action getRedoAction( final Model model ) return new RunnableAction( "redo", () -> model.redo() ); } - public static Action getEditAction( final TrackSchemeGraphComponent graphComponent ) + public static Action getEditAction( final Model model, final TrackSchemeGraphComponent graphComponent ) { - return new EditAction( "edit", EDIT_ICON, graphComponent ); + return new EditAction( "edit", EDIT_ICON, model, graphComponent ); } public static Action getHomeAction( final TrackSchemeGraphComponent graphComponent ) @@ -388,9 +388,12 @@ public static class EditAction extends AbstractAction private final TrackSchemeGraphComponent graphComponent; - public EditAction( final String name, final Icon icon, final TrackSchemeGraphComponent graphComponent ) + private final Model model; + + public EditAction( final String name, final Icon icon, final Model model, final TrackSchemeGraphComponent graphComponent ) { super( name, icon ); + this.model = model; this.graphComponent = graphComponent; } @@ -430,13 +433,24 @@ private void multiEditSpotName( final TrackSchemeGraphComponent lGraphComponent, @Override public void invoke( final Object sender, final mxEventObject evt ) { - for ( final mxCell cell : vertices ) + model.beginUpdate(); + try + { + for ( final mxCell cell : vertices ) + { + cell.setValue( tc.getValue() ); + final Spot spot = graph.getSpotFor( cell ); + model.flagForUndo( spot ); // name change undoable + spot.setName( tc.getValue().toString() ); + model.updateFeatures( spot ); + } + lGraphComponent.refresh(); + lGraphComponent.removeListener( this ); + } + finally { - cell.setValue( tc.getValue() ); - graph.getSpotFor( cell ).setName( tc.getValue().toString() ); + model.endUpdate(); } - lGraphComponent.refresh(); - lGraphComponent.removeListener( this ); } } ); } 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 a4758ac20..d49e3b08b 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java @@ -115,7 +115,7 @@ protected ActionMap createActionMap() { final ActionMap map = ( ActionMap ) UIManager.get( "ScrollPane.actionMap" ); - map.put( "edit", TrackSchemeActions.getEditAction( graphComponent ) ); + map.put( "edit", TrackSchemeActions.getEditAction( model, graphComponent ) ); map.put( "delete", mxGraphActions.getDeleteAction() ); map.put( "home", TrackSchemeActions.getHomeAction( graphComponent ) ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java index 23fcc8c74..28dac71dc 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.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 * . @@ -41,6 +41,7 @@ import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxEventSource.mxIEventListener; +import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.features.manual.ManualEdgeColorAnalyzer; import fiji.plugin.trackmate.features.manual.ManualSpotColorAnalyzerFactory; @@ -151,13 +152,25 @@ private void multiEditSpotName( final ArrayList< mxCell > vertices, final EventO @Override public void invoke( final Object sender, final mxEventObject evt ) { - for ( final mxCell lCell : vertices ) + final Model model = trackScheme.getModel(); + model.beginUpdate(); + try { - lCell.setValue( tc.getValue() ); - trackScheme.getGraph().getSpotFor( lCell ).setName( tc.getValue().toString() ); + for ( final mxCell lCell : vertices ) + { + lCell.setValue( tc.getValue() ); + final Spot spot = trackScheme.getGraph().getSpotFor( lCell ); + model.flagForUndo( spot ); // name change undoable + spot.setName( tc.getValue().toString() ); + model.updateFeatures( spot ); + } + graphComponent.refresh(); + graphComponent.removeListener( this ); + } + finally + { + model.endUpdate(); } - graphComponent.refresh(); - graphComponent.removeListener( this ); } } ); } From 57fc946ed0949be10f50fea564085d91217118fc Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 30 Jul 2026 16:19:57 +0200 Subject: [PATCH 260/371] Simplify flagging a spot for update. Now we require users to just call model.beforeEdit(Spot) before they modify a spot. No need for a second call to model.updateFeature(Spot) after the edit. Also renamed the method for clarity. --- .../java/fiji/plugin/trackmate/Model.java | 62 +++++++------------ .../autonaming/AutoNamingPerformer.java | 13 ++-- .../plugin/trackmate/undo/UndoRedoStack.java | 6 +- .../hyperstack/ModelEditActions.java | 34 +++++----- .../table/AllSpotsTableView.java | 3 +- .../visualization/table/TrackTableView.java | 3 +- .../trackscheme/JGraphXAdapter.java | 3 +- .../trackscheme/TrackSchemeActions.java | 3 +- .../trackscheme/TrackSchemePopupMenu.java | 3 +- .../edge/EdgeTimeAndLocationAnalyzerTest.java | 2 +- .../edge/EdgeVelocityAnalyzerTest.java | 2 +- .../track/TrackDurationAnalyzerTest.java | 2 +- .../TrackSpeedStatisticsAnalyzerTest.java | 2 +- 13 files changed, 55 insertions(+), 83 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java index 263e907f6..9abdd3eee 100644 --- a/src/main/java/fiji/plugin/trackmate/Model.java +++ b/src/main/java/fiji/plugin/trackmate/Model.java @@ -609,37 +609,6 @@ public synchronized Spot removeSpot( final Spot spotToRemove ) return null; } - /** - * Mark the specified spot for update. At the end of the model transaction, - * its features will be recomputed, and other edge and track features that - * depends on it will be as well. - *

    - * For the model update to happen correctly and listeners to be notified - * properly, a call to this method must happen within a transaction, as in: - * - *

    -	 * model.beginUpdate();
    -	 * try {
    -	 * 	... // model modifications here
    -	 * } finally {
    -	 * 	model.endUpdate();
    -	 * }
    -	 * 
    - * - * @param spotToUpdate - * the spot to mark for update - */ - public synchronized void updateFeatures( final Spot spotToUpdate ) - { - spotsUpdated.add( spotToUpdate ); // Enlist for feature update when - // transaction is marked as finished - final Set< DefaultWeightedEdge > touchingEdges = trackModel.edgesOf( spotToUpdate ); - if ( null != touchingEdges ) - { - trackModel.edgesModified.addAll( touchingEdges ); - } - } - /** * Creates a new edge between two spots, with the specified weight. *

    @@ -1017,18 +986,35 @@ public void redo() } /** - * Flags the specified spot for undo. + * Starts the edition of a spot. *

    - * This method must be called only when the spot is going to be modified - * (change position, radius, features, etc.) and before the - * modification is done. This will store the current state of the spot in - * the undo stack, so that it can be restored later if the user calls undo. + * This method must be called before a spot is modified (moving it, + * changing a feature value, editing its name, ...), so that the undo stack + * can record its current state. For the model update to happen correctly + * and listeners to be notified properly, a call to this method must happen + * within a transaction, as in: + * + *

    +	 * model.beginUpdate();
    +	 * try {
    +	 * 	model.beforeEdit( spot );
    +	 * 	... // model modifications here
    +	 * } finally {
    +	 * 	model.endUpdate();
    +	 * }
    +	 * 
    * * @param spot - * the spot to flag for undo. + * the spot to mark for update */ - public void flagForUndo( final Spot spot ) + public void beforeEdit( final Spot spot ) { + // Capture current state of the spot for undo undoRedoStack.flagForUndo( spot ); + // Enlist for feature update when transaction is marked as finished + spotsUpdated.add( spot ); + final Set< DefaultWeightedEdge > touchingEdges = trackModel.edgesOf( spot ); + if ( null != touchingEdges ) + trackModel.edgesModified.addAll( touchingEdges ); } } diff --git a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingPerformer.java b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingPerformer.java index b6f6971f4..9eeaa9346 100644 --- a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingPerformer.java +++ b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingPerformer.java @@ -76,18 +76,16 @@ private static void processTrack( { // Name the spots in the root branch. final Spot first = root.get( 0 ); - model.flagForUndo( first ); // undo name changes. + model.beforeEdit( first ); // undo name changes. rule.nameRoot( first, trackModel ); - model.updateFeatures( first ); // Other spots in the root branch. Spot predecessor = first; for ( int i = 1; i < root.size(); i++ ) { final Spot current = root.get( i ); - model.flagForUndo( current ); // undo name changes. + model.beforeEdit( current ); // undo name changes. rule.nameSpot( current, predecessor ); - model.updateFeatures( current ); predecessor = current; } @@ -116,10 +114,8 @@ private static void processTrack( // Name the branch first spots. for ( final Spot sibling : siblings ) - model.flagForUndo( sibling ); // undo name changes. + model.beforeEdit( sibling ); // undo name changes. rule.nameBranches( mother, siblings ); - for ( final Spot sibling : siblings ) - model.updateFeatures( sibling ); // Name the spots inside each branch. for ( final List< Spot > cb : childrenBranches ) @@ -128,9 +124,8 @@ private static void processTrack( for ( int i = 1; i < cb.size(); i++ ) { final Spot current = cb.get( i ); - model.flagForUndo( current ); // undo name changes. + model.beforeEdit( current ); // undo name changes. rule.nameSpot( current, parent ); - model.updateFeatures( current ); parent = current; } } diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index 4df71f340..1f2736914 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -209,6 +209,7 @@ public void restoreBefore( final Model model ) for ( final Spot spot : spotFeatureValuesBefore.keySet() ) { + model.beforeEdit( spot ); // to notify about update spot.setName( spotNameBefore.get( spot ) ); spotFeatureValuesBefore.get( spot ).forEach( ( key, value ) -> spot.putFeature( key, value ) ); if ( spot instanceof SpotRoi ) @@ -217,7 +218,6 @@ public void restoreBefore( final Model model ) final double[][] polygonBefore = spotPolygonValuesBefore.get( spotRoi ); updatePolygon( spotRoi, polygonBefore ); } - model.updateFeatures( spot ); } for ( final DefaultWeightedEdge edge : edgeFeatureValuesBefore.keySet() ) edgeFeatureValuesBefore.get( edge ).forEach( ( key, value ) -> { @@ -254,6 +254,7 @@ public void restoreAfter( final Model model ) for ( final Spot spot : spotFeatureValuesAfter.keySet() ) { + model.beforeEdit( spot ); // to notify about update spot.setName( spotNameAfter.get( spot ) ); spotFeatureValuesAfter.get( spot ).forEach( ( key, value ) -> spot.putFeature( key, value ) ); if ( spot instanceof SpotRoi ) @@ -262,7 +263,6 @@ public void restoreAfter( final Model model ) final double[][] polygonAfter = spotPolygonValuesAfter.get( spotRoi ); updatePolygon( spotRoi, polygonAfter ); } - model.updateFeatures( spot ); } for ( final DefaultWeightedEdge edge : edgeFeatureValuesAfter.keySet() ) edgeFeatureValuesAfter.get( edge ).forEach( ( key, value ) -> { @@ -322,6 +322,8 @@ private final Map< String, Double > copyEdgeFeatures( final DefaultWeightedEdge public void flagForUndo( final Spot spot ) { + if ( paused ) + return; spot.accept( undoStorer ); } 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 1c678fb26..8fae59984 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java @@ -238,7 +238,8 @@ public void startMoveSpot() if ( null == quickEditedSpot ) { quickEditedSpot = getSpotAtMouseLocation(); - model.flagForUndo( quickEditedSpot ); + model.beginUpdate(); + model.beforeEdit( quickEditedSpot ); } } @@ -263,15 +264,7 @@ public void endMoveSpot() { if ( null == quickEditedSpot ) return; - model.beginUpdate(); - try - { - model.updateFeatures( quickEditedSpot ); - } - finally - { - model.endUpdate(); - } + model.endUpdate(); quickEditedSpot = null; } @@ -281,7 +274,7 @@ public void changeSpotRadius( final boolean increase, final boolean fast ) if ( null == target ) return; - model.flagForUndo( target ); + // Compute new radius. final double radius = target.getFeature( Spot.RADIUS ); final int factor = ( increase ) ? -1 : 1; final double dx = imp.getCalibration().pixelWidth; @@ -293,21 +286,22 @@ public void changeSpotRadius( final boolean increase, final boolean fast ) if ( newRadius <= dx ) return; - // Store new value of radius for next spot creation. - previousRadius = newRadius; - // Actually scale the spot. - target.scale( radius / newRadius ); - - // Scale spot - target.putFeature( Spot.RADIUS, newRadius ); - model.beginUpdate(); try { - model.updateFeatures( target ); + model.beforeEdit( target ); + target.scale( radius / newRadius ); + // Store new value of radius for next spot creation. + previousRadius = newRadius; + // Scale spot + target.putFeature( Spot.RADIUS, newRadius ); logger.log( String.format( Locale.US, "Changed spot " + target + " radius to %.1f " + model.getSpaceUnits() + ".\n", radius ) ); } + catch ( final Exception e ) + { + e.printStackTrace(); + } finally { model.endUpdate(); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java index aa56efc2b..e4beedde9 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java @@ -201,9 +201,8 @@ public static final TablePanel< Spot > createSpotTable( final Model model, final model.beginUpdate(); try { - model.flagForUndo( spot ); // to make name change undoable + model.beforeEdit( spot ); // to make name change undoable spot.setName( label ); - model.updateFeatures( spot ); } finally { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java index 8fbdd71b7..3db26a84f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java @@ -351,9 +351,8 @@ public static final TablePanel< Spot > createSpotTable( final Model model, final model.beginUpdate(); try { - model.flagForUndo( spot ); // to make name change undoable + model.beforeEdit( spot ); // to make name change undoable spot.setName( label ); - model.updateFeatures( spot ); } finally { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/JGraphXAdapter.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/JGraphXAdapter.java index 5c3127165..d8c51a3e0 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/JGraphXAdapter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/JGraphXAdapter.java @@ -79,9 +79,8 @@ public void cellLabelChanged( final Object cell, final Object value, final boole if ( null == spot ) return; final String str = ( String ) value; - tmm.flagForUndo( spot ); // to make name change undoable + tmm.beforeEdit( spot ); // to make name change undoable spot.setName( str ); - tmm.updateFeatures( spot ); getModel().setValue( cell, str ); if ( autoSize ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java index 0cfadc5fb..9a038d75a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java @@ -440,9 +440,8 @@ public void invoke( final Object sender, final mxEventObject evt ) { cell.setValue( tc.getValue() ); final Spot spot = graph.getSpotFor( cell ); - model.flagForUndo( spot ); // name change undoable + model.beforeEdit( spot ); // name change undoable spot.setName( tc.getValue().toString() ); - model.updateFeatures( spot ); } lGraphComponent.refresh(); lGraphComponent.removeListener( this ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java index 28dac71dc..c134b79a3 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java @@ -160,9 +160,8 @@ public void invoke( final Object sender, final mxEventObject evt ) { lCell.setValue( tc.getValue() ); final Spot spot = trackScheme.getGraph().getSpotFor( lCell ); - model.flagForUndo( spot ); // name change undoable + model.beforeEdit( spot ); // name change undoable spot.setName( tc.getValue().toString() ); - model.updateFeatures( spot ); } graphComponent.refresh(); graphComponent.removeListener( this ); 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 07a2b8dab..10989ac75 100644 --- a/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTimeAndLocationAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/edge/EdgeTimeAndLocationAnalyzerTest.java @@ -183,8 +183,8 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { + model.beforeEdit( aspot ); aspot.putFeature( Spot.POSITION_X, -1000d ); - model.updateFeatures( aspot ); } finally { 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 f1e6ad8ab..90398a6aa 100644 --- a/src/test/java/fiji/plugin/trackmate/features/edge/EdgeVelocityAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/edge/EdgeVelocityAnalyzerTest.java @@ -168,8 +168,8 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { + model.beforeEdit( aspot ); aspot.putFeature( Spot.POSITION_X, -1000d ); - model.updateFeatures( aspot ); } 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 ff56c78af..8f6c231ac 100644 --- a/src/test/java/fiji/plugin/trackmate/features/track/TrackDurationAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/track/TrackDurationAnalyzerTest.java @@ -275,8 +275,8 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { + model.beforeEdit( aspot ); aspot.putFeature( Spot.POSITION_T, aspot.getFeature( Spot.POSITION_T ) + increment ); - model.updateFeatures( aspot ); } finally { 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 2a561a366..d956df98f 100644 --- a/src/test/java/fiji/plugin/trackmate/features/track/TrackSpeedStatisticsAnalyzerTest.java +++ b/src/test/java/fiji/plugin/trackmate/features/track/TrackSpeedStatisticsAnalyzerTest.java @@ -319,8 +319,8 @@ public void modelChanged( final ModelChangeEvent event ) model.beginUpdate(); try { + model.beforeEdit( lastSpot ); lastSpot.putFeature( Spot.POSITION_X, 2 * lastSpot.getFeature( Spot.POSITION_X ) ); - model.updateFeatures( lastSpot ); } finally { From 84d4231332e3a45f50ff0ef43dad6f0a9ef69ffd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 30 Jul 2026 18:19:15 +0200 Subject: [PATCH 261/371] Add undo / redo on all views and wizard window. --- .../plugin/trackmate/LoadTrackMatePlugIn.java | 2 ++ .../plugin/trackmate/TrackMatePlugIn.java | 2 ++ .../plugin/trackmate/TrackMateRunner.java | 3 ++ .../visualization/TrackMateModelView.java | 29 +++++++++++++++++++ .../visualization/bvv/TrackMateBVV.java | 8 +++++ .../table/AllSpotsTableView.java | 3 ++ .../visualization/table/BranchTableView.java | 3 ++ .../visualization/table/TrackTableView.java | 3 ++ .../trackscheme/TrackSchemeActions.java | 12 -------- .../trackscheme/TrackSchemeFrame.java | 4 +++ .../TrackSchemeKeyboardHandler.java | 9 ------ 11 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java b/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java index 70493a79f..c22e79f20 100644 --- a/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java @@ -220,6 +220,8 @@ public void run( final String filePath ) frame.setVisible( true ); final Dimension size = frame.getSize(); frame.setSize( size.width, size.height + 1 ); + // Undo / redo + TrackMateModelView.registerUndoShortcut( frame, model ); // Text final LogPanelDescriptor2 logDescriptor = ( LogPanelDescriptor2 ) sequence.logDescriptor(); diff --git a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java index 41c494a3b..855a70716 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java @@ -109,6 +109,8 @@ else if ( imp.getType() == ImagePlus.COLOR_RGB ) // Wizard. final WizardSequence sequence = createSequence( trackmate, selectionModel, displaySettings ); final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); + // Undo / redo + TrackMateModelView.registerUndoShortcut( frame, model ); frame.setIconImage( TRACKMATE_ICON.getImage() ); GuiUtils.positionWindow( frame, imp.getWindow() ); frame.setVisible( true ); diff --git a/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java b/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java index 55f65daeb..6296ed145 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java @@ -53,6 +53,7 @@ import fiji.plugin.trackmate.visualization.TrackMateModelView; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import fiji.util.SplitString; +import ij.IJ; import ij.ImageJ; import ij.ImagePlus; import ij.Macro; @@ -409,6 +410,8 @@ else if ( macroOptions.containsKey( ARG_INPUT_IMAGE_PATH ) ) // Wizard. final WizardSequence sequence = createSequence( trackmate, selectionModel, displaySettings ); final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); + // Undo / redo + TrackMateModelView.registerUndoShortcut( frame, model ); frame.setIconImage( TRACKMATE_ICON.getImage() ); GuiUtils.positionWindow( frame, imp.getWindow() ); frame.setVisible( true ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java index 591afb6c3..c6f6597d9 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java @@ -21,6 +21,16 @@ */ package fiji.plugin.trackmate.visualization; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; + +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JRootPane; +import javax.swing.KeyStroke; + +import org.scijava.ui.behaviour.util.RunnableAction; + import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; @@ -75,4 +85,23 @@ public interface TrackMateModelView */ public String getKey(); + /* + * Utilities + */ + + public static void registerUndoShortcut( final JFrame frame, final Model model ) + { + final JRootPane root = frame.getRootPane(); + + // Ctrl on Windows/Linux, Command on macOS + final int menuMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); + final KeyStroke undoKey = KeyStroke.getKeyStroke( KeyEvent.VK_Z, menuMask ); + final KeyStroke redoKey = KeyStroke.getKeyStroke( KeyEvent.VK_Z, menuMask | KeyEvent.SHIFT_DOWN_MASK ); + + root.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW ).put( undoKey, "undo" ); + root.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW ).put( redoKey, "redo" ); + + root.getActionMap().put( "undo", new RunnableAction( "undo", () -> model.undo() ) ); + root.getActionMap().put( "redo", new RunnableAction( "redo", () -> model.redo() ) ); + } } 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 27140275c..552493433 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -26,6 +26,9 @@ import java.util.Map; import java.util.Map.Entry; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + import org.joml.Matrix4f; import bdv.viewer.animate.TranslationAnimator; @@ -40,6 +43,7 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; +import fiji.plugin.trackmate.visualization.TrackMateModelView; import ij.ImagePlus; import net.imglib2.RealLocalizable; import net.imglib2.realtransform.AffineTransform3D; @@ -96,6 +100,10 @@ public void render() it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm, selectionModel.getSpotSelection().contains( s ) ) ); } } ); + + // Undo / redo + final JFrame frame = ( JFrame ) SwingUtilities.getWindowAncestor( viewer ); + TrackMateModelView.registerUndoShortcut( frame, model ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java index e4beedde9..98e37f3da 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java @@ -129,6 +129,9 @@ public AllSpotsTableView( final Model model, final SelectionModel selectionModel getContentPane().add( mainPanel ); pack(); + // Register key bindings for undo and redo. + TrackMateModelView.registerUndoShortcut( this, model ); + /* * Listeners. */ diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java index 05ebfd767..9428c32e5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java @@ -111,6 +111,9 @@ public BranchTableView( final Model model, final SelectionModel selectionModel, toolbar.add( Box.createHorizontalGlue() ); mainPanel.add( toolbar, BorderLayout.NORTH ); + // Undo/redo. + TrackMateModelView.registerUndoShortcut( this, model ); + getContentPane().add( mainPanel ); pack(); } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java index 3db26a84f..999a05342 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java @@ -157,6 +157,9 @@ public TrackTableView( final Model model, final SelectionModel selectionModel, f getContentPane().add( mainPanel ); pack(); + // Undo / redo + TrackMateModelView.registerUndoShortcut( this, model ); + /* * Listeners. */ diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java index 9a038d75a..54ee9b253 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java @@ -47,8 +47,6 @@ import javax.swing.Action; import javax.swing.Icon; -import org.scijava.ui.behaviour.util.RunnableAction; - import com.mxgraph.model.mxCell; import com.mxgraph.model.mxICell; import com.mxgraph.swing.util.mxGraphActions; @@ -87,16 +85,6 @@ public class TrackSchemeActions private TrackSchemeActions() {} - public static Action getUndoAction( final Model model ) - { - return new RunnableAction( "undo", () -> model.undo() ); - } - - public static Action getRedoAction( final Model model ) - { - return new RunnableAction( "redo", () -> model.redo() ); - } - public static Action getEditAction( final Model model, final TrackSchemeGraphComponent graphComponent ) { return new EditAction( "edit", EDIT_ICON, model, graphComponent ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java index 8e2e2d350..38e50666e 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java @@ -42,6 +42,7 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.util.TrackNavigator; +import fiji.plugin.trackmate.visualization.TrackMateModelView; public class TrackSchemeFrame extends JFrame { @@ -117,6 +118,9 @@ public void init( final JGraphXAdapter lGraph ) final TrackSchemeKeyboardHandler keyboardHandler = new TrackSchemeKeyboardHandler( trackScheme.getModel(), graphComponent, new TrackNavigator( trackScheme.getModel(), trackScheme.getSelectionModel() ) ); keyboardHandler.installKeyboardActions( graphComponent ); keyboardHandler.installKeyboardActions( infoPane ); + + // Undo / redo + TrackMateModelView.registerUndoShortcut( this, trackScheme.getModel() ); } /* 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 d49e3b08b..aa63abadd 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java @@ -98,11 +98,6 @@ protected InputMap getInputMap( final int condition ) map.put( KeyStroke.getKeyStroke( "PAGE_DOWN" ), "selectNextTrack" ); map.put( KeyStroke.getKeyStroke( "PAGE_UP" ), "selectPreviousTrack" ); - map.put( KeyStroke.getKeyStroke( "control Z" ), "undo" ); - map.put( KeyStroke.getKeyStroke( "meta Z" ), "undo" ); - map.put( KeyStroke.getKeyStroke( "control shift Z" ), "redo" ); - map.put( KeyStroke.getKeyStroke( "meta shift Z" ), "redo" ); - return map; } @@ -197,10 +192,6 @@ public void actionPerformed( final ActionEvent arg0 ) navigator.previousTrack(); } } ); - - map.put( "undo", TrackSchemeActions.getUndoAction( model ) ); - map.put( "redo", TrackSchemeActions.getRedoAction( model ) ); - return map; } From bc373f5e25f2def28d1433d9c3f6c85c77ee66d2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 30 Jul 2026 18:22:11 +0200 Subject: [PATCH 262/371] Undo / redo only follow model modification events. All other events result in the undo / redo stack to be cleared. --- .../plugin/trackmate/undo/UndoRedoStack.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index 1f2736914..9b94052de 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -87,18 +87,28 @@ public void redo() @Override public void modelChanged( final ModelChangeEvent event ) { + if ( event.getEventID() != ModelChangeEvent.MODEL_MODIFIED ) + { + // Only deal with model modified events. Other events are not + // undoable and results in clearing the undo/redo stack. + undoStack.clear(); + redoStack.clear(); + spotFeatureValuesBefore.clear(); + edgeFeatureValuesBefore.clear(); + spotPolygonValuesBefore.clear(); + spotNameBefore.clear(); + return; + } + if ( paused ) return; - if ( event.getEventID() == ModelChangeEvent.MODEL_MODIFIED ) - { - final ModelUndoableCommand command = toCommand( event ); - redoStack.clear(); - if ( undoStack.size() >= maxSize ) - undoStack.removeFirst(); + final ModelUndoableCommand command = toCommand( event ); + redoStack.clear(); + if ( undoStack.size() >= maxSize ) + undoStack.removeFirst(); - undoStack.addLast( command ); - } + undoStack.addLast( command ); } private ModelUndoableCommand toCommand( final ModelChangeEvent event ) From 7cca8bf1ac30b946e7b775b70e875175d25f816c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 30 Jul 2026 22:54:08 +0200 Subject: [PATCH 263/371] Release constraint on getSpotAt() method of SpotCollection. It now accepts a RealLocalizable instead of a Spot. What was I thinking? --- src/main/java/fiji/plugin/trackmate/SpotCollection.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotCollection.java b/src/main/java/fiji/plugin/trackmate/SpotCollection.java index 98d9c64f2..c94d872cc 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotCollection.java +++ b/src/main/java/fiji/plugin/trackmate/SpotCollection.java @@ -35,6 +35,7 @@ import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.util.Threads; +import net.imglib2.RealLocalizable; import net.imglib2.algorithm.MultiThreaded; /** @@ -379,7 +380,7 @@ public final Spot getClosestSpot( final Spot location, final int frame, final bo } /** - * Returns the {@link Spot} at the given location (encoded as a Spot), + * Returns the {@link Spot} at the given location (in world coordinates), * contained in the frame frame. A spot is returned only * if there exists a spot such that the given location is within the spot * radius. Otherwise null is returned. @@ -395,7 +396,7 @@ public final Spot getClosestSpot( final Spot location, final int frame, final bo * radius, member of this collection, or null is such a * spots cannot be found. */ - public final Spot getSpotAt( final Spot location, final int frame, final boolean visibleSpotsOnly ) + public final Spot getSpotAt( final RealLocalizable location, final int frame, final boolean visibleSpotsOnly ) { final Set< Spot > spots = content.get( frame ); if ( null == spots || spots.isEmpty() ) From db038add183e1924588a59ca517ff55373271b44 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 30 Jul 2026 22:54:43 +0200 Subject: [PATCH 264/371] The scale() method of Spot also updates the RADIUS feature. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 1 + src/main/java/fiji/plugin/trackmate/SpotRoi.java | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index a850d03c5..690b7a125 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -262,6 +262,7 @@ public double volume() @Override public void scale( final double alpha ) { + super.scale( alpha ); final net.imglib2.mesh.Vertices vertices = mesh.vertices(); final long nVertices = vertices.size(); for ( int v = 0; v < nVertices; v++ ) diff --git a/src/main/java/fiji/plugin/trackmate/SpotRoi.java b/src/main/java/fiji/plugin/trackmate/SpotRoi.java index cc59260b7..ecfff1d45 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotRoi.java +++ b/src/main/java/fiji/plugin/trackmate/SpotRoi.java @@ -296,6 +296,7 @@ public double area() @Override public void scale( final double alpha ) { + super.scale( alpha ); for ( int i = 0; i < x.length; i++ ) { final double x = this.x[ i ]; From 1290d9fa6168842ad6f89e2223628101e48722d5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 30 Jul 2026 23:08:55 +0200 Subject: [PATCH 265/371] Try to build a 'thing' that adapt ui-behaviour to AWT canvas. Basically we need to: 1. Deregister and re-register the IJ key listener, so that it is called AFTER the MouseAndKeyHandler. 2. For beaviors, make a proxy for MouseAndKeyHandler, that can consume events IF there is a matching input trigger, because otherwise the event will be sent to the IJ listener. 3. For actions, intercept the KeyStrokes on the IJ canvas and route them to our Action map. This is a WIP with 2 behaviours to move and resize spots. --- .../behaviours/AbstractSpotEditBehaviour.java | 45 ++++ .../behaviours/MouseEventProxy.java | 199 ++++++++++++++++++ .../behaviours/MoveSpotBehaviour.java | 51 +++++ .../behaviours/ResizeSpotBehaviour.java | 73 +++++++ .../behaviours/TrackMateImpBehaviour.java | 123 +++++++++++ 5 files changed, 491 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MouseEventProxy.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MoveSpotBehaviour.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ResizeSpotBehaviour.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java new file mode 100644 index 000000000..de09c45c9 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java @@ -0,0 +1,45 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.util.TMUtils; +import ij.ImagePlus; +import ij.gui.ImageCanvas; +import net.imglib2.RealLocalizable; +import net.imglib2.RealPoint; + +public class AbstractSpotEditBehaviour +{ + + protected Spot movedSpot; + + protected final Model model; + + protected final ImagePlus imp; + + protected final double[] calibration; + + + public AbstractSpotEditBehaviour( final Model model, final ImagePlus imp ) + { + this.model = model; + this.imp = imp; + this.calibration = TMUtils.getSpatialCalibration( imp ); + } + + protected Spot getSpotAtMouseLocation( final RealLocalizable pos ) + { + final int frame = imp.getFrame() - 1; + return model.getSpots().getSpotAt( pos, frame, true ); + } + + protected RealLocalizable toWorldCoords( final int x, final int y ) + { + final ImageCanvas canvas = imp.getCanvas(); + final double xw = ( -0.5 + canvas.offScreenXD( x ) ) * calibration[ 0 ]; + final double yw = ( -0.5 + canvas.offScreenYD( y ) ) * calibration[ 1 ]; + final double zw = ( imp.getSlice() - 1 ) * calibration[ 2 ]; + return new RealPoint( xw, yw, zw ); + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MouseEventProxy.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MouseEventProxy.java new file mode 100644 index 000000000..6ab7b6dc7 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MouseEventProxy.java @@ -0,0 +1,199 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; + +import org.scijava.ui.behaviour.GlobalKeyEventDispatcher; +import org.scijava.ui.behaviour.InputTrigger; +import org.scijava.ui.behaviour.InputTriggerMap; +import org.scijava.ui.behaviour.MouseAndKeyHandler; + +/** + * A proxy wrapper for SciJava's {@link MouseAndKeyHandler} that intercepts AWT + * mouse event pipelines and selectively calls {@code e.consume()} only if an + * explicit behavior trigger mapping exists inside the configuration model. + *

    + * We need this because the default {@link MouseAndKeyHandler} does not consume + * events when a trigger is matched, which can lead to unintended propagation of + * events to other components in the UI. + */ +public class MouseEventProxy implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener +{ + private final MouseAndKeyHandler delegate; + + private final InputTriggerMap triggerMap; + + private Method getMaskMethod; + + /** + * Constructs a new MouseEventProxy. + * + * @param delegate + * The active MouseAndKeyHandler driving the interaction updates. + * @param triggerMap + * The mapping table containing target behavioral shortcuts. + */ + public MouseEventProxy( final MouseAndKeyHandler delegate, final InputTriggerMap triggerMap ) + { + this.delegate = delegate; + this.triggerMap = triggerMap; + try + { + // Extract ui-behaviour's internal structural input mask calculator + // via reflection + this.getMaskMethod = MouseAndKeyHandler.class.getDeclaredMethod( "getMask", InputEvent.class ); + this.getMaskMethod.setAccessible( true ); + } + catch ( final Exception ex ) + { + this.getMaskMethod = null; + } + } + + /** + * Safely determines if the active hardware interaction features a + * behavioral binding without causing NullPointerExceptions in child trigger + * maps. + */ + private boolean hasMatchingBehaviour( final InputEvent e ) + { + if ( getMaskMethod == null || triggerMap == null ) + return false; + + try + { + // Retrieve the calculated normalization bitmask from the handler + // instance + final int mask = ( Integer ) getMaskMethod.invoke( delegate, e ); + + // FIX: Keep the primitive TIntSet collection directly without + // calling .toArray() + final gnu.trove.set.TIntSet pressedKeys = GlobalKeyEventDispatcher.getInstance().pressedKeys(); + + // Safely pull the flattened trigger assignments map from our + // configuration + final Map< InputTrigger, Set< String > > bindings = triggerMap.getAllBindings(); + + if ( bindings != null ) + { + for ( final Map.Entry< InputTrigger, Set< String > > entry : bindings.entrySet() ) + { + final InputTrigger trigger = entry.getKey(); + final Set< String > keys = entry.getValue(); + + // Check if trigger conditions match and that it contains + // actual bound behaviors + if ( trigger != null && trigger.matches( mask, pressedKeys ) && keys != null && !keys.isEmpty() ) + { return true; } + } + } + } + catch ( final Exception ex ) + { + // Prevent crashes from faulty reflections, letting the event + // propagate unconsumed + } + return false; + } + + @Override + public void mouseClicked( final MouseEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mouseClicked( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void mousePressed( final MouseEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mousePressed( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void mouseReleased( final MouseEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mouseReleased( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void mouseEntered( final MouseEvent e ) + { + delegate.mouseEntered( e ); + } + + @Override + public void mouseExited( final MouseEvent e ) + { + delegate.mouseExited( e ); + } + + @Override + public void mouseDragged( final MouseEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mouseDragged( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void mouseMoved( final MouseEvent e ) + { + // Hover coordinates pass straight through unconsumed to ensure legacy + // UI refreshes continue + delegate.mouseMoved( e ); + } + + @Override + public void mouseWheelMoved( final MouseWheelEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mouseWheelMoved( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void keyPressed( final KeyEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.keyPressed( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void keyReleased( final KeyEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.keyReleased( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void keyTyped( final KeyEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.keyTyped( e ); + if ( shouldConsume ) + e.consume(); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MoveSpotBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MoveSpotBehaviour.java new file mode 100644 index 000000000..d8d50d71f --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MoveSpotBehaviour.java @@ -0,0 +1,51 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import org.scijava.ui.behaviour.DragBehaviour; + +import fiji.plugin.trackmate.Model; +import ij.ImagePlus; +import net.imglib2.RealLocalizable; + +public class MoveSpotBehaviour extends AbstractSpotEditBehaviour implements DragBehaviour +{ + + /** Offset between mouse click and spot center, in world coordinates. */ + private final double[] delta = new double[ 2 ]; + + public MoveSpotBehaviour( final Model model, final ImagePlus imp ) + { + super( model, imp ); + } + + @Override + public void init( final int x, final int y ) + { + if ( null != movedSpot ) + return; + final RealLocalizable pos = toWorldCoords( x, y ); + movedSpot = getSpotAtMouseLocation( pos ); + if ( null == movedSpot ) + return; + model.beginUpdate(); + model.beforeEdit( movedSpot ); + delta[ 0 ] = movedSpot.getDoublePosition( 0 ) - pos.getDoublePosition( 0 ); + delta[ 1 ] = movedSpot.getDoublePosition( 1 ) - pos.getDoublePosition( 1 ); + } + + @Override + public void drag( final int x, final int y ) + { + final RealLocalizable pos = toWorldCoords( x, y ); + movedSpot.setPosition( pos.getDoublePosition( 0 ) + delta[ 0 ], 0 ); + movedSpot.setPosition( pos.getDoublePosition( 1 ) + delta[ 1 ], 1 ); + imp.updateAndDraw(); + } + + @Override + public void end( final int x, final int y ) + { + model.endUpdate(); + movedSpot = null; + imp.updateAndDraw(); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ResizeSpotBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ResizeSpotBehaviour.java new file mode 100644 index 000000000..ac489bb9f --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ResizeSpotBehaviour.java @@ -0,0 +1,73 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import org.scijava.ui.behaviour.ClickBehaviour; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import ij.ImagePlus; + +public class ResizeSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour +{ + + /** + * Fall back default radius when the settings does not give a default radius + * to use. + */ + private static final double FALL_BACK_RADIUS = 5.; + + private static final double COARSE_STEP = 2; + + private static final double FINE_STEP = 0.2f; + + private final boolean increase; + + private final boolean fast; + + /** The previous radius to be used for spot creation. */ + static double previousRadius = FALL_BACK_RADIUS; + + public ResizeSpotBehaviour( final Model model, final ImagePlus imp, final boolean increase, final boolean fast ) + { + super( model, imp ); + this.increase = increase; + this.fast = fast; + } + + @Override + public void click( final int x, final int y ) + { + final Spot spot = getSpotAtMouseLocation( toWorldCoords( x, y ) ); + if ( null == spot ) + return; + + // Compute new radius. + final double radius = spot.getFeature( Spot.RADIUS ); + 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; + + // Actually scale the spot. + model.beginUpdate(); + try + { + model.beforeEdit( spot ); + spot.scale( radius / newRadius ); + // Store new value of radius for next spot creation. + previousRadius = newRadius; + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + finally + { + model.endUpdate(); + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java new file mode 100644 index 000000000..641079632 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java @@ -0,0 +1,123 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.KeyStroke; + +import org.scijava.ui.behaviour.BehaviourMap; +import org.scijava.ui.behaviour.InputTriggerMap; +import org.scijava.ui.behaviour.MouseAndKeyHandler; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Actions; +import org.scijava.ui.behaviour.util.Behaviours; +import org.scijava.ui.behaviour.util.InputActionBindings; +import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; + +import fiji.plugin.trackmate.Model; +import ij.ImagePlus; +import ij.gui.ImageCanvas; + +public class TrackMateImpBehaviour +{ + + /** + * Attaches ui-behaviour interaction handling to a given AWT Canvas. + * + * @param canvas + * The target AWT Canvas to bind actions and behaviours to. + */ + public static void install( final Model model, final ImagePlus imp ) + { + // 1. Ensure the canvas can accept focus for keyboard shortcuts + final ImageCanvas canvas = imp.getCanvas(); + canvas.setFocusable( true ); + + // A. Behaviours framework + + // Initialize configuration and binding registries + final InputTriggerConfig config = new InputTriggerConfig(); + final InputActionBindings actionBindings = new InputActionBindings(); + final TriggerBehaviourBindings behaviourBindings = new TriggerBehaviourBindings(); + + // Initialize the Behaviours framework + final MouseAndKeyHandler handler = new MouseAndKeyHandler(); + final InputTriggerMap inputTriggerMap = new InputTriggerMap(); + final BehaviourMap behaviourMap = new BehaviourMap(); + handler.setInputMap( inputTriggerMap ); + handler.setBehaviourMap( behaviourMap ); + + // Add the TrackMate listener first. + final KeyListener[] keyListeners = canvas.getKeyListeners(); + final KeyListener ijKeyListener = keyListeners[ 0 ]; + canvas.removeKeyListener( ijKeyListener ); + + // Connect the handler to the AWT component for Mouse handling + // We need the proxy because the default MouseAndKeyHandler does not + // consume events when a trigger is matched, and without it the events + // are sent to the ImageJ original KeyListener. + final MouseEventProxy proxy = new MouseEventProxy( handler, inputTriggerMap ); + canvas.addKeyListener( proxy ); + canvas.addMouseListener( proxy ); + canvas.addMouseMotionListener( proxy ); + canvas.addMouseWheelListener( proxy ); + + // Re-add the original ImageJ KeyListener after the proxy + canvas.addKeyListener( ijKeyListener ); + + // The behaviours. + final Behaviours behaviours = new Behaviours( inputTriggerMap, behaviourMap, config ); + behaviours.install( behaviourBindings, "trackmate-beaviors" ); + + // Actions + final InputMap inputMap = actionBindings.getConcatenatedInputMap(); + final ActionMap actionMap = actionBindings.getConcatenatedActionMap(); + final Actions actions = new Actions( inputMap, actionMap, config ); + actions.install( actionBindings, "trackmate-actions" ); + + // This is the debug + actions.runnableAction( () -> { + System.out.println( "Reset action triggered!" ); + }, "reset-view", "R" ); + + // Direct Key Event Proxy Bridge. This was done with Gemini. + // Because an AWT Canvas bypasses Swing's ActionMap dispatch pipeline, + // we manually intercept the KeyStrokes and route them to our Action + // map. + canvas.addKeyListener( new KeyAdapter() + { + @Override + public void keyPressed( final KeyEvent e ) + { + // Get the keystroke matching this precise key event + final KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent( e ); + if ( keyStroke == null ) + return; + + // Lookup the unique Action ID string assigned to this key combo + final Object actionKey = inputMap.get( keyStroke ); + if ( actionKey != null ) + { + // Pull the runnable action associated with that ID + final Action action = actionMap.get( actionKey ); + if ( action != null && action.isEnabled() ) + { + // Fire the shortcut action directly on the EDT + action.actionPerformed( null ); + e.consume(); // Block from propagating to IJ listener + } + } + } + } ); + + behaviours.behaviour( new MoveSpotBehaviour( model, imp ), "move-spot", "SPACE" ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, false ), "increase-spot-radius", "E" ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, true ), "increase-spot-radius-fast", "shift E" ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, false ), "decrease-spot-radius", "Q" ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, true ), "decrease-spot-radius-fast", "shift Q" ); + } +} From 38d54c1985241dacf2a12b5057b5bd11d811fc0b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 30 Jul 2026 23:09:36 +0200 Subject: [PATCH 266/371] WIP: Toy with the UI behaviour framework in the HyperstackDisplayer. --- .../visualization/hyperstack/HyperStackDisplayer.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 ed5c9bb48..fca5612aa 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -29,6 +29,7 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; import fiji.plugin.trackmate.visualization.ViewUtils; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.TrackMateImpBehaviour; import ij.ImagePlus; import ij.gui.Overlay; import ij.gui.Roi; @@ -159,7 +160,14 @@ public void render() addOverlay( spotOverlay ); addOverlay( trackOverlay ); imp.updateAndDraw(); - registerEditTool(); +// registerEditTool(); + + /* + * Play with UI behaviour + */ + + // Print all registered key listeners to console + TrackMateImpBehaviour.install( model, imp ); } @Override From 07e385a117c388e5dcbd37a3cc978ece4c7a34b7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 09:55:25 +0200 Subject: [PATCH 267/371] Code style changes. --- .../plugin/trackmate/util/TrackNavigator.java | 251 ++++++++++-------- 1 file changed, 133 insertions(+), 118 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/util/TrackNavigator.java b/src/main/java/fiji/plugin/trackmate/util/TrackNavigator.java index f6b88d6be..5707ee04b 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TrackNavigator.java +++ b/src/main/java/fiji/plugin/trackmate/util/TrackNavigator.java @@ -21,52 +21,59 @@ */ package fiji.plugin.trackmate.util; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.graph.TimeDirectedNeighborIndex; - import java.util.Iterator; import java.util.Set; import java.util.TreeSet; import org.jgrapht.graph.DefaultWeightedEdge; -public class TrackNavigator { +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.graph.TimeDirectedNeighborIndex; + +public class TrackNavigator +{ private final Model model; + private final SelectionModel selectionModel; + private final TimeDirectedNeighborIndex neighborIndex; - public TrackNavigator(final Model model, final SelectionModel selectionModel) { + public TrackNavigator( final Model model, final SelectionModel selectionModel ) + { this.model = model; this.selectionModel = selectionModel; this.neighborIndex = model.getTrackModel().getDirectedNeighborIndex(); } - public synchronized void nextTrack() { + public synchronized void nextTrack() + { final Spot spot = getASpot(); - if (null == spot) { + if ( null == spot ) return; - } - final Set trackIDs = model.getTrackModel().trackIDs(true); // if only it was navigable... - if (trackIDs.isEmpty()) { + final Set< Integer > trackIDs = model.getTrackModel().trackIDs( true ); + if ( trackIDs.isEmpty() ) return; - } - Integer trackID = model.getTrackModel().trackIDOf(spot); - if (null == trackID) { + Integer trackID = model.getTrackModel().trackIDOf( spot ); + if ( null == trackID ) + { // No track? Then move to the first one. - trackID = model.getTrackModel().trackIDs(true).iterator().next(); + trackID = model.getTrackModel().trackIDs( true ).iterator().next(); } - final Iterator it = trackIDs.iterator(); + final Iterator< Integer > it = trackIDs.iterator(); Integer nextTrackID = null; - while (it.hasNext()) { + while ( it.hasNext() ) + { final Integer id = it.next(); - if (id.equals(trackID)) { - if (it.hasNext()) { + if ( id.equals( trackID ) ) + { + if ( it.hasNext() ) + { nextTrackID = it.next(); break; } @@ -74,155 +81,156 @@ public synchronized void nextTrack() { } } - final Set spots = model.getTrackModel().trackSpots(nextTrackID); - final TreeSet ring = new TreeSet<>(Spot.frameComparator); - ring.addAll(spots); - Spot target = ring.ceiling(spot); - if (null == target) { - target = ring.floor(spot); - } + final Set< Spot > spots = model.getTrackModel().trackSpots( nextTrackID ); + final TreeSet< Spot > ring = new TreeSet<>( Spot.frameComparator ); + ring.addAll( spots ); + Spot target = ring.ceiling( spot ); + if ( null == target ) + target = ring.floor( spot ); selectionModel.clearSelection(); - selectionModel.addSpotToSelection(target); + selectionModel.addSpotToSelection( target ); } - public synchronized void previousTrack() { + public synchronized void previousTrack() + { final Spot spot = getASpot(); - if (null == spot) { + if ( null == spot ) return; - } - Integer trackID = model.getTrackModel().trackIDOf(spot); - final Set trackIDs = model.getTrackModel().trackIDs(true); // if only it was navigable... - if (trackIDs.isEmpty()) { + Integer trackID = model.getTrackModel().trackIDOf( spot ); + final Set< Integer > trackIDs = model.getTrackModel().trackIDs( true ); + if ( trackIDs.isEmpty() ) return; - } Integer lastID = null; - for (final Integer id : trackIDs) { + for ( final Integer id : trackIDs ) lastID = id; - } - if (null == trackID) { + if ( null == trackID ) + { // No track? Then take the last one. trackID = lastID; } - final Iterator it = trackIDs.iterator(); + final Iterator< Integer > it = trackIDs.iterator(); Integer previousTrackID = null; - while (it.hasNext()) { + while ( it.hasNext() ) + { final Integer id = it.next(); - if (id.equals(trackID)) { - if (previousTrackID != null) { + if ( id.equals( trackID ) ) + { + if ( previousTrackID != null ) break; - } + previousTrackID = lastID; break; } previousTrackID = id; } - final Set spots = model.getTrackModel().trackSpots(previousTrackID); - final TreeSet ring = new TreeSet<>(Spot.frameComparator); - ring.addAll(spots); - Spot target = ring.ceiling(spot); - if (null == target) { - target = ring.floor(spot); - } + final Set< Spot > spots = model.getTrackModel().trackSpots( previousTrackID ); + final TreeSet< Spot > ring = new TreeSet<>( Spot.frameComparator ); + ring.addAll( spots ); + Spot target = ring.ceiling( spot ); + if ( null == target ) + target = ring.floor( spot ); selectionModel.clearSelection(); - selectionModel.addSpotToSelection(target); + selectionModel.addSpotToSelection( target ); } - public synchronized void nextSibling() { + public synchronized void nextSibling() + { final Spot spot = getASpot(); - if (null == spot) { + if ( null == spot ) return; - } - final Integer trackID = model.getTrackModel().trackIDOf(spot); - if (null == trackID) { + final Integer trackID = model.getTrackModel().trackIDOf( spot ); + if ( null == trackID ) return; - } - final int frame = spot.getFeature(Spot.FRAME).intValue(); - final TreeSet ring = new TreeSet<>(Spot.nameComparator); + final int frame = spot.getFeature( Spot.FRAME ).intValue(); + final TreeSet< Spot > ring = new TreeSet<>( Spot.nameComparator ); - final Set spots = model.getTrackModel().trackSpots(trackID); - for (final Spot s : spots) { - final int fs = s.getFeature(Spot.FRAME).intValue(); - if (frame == fs && s != spot) { - ring.add(s); - } + final Set< Spot > spots = model.getTrackModel().trackSpots( trackID ); + for ( final Spot s : spots ) + { + final int fs = s.getFeature( Spot.FRAME ).intValue(); + if ( frame == fs && s != spot ) + ring.add( s ); } - if (!ring.isEmpty()) { - Spot nextSibling = ring.ceiling(spot); - if (null == nextSibling) { + if ( !ring.isEmpty() ) + { + Spot nextSibling = ring.ceiling( spot ); + if ( null == nextSibling ) nextSibling = ring.first(); // loop - } + selectionModel.clearSelection(); - selectionModel.addSpotToSelection(nextSibling); + selectionModel.addSpotToSelection( nextSibling ); } } - public synchronized void previousSibling() { + public synchronized void previousSibling() + { final Spot spot = getASpot(); - if (null == spot) { + if ( null == spot ) return; - } - final Integer trackID = model.getTrackModel().trackIDOf(spot); - if (null == trackID) { + final Integer trackID = model.getTrackModel().trackIDOf( spot ); + if ( null == trackID ) return; - } - final int frame = spot.getFeature(Spot.FRAME).intValue(); - final TreeSet ring = new TreeSet<>(Spot.nameComparator); + final int frame = spot.getFeature( Spot.FRAME ).intValue(); + final TreeSet< Spot > ring = new TreeSet<>( Spot.nameComparator ); - final Set spots = model.getTrackModel().trackSpots(trackID); - for (final Spot s : spots) { - final int fs = s.getFeature(Spot.FRAME).intValue(); - if (frame == fs && s != spot) { - ring.add(s); - } + final Set< Spot > spots = model.getTrackModel().trackSpots( trackID ); + for ( final Spot s : spots ) + { + final int fs = s.getFeature( Spot.FRAME ).intValue(); + if ( frame == fs && s != spot ) + ring.add( s ); } - if (!ring.isEmpty()) { - Spot previousSibling = ring.floor(spot); - if (null == previousSibling) { + if ( !ring.isEmpty() ) + { + Spot previousSibling = ring.floor( spot ); + if ( null == previousSibling ) previousSibling = ring.last(); // loop - } + selectionModel.clearSelection(); - selectionModel.addSpotToSelection(previousSibling); + selectionModel.addSpotToSelection( previousSibling ); } } - public synchronized void previousInTime() { + public synchronized void previousInTime() + { final Spot spot = getASpot(); - if (null == spot) { + if ( null == spot ) return; - } - final Set predecessors = neighborIndex.predecessorsOf(spot); - if (!predecessors.isEmpty()) { + final Set< Spot > predecessors = neighborIndex.predecessorsOf( spot ); + if ( !predecessors.isEmpty() ) + { final Spot next = predecessors.iterator().next(); selectionModel.clearSelection(); - selectionModel.addSpotToSelection(next); + selectionModel.addSpotToSelection( next ); } } - public synchronized void nextInTime() { + public synchronized void nextInTime() + { final Spot spot = getASpot(); - if (null == spot) { + if ( null == spot ) return; - } - final Set successors = neighborIndex.successorsOf(spot); - if (!successors.isEmpty()) { + final Set< Spot > successors = neighborIndex.successorsOf( spot ); + if ( !successors.isEmpty() ) + { final Spot next = successors.iterator().next(); selectionModel.clearSelection(); - selectionModel.addSpotToSelection(next); + selectionModel.addSpotToSelection( next ); } } @@ -234,17 +242,21 @@ public synchronized void nextInTime() { * Return a meaningful spot from the current selection, or null * if the selection is empty. */ - private Spot getASpot() { + private Spot getASpot() + { // Get it from spot selection - final Set spotSelection = selectionModel.getSpotSelection(); - if (!spotSelection.isEmpty()) { - final Iterator it = spotSelection.iterator(); + final Set< Spot > spotSelection = selectionModel.getSpotSelection(); + if ( !spotSelection.isEmpty() ) + { + final Iterator< Spot > it = spotSelection.iterator(); Spot spot = it.next(); - int minFrame = spot.getFeature(Spot.FRAME).intValue(); - while (it.hasNext()) { + int minFrame = spot.getFeature( Spot.FRAME ).intValue(); + while ( it.hasNext() ) + { final Spot s = it.next(); - final int frame = s.getFeature(Spot.FRAME).intValue(); - if (frame < minFrame) { + final int frame = s.getFeature( Spot.FRAME ).intValue(); + if ( frame < minFrame ) + { minFrame = frame; spot = s; } @@ -253,17 +265,20 @@ private Spot getASpot() { } // Nope? Then get it from edges - final Set edgeSelection = selectionModel.getEdgeSelection(); - if (!edgeSelection.isEmpty()) { - final Iterator it = edgeSelection.iterator(); + final Set< DefaultWeightedEdge > edgeSelection = selectionModel.getEdgeSelection(); + if ( !edgeSelection.isEmpty() ) + { + final Iterator< DefaultWeightedEdge > it = edgeSelection.iterator(); final DefaultWeightedEdge edge = it.next(); - Spot spot = model.getTrackModel().getEdgeSource(edge); - int minFrame = spot.getFeature(Spot.FRAME).intValue(); - while (it.hasNext()) { + Spot spot = model.getTrackModel().getEdgeSource( edge ); + int minFrame = spot.getFeature( Spot.FRAME ).intValue(); + while ( it.hasNext() ) + { final DefaultWeightedEdge e = it.next(); - final Spot s = model.getTrackModel().getEdgeSource(e); - final int frame = s.getFeature(Spot.FRAME).intValue(); - if (frame < minFrame) { + final Spot s = model.getTrackModel().getEdgeSource( e ); + final int frame = s.getFeature( Spot.FRAME ).intValue(); + if ( frame < minFrame ) + { minFrame = frame; spot = s; } From 1e0c2830fb7f06f1fa72b400fbe86d55bf1b223a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 10:46:44 +0200 Subject: [PATCH 268/371] Create a reversed DFS for the track model. This simplifies navigating a track, particularly to select spots in a track in a given time direction, or to access the root of a specific track. --- .../fiji/plugin/trackmate/SelectionModel.java | 81 +++++-------------- .../fiji/plugin/trackmate/TrackModel.java | 44 +++++++--- .../graph/TimeDirectedDepthFirstIterator.java | 34 ++++---- ...seGapsByLinearInterpolationActionTest.java | 6 +- .../trackmate/interactivetests/GraphTest.java | 2 +- 5 files changed, 73 insertions(+), 94 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SelectionModel.java b/src/main/java/fiji/plugin/trackmate/SelectionModel.java index 6c5fea292..7ac5a4951 100644 --- a/src/main/java/fiji/plugin/trackmate/SelectionModel.java +++ b/src/main/java/fiji/plugin/trackmate/SelectionModel.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Stack; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.traverse.GraphIterator; @@ -314,67 +313,26 @@ public void selectTrack( final Collection< Spot > spots, final Collection< Defau final HashSet< Spot > lSpotSelection = new HashSet<>(); final HashSet< DefaultWeightedEdge > lEdgeSelection = new HashSet<>(); - if ( direction == 0 ) - { // Unconditionally - for ( final Spot spot : inspectionSpots ) - { - lSpotSelection.add( spot ); - final GraphIterator< Spot, DefaultWeightedEdge > walker = model.getTrackModel().getDepthFirstIterator( spot, false ); - while ( walker.hasNext() ) - { - final Spot target = walker.next(); - lSpotSelection.add( target ); - // Deal with edges - final Set< DefaultWeightedEdge > targetEdges = model.getTrackModel().edgesOf( target ); - for ( final DefaultWeightedEdge targetEdge : targetEdges ) - { - lEdgeSelection.add( targetEdge ); - } - } - } + for ( final Spot spot : inspectionSpots ) + { + lSpotSelection.add( spot ); - } - else - { // Only upward or backward in time - for ( final Spot spot : inspectionSpots ) + final GraphIterator< Spot, DefaultWeightedEdge > walker; + if ( direction == 0 ) + walker = model.getTrackModel().getDepthFirstIterator( spot ); + else if ( direction > 0 ) + walker = model.getTrackModel().getDirectedDepthFirstIterator( spot, true ); + else + walker = model.getTrackModel().getDirectedDepthFirstIterator( spot, false ); + + while ( walker.hasNext() ) { - lSpotSelection.add( spot ); - - /* - * A bit more complicated: we want to walk in only one - * direction, when branching is occurring, we do not want to get - * back in time. - */ - final Stack< Spot > stack = new Stack<>(); - stack.add( spot ); - while ( !stack.isEmpty() ) - { - final Spot inspected = stack.pop(); - final Set< DefaultWeightedEdge > targetEdges = model.getTrackModel().edgesOf( inspected ); - for ( final DefaultWeightedEdge targetEdge : targetEdges ) - { - Spot other; - if ( direction > 0 ) - { - /* - * Upward in time: we just have to search through - * edges using their source spots. - */ - other = model.getTrackModel().getEdgeSource( targetEdge ); - } - else - { - other = model.getTrackModel().getEdgeTarget( targetEdge ); - } - - if ( other != inspected ) - { - lSpotSelection.add( other ); - lEdgeSelection.add( targetEdge ); - stack.add( other ); - } - } - } + final Spot target = walker.next(); + lSpotSelection.add( target ); + // Deal with edges + final Set< DefaultWeightedEdge > targetEdges = model.getTrackModel().edgesOf( target ); + for ( final DefaultWeightedEdge targetEdge : targetEdges ) + lEdgeSelection.add( targetEdge ); } } @@ -388,9 +346,7 @@ public void selectTrack( final Collection< Spot > spots, final Collection< Defau final Spot source = model.getTrackModel().getEdgeSource( edge ); final Spot target = model.getTrackModel().getEdgeTarget( edge ); if ( !( lSpotSelection.contains( source ) && lSpotSelection.contains( target ) ) ) - { edgesToRemove.add( edge ); - } } lEdgeSelection.removeAll( edgesToRemove ); @@ -398,5 +354,4 @@ public void selectTrack( final Collection< Spot > spots, final Collection< Defau addSpotToSelection( lSpotSelection ); addEdgeToSelection( lEdgeSelection ); } - } diff --git a/src/main/java/fiji/plugin/trackmate/TrackModel.java b/src/main/java/fiji/plugin/trackmate/TrackModel.java index bd7b8cb4c..0173ece42 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackModel.java +++ b/src/main/java/fiji/plugin/trackmate/TrackModel.java @@ -518,6 +518,14 @@ public boolean isVisible( final Integer ID ) /** * Returns the set of track IDs managed by this model, ordered by track * names (alpha-numerically sorted). + *

    + * The set maintains the order of the track IDs, so that iterating over it + * will return the track IDs in the order of their names. It is possible to + * add it to a {@link java.util.List} to facilitate navigating the ids: + *

    +	 * List< String > trackIDList = new ArrayList<>( trackIDs );
    +	 * ListIterator< String > iterator = trackIDList.listIterator();
    +	 * 
    * * @param visibleOnly * if true, only visible track IDs will be returned. @@ -733,27 +741,43 @@ public String echo() */ /** - * Returns a new depth first iterator over the spots connected by links in - * this model. A boolean flag allow to set whether the returned iterator - * does take into account the edge direction. If true, the iterator will not - * be able to iterate backward in time. + * Returns a new, undirected depth first iterator over the spots connected + * by links in this model. The iterator will iterate backward and forward in + * time. * * @param start * the spot to start iteration with. Can be null, * then the start will be taken randomly and will traverse all * the links. - * @param directed - * if true returns a directed iterator, undirected if false. * @return a new depth-first iterator. */ - public GraphIterator< Spot, DefaultWeightedEdge > getDepthFirstIterator( final Spot start, final boolean directed ) + public GraphIterator< Spot, DefaultWeightedEdge > getDepthFirstIterator( final Spot start ) { - if ( directed ) - return new TimeDirectedDepthFirstIterator( graph, start ); - return new DepthFirstIterator<>( graph, start ); } + /** + * Returns a new, directed depth first iterator over the spots connected by + * links in this model. + *

    + * The iterator will iterate forward in time only. If the boolean flag + * reversed is set to true, the iterator will + * iterate backward in time only. + * + * @param start + * the spot to start iteration with. Can be null, + * then the start will be taken randomly and will traverse all + * the links. + * @param reversed + * if true, the iterator will iterate backward in + * time only, otherwise it will iterate forward in time only. + * @return a new depth-first iterator. + */ + public GraphIterator< Spot, DefaultWeightedEdge > getDirectedDepthFirstIterator( final Spot start, final boolean reversed ) + { + return new TimeDirectedDepthFirstIterator( graph, start, reversed ); + } + /** * Returns a new depth first iterator over the spots connected by links in * this model. This iterator is sorted: when branching, it chooses the next diff --git a/src/main/java/fiji/plugin/trackmate/graph/TimeDirectedDepthFirstIterator.java b/src/main/java/fiji/plugin/trackmate/graph/TimeDirectedDepthFirstIterator.java index 68a42a757..eebdcb391 100644 --- a/src/main/java/fiji/plugin/trackmate/graph/TimeDirectedDepthFirstIterator.java +++ b/src/main/java/fiji/plugin/trackmate/graph/TimeDirectedDepthFirstIterator.java @@ -24,47 +24,47 @@ */ package fiji.plugin.trackmate.graph; -import fiji.plugin.trackmate.Spot; - import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.graph.DefaultWeightedEdge; +import fiji.plugin.trackmate.Spot; + public class TimeDirectedDepthFirstIterator extends SortedDepthFirstIterator< Spot, DefaultWeightedEdge > { - public TimeDirectedDepthFirstIterator( Graph< Spot, DefaultWeightedEdge > g, Spot startVertex ) + private final boolean reversed; + + public TimeDirectedDepthFirstIterator( final Graph< Spot, DefaultWeightedEdge > g, final Spot startVertex ) + { + this( g, startVertex, false ); + } + + public TimeDirectedDepthFirstIterator( final Graph< Spot, DefaultWeightedEdge > g, final Spot startVertex, final boolean reversed ) { super( g, startVertex, null ); + this.reversed = reversed; } @Override - protected void addUnseenChildrenOf( Spot vertex ) + protected void addUnseenChildrenOf( final Spot vertex ) { - int ts = vertex.getFeature( Spot.FRAME ).intValue(); - for ( DefaultWeightedEdge edge : specifics.edgesOf( vertex ) ) + final int ts = vertex.getFeature( Spot.FRAME ).intValue(); + for ( final DefaultWeightedEdge edge : specifics.edgesOf( vertex ) ) { if ( nListeners != 0 ) - { fireEdgeTraversed( createEdgeTraversalEvent( edge ) ); - } - Spot oppositeV = Graphs.getOppositeVertex( graph, edge, vertex ); - int tt = oppositeV.getFeature( Spot.FRAME ).intValue(); - if ( tt <= ts ) - { + final Spot oppositeV = Graphs.getOppositeVertex( graph, edge, vertex ); + final int tt = oppositeV.getFeature( Spot.FRAME ).intValue(); + if ( reversed ? tt >= ts : tt <= ts ) continue; - } if ( seen.containsKey( oppositeV ) ) - { encounterVertexAgain( oppositeV, edge ); - } else - { encounterVertex( oppositeV, edge ); - } } } diff --git a/src/test/java/fiji/plugin/trackmate/action/CloseGapsByLinearInterpolationActionTest.java b/src/test/java/fiji/plugin/trackmate/action/CloseGapsByLinearInterpolationActionTest.java index 5ccc8d78a..b2c83cb07 100644 --- a/src/test/java/fiji/plugin/trackmate/action/CloseGapsByLinearInterpolationActionTest.java +++ b/src/test/java/fiji/plugin/trackmate/action/CloseGapsByLinearInterpolationActionTest.java @@ -74,7 +74,7 @@ public void testIfGapsInLinearTracksAreClosed() final TrackModel trackModel = model.getTrackModel(); // Check if positions were interpolated in the right way - final GraphIterator< Spot, DefaultWeightedEdge > spots = trackModel.getDepthFirstIterator( spot0, true ); + final GraphIterator< Spot, DefaultWeightedEdge > spots = trackModel.getDirectedDepthFirstIterator( spot0, false ); final double[][] referencePositions = { { 0, 0 }, { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 } }; @@ -116,7 +116,7 @@ public void testIfGapsInDividingTracksAreClosed() final TrackModel trackModel = model.getTrackModel(); // Check if positions were interpolated in the right way - final GraphIterator< Spot, DefaultWeightedEdge > spots = trackModel.getDepthFirstIterator( spot0, true ); + final GraphIterator< Spot, DefaultWeightedEdge > spots = trackModel.getDirectedDepthFirstIterator( spot0, false ); final double[][] referencePositions = { { 0, 0 }, { 1, 1 }, { 2, 2 }, { 4, 4 }, { 6, 6 }, { 8, 8 }, { 3, 3 }, { 4, 4 }, { 5, 5 } }; @@ -158,7 +158,7 @@ public void testIfGapsInDividingBackwardsTracksAreClosed() final TrackModel trackModel = model.getTrackModel(); // Check if positions were interpolated in the right way - final GraphIterator< Spot, DefaultWeightedEdge > spots = trackModel.getDepthFirstIterator( spot0, false ); + final GraphIterator< Spot, DefaultWeightedEdge > spots = trackModel.getDepthFirstIterator( spot0 ); final double[][] referencePositions = { { 0, 0 }, { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 }, { 4, 4 }, { 6, 6 }, { 8, 8 } }; diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java b/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java index f8d32ad63..767cc29a2 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java @@ -56,7 +56,7 @@ private static void pickLeavesOfOneTrack( final Model model ) final TreeSet< Spot > spots = new TreeSet<>( Spot.frameComparator ); spots.addAll( model.getTrackModel().vertexSet() ); final Spot first = spots.first(); - final GraphIterator< Spot, DefaultWeightedEdge > iterator = model.getTrackModel().getDepthFirstIterator( first, true ); + final GraphIterator< Spot, DefaultWeightedEdge > iterator = model.getTrackModel().getDirectedDepthFirstIterator( first, false ); while ( iterator.hasNext() ) { From e01cecc6f1137ed654c84bbf300524d1ab618264 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 11:02:16 +0200 Subject: [PATCH 269/371] Add methods to navigate to a root an to a leaf of a track. --- .../plugin/trackmate/util/TrackNavigator.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/util/TrackNavigator.java b/src/main/java/fiji/plugin/trackmate/util/TrackNavigator.java index 5707ee04b..72fb323a6 100644 --- a/src/main/java/fiji/plugin/trackmate/util/TrackNavigator.java +++ b/src/main/java/fiji/plugin/trackmate/util/TrackNavigator.java @@ -26,6 +26,7 @@ import java.util.TreeSet; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.traverse.GraphIterator; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; @@ -234,6 +235,52 @@ public synchronized void nextInTime() } } + public synchronized void root() + { + final Spot spot = getASpot(); + if ( null == spot ) + return; + + final GraphIterator< Spot, DefaultWeightedEdge > it = model.getTrackModel().getDirectedDepthFirstIterator( spot, true ); + NEXT_SPOT: while ( it.hasNext() ) + { + final Spot next = it.next(); + final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( next ); + for ( final DefaultWeightedEdge edge : edges ) + { + if ( model.getTrackModel().getEdgeTarget( edge ).equals( next ) ) + continue NEXT_SPOT; + } + + selectionModel.clearSelection(); + selectionModel.addSpotToSelection( next ); + return; + } + } + + public synchronized void leaf() + { + final Spot spot = getASpot(); + if ( null == spot ) + return; + + final GraphIterator< Spot, DefaultWeightedEdge > it = model.getTrackModel().getDirectedDepthFirstIterator( spot, false ); + NEXT_SPOT: while ( it.hasNext() ) + { + final Spot next = it.next(); + final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( next ); + for ( final DefaultWeightedEdge edge : edges ) + { + if ( model.getTrackModel().getEdgeSource( edge ).equals( next ) ) + continue NEXT_SPOT; + } + + selectionModel.clearSelection(); + selectionModel.addSpotToSelection( next ); + return; + } + } + /* * STATIC METHODS */ From eead56762c78fe11550d9020eb38ea92618b3969 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 11:07:38 +0200 Subject: [PATCH 270/371] Move the spot edit bahviour classes inside a utility class. --- .../behaviours/AbstractSpotEditBehaviour.java | 45 ----- .../behaviours/MoveSpotBehaviour.java | 51 ------ .../behaviours/ResizeSpotBehaviour.java | 73 -------- .../behaviours/SpotEditBehaviours.java | 171 ++++++++++++++++++ 4 files changed, 171 insertions(+), 169 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MoveSpotBehaviour.java delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ResizeSpotBehaviour.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java deleted file mode 100644 index de09c45c9..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java +++ /dev/null @@ -1,45 +0,0 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.util.TMUtils; -import ij.ImagePlus; -import ij.gui.ImageCanvas; -import net.imglib2.RealLocalizable; -import net.imglib2.RealPoint; - -public class AbstractSpotEditBehaviour -{ - - protected Spot movedSpot; - - protected final Model model; - - protected final ImagePlus imp; - - protected final double[] calibration; - - - public AbstractSpotEditBehaviour( final Model model, final ImagePlus imp ) - { - this.model = model; - this.imp = imp; - this.calibration = TMUtils.getSpatialCalibration( imp ); - } - - protected Spot getSpotAtMouseLocation( final RealLocalizable pos ) - { - final int frame = imp.getFrame() - 1; - return model.getSpots().getSpotAt( pos, frame, true ); - } - - protected RealLocalizable toWorldCoords( final int x, final int y ) - { - final ImageCanvas canvas = imp.getCanvas(); - final double xw = ( -0.5 + canvas.offScreenXD( x ) ) * calibration[ 0 ]; - final double yw = ( -0.5 + canvas.offScreenYD( y ) ) * calibration[ 1 ]; - final double zw = ( imp.getSlice() - 1 ) * calibration[ 2 ]; - return new RealPoint( xw, yw, zw ); - } - -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MoveSpotBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MoveSpotBehaviour.java deleted file mode 100644 index d8d50d71f..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MoveSpotBehaviour.java +++ /dev/null @@ -1,51 +0,0 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; - -import org.scijava.ui.behaviour.DragBehaviour; - -import fiji.plugin.trackmate.Model; -import ij.ImagePlus; -import net.imglib2.RealLocalizable; - -public class MoveSpotBehaviour extends AbstractSpotEditBehaviour implements DragBehaviour -{ - - /** Offset between mouse click and spot center, in world coordinates. */ - private final double[] delta = new double[ 2 ]; - - public MoveSpotBehaviour( final Model model, final ImagePlus imp ) - { - super( model, imp ); - } - - @Override - public void init( final int x, final int y ) - { - if ( null != movedSpot ) - return; - final RealLocalizable pos = toWorldCoords( x, y ); - movedSpot = getSpotAtMouseLocation( pos ); - if ( null == movedSpot ) - return; - model.beginUpdate(); - model.beforeEdit( movedSpot ); - delta[ 0 ] = movedSpot.getDoublePosition( 0 ) - pos.getDoublePosition( 0 ); - delta[ 1 ] = movedSpot.getDoublePosition( 1 ) - pos.getDoublePosition( 1 ); - } - - @Override - public void drag( final int x, final int y ) - { - final RealLocalizable pos = toWorldCoords( x, y ); - movedSpot.setPosition( pos.getDoublePosition( 0 ) + delta[ 0 ], 0 ); - movedSpot.setPosition( pos.getDoublePosition( 1 ) + delta[ 1 ], 1 ); - imp.updateAndDraw(); - } - - @Override - public void end( final int x, final int y ) - { - model.endUpdate(); - movedSpot = null; - imp.updateAndDraw(); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ResizeSpotBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ResizeSpotBehaviour.java deleted file mode 100644 index ac489bb9f..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ResizeSpotBehaviour.java +++ /dev/null @@ -1,73 +0,0 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; - -import org.scijava.ui.behaviour.ClickBehaviour; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.Spot; -import ij.ImagePlus; - -public class ResizeSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour -{ - - /** - * Fall back default radius when the settings does not give a default radius - * to use. - */ - private static final double FALL_BACK_RADIUS = 5.; - - private static final double COARSE_STEP = 2; - - private static final double FINE_STEP = 0.2f; - - private final boolean increase; - - private final boolean fast; - - /** The previous radius to be used for spot creation. */ - static double previousRadius = FALL_BACK_RADIUS; - - public ResizeSpotBehaviour( final Model model, final ImagePlus imp, final boolean increase, final boolean fast ) - { - super( model, imp ); - this.increase = increase; - this.fast = fast; - } - - @Override - public void click( final int x, final int y ) - { - final Spot spot = getSpotAtMouseLocation( toWorldCoords( x, y ) ); - if ( null == spot ) - return; - - // Compute new radius. - final double radius = spot.getFeature( Spot.RADIUS ); - 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; - - // Actually scale the spot. - model.beginUpdate(); - try - { - model.beforeEdit( spot ); - spot.scale( radius / newRadius ); - // Store new value of radius for next spot creation. - previousRadius = newRadius; - } - catch ( final Exception e ) - { - e.printStackTrace(); - } - finally - { - model.endUpdate(); - } - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java new file mode 100644 index 000000000..d8a4b657a --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java @@ -0,0 +1,171 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import org.scijava.ui.behaviour.ClickBehaviour; +import org.scijava.ui.behaviour.DragBehaviour; +import org.scijava.ui.behaviour.util.Behaviours; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.util.TMUtils; +import ij.ImagePlus; +import ij.gui.ImageCanvas; +import net.imglib2.RealLocalizable; +import net.imglib2.RealPoint; + +public class SpotEditBehaviours +{ + + public static final void install( final Behaviours behaviours, final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + behaviours.behaviour( new MoveSpotBehaviour( model, imp ), "move-spot", "SPACE" ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, false ), "increase-spot-radius", "E" ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, true ), "increase-spot-radius-fast", "shift E" ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, false ), "decrease-spot-radius", "Q" ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, true ), "decrease-spot-radius-fast", "shift Q" ); + } + + private static class AbstractSpotEditBehaviour + { + + protected Spot movedSpot; + + protected final Model model; + + protected final ImagePlus imp; + + protected final double[] calibration; + + public AbstractSpotEditBehaviour( final Model model, final ImagePlus imp ) + { + this.model = model; + this.imp = imp; + this.calibration = TMUtils.getSpatialCalibration( imp ); + } + + protected Spot getSpotAtMouseLocation( final RealLocalizable pos ) + { + final int frame = imp.getFrame() - 1; + return model.getSpots().getSpotAt( pos, frame, true ); + } + + protected RealLocalizable toWorldCoords( final int x, final int y ) + { + final ImageCanvas canvas = imp.getCanvas(); + final double xw = ( -0.5 + canvas.offScreenXD( x ) ) * calibration[ 0 ]; + final double yw = ( -0.5 + canvas.offScreenYD( y ) ) * calibration[ 1 ]; + final double zw = ( imp.getSlice() - 1 ) * calibration[ 2 ]; + return new RealPoint( xw, yw, zw ); + } + } + + private static class MoveSpotBehaviour extends AbstractSpotEditBehaviour implements DragBehaviour + { + + /** Offset between mouse click and spot center, in world coordinates. */ + private final double[] delta = new double[ 2 ]; + + public MoveSpotBehaviour( final Model model, final ImagePlus imp ) + { + super( model, imp ); + } + + @Override + public void init( final int x, final int y ) + { + if ( null != movedSpot ) + return; + final RealLocalizable pos = toWorldCoords( x, y ); + movedSpot = getSpotAtMouseLocation( pos ); + if ( null == movedSpot ) + return; + model.beginUpdate(); + model.beforeEdit( movedSpot ); + delta[ 0 ] = movedSpot.getDoublePosition( 0 ) - pos.getDoublePosition( 0 ); + delta[ 1 ] = movedSpot.getDoublePosition( 1 ) - pos.getDoublePosition( 1 ); + } + + @Override + public void drag( final int x, final int y ) + { + final RealLocalizable pos = toWorldCoords( x, y ); + movedSpot.setPosition( pos.getDoublePosition( 0 ) + delta[ 0 ], 0 ); + movedSpot.setPosition( pos.getDoublePosition( 1 ) + delta[ 1 ], 1 ); + imp.updateAndDraw(); + } + + @Override + public void end( final int x, final int y ) + { + model.endUpdate(); + movedSpot = null; + imp.updateAndDraw(); + } + } + + private static class ResizeSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour + { + + /** + * Fall back default radius when the settings does not give a default + * radius to use. + */ + private static final double FALL_BACK_RADIUS = 5.; + + private static final double COARSE_STEP = 2; + + private static final double FINE_STEP = 0.2f; + + private final boolean increase; + + private final boolean fast; + + /** The previous radius to be used for spot creation. */ + static double previousRadius = FALL_BACK_RADIUS; + + public ResizeSpotBehaviour( final Model model, final ImagePlus imp, final boolean increase, final boolean fast ) + { + super( model, imp ); + this.increase = increase; + this.fast = fast; + } + + @Override + public void click( final int x, final int y ) + { + final Spot spot = getSpotAtMouseLocation( toWorldCoords( x, y ) ); + if ( null == spot ) + return; + + // Compute new radius. + final double radius = spot.getFeature( Spot.RADIUS ); + 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; + + // Actually scale the spot. + model.beginUpdate(); + try + { + model.beforeEdit( spot ); + spot.scale( radius / newRadius ); + // Store new value of radius for next spot creation. + previousRadius = newRadius; + } + catch ( final Exception e ) + { + e.printStackTrace(); + } + finally + { + model.endUpdate(); + } + } + } +} From 4c722441d1758c23ead0a63346d24b6f656ad850 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 17:44:48 +0200 Subject: [PATCH 271/371] Move the AbstractSpotEditBehaviour to its own class. --- .../behaviours/AbstractSpotEditBehaviour.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java new file mode 100644 index 000000000..8e098143e --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AbstractSpotEditBehaviour.java @@ -0,0 +1,49 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.util.TMUtils; +import ij.ImagePlus; +import ij.gui.ImageCanvas; +import net.imglib2.RealLocalizable; +import net.imglib2.RealPoint; + +public class AbstractSpotEditBehaviour +{ + + protected final Model model; + + protected final ImagePlus imp; + + protected final double[] calibration; + + public AbstractSpotEditBehaviour( final Model model, final ImagePlus imp ) + { + this.model = model; + this.imp = imp; + this.calibration = TMUtils.getSpatialCalibration( imp ); + } + + protected Spot getSpotAtMouseLocation( final RealLocalizable pos ) + { + final int frame = imp.getFrame() - 1; + return model.getSpots().getSpotAt( pos, frame, true ); + } + + protected RealLocalizable toWorldCoords( final int x, final int y ) + { + final ImageCanvas canvas = imp.getCanvas(); + final double xw = ( -0.5 + canvas.offScreenXD( x ) ) * calibration[ 0 ]; + final double yw = ( -0.5 + canvas.offScreenYD( y ) ) * calibration[ 1 ]; + final double zw = ( imp.getSlice() - 1 ) * calibration[ 2 ]; + return new RealPoint( xw, yw, zw ); + } + + protected RealLocalizable toScreenCoords( final RealLocalizable pos ) + { + final ImageCanvas canvas = imp.getCanvas(); + final double xs = canvas.screenXD( pos.getDoublePosition( 0 ) / calibration[ 0 ] + 0.5 ); + final double ys = canvas.screenYD( pos.getDoublePosition( 1 ) / calibration[ 1 ] + 0.5 ); + return new RealPoint( xs, ys ); + } +} \ No newline at end of file From 89f300f85171986e411fc68d867dd90ec2ed4d9e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 17:45:15 +0200 Subject: [PATCH 272/371] A drag behaviour, to toggle links between spots. Strongly inspired from what we have in Mastodon. --- .../behaviours/LinkSpotsBehaviour.java | 441 ++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java new file mode 100644 index 000000000..de5b890b3 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java @@ -0,0 +1,441 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import java.awt.BasicStroke; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Path2D; + +import org.scijava.ui.behaviour.DragBehaviour; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import ij.ImagePlus; +import ij.gui.Roi; +import net.imglib2.KDTree; +import net.imglib2.RealLocalizable; +import net.imglib2.neighborsearch.NearestNeighborSearchOnKDTree; + +public class LinkSpotsBehaviour extends AbstractSpotEditBehaviour implements DragBehaviour +{ + + private static final String OVERLAY_NAME = "LinkSpotActionOverlay"; + + private final boolean backward; + + private Spot source; + + private Spot target; + + private NearestNeighborSearchOnKDTree< Spot > search; + + private final LinkSpotsOverlay overlay; + + public LinkSpotsBehaviour( final Model model, final ImagePlus imp, final boolean backward ) + { + super( model, imp ); + this.backward = backward; + this.overlay = new LinkSpotsOverlay( imp ); + } + + @Override + public void init( final int x, final int y ) + { + if ( source != null ) + return; + + final RealLocalizable pos = toWorldCoords( x, y ); + final Spot spot = getSpotAtMouseLocation( pos ); + if ( spot == null ) + return; + + // Cannot link if at first frame and backward, etc. + final int frame = spot.getFeature( Spot.FRAME ).intValue(); + if ( backward && frame == 0 ) + return; + if ( !backward && frame == imp.getNFrames() - 1 ) + return; + + // Keep track of the source. + this.source = spot; + overlay.source = source; + + // Move to next frame if forward, previous frame if backward. + final int targetFrame = backward ? frame - 1 : frame + 1; + + // Build KD-tree of target frame spots. + final Iterable< Spot > targetSpots = model.getSpots().iterable( targetFrame, true ); + final int nTargetSpots = model.getSpots().getNSpots( targetFrame, true ); + final KDTree< Spot > tree = new KDTree< Spot >( nTargetSpots, targetSpots, targetSpots ); + this.search = new NearestNeighborSearchOnKDTree<>( tree ); + + overlay.targetPixelPos[ 0 ] = x; + overlay.targetPixelPos[ 1 ] = y; + imp.setT( targetFrame + 1 ); + imp.getOverlay().add( overlay, OVERLAY_NAME ); + imp.updateAndDraw(); + } + + @Override + public void drag( final int x, final int y ) + { + search.search( toWorldCoords( x, y ) ); + final Spot spot = search.getSampler().get(); + final double r = spot.getFeature( Spot.RADIUS ); + if ( search.getSquareDistance() < r * r ) + { + target = spot; + final RealLocalizable screenPos = toScreenCoords( target ); + overlay.targetPixelPos[ 0 ] = ( int ) Math.round( screenPos.getDoublePosition( 0 ) ); + overlay.targetPixelPos[ 1 ] = ( int ) Math.round( screenPos.getDoublePosition( 1 ) ); + + // Is there an existing link between source and target? + overlay.crossedLine.crossed = model.getTrackModel().containsEdge( source, target ); + } + else + { + target = null; + overlay.targetPixelPos[ 0 ] = x; + overlay.targetPixelPos[ 1 ] = y; + overlay.crossedLine.crossed = false; + } + overlay.target = target; + imp.updateAndDraw(); + } + + @Override + public void end( final int x, final int y ) + { + try + { + if ( target == null ) + return; + + model.beginUpdate(); + try + { + // Add or remove? + if ( model.getTrackModel().containsEdge( source, target ) ) + { + model.removeEdge( source, target ); + } + else + { + if ( backward ) + model.addEdge( target, source, -1 ); + else + model.addEdge( source, target, -1 ); + } + } + finally + { + model.endUpdate(); + } + } + finally + { + source = null; + target = null; + search = null; + overlay.source = null; + overlay.target = null; + imp.updateAndDraw(); + imp.getOverlay().remove( OVERLAY_NAME ); + } + } + + private class LinkSpotsOverlay extends Roi + { + + private static final long serialVersionUID = 1L; + + private static final Stroke sourceStroke = new BasicStroke( 1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, new float[] { 5f, 5f }, 0.0f ); + + private static final Stroke targetStroke = new BasicStroke( 1f ); + + private Spot source; + + private Spot target; + + public final int[] targetPixelPos = new int[ 2 ]; + + private final int[] bb = new int[ 4 ]; + + private final ArrowShape arrow = new ArrowShape(); + + private final CrossedLineShape crossedLine = new CrossedLineShape(); + + public LinkSpotsOverlay( final ImagePlus imp ) + { + super( 0, 0, imp ); + } + + @Override + public void drawOverlay( final Graphics g ) + { + if ( source == null ) + return; + + final int xcorner = ic.offScreenX( 0 ); + final int ycorner = ic.offScreenY( 0 ); + final double magnification = getMagnification(); + final Graphics2D g2d = ( Graphics2D ) g; + + // Source bounding box + boundingBox( source, xcorner, ycorner, magnification ); + g2d.setStroke( sourceStroke ); + g2d.drawRect( bb[ 0 ], bb[ 1 ], bb[ 2 ], bb[ 3 ] ); + + // Arrow to current pos. + if ( backward ) + { + arrow.x1d = targetPixelPos[ 0 ]; + arrow.y1d = targetPixelPos[ 1 ]; + arrow.x2d = bb[ 0 ] + bb[ 2 ] / 2; + arrow.y2d = bb[ 1 ] + bb[ 3 ] / 2; + } + else + { + arrow.x1d = bb[ 0 ] + bb[ 2 ] / 2; + arrow.y1d = bb[ 1 ] + bb[ 3 ] / 2; + arrow.x2d = targetPixelPos[ 0 ]; + arrow.y2d = targetPixelPos[ 1 ]; + } + crossedLine.x1d = arrow.x1d; + crossedLine.y1d = arrow.y1d; + crossedLine.x2d = arrow.x2d; + crossedLine.y2d = arrow.y2d; + + g2d.setStroke( targetStroke ); + g2d.draw( crossedLine.getPath() ); + if ( !crossedLine.crossed ) + g2d.fill( arrow.getPath() ); + + // Target bounding box + if ( target != null ) + { + boundingBox( target, xcorner, ycorner, magnification ); + g2d.drawRect( bb[ 0 ], bb[ 1 ], bb[ 2 ], bb[ 3 ] ); + } + } + + private final void boundingBox( + final Spot spot, + final double xcorner, + final double ycorner, + final double magnification ) + { + // Pixel coords. + final double xpmin = spot.realMin( 0 ) / calibration[ 0 ] + 0.5f; + final double ypmin = spot.realMin( 1 ) / calibration[ 1 ] + 0.5f; + final double xpmax = spot.realMax( 0 ) / calibration[ 0 ] + 0.5f; + final double ypmax = spot.realMax( 1 ) / calibration[ 1 ] + 0.5f; + // Display window coordinates. + final double xsmin = ( xpmin - xcorner ) * magnification; + final double ysmin = ( ypmin - ycorner ) * magnification; + final double xsmax = ( xpmax - xcorner ) * magnification; + final double ysmax = ( ypmax - ycorner ) * magnification; + bb[ 0 ] = ( int ) Math.round( xsmin ); + bb[ 1 ] = ( int ) Math.round( ysmin ); + bb[ 2 ] = ( int ) Math.round( xsmax - xsmin ); + bb[ 3 ] = ( int ) Math.round( ysmax - ysmin ); + } + } + + /** + * Adapted from ImageJ, but the code below was adapted from mine: "Based on + * the method with the same name in Fiji's Arrow plugin, written by + * Jean-Yves Tinevez and Johannes Schindelin." + */ + private static class ArrowShape + { + + public static final int FILLED = 0, NOTCHED = 1, OPEN = 2, HEADLESS = 3, BAR = 4; + + private static final int style = FILLED; + + private static final boolean outline = false; + + private final double headSize = 10; // 0-30 + + private final double[] points = new double[ 2 * 5 ]; + + private double x1d, y1d, x2d, y2d; + + private final Path2D.Double path = new Path2D.Double(); + + @SuppressWarnings( "unused" ) + private void calculatePoints() + { + double tip = 0.0; + double base; + final double shaftWidth = 1.; + double length = 8 + 10 * shaftWidth * 0.5; + length = length * ( headSize / 10.0 ); + length -= shaftWidth * 1.42; + if ( style == NOTCHED ) + length *= 0.74; + if ( style == OPEN ) + length *= 1.32; + if ( length < 0.0 || style == HEADLESS ) + length = 0.0; + + double dx = x2d - x1d, dy = y2d - y1d; + final double arrowLength = Math.sqrt( dx * dx + dy * dy ); + dx = dx / arrowLength; + dy = dy / arrowLength; + if ( style != HEADLESS ) + { + points[ 0 ] = ( float ) ( x1d + dx * shaftWidth * 2.0 ); + points[ 1 ] = ( float ) ( y1d + dy * shaftWidth * 2.0 ); + } + else + { + points[ 0 ] = ( float ) x1d; + points[ 1 ] = ( float ) y1d; + } + if ( length > 0 ) + { + final double factor = style == OPEN ? 1.3 : 1.42; + points[ 2 * 3 ] = ( float ) ( x2d - dx * shaftWidth * factor ); + points[ 2 * 3 + 1 ] = ( float ) ( y2d - dy * shaftWidth * factor ); + if ( style == BAR ) + { + points[ 2 * 3 ] = ( float ) ( x2d - dx * shaftWidth * 0.5 ); + points[ 2 * 3 + 1 ] = ( float ) ( y2d - dy * shaftWidth * 0.5 ); + } + } + else + { + points[ 2 * 3 ] = ( float ) x2d; + points[ 2 * 3 + 1 ] = ( float ) y2d; + } + final double alpha = Math.atan2( points[ 2 * 3 + 1 ] - points[ 1 ], points[ 2 * 3 ] - points[ 0 ] ); + double SL = 0.0; + switch ( style ) + { + case FILLED: + case HEADLESS: + tip = Math.toRadians( 20.0 ); + base = Math.toRadians( 90.0 ); + points[ 1 * 2 ] = ( float ) ( points[ 2 * 3 ] - length * Math.cos( alpha ) ); + points[ 1 * 2 + 1 ] = ( float ) ( points[ 2 * 3 + 1 ] - length * Math.sin( alpha ) ); + SL = length * Math.sin( base ) / Math.sin( base + tip );; + break; + case NOTCHED: + tip = Math.toRadians( 20 ); + base = Math.toRadians( 120 ); + points[ 1 * 2 ] = ( float ) ( points[ 2 * 3 ] - length * Math.cos( alpha ) ); + points[ 1 * 2 + 1 ] = ( float ) ( points[ 2 * 3 + 1 ] - length * Math.sin( alpha ) ); + SL = length * Math.sin( base ) / Math.sin( base + tip );; + break; + case OPEN: + tip = Math.toRadians( 25 ); // 30 + points[ 1 * 2 ] = points[ 2 * 3 ]; + points[ 1 * 2 + 1 ] = points[ 2 * 3 + 1 ]; + SL = length; + break; + case BAR: + tip = Math.toRadians( 90 ); // 30 + points[ 1 * 2 ] = points[ 2 * 3 ]; + points[ 1 * 2 + 1 ] = points[ 2 * 3 + 1 ]; + SL = length; + break; + } + // P2 = P3 - SL*alpha+tip + points[ 2 * 2 ] = ( float ) ( points[ 2 * 3 ] - SL * Math.cos( alpha + tip ) ); + points[ 2 * 2 + 1 ] = ( float ) ( points[ 2 * 3 + 1 ] - SL * Math.sin( alpha + tip ) ); + // P4 = P3 - SL*alpha-tip + points[ 2 * 4 ] = ( float ) ( points[ 2 * 3 ] - SL * Math.cos( alpha - tip ) ); + points[ 2 * 4 + 1 ] = ( float ) ( points[ 2 * 3 + 1 ] - SL * Math.sin( alpha - tip ) ); + } + + @SuppressWarnings( "unused" ) + private Shape getPath() + { + path.reset(); + calculatePoints(); + final double tailx = points[ 0 ]; + final double taily = points[ 1 ]; + final double headbackx = points[ 2 * 1 ]; + final double headbacky = points[ 2 * 1 + 1 ]; + final double headtipx = points[ 2 * 3 ]; + final double headtipy = points[ 2 * 3 + 1 ]; + if ( outline ) + { + double dx = headtipx - tailx; + double dy = headtipy - taily; + final double shaftLength = Math.sqrt( dx * dx + dy * dy ); + dx = headtipx - headbackx; + dy = headtipy - headbacky; + final double headLength = Math.sqrt( dx * dx + dy * dy ); + } + path.moveTo( tailx, taily ); // tail + path.lineTo( headbackx, headbacky ); // head back + path.moveTo( headbackx, headbacky ); // head back + if ( style == OPEN ) + path.moveTo( points[ 2 * 2 ], points[ 2 * 2 + 1 ] ); + else + path.lineTo( points[ 2 * 2 ], points[ 2 * 2 + 1 ] ); + path.lineTo( headtipx, headtipy ); // head tip + path.lineTo( points[ 2 * 4 ], points[ 2 * 4 + 1 ] ); // right point + path.lineTo( headbackx, headbacky ); // back to the head back + return path; + } + } + + private static class CrossedLineShape + { + + private final Path2D.Double path = new Path2D.Double( Path2D.WIND_NON_ZERO ); + + private final double spacing = 20.; + + private final double crossSize = 10.; + + private double x1d, y1d, x2d, y2d; + + private boolean crossed = false; + + private Path2D.Double getPath() + { + path.reset(); + path.moveTo( x1d, y1d ); + path.lineTo( x2d, y2d ); + + if ( !crossed ) + return path; + + final double dx = x2d - x1d; + final double dy = y2d - y1d; + final double lineLength = Math.hypot( dx, dy ); + if ( lineLength < spacing ) + return path; + + final double ux = dx / lineLength; + final double uy = dy / lineLength; + final double px = -uy; + final double py = ux; + + final double scale = ( crossSize / 2.0 ) * Math.cos( Math.toRadians( 45 ) ); + final double d1x = ( ux + px ) * scale; + final double d1y = ( uy + py ) * scale; + final double d2x = ( ux - px ) * scale; + final double d2y = ( uy - py ) * scale; + + for ( double d = 0; d <= lineLength; d += spacing ) + { + final double cx = x1d + d * ux; + final double cy = y1d + d * uy; + + path.moveTo( cx - d1x, cy - d1y ); + path.lineTo( cx + d1x, cy + d1y ); + + path.moveTo( cx - d2x, cy - d2y ); + path.lineTo( cx + d2x, cy + d2y ); + } + return path; + } + } +} From 309e5afe1d565d05006f09f4f00504a3edb391d2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 17:46:01 +0200 Subject: [PATCH 273/371] Add and register behaviours to add, delete and link spots. --- .../behaviours/SpotEditBehaviours.java | 151 ++++++++++++++---- 1 file changed, 123 insertions(+), 28 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java index d8a4b657a..1ff9f511f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java @@ -1,5 +1,7 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours; +import java.util.Set; + import org.scijava.ui.behaviour.ClickBehaviour; import org.scijava.ui.behaviour.DragBehaviour; import org.scijava.ui.behaviour.util.Behaviours; @@ -7,64 +9,157 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.SpotBase; import ij.ImagePlus; -import ij.gui.ImageCanvas; import net.imglib2.RealLocalizable; -import net.imglib2.RealPoint; public class SpotEditBehaviours { + + private static final String MOVE_SPOT = "move spot"; + private static final String INCREASE_SPOT_RADIUS = "increase spot radius"; + private static final String INCREASE_SPOT_RADIUS_FAST = "increase spot radius fast"; + private static final String DECREASE_SPOT_RADIUS = "decrease spot radius"; + private static final String DECREASE_SPOT_RADIUS_FAST = "decrease spot radius fast"; + private static final String ADD_SPOT = "add spot"; + private static final String DELETE_SPOT = "delete spot"; + private static final String LINK_SPOTS = "link spots"; + private static final String LINK_SPOTS_BACKWARD = "link spots backward"; + + private static final String[] MOVE_SPOT_KEYS = new String[] { "SPACE" }; + private static final String[] INCREASE_SPOT_RADIUS_KEYS = new String[] { "E" }; + private static final String[] INCREASE_SPOT_RADIUS_FAST_KEYS = new String[] { "shift E" }; + private static final String[] DECREASE_SPOT_RADIUS_KEYS = new String[] { "Q" }; + private static final String[] DECREASE_SPOT_RADIUS_FAST_KEYS = new String[] { "shift Q" }; + private static final String[] ADD_SPOT_KEYS = new String[] { "A" }; + private static final String[] DELETE_SPOT_KEYS = new String[] { "D" }; + private static final String[] LINK_SPOTS_KEYS = new String[] { "L" }; + private static final String[] LINK_SPOTS_BACKWARD_KEYS = new String[] { "shift L" }; + + + static boolean autoLinkingmode = false; public static final void install( final Behaviours behaviours, final Model model, final SelectionModel selectionModel, final ImagePlus imp ) { - behaviours.behaviour( new MoveSpotBehaviour( model, imp ), "move-spot", "SPACE" ); - behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, false ), "increase-spot-radius", "E" ); - behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, true ), "increase-spot-radius-fast", "shift E" ); - behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, false ), "decrease-spot-radius", "Q" ); - behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, true ), "decrease-spot-radius-fast", "shift Q" ); + behaviours.behaviour( new MoveSpotBehaviour( model, imp ), MOVE_SPOT, MOVE_SPOT_KEYS ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, false ), INCREASE_SPOT_RADIUS, INCREASE_SPOT_RADIUS_KEYS ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, true ), INCREASE_SPOT_RADIUS_FAST, INCREASE_SPOT_RADIUS_FAST_KEYS ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, false ), DECREASE_SPOT_RADIUS, DECREASE_SPOT_RADIUS_KEYS ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, true ), DECREASE_SPOT_RADIUS_FAST, DECREASE_SPOT_RADIUS_FAST_KEYS ); + behaviours.behaviour( new AddSpotBehaviour( model, selectionModel, imp ), ADD_SPOT, ADD_SPOT_KEYS ); + behaviours.behaviour( new DeleteSpotBehaviour( model, selectionModel, imp ), DELETE_SPOT, DELETE_SPOT_KEYS ); + behaviours.behaviour( new LinkSpotsBehaviour( model, imp, false ), LINK_SPOTS, LINK_SPOTS_KEYS ); + behaviours.behaviour( new LinkSpotsBehaviour( model, imp, true ), LINK_SPOTS_BACKWARD, LINK_SPOTS_BACKWARD_KEYS ); } - private static class AbstractSpotEditBehaviour + private static class DeleteSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour { - protected Spot movedSpot; + private final SelectionModel selectionModel; + + public DeleteSpotBehaviour( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + super( model, imp ); + this.selectionModel = selectionModel; + } - protected final Model model; + @Override + public void click( final int x, final int y ) + { + final RealLocalizable pos = toWorldCoords( x, y ); + final Spot target = getSpotAtMouseLocation( pos ); + if ( null == target ) + return; - protected final ImagePlus imp; + selectionModel.removeSpotFromSelection( target ); + model.beginUpdate(); + try + { + model.removeSpot( target ); + } + finally + { + model.endUpdate(); + } + } + } + + private static class AddSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour + { - protected final double[] calibration; + private final SelectionModel selectionModel; - public AbstractSpotEditBehaviour( final Model model, final ImagePlus imp ) + public AddSpotBehaviour( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) { - this.model = model; - this.imp = imp; - this.calibration = TMUtils.getSpatialCalibration( imp ); + super( model, imp ); + this.selectionModel = selectionModel; } - protected Spot getSpotAtMouseLocation( final RealLocalizable pos ) + @Override + public void click( final int x, final int y ) { + final RealLocalizable pos = toWorldCoords( x, y ); + // Forbid adding a spot if there is already one at this location. + if ( getSpotAtMouseLocation( pos ) != null ) + return; + + final double radius = ResizeSpotBehaviour.previousRadius; + final SpotBase newSpot = new SpotBase( pos, radius, -1. ); + + final double dt = imp.getCalibration().frameInterval; final int frame = imp.getFrame() - 1; - return model.getSpots().getSpotAt( pos, frame, true ); - } + newSpot.putFeature( Spot.POSITION_T, frame * dt ); + newSpot.putFeature( Spot.FRAME, Double.valueOf( frame ) ); - protected RealLocalizable toWorldCoords( final int x, final int y ) - { - final ImageCanvas canvas = imp.getCanvas(); - final double xw = ( -0.5 + canvas.offScreenXD( x ) ) * calibration[ 0 ]; - final double yw = ( -0.5 + canvas.offScreenYD( y ) ) * calibration[ 1 ]; - final double zw = ( imp.getSlice() - 1 ) * calibration[ 2 ]; - return new RealPoint( xw, yw, zw ); + model.beginUpdate(); + try + { + model.addSpotTo( newSpot, frame ); + } + finally + { + model.endUpdate(); + } + + /* + * If we are in auto-link mode, we create an edge with spot in + * selection, if there is just one and if it is in a previous frame + */ + if ( autoLinkingmode ) + { + final Set< Spot > spotSelection = selectionModel.getSpotSelection(); + if ( spotSelection.size() == 1 ) + { + final Spot source = spotSelection.iterator().next(); + if ( newSpot.diffTo( source, Spot.FRAME ) != 0 ) + { + model.beginUpdate(); + try + { + model.addEdge( source, newSpot, -1 ); + } + finally + { + model.endUpdate(); + } + } + } + selectionModel.clearSpotSelection(); + selectionModel.addSpotToSelection( newSpot ); + } } } private static class MoveSpotBehaviour extends AbstractSpotEditBehaviour implements DragBehaviour { - /** Offset between mouse click and spot center, in world coordinates. */ + /** + * Offset between mouse click and spot center, in world coordinates. + */ private final double[] delta = new double[ 2 ]; + private Spot movedSpot; + public MoveSpotBehaviour( final Model model, final ImagePlus imp ) { super( model, imp ); From 024e91271e25e72372c595100738fb68d9de7de2 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 17:55:21 +0200 Subject: [PATCH 274/371] Add actions that for several edit actions. - undo / redo - navigate on tracks (plus the new root() and leaf()) - delete selection - toggle auto linking - and block the 'W' IJ shortcut that closes the window -.- --- .../hyperstack/HyperStackDisplayer.java | 2 +- .../behaviours/SpotEditActions.java | 105 ++++++++++++++++++ .../behaviours/TrackMateImpBehaviour.java | 31 ++++-- 3 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java 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 fca5612aa..8a6d6a527 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -167,7 +167,7 @@ public void render() */ // Print all registered key listeners to console - TrackMateImpBehaviour.install( model, imp ); + TrackMateImpBehaviour.install( model, selectionModel, imp ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java new file mode 100644 index 000000000..b756ed9e7 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java @@ -0,0 +1,105 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import java.awt.Toolkit; +import java.awt.event.InputEvent; +import java.util.ArrayList; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.scijava.ui.behaviour.util.Actions; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.util.TrackNavigator; +import ij.ImagePlus; + +public class SpotEditActions +{ + + private static final String UNDO_ACTION = "undo"; + private static final String REDO_ACTION = "redo"; + + private static final String[] UNDO_ACTION_KEYS; + private static final String[] REDO_ACTION_KEYS; + + private static final String NAVIGATE_TO_PARENT = "navigate to parent"; + private static final String NAVIGATE_TO_CHILD = "navigate to child"; + private static final String NAVIGATE_TO_PREVIOUS_SIBLING = "navigate to previous sibling"; + private static final String NAVIGATE_TO_NEXT_SIBLING = "navigate to next sibling"; + private static final String NAVIGATE_TO_ROOT = "navigate to root"; + private static final String NAVIGATE_TO_LEAF = "navigate to leaf"; + private static final String NAVIGATE_TO_PREVIOUS_TRACK = "navigate to previous track"; + private static final String NAVIGATE_TO_NEXT_TRACK = "navigate to next track"; + + private static final String DELETE_SELECTED_SPOTS = "delete selected spots"; + + private static final String TOGGLE_AUTO_LINKING = "toggle auto-linking"; + + private static final String[] NAVIGATE_TO_PARENT_KEYS = new String[] { "UP" }; + private static final String[] NAVIGATE_TO_CHILD_KEYS = new String[] { "DOWN" }; + private static final String[] NAVIGATE_TO_PREVIOUS_SIBLING_KEYS = new String[] { "LEFT" }; + private static final String[] NAVIGATE_TO_NEXT_SIBLING_KEYS = new String[] { "RIGHT" }; + private static final String[] NAVIGATE_TO_ROOT_KEYS = new String[] { "HOME", "meta UP" }; + private static final String[] NAVIGATE_TO_LEAF_KEYS = new String[] { "END", "meta DOWN" }; + private static final String[] NAVIGATE_TO_PREVIOUS_TRACK_KEYS = new String[] { "PAGE_UP" }; + private static final String[] NAVIGATE_TO_NEXT_TRACK_KEYS = new String[] { "PAGE_DOWN" }; + + private static final String[] DELETE_SELECTED_SPOTS_KEYS = new String[] { "BACK_SPACE", "DELETE" }; + + private static final String[] TOGGLE_AUTO_LINKING_KEYS = new String[] { "ctrl L" }; + + static + { + final int menuMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); + final String modifier = ( menuMask == InputEvent.CTRL_DOWN_MASK ) ? "ctrl" : "meta"; + UNDO_ACTION_KEYS = new String[] { modifier + " Z" }; + REDO_ACTION_KEYS = new String[] { modifier + " Y", modifier + " shift Z" }; + } + + public static final void install( final Actions actions, final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + final TrackNavigator trackNavigator = new TrackNavigator( model, selectionModel ); + + // Undo / redo + actions.runnableAction( () -> model.undo(), UNDO_ACTION, UNDO_ACTION_KEYS ); + actions.runnableAction( () -> model.redo(), REDO_ACTION, REDO_ACTION_KEYS ); + + // Navigate + actions.runnableAction( () -> trackNavigator.previousInTime(), NAVIGATE_TO_PARENT, NAVIGATE_TO_PARENT_KEYS ); + actions.runnableAction( () -> trackNavigator.nextInTime(), NAVIGATE_TO_CHILD, NAVIGATE_TO_CHILD_KEYS ); + actions.runnableAction( () -> trackNavigator.previousSibling(), NAVIGATE_TO_PREVIOUS_SIBLING, NAVIGATE_TO_PREVIOUS_SIBLING_KEYS ); + actions.runnableAction( () -> trackNavigator.nextSibling(), NAVIGATE_TO_NEXT_SIBLING, NAVIGATE_TO_NEXT_SIBLING_KEYS ); + actions.runnableAction( () -> trackNavigator.root(), NAVIGATE_TO_ROOT, NAVIGATE_TO_ROOT_KEYS ); + actions.runnableAction( () -> trackNavigator.leaf(), NAVIGATE_TO_LEAF, NAVIGATE_TO_LEAF_KEYS ); + actions.runnableAction( () -> trackNavigator.previousTrack(), NAVIGATE_TO_PREVIOUS_TRACK, NAVIGATE_TO_PREVIOUS_TRACK_KEYS ); + actions.runnableAction( () -> trackNavigator.nextTrack(), NAVIGATE_TO_NEXT_TRACK, NAVIGATE_TO_NEXT_TRACK_KEYS ); + + // Delete + actions.runnableAction( () -> deleteSpotSelection( model, selectionModel ), DELETE_SELECTED_SPOTS, DELETE_SELECTED_SPOTS_KEYS ); + + // Toggle auto-linking + actions.runnableAction( () -> SpotEditBehaviours.autoLinkingmode = !SpotEditBehaviours.autoLinkingmode, TOGGLE_AUTO_LINKING, TOGGLE_AUTO_LINKING_KEYS ); + + // Avoid closing the window when pressing W + actions.runnableAction( () -> {}, "do nothing", new String[] { "W" } ); + } + + private static void deleteSpotSelection( final Model model, final SelectionModel selectionModel ) + { + final ArrayList< Spot > spotSelection = new ArrayList<>( selectionModel.getSpotSelection() ); + final ArrayList< DefaultWeightedEdge > edgeSelection = new ArrayList<>( selectionModel.getEdgeSelection() ); + model.beginUpdate(); + try + { + selectionModel.clearSelection(); + for ( final DefaultWeightedEdge edge : edgeSelection ) + model.removeEdge( edge ); + for ( final Spot spot : spotSelection ) + model.removeSpot( spot ); + } + finally + { + model.endUpdate(); + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java index 641079632..4ca5d1678 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java @@ -19,6 +19,7 @@ import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; import ij.ImagePlus; import ij.gui.ImageCanvas; @@ -26,12 +27,17 @@ public class TrackMateImpBehaviour { /** - * Attaches ui-behaviour interaction handling to a given AWT Canvas. + * Attaches ui-behaviour interaction handling to a given {@link ImagePlus}. * - * @param canvas - * The target AWT Canvas to bind actions and behaviours to. + * @param model + * the model to operate on. + * @param selectionModel + * the selection model to read what is selected and to update the + * selection. + * @param imp + * the ImagePlus to attach the behaviours to. */ - public static void install( final Model model, final ImagePlus imp ) + public static void install( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) { // 1. Ensure the canvas can accept focus for keyboard shortcuts final ImageCanvas canvas = imp.getCanvas(); @@ -66,9 +72,6 @@ public static void install( final Model model, final ImagePlus imp ) canvas.addMouseMotionListener( proxy ); canvas.addMouseWheelListener( proxy ); - // Re-add the original ImageJ KeyListener after the proxy - canvas.addKeyListener( ijKeyListener ); - // The behaviours. final Behaviours behaviours = new Behaviours( inputTriggerMap, behaviourMap, config ); behaviours.install( behaviourBindings, "trackmate-beaviors" ); @@ -114,10 +117,14 @@ public void keyPressed( final KeyEvent e ) } } ); - behaviours.behaviour( new MoveSpotBehaviour( model, imp ), "move-spot", "SPACE" ); - behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, false ), "increase-spot-radius", "E" ); - behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, true ), "increase-spot-radius-fast", "shift E" ); - behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, false ), "decrease-spot-radius", "Q" ); - behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, true ), "decrease-spot-radius-fast", "shift Q" ); + // Re-add the original ImageJ KeyListener after all proxies + canvas.addKeyListener( ijKeyListener ); + + /* + * Flesh out commands. + */ + + SpotEditBehaviours.install( behaviours, model, selectionModel, imp ); + SpotEditActions.install( actions, model, selectionModel, imp ); } } From 2ab2c907bd9a7faedcb14fba6b6583c58bb1a7ca Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 18:00:58 +0200 Subject: [PATCH 275/371] Actions to change timepoints. --- .../hyperstack/behaviours/SpotEditActions.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java index b756ed9e7..951d152bb 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java @@ -31,10 +31,6 @@ public class SpotEditActions private static final String NAVIGATE_TO_PREVIOUS_TRACK = "navigate to previous track"; private static final String NAVIGATE_TO_NEXT_TRACK = "navigate to next track"; - private static final String DELETE_SELECTED_SPOTS = "delete selected spots"; - - private static final String TOGGLE_AUTO_LINKING = "toggle auto-linking"; - private static final String[] NAVIGATE_TO_PARENT_KEYS = new String[] { "UP" }; private static final String[] NAVIGATE_TO_CHILD_KEYS = new String[] { "DOWN" }; private static final String[] NAVIGATE_TO_PREVIOUS_SIBLING_KEYS = new String[] { "LEFT" }; @@ -44,9 +40,16 @@ public class SpotEditActions private static final String[] NAVIGATE_TO_PREVIOUS_TRACK_KEYS = new String[] { "PAGE_UP" }; private static final String[] NAVIGATE_TO_NEXT_TRACK_KEYS = new String[] { "PAGE_DOWN" }; + private static final String DELETE_SELECTED_SPOTS = "delete selected spots"; private static final String[] DELETE_SELECTED_SPOTS_KEYS = new String[] { "BACK_SPACE", "DELETE" }; - + + private static final String TOGGLE_AUTO_LINKING = "toggle auto-linking"; private static final String[] TOGGLE_AUTO_LINKING_KEYS = new String[] { "ctrl L" }; + + private static final String NEXT_TIMEPOINT = "next timepoint"; + private static final String PREVIOUS_TIMEPOINT = "previous timepoint"; + private static final String[] NEXT_TIMEPOINT_KEYS = new String[] { "RIGHT" }; + private static final String[] PREVIOUS_TIMEPOINT_KEYS = new String[] { "LEFT" }; static { @@ -82,6 +85,10 @@ public static final void install( final Actions actions, final Model model, fina // Avoid closing the window when pressing W actions.runnableAction( () -> {}, "do nothing", new String[] { "W" } ); + + // Change timepoint + actions.runnableAction( () -> imp.setT( imp.getT() + 1 ), NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS ); + actions.runnableAction( () -> imp.setT( imp.getT() - 1 ), PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS ); } private static void deleteSpotSelection( final Model model, final SelectionModel selectionModel ) From 4b9498aea3104c93132e296e338c3dbd17104911 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 21:07:51 +0200 Subject: [PATCH 276/371] Spot selection and semi-auto tracking. TODO: Put the panel to configure semi-auto tracking somewhere. --- .../behaviours/SemiAutoTracking.java | 47 +++++++++++++++ .../behaviours/SpotEditActions.java | 45 +++++++++++++- .../behaviours/SpotEditBehaviours.java | 59 ++++++++++++++++++- .../behaviours/SpotEditToolParams.java | 35 +++++++++++ 4 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SemiAutoTracking.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditToolParams.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SemiAutoTracking.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SemiAutoTracking.java new file mode 100644 index 000000000..918848894 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SemiAutoTracking.java @@ -0,0 +1,47 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.detection.semiauto.SemiAutoTracker; +import fiji.plugin.trackmate.util.Threads; +import ij.ImagePlus; +import ij.Prefs; + +public class SemiAutoTracking implements Runnable +{ + + private final Model model; + + private final SelectionModel selectionModel; + + private final ImagePlus imp; + + public static final SpotEditToolParams params = new SpotEditToolParams(); + + private final Logger logger = Logger.IJ_LOGGER; + + public SemiAutoTracking( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + this.model = model; + this.selectionModel = selectionModel; + this.imp = imp; + } + + @Override + public void run() + { + final double qualityThreshold = params.qualityThreshold; + final double distanceTolerance = params.distanceTolerance; + final int nFrames = params.nFrames; + final SemiAutoTracker< ? > autotracker = new SemiAutoTracker<>( model, selectionModel, imp, logger ); + autotracker.setParameters( qualityThreshold, distanceTolerance, nFrames ); + autotracker.setNumThreads( Prefs.getThreads() / 2 ); + Threads.run( "TrackMate semi-automated tracking thread", () -> { + final boolean ok = autotracker.checkInput() && autotracker.process(); + if ( !ok ) + logger.error( autotracker.getErrorMessage() ); + } ); + + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java index 951d152bb..b43f2406e 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java @@ -51,6 +51,9 @@ public class SpotEditActions private static final String[] NEXT_TIMEPOINT_KEYS = new String[] { "RIGHT" }; private static final String[] PREVIOUS_TIMEPOINT_KEYS = new String[] { "LEFT" }; + private static final String SEMI_AUTOMATIC_TRACKING = "semi-automatic tracking"; + private static final String[] SEMI_AUTOMATIC_TRACKING_KEYS = new String[] { "shift A" }; + static { final int menuMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); @@ -87,8 +90,12 @@ public static final void install( final Actions actions, final Model model, fina actions.runnableAction( () -> {}, "do nothing", new String[] { "W" } ); // Change timepoint - actions.runnableAction( () -> imp.setT( imp.getT() + 1 ), NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS ); - actions.runnableAction( () -> imp.setT( imp.getT() - 1 ), PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS ); + actions.runnableAction( () -> imp.setT( imp.getT() + SemiAutoTracking.params.stepwiseTimeBrowsing ), NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS ); + actions.runnableAction( () -> imp.setT( imp.getT() - SemiAutoTracking.params.stepwiseTimeBrowsing ), PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS ); + + // Semi-automatic tracking + final SemiAutoTracking semiAutoTracking = new SemiAutoTracking( model, selectionModel, imp ); + actions.runnableAction( () -> semiAutoTracking.run(), SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS ); } private static void deleteSpotSelection( final Model model, final SelectionModel selectionModel ) @@ -109,4 +116,38 @@ private static void deleteSpotSelection( final Model model, final SelectionModel model.endUpdate(); } } + + public static class SpotEditToolParams + { + + /* + * Semi-auto tracking parameters + */ + /** + * The fraction of the initial quality above which we keep new spots. + * The highest, the more intolerant. + */ + double qualityThreshold = 0.5; + + /** + * How close must be the new spot found to be accepted, in radius units. + */ + double distanceTolerance = 2d; + + /** + * We process at most nFrames. Make it 0 or negative to have no bounds. + */ + int nFrames = 10; + + /** + * By how many frames to jump when we do step-wide time browsing. + */ + int stepwiseTimeBrowsing = 1; + + @Override + public String toString() + { + return super.toString() + ": " + "QualityThreshold = " + qualityThreshold + ", DistanceTolerance = " + distanceTolerance + ", nFrames = " + nFrames; + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java index 1ff9f511f..d92ea292d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java @@ -25,6 +25,8 @@ public class SpotEditBehaviours private static final String DELETE_SPOT = "delete spot"; private static final String LINK_SPOTS = "link spots"; private static final String LINK_SPOTS_BACKWARD = "link spots backward"; + private static final String CLICK_SELECT_SPOT = "click select spot"; + private static final String CLICK_SELECT_ADD_SPOT = "click select add spot"; private static final String[] MOVE_SPOT_KEYS = new String[] { "SPACE" }; private static final String[] INCREASE_SPOT_RADIUS_KEYS = new String[] { "E" }; @@ -35,21 +37,76 @@ public class SpotEditBehaviours private static final String[] DELETE_SPOT_KEYS = new String[] { "D" }; private static final String[] LINK_SPOTS_KEYS = new String[] { "L" }; private static final String[] LINK_SPOTS_BACKWARD_KEYS = new String[] { "shift L" }; - + private static final String[] CLICK_SELECT_SPOT_KEYS = new String[] { "button1" }; + private static final String[] CLICK_SELECT_ADD_SPOT_KEYS = new String[] { "shift button1" }; static boolean autoLinkingmode = false; public static final void install( final Behaviours behaviours, final Model model, final SelectionModel selectionModel, final ImagePlus imp ) { behaviours.behaviour( new MoveSpotBehaviour( model, imp ), MOVE_SPOT, MOVE_SPOT_KEYS ); + behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, false ), INCREASE_SPOT_RADIUS, INCREASE_SPOT_RADIUS_KEYS ); behaviours.behaviour( new ResizeSpotBehaviour( model, imp, true, true ), INCREASE_SPOT_RADIUS_FAST, INCREASE_SPOT_RADIUS_FAST_KEYS ); behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, false ), DECREASE_SPOT_RADIUS, DECREASE_SPOT_RADIUS_KEYS ); behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, true ), DECREASE_SPOT_RADIUS_FAST, DECREASE_SPOT_RADIUS_FAST_KEYS ); + behaviours.behaviour( new AddSpotBehaviour( model, selectionModel, imp ), ADD_SPOT, ADD_SPOT_KEYS ); behaviours.behaviour( new DeleteSpotBehaviour( model, selectionModel, imp ), DELETE_SPOT, DELETE_SPOT_KEYS ); + behaviours.behaviour( new LinkSpotsBehaviour( model, imp, false ), LINK_SPOTS, LINK_SPOTS_KEYS ); behaviours.behaviour( new LinkSpotsBehaviour( model, imp, true ), LINK_SPOTS_BACKWARD, LINK_SPOTS_BACKWARD_KEYS ); + + behaviours.behaviour( new ClickSelectSpotBehaviour( model, selectionModel, imp ), CLICK_SELECT_SPOT, CLICK_SELECT_SPOT_KEYS ); + behaviours.behaviour( new ClickSelectAddSpotBehaviour( model, selectionModel, imp ), CLICK_SELECT_ADD_SPOT, CLICK_SELECT_ADD_SPOT_KEYS ); + } + + private static class ClickSelectAddSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour + { + + private final SelectionModel selectionModel; + + public ClickSelectAddSpotBehaviour( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + super( model, imp ); + this.selectionModel = selectionModel; + } + + @Override + public void click( final int x, final int y ) + { + final RealLocalizable pos = toWorldCoords( x, y ); + final Spot target = getSpotAtMouseLocation( pos ); + if ( null == target ) + return; + if ( selectionModel.getSpotSelection().contains( target ) ) + selectionModel.removeSpotFromSelection( target ); + else + selectionModel.addSpotToSelection( target ); + } + } + + private static class ClickSelectSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour + { + + private final SelectionModel selectionModel; + + public ClickSelectSpotBehaviour( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + super( model, imp ); + this.selectionModel = selectionModel; + } + + @Override + public void click( final int x, final int y ) + { + selectionModel.clearSelection(); + final RealLocalizable pos = toWorldCoords( x, y ); + final Spot target = getSpotAtMouseLocation( pos ); + if ( null == target ) + return; + selectionModel.addSpotToSelection( target ); + } } private static class DeleteSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditToolParams.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditToolParams.java new file mode 100644 index 000000000..a11666b16 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditToolParams.java @@ -0,0 +1,35 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +public class SpotEditToolParams +{ + + /* + * Semi-auto tracking parameters + */ + /** + * The fraction of the initial quality above which we keep new spots. + * The highest, the more intolerant. + */ + double qualityThreshold = 0.5; + + /** + * How close must be the new spot found to be accepted, in radius units. + */ + double distanceTolerance = 2d; + + /** + * We process at most nFrames. Make it 0 or negative to have no bounds. + */ + int nFrames = 10; + + /** + * By how many frames to jump when we do step-wide time browsing. + */ + int stepwiseTimeBrowsing = 1; + + @Override + public String toString() + { + return super.toString() + ": " + "QualityThreshold = " + qualityThreshold + ", DistanceTolerance = " + distanceTolerance + ", nFrames = " + nFrames; + } +} From 7e73ae886b56e5e5a4a1f7d5298d437d2b2edce3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 22:19:42 +0200 Subject: [PATCH 277/371] Select spots with the FreehandRoi, no need for a special IJ tool. NOTE: For some reason this does not work as expected. For instance, the rectangular ROI does not fire a RoiListener.COMPLETED when the user finishes drawing it. The PolygonRoi does fire this event, but the containsPoint() method does not work. So in effect, this feature only works with the FreehandRoi. --- .../SelectSpotsWithRoiListener.java | 97 +++++++++++++++++++ .../behaviours/TrackMateImpBehaviour.java | 2 + 2 files changed, 99 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SelectSpotsWithRoiListener.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SelectSpotsWithRoiListener.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SelectSpotsWithRoiListener.java new file mode 100644 index 000000000..2d411ee21 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SelectSpotsWithRoiListener.java @@ -0,0 +1,97 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.List; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.util.TMUtils; +import ij.ImagePlus; +import ij.gui.Roi; +import ij.gui.RoiListener; +import ij.plugin.PlugIn; + +public class SelectSpotsWithRoiListener implements PlugIn, RoiListener +{ + + private final Model model; + + private final SelectionModel selectionModel; + + private final ImagePlus sourceImp; + + private final double[] calibration; + + public SelectSpotsWithRoiListener( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + this.model = model; + this.selectionModel = selectionModel; + this.sourceImp = imp; + this.calibration = TMUtils.getSpatialCalibration( sourceImp ); + + // De-register the listener when the source image is closed + sourceImp.getWindow().addWindowListener( new WindowAdapter() + { + @Override + public void windowClosing( final WindowEvent e ) + { + Roi.removeRoiListener( SelectSpotsWithRoiListener.this ); + } + } ); + } + + @Override + public void run( final String arg ) + { + Roi.addRoiListener( this ); + } + + /* + * NOTE: For some reason this does not work as expected. For instance, the + * rectangular ROI does not fire a RoiListener.COMPLETED when the user + * finishes drawing it. The PolygonRoi does fire this event, but the + * containsPoint() method does not work. So in effect, this feature only + * works with the FreehandRoi. + */ + @Override + public void roiModified( final ImagePlus imp, final int id ) + { + if ( imp != sourceImp || id != RoiListener.COMPLETED ) + return; + + final Roi roi = imp.getRoi(); + if ( roi == null ) + return; + + final List< Spot > spotsInRoi = new ArrayList<>(); + + final Iterable iterable; + final boolean isShiftDown = ( imp.getCanvas().getModifiers() & 1 ) != 0; + if ( isShiftDown ) + { + final int frame = imp.getT() - 1; + iterable = model.getSpots().iterable( frame, true ); + } + else + { + iterable = model.getSpots().iterable( true ); + } + + for ( final Spot spot : iterable ) + { + final double x = spot.getDoublePosition( 0 ) / calibration[ 0 ]; + final double y = spot.getDoublePosition( 1 ) / calibration[ 1 ]; + if ( roi.containsPoint( x, y ) ) + spotsInRoi.add( spot ); + } + selectionModel.addSpotToSelection( spotsInRoi ); + } + + public static void install( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + new SelectSpotsWithRoiListener( model, selectionModel, imp ).run( null ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java index 4ca5d1678..e5be75a86 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java @@ -126,5 +126,7 @@ public void keyPressed( final KeyEvent e ) SpotEditBehaviours.install( behaviours, model, selectionModel, imp ); SpotEditActions.install( actions, model, selectionModel, imp ); + // Select spots with freehand ROI. + SelectSpotsWithRoiListener.install( model, selectionModel, imp ); } } From bb4f368b03b4d040dd43f4f1621e7e598fdfcfab Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 31 Jul 2026 23:43:31 +0200 Subject: [PATCH 278/371] Remove duplicate class. --- .../behaviours/SpotEditActions.java | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java index b43f2406e..59d1a2c6c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java @@ -116,38 +116,4 @@ private static void deleteSpotSelection( final Model model, final SelectionModel model.endUpdate(); } } - - public static class SpotEditToolParams - { - - /* - * Semi-auto tracking parameters - */ - /** - * The fraction of the initial quality above which we keep new spots. - * The highest, the more intolerant. - */ - double qualityThreshold = 0.5; - - /** - * How close must be the new spot found to be accepted, in radius units. - */ - double distanceTolerance = 2d; - - /** - * We process at most nFrames. Make it 0 or negative to have no bounds. - */ - int nFrames = 10; - - /** - * By how many frames to jump when we do step-wide time browsing. - */ - int stepwiseTimeBrowsing = 1; - - @Override - public String toString() - { - return super.toString() + ": " + "QualityThreshold = " + qualityThreshold + ", DistanceTolerance = " + distanceTolerance + ", nFrames = " + nFrames; - } - } } From b27fe30e10f39b552f36db642553e809f4e24a31 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 12:45:43 +0200 Subject: [PATCH 279/371] Make key bindings documented and editable via a Preferences dialog. TODO: Right now the keymaps are not serialized. --- .../behaviours/SpotEditActions.java | 43 ++++++++++-- .../behaviours/SpotEditBehaviours.java | 28 ++++++++ .../behaviours/TrackMateConfigDialog.java | 44 ++++++++++++ .../behaviours/TrackMateImpBehaviour.java | 67 +++++++++++++------ 4 files changed, 159 insertions(+), 23 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java index 59d1a2c6c..0b79c5119 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java @@ -5,6 +5,9 @@ import java.util.ArrayList; import org.jgrapht.graph.DefaultWeightedEdge; +import org.scijava.plugin.Plugin; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; import org.scijava.ui.behaviour.util.Actions; import fiji.plugin.trackmate.Model; @@ -40,8 +43,8 @@ public class SpotEditActions private static final String[] NAVIGATE_TO_PREVIOUS_TRACK_KEYS = new String[] { "PAGE_UP" }; private static final String[] NAVIGATE_TO_NEXT_TRACK_KEYS = new String[] { "PAGE_DOWN" }; - private static final String DELETE_SELECTED_SPOTS = "delete selected spots"; - private static final String[] DELETE_SELECTED_SPOTS_KEYS = new String[] { "BACK_SPACE", "DELETE" }; + private static final String DELETE_SELECTION = "delete selection"; + private static final String[] DELETE_SELECTION_KEYS = new String[] { "BACK_SPACE", "DELETE" }; private static final String TOGGLE_AUTO_LINKING = "toggle auto-linking"; private static final String[] TOGGLE_AUTO_LINKING_KEYS = new String[] { "ctrl L" }; @@ -81,7 +84,7 @@ public static final void install( final Actions actions, final Model model, fina actions.runnableAction( () -> trackNavigator.nextTrack(), NAVIGATE_TO_NEXT_TRACK, NAVIGATE_TO_NEXT_TRACK_KEYS ); // Delete - actions.runnableAction( () -> deleteSpotSelection( model, selectionModel ), DELETE_SELECTED_SPOTS, DELETE_SELECTED_SPOTS_KEYS ); + actions.runnableAction( () -> deleteSelection( model, selectionModel ), DELETE_SELECTION, DELETE_SELECTION_KEYS ); // Toggle auto-linking actions.runnableAction( () -> SpotEditBehaviours.autoLinkingmode = !SpotEditBehaviours.autoLinkingmode, TOGGLE_AUTO_LINKING, TOGGLE_AUTO_LINKING_KEYS ); @@ -98,7 +101,7 @@ public static final void install( final Actions actions, final Model model, fina actions.runnableAction( () -> semiAutoTracking.run(), SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS ); } - private static void deleteSpotSelection( final Model model, final SelectionModel selectionModel ) + private static void deleteSelection( final Model model, final SelectionModel selectionModel ) { final ArrayList< Spot > spotSelection = new ArrayList<>( selectionModel.getSpotSelection() ); final ArrayList< DefaultWeightedEdge > edgeSelection = new ArrayList<>( selectionModel.getEdgeSelection() ); @@ -116,4 +119,36 @@ private static void deleteSpotSelection( final Model model, final SelectionModel model.endUpdate(); } } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class Descriptions extends CommandDescriptionProvider + { + public Descriptions() + { + super( TrackMateImpBehaviour.KEY_CONFIG_SCOPE, TrackMateImpBehaviour.KEY_CONFIG_CONTEXT ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( UNDO_ACTION, UNDO_ACTION_KEYS, "Undo the last edit." ); + descriptions.add( REDO_ACTION, REDO_ACTION_KEYS, "Redo the last undone edit." ); + + descriptions.add( NAVIGATE_TO_PARENT, NAVIGATE_TO_PARENT_KEYS, "Navigate to the parent of the selected spot." ); + descriptions.add( NAVIGATE_TO_CHILD, NAVIGATE_TO_CHILD_KEYS, "Navigate to the child of the selected spot." ); + descriptions.add( NAVIGATE_TO_PREVIOUS_SIBLING, NAVIGATE_TO_PREVIOUS_SIBLING_KEYS, "Navigate to the previous sibling of the selected spot." ); + descriptions.add( NAVIGATE_TO_NEXT_SIBLING, NAVIGATE_TO_NEXT_SIBLING_KEYS, "Navigate to the next sibling of the selected spot." ); + descriptions.add( NAVIGATE_TO_ROOT, NAVIGATE_TO_ROOT_KEYS, "Navigate to the root of the current track." ); + descriptions.add( NAVIGATE_TO_LEAF, NAVIGATE_TO_LEAF_KEYS, "Navigate to the leaf of the current track." ); + descriptions.add( NAVIGATE_TO_PREVIOUS_TRACK, NAVIGATE_TO_PREVIOUS_TRACK_KEYS, "Navigate to the previous track." ); + descriptions.add( NAVIGATE_TO_NEXT_TRACK, NAVIGATE_TO_NEXT_TRACK_KEYS, "Navigate to the next track." ); + + descriptions.add( DELETE_SELECTION, DELETE_SELECTION_KEYS, "Delete the selected spots and edges." ); + descriptions.add( TOGGLE_AUTO_LINKING, TOGGLE_AUTO_LINKING_KEYS, "Toggle the auto-linking mode." ); + descriptions.add( NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS, "Go to the next timepoint." ); + descriptions.add( PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS, "Go to the previous timepoint." ); + descriptions.add( SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS, "Run semi-automatic tracking on the selected spots." ); + descriptions.add( "do nothing", new String[] { "W" }, "Do nothing. This is to avoid closing the window when pressing W." ); + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java index d92ea292d..8985e0dc1 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java @@ -2,8 +2,11 @@ import java.util.Set; +import org.scijava.plugin.Plugin; import org.scijava.ui.behaviour.ClickBehaviour; import org.scijava.ui.behaviour.DragBehaviour; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; import org.scijava.ui.behaviour.util.Behaviours; import fiji.plugin.trackmate.Model; @@ -320,4 +323,29 @@ public void click( final int x, final int y ) } } } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class Descriptions extends CommandDescriptionProvider + { + public Descriptions() + { + super( TrackMateImpBehaviour.KEY_CONFIG_SCOPE, TrackMateImpBehaviour.KEY_CONFIG_CONTEXT ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( MOVE_SPOT, MOVE_SPOT_KEYS, "Move a spot by dragging it." ); + descriptions.add( INCREASE_SPOT_RADIUS, INCREASE_SPOT_RADIUS_KEYS, "Increase the radius of a spot." ); + descriptions.add( INCREASE_SPOT_RADIUS_FAST, INCREASE_SPOT_RADIUS_FAST_KEYS, "Increase the radius of a spot (fast)." ); + descriptions.add( DECREASE_SPOT_RADIUS, DECREASE_SPOT_RADIUS_KEYS, "Decrease the radius of a spot." ); + descriptions.add( DECREASE_SPOT_RADIUS_FAST, DECREASE_SPOT_RADIUS_FAST_KEYS, "Decrease the radius of a spot (fast)." ); + descriptions.add( ADD_SPOT, ADD_SPOT_KEYS, "Add a new spot at the mouse location." ); + descriptions.add( DELETE_SPOT, DELETE_SPOT_KEYS, "Delete the spot at the mouse location." ); + descriptions.add( LINK_SPOTS, LINK_SPOTS_KEYS, "Link two spots forward in time by dragging from a source spot to a target spot in the next time-point." ); + descriptions.add( LINK_SPOTS_BACKWARD, LINK_SPOTS_BACKWARD_KEYS, "Link two spots backward in time by dragging from a source spot to a target spot in the previous time-point." ); + descriptions.add( CLICK_SELECT_SPOT, CLICK_SELECT_SPOT_KEYS, "Select a spot at the mouse location." ); + descriptions.add( CLICK_SELECT_ADD_SPOT, CLICK_SELECT_ADD_SPOT_KEYS, "Add or remove a spot from the selection at the mouse location." ); + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java new file mode 100644 index 000000000..06a1c9bb4 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java @@ -0,0 +1,44 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import java.awt.Frame; + +import org.scijava.plugin.Plugin; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; +import org.scijava.ui.behaviour.util.Actions; + +import bdv.BigDataViewerActions; +import bdv.tools.CloseWindowActions; +import bdv.tools.PreferencesDialog; +import bdv.ui.keymap.Keymap; +import bdv.ui.keymap.KeymapManager; +import bdv.ui.keymap.KeymapSettingsPage; + +public class TrackMateConfigDialog +{ + + public static void prefDialog( final Frame frame, final Keymap keymap, final KeymapManager keymapManager, final Actions actions ) + { + final PreferencesDialog preferencesDialog = new PreferencesDialog( frame, keymap, new String[] { TrackMateImpBehaviour.KEY_CONFIG_CONTEXT } ); + fiji.plugin.trackmate.gui.GuiUtils.positionWindow( preferencesDialog, frame ); + BigDataViewerActions.toggleDialogAction( actions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); + preferencesDialog.addPage( new KeymapSettingsPage( "Keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); + } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class Descriptions extends CommandDescriptionProvider + { + public Descriptions() + { + super( TrackMateImpBehaviour.KEY_CONFIG_SCOPE, TrackMateImpBehaviour.KEY_CONFIG_CONTEXT ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( CloseWindowActions.CLOSE_DIALOG, CloseWindowActions.CLOSE_DIALOG_KEYS, "Close the active dialog." ); + descriptions.add( BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS, "Open the preferences dialog." ); + } + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java index e5be75a86..bc31eb089 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java @@ -9,23 +9,33 @@ import javax.swing.InputMap; import javax.swing.KeyStroke; +import org.scijava.Context; import org.scijava.ui.behaviour.BehaviourMap; import org.scijava.ui.behaviour.InputTriggerMap; import org.scijava.ui.behaviour.MouseAndKeyHandler; import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider.Scope; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionsBuilder; import org.scijava.ui.behaviour.util.Actions; import org.scijava.ui.behaviour.util.Behaviours; import org.scijava.ui.behaviour.util.InputActionBindings; import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; +import bdv.ui.keymap.Keymap; +import bdv.ui.keymap.KeymapManager; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.util.TMUtils; import ij.ImagePlus; import ij.gui.ImageCanvas; public class TrackMateImpBehaviour { + static final String KEY_CONFIG_CONTEXT = "trackmate"; + + public static final Scope KEY_CONFIG_SCOPE = new Scope( "TrackMate" ); + /** * Attaches ui-behaviour interaction handling to a given {@link ImagePlus}. * @@ -46,14 +56,46 @@ public static void install( final Model model, final SelectionModel selectionMod // A. Behaviours framework // Initialize configuration and binding registries - final InputTriggerConfig config = new InputTriggerConfig(); final InputActionBindings actionBindings = new InputActionBindings(); final TriggerBehaviourBindings behaviourBindings = new TriggerBehaviourBindings(); - // Initialize the Behaviours framework + final KeymapManager keymapManager = new KeymapManager() + { + @Override + public synchronized void discoverCommandDescriptions() + { + final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); + final Context context = TMUtils.getContext(); + context.inject( builder ); + builder.discoverProviders( KEY_CONFIG_SCOPE ); + setCommandDescriptions( builder.build() ); + } + }; + keymapManager.discoverCommandDescriptions(); + final InputTriggerConfig config = keymapManager.getForwardSelectedKeymap().getConfig(); + + // Behaviours. + final Behaviours behaviours = new Behaviours( config, KEY_CONFIG_CONTEXT ); + behaviours.install( behaviourBindings, "trackmate-beaviors" ); + + // Actions + final InputMap inputMap = actionBindings.getConcatenatedInputMap(); + final ActionMap actionMap = actionBindings.getConcatenatedActionMap(); + final Actions actions = new Actions( config, KEY_CONFIG_CONTEXT ); + actions.install( actionBindings, "trackmate-actions" ); + + final Keymap keymap = keymapManager.getForwardSelectedKeymap(); + keymap.updateListeners().add( () -> { + actions.updateKeyConfig( keymap.getConfig() ); + behaviours.updateKeyConfig( keymap.getConfig() ); + } ); + actions.updateKeyConfig( keymap.getConfig() ); + behaviours.updateKeyConfig( keymap.getConfig() ); + + // Initialize the handler final MouseAndKeyHandler handler = new MouseAndKeyHandler(); - final InputTriggerMap inputTriggerMap = new InputTriggerMap(); - final BehaviourMap behaviourMap = new BehaviourMap(); + final InputTriggerMap inputTriggerMap = behaviours.getInputTriggerMap(); + final BehaviourMap behaviourMap = behaviours.getBehaviourMap(); handler.setInputMap( inputTriggerMap ); handler.setBehaviourMap( behaviourMap ); @@ -72,21 +114,6 @@ public static void install( final Model model, final SelectionModel selectionMod canvas.addMouseMotionListener( proxy ); canvas.addMouseWheelListener( proxy ); - // The behaviours. - final Behaviours behaviours = new Behaviours( inputTriggerMap, behaviourMap, config ); - behaviours.install( behaviourBindings, "trackmate-beaviors" ); - - // Actions - final InputMap inputMap = actionBindings.getConcatenatedInputMap(); - final ActionMap actionMap = actionBindings.getConcatenatedActionMap(); - final Actions actions = new Actions( inputMap, actionMap, config ); - actions.install( actionBindings, "trackmate-actions" ); - - // This is the debug - actions.runnableAction( () -> { - System.out.println( "Reset action triggered!" ); - }, "reset-view", "R" ); - // Direct Key Event Proxy Bridge. This was done with Gemini. // Because an AWT Canvas bypasses Swing's ActionMap dispatch pipeline, // we manually intercept the KeyStrokes and route them to our Action @@ -128,5 +155,7 @@ public void keyPressed( final KeyEvent e ) SpotEditActions.install( actions, model, selectionModel, imp ); // Select spots with freehand ROI. SelectSpotsWithRoiListener.install( model, selectionModel, imp ); + + TrackMateConfigDialog.prefDialog( imp.getWindow(), keymap, keymapManager, actions ); } } From 79b221ef710fcbaa6b9c1eb0066eb6ee7a2cf224 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 18:07:07 +0200 Subject: [PATCH 280/371] A specific KeymapManager for TrackMate. TODO: serialize keymaps to disk. --- .../ui/TrackMateKeymapManager.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java new file mode 100644 index 000000000..fc95cc4de --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java @@ -0,0 +1,24 @@ +package fiji.plugin.trackmate.visualization.ui; + +import org.scijava.Context; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider.Scope; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionsBuilder; + +import bdv.ui.keymap.KeymapManager; +import fiji.plugin.trackmate.util.TMUtils; + +public class TrackMateKeymapManager extends KeymapManager +{ + + public static final Scope KEY_CONFIG_SCOPE = new Scope( "TrackMate" ); + + @Override + public synchronized void discoverCommandDescriptions() + { + final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); + final Context context = TMUtils.getContext(); + context.inject( builder ); + builder.discoverProviders( KEY_CONFIG_SCOPE ); + setCommandDescriptions( builder.build() ); + } +} From b729662418a4acfd2429d25d2056579e8a10429d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 18:08:05 +0200 Subject: [PATCH 281/371] Refactor: a class that adapts ui-behaviour to an ImagePlus. Maybe reusable? Could be used outside ImageJ? --- .../ImagePlusBehavioursAdapter.java | 342 ++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java new file mode 100644 index 000000000..88a673dcc --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java @@ -0,0 +1,342 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import java.awt.event.InputEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; + +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.KeyStroke; + +import org.scijava.ui.behaviour.BehaviourMap; +import org.scijava.ui.behaviour.GlobalKeyEventDispatcher; +import org.scijava.ui.behaviour.InputTrigger; +import org.scijava.ui.behaviour.InputTriggerMap; +import org.scijava.ui.behaviour.MouseAndKeyHandler; +import org.scijava.ui.behaviour.io.InputTriggerConfig; +import org.scijava.ui.behaviour.util.Actions; +import org.scijava.ui.behaviour.util.Behaviours; +import org.scijava.ui.behaviour.util.InputActionBindings; +import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; + +import bdv.ui.keymap.Keymap; +import bdv.ui.keymap.KeymapManager; +import gnu.trove.set.TIntSet; +import ij.ImagePlus; +import ij.gui.ImageCanvas; + +/** + * Adapter class that connects a {@link ImagePlus} to the SciJava Behaviours + * framework, and provides access to the {@link Actions} and {@link Behaviours} + * objects. + *

    + * The trick is that the scijava ui-behaviour framework is designed for Swing + * components, and the ImagePlus canvas is an AWT component. This class bridges + * the gap by using a proxy to intercept mouse and key events and route them to + * the appropriate actions and behaviours. + *

    + * The actions and behaviours are positioned 'before' the original ImageJ key + * listener, so that the key bindings defined in the keymap override the default + * ImageJ key bindings. However, if a key binding exists for ImageJ but not for + * the actions and behaviours created here, the event will be passed to the + * original ImageJ key listener. + * + * @author Jean-Yves Tinevez + */ +public class ImagePlusBehavioursAdapter +{ + + private final Actions actions; + + private final Behaviours behaviours; + + public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keymapManager, final String keyConfigContext ) + { + final ImageCanvas canvas = imp.getCanvas(); + canvas.setFocusable( true ); + + // Initialize configuration and binding registries + final InputActionBindings actionBindings = new InputActionBindings(); + final TriggerBehaviourBindings behaviourBindings = new TriggerBehaviourBindings(); + keymapManager.discoverCommandDescriptions(); + final InputTriggerConfig config = keymapManager.getForwardSelectedKeymap().getConfig(); + + // Behaviours + this.behaviours = new Behaviours( config, keyConfigContext ); + behaviours.install( behaviourBindings, keyConfigContext + "-beaviors" ); + + // Actions + final InputMap inputMap = actionBindings.getConcatenatedInputMap(); + final ActionMap actionMap = actionBindings.getConcatenatedActionMap(); + this.actions = new Actions( config, keyConfigContext ); + actions.install( actionBindings, keyConfigContext + "-actions" ); + + final Keymap keymap = keymapManager.getForwardSelectedKeymap(); + keymap.updateListeners().add( () -> { + actions.updateKeyConfig( keymap.getConfig() ); + behaviours.updateKeyConfig( keymap.getConfig() ); + } ); + actions.updateKeyConfig( keymap.getConfig() ); + behaviours.updateKeyConfig( keymap.getConfig() ); + + // Initialize the handler + final MouseAndKeyHandler handler = new MouseAndKeyHandler(); + final InputTriggerMap inputTriggerMap = behaviours.getInputTriggerMap(); + final BehaviourMap behaviourMap = behaviours.getBehaviourMap(); + handler.setInputMap( inputTriggerMap ); + handler.setBehaviourMap( behaviourMap ); + + // Put the IJ key listener at the end of the chain, so that we can + // intercept events before they reach it. + final KeyListener[] keyListeners = canvas.getKeyListeners(); + for ( final KeyListener keyListener : keyListeners ) + canvas.removeKeyListener( keyListener ); + + /* + * Connect the handler to the AWT component for Mouse handling We need + * the proxy because the default MouseAndKeyHandler does not consume + * events when a trigger is matched, and without it the events are sent + * to the ImageJ original KeyListener. + */ + final MouseEventProxy proxy = new MouseEventProxy( handler, inputTriggerMap ); + canvas.addKeyListener( proxy ); + canvas.addMouseListener( proxy ); + canvas.addMouseMotionListener( proxy ); + canvas.addMouseWheelListener( proxy ); + + /* + * Direct Key Event Proxy Bridge. Because an AWT Canvas bypasses Swing's + * ActionMap dispatch pipeline, we manually intercept the KeyStrokes and + * route them to our Action map. + */ + canvas.addKeyListener( new KeyAdapter() + { + @Override + public void keyPressed( final KeyEvent e ) + { + // Get the keystroke matching this precise key event + final KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent( e ); + if ( keyStroke == null ) + return; + + // Lookup the unique Action ID string assigned to this key combo + final Object actionKey = inputMap.get( keyStroke ); + if ( actionKey != null ) + { + // Pull the runnable action associated with that ID + final Action action = actionMap.get( actionKey ); + if ( action != null && action.isEnabled() ) + { + // Fire the shortcut action directly on the EDT + action.actionPerformed( null ); + e.consume(); // Block from propagating to IJ listener + } + } + } + } ); + + // Re-add the original ImageJ KeyListener after all proxies + for ( final KeyListener keyListener : keyListeners ) + canvas.addKeyListener( keyListener ); + } + + public Actions actions() + { + return actions; + } + + public Behaviours behaviours() + { + return behaviours; + } + + /** + * A proxy that intercepts mouse and key events, and routes them to the + * appropriate actions and behaviours. It also consumes the events if they + * match a trigger, preventing them from reaching the original ImageJ key + * listener. + */ + private static class MouseEventProxy implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener + { + private final MouseAndKeyHandler delegate; + + private final InputTriggerMap triggerMap; + + private Method getMaskMethod; + + /** + * Constructs a new MouseEventProxy. + * + * @param delegate + * The active MouseAndKeyHandler driving the interaction + * updates. + * @param triggerMap + * The mapping table containing target behavioral shortcuts. + */ + public MouseEventProxy( final MouseAndKeyHandler delegate, final InputTriggerMap triggerMap ) + { + this.delegate = delegate; + this.triggerMap = triggerMap; + try + { + // Extract ui-behaviour's internal structural input mask + // calculator via reflection + this.getMaskMethod = MouseAndKeyHandler.class.getDeclaredMethod( "getMask", InputEvent.class ); + this.getMaskMethod.setAccessible( true ); + } + catch ( final Exception ex ) + { + this.getMaskMethod = null; + } + } + + /** + * Safely determines if the active hardware interaction features a + * behavioral binding without causing NullPointerExceptions in child + * trigger maps. + */ + private boolean hasMatchingBehaviour( final InputEvent e ) + { + if ( getMaskMethod == null || triggerMap == null ) + return false; + + try + { + // Retrieve the calculated normalization bitmask from the + // handler + // instance + final int mask = ( Integer ) getMaskMethod.invoke( delegate, e ); + + // Keep the primitive TIntSet collection directly without + // calling .toArray() + final TIntSet pressedKeys = GlobalKeyEventDispatcher.getInstance().pressedKeys(); + + // Safely pull the flattened trigger assignments map from our + // configuration + final Map< InputTrigger, Set< String > > bindings = triggerMap.getAllBindings(); + + if ( bindings != null ) + { + for ( final Map.Entry< InputTrigger, Set< String > > entry : bindings.entrySet() ) + { + final InputTrigger trigger = entry.getKey(); + final Set< String > keys = entry.getValue(); + + // Check if trigger conditions match and that it + // contains actual bound behaviors + if ( trigger != null && trigger.matches( mask, pressedKeys ) && keys != null && !keys.isEmpty() ) + return true; + } + } + } + catch ( final Exception ex ) + { + // Prevent crashes from faulty reflections, letting the event + // propagate unconsumed + } + return false; + } + + @Override + public void mouseClicked( final MouseEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mouseClicked( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void mousePressed( final MouseEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mousePressed( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void mouseReleased( final MouseEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mouseReleased( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void mouseEntered( final MouseEvent e ) + { + delegate.mouseEntered( e ); + } + + @Override + public void mouseExited( final MouseEvent e ) + { + delegate.mouseExited( e ); + } + + @Override + public void mouseDragged( final MouseEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mouseDragged( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void mouseMoved( final MouseEvent e ) + { + // Hover coordinates pass straight through unconsumed to ensure + // legacy UI refreshes continue + delegate.mouseMoved( e ); + } + + @Override + public void mouseWheelMoved( final MouseWheelEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.mouseWheelMoved( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void keyPressed( final KeyEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.keyPressed( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void keyReleased( final KeyEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.keyReleased( e ); + if ( shouldConsume ) + e.consume(); + } + + @Override + public void keyTyped( final KeyEvent e ) + { + final boolean shouldConsume = hasMatchingBehaviour( e ); + delegate.keyTyped( e ); + if ( shouldConsume ) + e.consume(); + } + } +} From ee4053bf812c21305145966cd3d44c65d5a1e5ce Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 18:09:01 +0200 Subject: [PATCH 282/371] Store the TrackMateKeymapManager as a static instance. Good idea? Do we need one instance per TrackMate? --- .../visualization/AbstractTrackMateModelView.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java index a90af2f7a..5d9ae819d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java @@ -30,13 +30,12 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; /** - * An abstract class for spot displayers, that can overlay detected spots and - * tracks on top of the image data. - *

    + * An abstract class for TrackMate views. * - * @author Jean-Yves Tinevez <jeanyves.tinevez@gmail.com> Jan 2011 + * @author Jean-Yves Tinevez */ public abstract class AbstractTrackMateModelView implements SelectionChangeListener, TrackMateModelView, ModelChangeListener { @@ -52,6 +51,12 @@ public abstract class AbstractTrackMateModelView implements SelectionChangeListe protected final DisplaySettings displaySettings; + /* + * STATIC FIELD + */ + + protected static final TrackMateKeymapManager keymapManager = new TrackMateKeymapManager(); + /* * PROTECTED CONSTRUCTOR */ From 1efb628009509f9f0ae03a6aba19d7d2cfeab234 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 18:09:50 +0200 Subject: [PATCH 283/371] Refactor: use the new ui-behaviour classes in the main view. --- .../hyperstack/HyperStackDisplayer.java | 41 ++-- .../behaviours/MouseEventProxy.java | 199 ------------------ .../behaviours/SpotEditActions.java | 4 +- .../behaviours/SpotEditBehaviours.java | 4 +- .../behaviours/TrackMateConfigDialog.java | 11 +- .../behaviours/TrackMateImpBehaviour.java | 161 -------------- 6 files changed, 34 insertions(+), 386 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MouseEventProxy.java delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java 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 8a6d6a527..e77669cff 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -21,6 +21,7 @@ */ package fiji.plugin.trackmate.visualization.hyperstack; +import bdv.ui.keymap.Keymap; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.SelectionChangeEvent; @@ -29,7 +30,11 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; import fiji.plugin.trackmate.visualization.ViewUtils; -import fiji.plugin.trackmate.visualization.hyperstack.behaviours.TrackMateImpBehaviour; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.ImagePlusBehavioursAdapter; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SelectSpotsWithRoiListener; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SpotEditActions; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SpotEditBehaviours; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.TrackMateConfigDialog; import ij.ImagePlus; import ij.gui.Overlay; import ij.gui.Roi; @@ -43,10 +48,14 @@ public class HyperStackDisplayer extends AbstractTrackMateModelView protected TrackOverlay trackOverlay; - private SpotEditTool editTool; - public static final String KEY = "HYPERSTACKDISPLAYER"; + /** + * The key configuration context for actions and behaviours specific to this + * displayer. + */ + public static final String KEY_CONFIG_CONTEXT = "trackmate-main-view"; + /* * CONSTRUCTORS */ @@ -160,14 +169,19 @@ public void render() addOverlay( spotOverlay ); addOverlay( trackOverlay ); imp.updateAndDraw(); -// registerEditTool(); /* - * Play with UI behaviour + * UI behaviours and actions */ - // Print all registered key listeners to console - TrackMateImpBehaviour.install( model, selectionModel, imp ); + final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, KEY_CONFIG_CONTEXT ); + SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); + SpotEditActions.install( adapter.actions(), model, selectionModel, imp ); + // Select spots with freehand ROI. + SelectSpotsWithRoiListener.install( model, selectionModel, imp ); + // Pref dialog. + final Keymap keymap = keymapManager.getForwardSelectedKeymap(); + TrackMateConfigDialog.prefDialog( imp.getWindow(), keymap, keymapManager, adapter.actions() ); } @Override @@ -200,19 +214,6 @@ public SelectionModel getSelectionModel() return selectionModel; } - /* - * PRIVATE METHODS - */ - - private void registerEditTool() - { - editTool = SpotEditTool.getInstance(); - if ( !SpotEditTool.isLaunched() ) - editTool.run( "" ); - - editTool.register( this ); - } - @Override public String getKey() { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MouseEventProxy.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MouseEventProxy.java deleted file mode 100644 index 6ab7b6dc7..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/MouseEventProxy.java +++ /dev/null @@ -1,199 +0,0 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; - -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; -import java.lang.reflect.Method; -import java.util.Map; -import java.util.Set; - -import org.scijava.ui.behaviour.GlobalKeyEventDispatcher; -import org.scijava.ui.behaviour.InputTrigger; -import org.scijava.ui.behaviour.InputTriggerMap; -import org.scijava.ui.behaviour.MouseAndKeyHandler; - -/** - * A proxy wrapper for SciJava's {@link MouseAndKeyHandler} that intercepts AWT - * mouse event pipelines and selectively calls {@code e.consume()} only if an - * explicit behavior trigger mapping exists inside the configuration model. - *

    - * We need this because the default {@link MouseAndKeyHandler} does not consume - * events when a trigger is matched, which can lead to unintended propagation of - * events to other components in the UI. - */ -public class MouseEventProxy implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener -{ - private final MouseAndKeyHandler delegate; - - private final InputTriggerMap triggerMap; - - private Method getMaskMethod; - - /** - * Constructs a new MouseEventProxy. - * - * @param delegate - * The active MouseAndKeyHandler driving the interaction updates. - * @param triggerMap - * The mapping table containing target behavioral shortcuts. - */ - public MouseEventProxy( final MouseAndKeyHandler delegate, final InputTriggerMap triggerMap ) - { - this.delegate = delegate; - this.triggerMap = triggerMap; - try - { - // Extract ui-behaviour's internal structural input mask calculator - // via reflection - this.getMaskMethod = MouseAndKeyHandler.class.getDeclaredMethod( "getMask", InputEvent.class ); - this.getMaskMethod.setAccessible( true ); - } - catch ( final Exception ex ) - { - this.getMaskMethod = null; - } - } - - /** - * Safely determines if the active hardware interaction features a - * behavioral binding without causing NullPointerExceptions in child trigger - * maps. - */ - private boolean hasMatchingBehaviour( final InputEvent e ) - { - if ( getMaskMethod == null || triggerMap == null ) - return false; - - try - { - // Retrieve the calculated normalization bitmask from the handler - // instance - final int mask = ( Integer ) getMaskMethod.invoke( delegate, e ); - - // FIX: Keep the primitive TIntSet collection directly without - // calling .toArray() - final gnu.trove.set.TIntSet pressedKeys = GlobalKeyEventDispatcher.getInstance().pressedKeys(); - - // Safely pull the flattened trigger assignments map from our - // configuration - final Map< InputTrigger, Set< String > > bindings = triggerMap.getAllBindings(); - - if ( bindings != null ) - { - for ( final Map.Entry< InputTrigger, Set< String > > entry : bindings.entrySet() ) - { - final InputTrigger trigger = entry.getKey(); - final Set< String > keys = entry.getValue(); - - // Check if trigger conditions match and that it contains - // actual bound behaviors - if ( trigger != null && trigger.matches( mask, pressedKeys ) && keys != null && !keys.isEmpty() ) - { return true; } - } - } - } - catch ( final Exception ex ) - { - // Prevent crashes from faulty reflections, letting the event - // propagate unconsumed - } - return false; - } - - @Override - public void mouseClicked( final MouseEvent e ) - { - final boolean shouldConsume = hasMatchingBehaviour( e ); - delegate.mouseClicked( e ); - if ( shouldConsume ) - e.consume(); - } - - @Override - public void mousePressed( final MouseEvent e ) - { - final boolean shouldConsume = hasMatchingBehaviour( e ); - delegate.mousePressed( e ); - if ( shouldConsume ) - e.consume(); - } - - @Override - public void mouseReleased( final MouseEvent e ) - { - final boolean shouldConsume = hasMatchingBehaviour( e ); - delegate.mouseReleased( e ); - if ( shouldConsume ) - e.consume(); - } - - @Override - public void mouseEntered( final MouseEvent e ) - { - delegate.mouseEntered( e ); - } - - @Override - public void mouseExited( final MouseEvent e ) - { - delegate.mouseExited( e ); - } - - @Override - public void mouseDragged( final MouseEvent e ) - { - final boolean shouldConsume = hasMatchingBehaviour( e ); - delegate.mouseDragged( e ); - if ( shouldConsume ) - e.consume(); - } - - @Override - public void mouseMoved( final MouseEvent e ) - { - // Hover coordinates pass straight through unconsumed to ensure legacy - // UI refreshes continue - delegate.mouseMoved( e ); - } - - @Override - public void mouseWheelMoved( final MouseWheelEvent e ) - { - final boolean shouldConsume = hasMatchingBehaviour( e ); - delegate.mouseWheelMoved( e ); - if ( shouldConsume ) - e.consume(); - } - - @Override - public void keyPressed( final KeyEvent e ) - { - final boolean shouldConsume = hasMatchingBehaviour( e ); - delegate.keyPressed( e ); - if ( shouldConsume ) - e.consume(); - } - - @Override - public void keyReleased( final KeyEvent e ) - { - final boolean shouldConsume = hasMatchingBehaviour( e ); - delegate.keyReleased( e ); - if ( shouldConsume ) - e.consume(); - } - - @Override - public void keyTyped( final KeyEvent e ) - { - final boolean shouldConsume = hasMatchingBehaviour( e ); - delegate.keyTyped( e ); - if ( shouldConsume ) - e.consume(); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java index 0b79c5119..c036f8832 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java @@ -14,6 +14,8 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.util.TrackNavigator; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; import ij.ImagePlus; public class SpotEditActions @@ -125,7 +127,7 @@ public static class Descriptions extends CommandDescriptionProvider { public Descriptions() { - super( TrackMateImpBehaviour.KEY_CONFIG_SCOPE, TrackMateImpBehaviour.KEY_CONFIG_CONTEXT ); + super( TrackMateKeymapManager.KEY_CONFIG_SCOPE, HyperStackDisplayer.KEY_CONFIG_CONTEXT ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java index 8985e0dc1..5fc25653f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java @@ -13,6 +13,8 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; import ij.ImagePlus; import net.imglib2.RealLocalizable; @@ -329,7 +331,7 @@ public static class Descriptions extends CommandDescriptionProvider { public Descriptions() { - super( TrackMateImpBehaviour.KEY_CONFIG_SCOPE, TrackMateImpBehaviour.KEY_CONFIG_CONTEXT ); + super( TrackMateKeymapManager.KEY_CONFIG_SCOPE, HyperStackDisplayer.KEY_CONFIG_CONTEXT ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java index 06a1c9bb4..db7300e06 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java @@ -13,14 +13,18 @@ import bdv.ui.keymap.Keymap; import bdv.ui.keymap.KeymapManager; import bdv.ui.keymap.KeymapSettingsPage; +import fiji.plugin.trackmate.gui.GuiUtils; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; public class TrackMateConfigDialog { public static void prefDialog( final Frame frame, final Keymap keymap, final KeymapManager keymapManager, final Actions actions ) { - final PreferencesDialog preferencesDialog = new PreferencesDialog( frame, keymap, new String[] { TrackMateImpBehaviour.KEY_CONFIG_CONTEXT } ); - fiji.plugin.trackmate.gui.GuiUtils.positionWindow( preferencesDialog, frame ); + final PreferencesDialog preferencesDialog = new PreferencesDialog( frame, keymap, + new String[] { HyperStackDisplayer.KEY_CONFIG_CONTEXT } ); + GuiUtils.positionWindow( preferencesDialog, frame ); BigDataViewerActions.toggleDialogAction( actions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); preferencesDialog.addPage( new KeymapSettingsPage( "Keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); } @@ -30,7 +34,7 @@ public static class Descriptions extends CommandDescriptionProvider { public Descriptions() { - super( TrackMateImpBehaviour.KEY_CONFIG_SCOPE, TrackMateImpBehaviour.KEY_CONFIG_CONTEXT ); + super( TrackMateKeymapManager.KEY_CONFIG_SCOPE, HyperStackDisplayer.KEY_CONFIG_CONTEXT ); } @Override @@ -40,5 +44,4 @@ public void getCommandDescriptions( final CommandDescriptions descriptions ) descriptions.add( BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS, "Open the preferences dialog." ); } } - } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java deleted file mode 100644 index bc31eb089..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateImpBehaviour.java +++ /dev/null @@ -1,161 +0,0 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; - -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; - -import javax.swing.Action; -import javax.swing.ActionMap; -import javax.swing.InputMap; -import javax.swing.KeyStroke; - -import org.scijava.Context; -import org.scijava.ui.behaviour.BehaviourMap; -import org.scijava.ui.behaviour.InputTriggerMap; -import org.scijava.ui.behaviour.MouseAndKeyHandler; -import org.scijava.ui.behaviour.io.InputTriggerConfig; -import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider.Scope; -import org.scijava.ui.behaviour.io.gui.CommandDescriptionsBuilder; -import org.scijava.ui.behaviour.util.Actions; -import org.scijava.ui.behaviour.util.Behaviours; -import org.scijava.ui.behaviour.util.InputActionBindings; -import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; - -import bdv.ui.keymap.Keymap; -import bdv.ui.keymap.KeymapManager; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.util.TMUtils; -import ij.ImagePlus; -import ij.gui.ImageCanvas; - -public class TrackMateImpBehaviour -{ - - static final String KEY_CONFIG_CONTEXT = "trackmate"; - - public static final Scope KEY_CONFIG_SCOPE = new Scope( "TrackMate" ); - - /** - * Attaches ui-behaviour interaction handling to a given {@link ImagePlus}. - * - * @param model - * the model to operate on. - * @param selectionModel - * the selection model to read what is selected and to update the - * selection. - * @param imp - * the ImagePlus to attach the behaviours to. - */ - public static void install( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) - { - // 1. Ensure the canvas can accept focus for keyboard shortcuts - final ImageCanvas canvas = imp.getCanvas(); - canvas.setFocusable( true ); - - // A. Behaviours framework - - // Initialize configuration and binding registries - final InputActionBindings actionBindings = new InputActionBindings(); - final TriggerBehaviourBindings behaviourBindings = new TriggerBehaviourBindings(); - - final KeymapManager keymapManager = new KeymapManager() - { - @Override - public synchronized void discoverCommandDescriptions() - { - final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); - final Context context = TMUtils.getContext(); - context.inject( builder ); - builder.discoverProviders( KEY_CONFIG_SCOPE ); - setCommandDescriptions( builder.build() ); - } - }; - keymapManager.discoverCommandDescriptions(); - final InputTriggerConfig config = keymapManager.getForwardSelectedKeymap().getConfig(); - - // Behaviours. - final Behaviours behaviours = new Behaviours( config, KEY_CONFIG_CONTEXT ); - behaviours.install( behaviourBindings, "trackmate-beaviors" ); - - // Actions - final InputMap inputMap = actionBindings.getConcatenatedInputMap(); - final ActionMap actionMap = actionBindings.getConcatenatedActionMap(); - final Actions actions = new Actions( config, KEY_CONFIG_CONTEXT ); - actions.install( actionBindings, "trackmate-actions" ); - - final Keymap keymap = keymapManager.getForwardSelectedKeymap(); - keymap.updateListeners().add( () -> { - actions.updateKeyConfig( keymap.getConfig() ); - behaviours.updateKeyConfig( keymap.getConfig() ); - } ); - actions.updateKeyConfig( keymap.getConfig() ); - behaviours.updateKeyConfig( keymap.getConfig() ); - - // Initialize the handler - final MouseAndKeyHandler handler = new MouseAndKeyHandler(); - final InputTriggerMap inputTriggerMap = behaviours.getInputTriggerMap(); - final BehaviourMap behaviourMap = behaviours.getBehaviourMap(); - handler.setInputMap( inputTriggerMap ); - handler.setBehaviourMap( behaviourMap ); - - // Add the TrackMate listener first. - final KeyListener[] keyListeners = canvas.getKeyListeners(); - final KeyListener ijKeyListener = keyListeners[ 0 ]; - canvas.removeKeyListener( ijKeyListener ); - - // Connect the handler to the AWT component for Mouse handling - // We need the proxy because the default MouseAndKeyHandler does not - // consume events when a trigger is matched, and without it the events - // are sent to the ImageJ original KeyListener. - final MouseEventProxy proxy = new MouseEventProxy( handler, inputTriggerMap ); - canvas.addKeyListener( proxy ); - canvas.addMouseListener( proxy ); - canvas.addMouseMotionListener( proxy ); - canvas.addMouseWheelListener( proxy ); - - // Direct Key Event Proxy Bridge. This was done with Gemini. - // Because an AWT Canvas bypasses Swing's ActionMap dispatch pipeline, - // we manually intercept the KeyStrokes and route them to our Action - // map. - canvas.addKeyListener( new KeyAdapter() - { - @Override - public void keyPressed( final KeyEvent e ) - { - // Get the keystroke matching this precise key event - final KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent( e ); - if ( keyStroke == null ) - return; - - // Lookup the unique Action ID string assigned to this key combo - final Object actionKey = inputMap.get( keyStroke ); - if ( actionKey != null ) - { - // Pull the runnable action associated with that ID - final Action action = actionMap.get( actionKey ); - if ( action != null && action.isEnabled() ) - { - // Fire the shortcut action directly on the EDT - action.actionPerformed( null ); - e.consume(); // Block from propagating to IJ listener - } - } - } - } ); - - // Re-add the original ImageJ KeyListener after all proxies - canvas.addKeyListener( ijKeyListener ); - - /* - * Flesh out commands. - */ - - SpotEditBehaviours.install( behaviours, model, selectionModel, imp ); - SpotEditActions.install( actions, model, selectionModel, imp ); - // Select spots with freehand ROI. - SelectSpotsWithRoiListener.install( model, selectionModel, imp ); - - TrackMateConfigDialog.prefDialog( imp.getWindow(), keymap, keymapManager, actions ); - } -} From 8bc69b1fb74610b9d8bffbafda6393ced7c49f16 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 18:10:35 +0200 Subject: [PATCH 284/371] =?UTF-8?q?Remove=20the=20old=20spot=20edit=20tool?= =?UTF-8?q?=20=F0=9F=A5=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hyperstack/SpotEditTool.java | 572 ------------------ .../hyperstack/SpotEditToolConfigPanel.java | 347 ----------- 2 files changed, 919 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditToolConfigPanel.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java deleted file mode 100644 index 253c32df3..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java +++ /dev/null @@ -1,572 +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.visualization.hyperstack; - -import java.awt.Color; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; -import java.util.HashMap; -import java.util.Map; - -import fiji.plugin.trackmate.Logger; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.tool.AbstractTool; -import fiji.tool.ToolWithOptions; -import ij.ImageListener; -import ij.ImagePlus; -import ij.WindowManager; -import ij.gui.ImageCanvas; -import ij.gui.Toolbar; - -public class SpotEditTool extends AbstractTool implements MouseMotionListener, MouseListener, KeyListener, ToolWithOptions -{ - - private static final String TOOL_NAME = "Spot edit tool"; - - private static final String TOOL_ICON = "CeacD70Cd8bD80" - + "D71Cc69D81CfefD91" - + "CdbcD72Cb9bD82" - + "Cd9bD73Cc8aD83CfefD93" - + "CdddD54CbaaD64Cb69D74Cb59D84Cb9aD94CdddDa4" - + "CfefD25Cd9bD35Cb8aD45CaaaD55CcccD65CfdeL7585CdccD95CaaaDa5Cb8aDb5Cd7aDc5CfceDd5" - + "CfeeD26Cc69D36Cc8aD46CdacDb6Cb59Dc6CecdDd6" - + "Cb9aD37CdcdD47CeeeDb7Ca89Dc7" - + "CfefD28Cc7aD38Cd9cD48CecdDb8Cb79Dc8CfdeDd8" - + "CcabD29Cb59D39Cb69D49CedeD59CeacDb9Cc59Dc9CebdDd9" - + "CfdeD0aCc7aD1aCb8aD2aCedeD3aCcbcD4aCb7aD5aCe9cD6aCeeeDbaCa89DcaCfefDda" - + "CebdD0bCc59D1bCebdD2bCfefD4bCc7aL5b6bCeceDbbCb79DcbCfdeDdb" - + "CfeeD0cCa89D1cCfefD2cCcabL5c6cCc9bDbcCc59DccCdabDdc" - + "CedeD0dCb79D1dCedeD2dCc9bL5d6dCecdD9dCc8aDadCb9aDbdCdbcDcdCb8aDddCd8bDedCfceDfd" - + "CebdD0eCc59D1eCebdD2eCfeeD4eCc7aD5eCc6aD6eCfeeD7eCd9bD9eCc59DaeCfdeDbeCebdDdeCc59DeeCeacDfe" - + "CfefD0fCdbcD1fCdddD4fCdcdL5f6fCdddD7fCfdeD9fCdbdDafCebdDefCfefDff"; - - /** The singleton instance. */ - private static SpotEditTool instance; - - /** Stores the editor possibly attached to each {@link ImagePlus}. */ - private final Map< ImagePlus, ModelEditActions > editorMap = new HashMap<>(); - - /** Flag for the auto-linking mode. */ - private boolean autolinkingmode = false; - - private final SpotEditToolParams params = new SpotEditToolParams(); - - private final Logger logger = new MyLogger(); - - private final SpotEditToolConfigPanel configPanel; - - /* - * CONSTRUCTOR - */ - - /** - * Singleton - */ - private SpotEditTool() - { - // Config panel actions. - this.configPanel = new SpotEditToolConfigPanel( params ); - configPanel.buttonSelectTrackDown.addActionListener( e -> selectTrackDownward() ); - configPanel.buttonSelectTrackUp.addActionListener( e -> selectTrackUpward() ); - configPanel.buttonSemiAutoTracking.addActionListener( e -> semiAutoTracking() ); - configPanel.buttonSelectTrack.addActionListener( e -> selectTrack() ); - - // De-register on closing. - ImagePlus.addImageListener( new ImageListener() - { - - @Override - public void imageUpdated( final ImagePlus imp ) - {} - - @Override - public void imageOpened( final ImagePlus imp ) - {} - - @Override - public void imageClosed( final ImagePlus imp ) - { - editorMap.remove( imp ); - } - } ); - } - - /** - * Returns the singleton instance for this tool. If it was not previously - * instantiated, this calls instantiates it. - * - * @return the instance. - */ - public static SpotEditTool getInstance() - { - if ( null == instance ) - instance = new SpotEditTool(); - - return instance; - } - - /** - * 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() - { - final Toolbar toolbar = Toolbar.getInstance(); - if ( null != toolbar && toolbar.getToolId( TOOL_NAME ) >= 0 ) - return true; - return false; - } - - /* - * METHODS - */ - - @Override - public String getToolName() - { - return TOOL_NAME; - } - - @Override - public String getToolIcon() - { - return TOOL_ICON; - } - - @Override - 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 - * of the occurrence of the double call to this method or not. - */ - - final MouseListener[] listeners = canvas.getMouseListeners(); - for ( final MouseListener listener : listeners ) - { - if ( listener == this.mouseProxy ) - return; - } - - super.registerTool( canvas ); - } - - /** - * 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 ) - { - final ImagePlus imp = displayer.getImp(); - final Model model = displayer.getModel(); - final SelectionModel selectionModel = displayer.getSelectionModel(); - final ModelEditActions actions = new ModelEditActions( imp, model, selectionModel, logger ); - editorMap.put( imp, actions ); - } - - /* - * MOUSE AND MOUSE MOTION - */ - - @Override - public void mouseClicked( final MouseEvent e ) - { - final ImagePlus lImp = getImagePlus( e ); - final ModelEditActions actions = editorMap.get( lImp ); - if ( null == actions ) - return; - - final int addToSelectionMask = InputEvent.SHIFT_DOWN_MASK; - final boolean addToSelection = ( e.getModifiersEx() & addToSelectionMask ) == addToSelectionMask; - actions.select( e.getPoint(), addToSelection, !autolinkingmode ); - } - - @Override - public void mousePressed( final MouseEvent e ) - {} - - @Override - public void mouseReleased( final MouseEvent e ) - { - final ImagePlus imp = getImagePlus( e ); - final ModelEditActions actions = editorMap.get( imp ); - if ( actions == null ) - return; - - actions.selectInRoi( e ); - } - - @Override - public void mouseEntered( final MouseEvent e ) - {} - - @Override - public void mouseExited( final MouseEvent e ) - {} - - @Override - public void mouseDragged( final MouseEvent e ) - { - final ImagePlus imp = getImagePlus( e ); - final ModelEditActions actions = editorMap.get( imp ); - if ( null == actions ) - return; - - actions.roiEdit( e ); - } - - @Override - public void mouseMoved( final MouseEvent e ) - { - final ImagePlus imp = getImagePlus( e ); - final ModelEditActions actions = editorMap.get( imp ); - if ( actions == null ) - return; - - actions.moveSpot( e.getPoint() ); - } - - /* - * KEYLISTENER - */ - - @Override - public void keyTyped( final KeyEvent e ) - {} - - @Override - public void keyPressed( final KeyEvent e ) - { - final ImagePlus imp = getImagePlus( e ); - final ModelEditActions actions = editorMap.get( imp ); - if ( null == actions ) - return; - - switch ( e.getKeyCode() ) - { - - // Undo / redo - case KeyEvent.VK_Z: - { - - if ( e.isControlDown() || e.isMetaDown() ) - { - if ( e.isShiftDown() ) - actions.redo(); - else - actions.undo(); - e.consume(); - } - break; - } - - // Track navigation actions. - case KeyEvent.VK_UP: - { - actions.navigateToParent(); - e.consume(); - break; - } - case KeyEvent.VK_DOWN: - { - actions.navigateToChild(); - e.consume(); - break; - } - case KeyEvent.VK_LEFT: - { - actions.navigateToPreviousSibling(); - e.consume(); - break; - } - case KeyEvent.VK_RIGHT: - { - actions.navigateToNextSibling(); - e.consume(); - break; - } - case KeyEvent.VK_PAGE_DOWN: - { - actions.navigateToNextTrack(); - e.consume(); - break; - } - case KeyEvent.VK_PAGE_UP: - { - actions.navigateToPreviousTrack(); - e.consume(); - break; - } - - // Delete currently edited spot - case KeyEvent.VK_DELETE: - { - actions.deleteSpotSelection(); - e.consume(); - break; - } - - // Quick add spot at mouse - case KeyEvent.VK_A: - { - if ( e.isShiftDown() ) - { - // Semi-auto tracking - actions.semiAutoTracking( params.qualityThreshold, params.distanceTolerance, params.nFrames ); - } - else - { - // Create and drop a new spot - actions.addSpot( autolinkingmode ); - } - e.consume(); - break; - } - - // Quick delete spot under mouse - case KeyEvent.VK_D: - { - actions.deleteSpot(); - e.consume(); - break; - } - - // Quick move spot under the mouse - case KeyEvent.VK_SPACE: - { - actions.startMoveSpot(); - break; - - } - - // Quick change spot radius - case KeyEvent.VK_Q: - case KeyEvent.VK_E: - { - e.consume(); - actions.changeSpotRadius( e.getKeyCode() == KeyEvent.VK_E, e.isShiftDown() ); - break; - } - - case KeyEvent.VK_L: - { - - if ( e.isShiftDown() ) - { - // Toggle auto-linking mode - autolinkingmode = !autolinkingmode; - logger.log( "Toggled auto-linking mode " + ( autolinkingmode ? "on.\n" : "off.\n" ) ); - - } - else - { - // Toggle a link between two spots. - actions.toggleLink(); - } - e.consume(); - break; - - } - - case KeyEvent.VK_G: - case KeyEvent.VK_F: - { - actions.stepInTime( e.getKeyCode() == KeyEvent.VK_G, params.stepwiseTimeBrowsing ); - e.consume(); - break; - } - - case KeyEvent.VK_W: - { - e.consume(); // consume it: we do not want IJ to close the window - break; - } - } - } - - @Override - public void keyReleased( final KeyEvent e ) - { - switch ( e.getKeyCode() ) - { - case KeyEvent.VK_SPACE: - { - final ImagePlus imp = getImagePlus( e ); - final ModelEditActions actions = editorMap.get( imp ); - if ( actions != null ) - actions.endMoveSpot(); - break; - } - } - } - - @Override - public void showOptionDialog() - { - configPanel.setLocation( toolbar.getLocationOnScreen() ); - configPanel.setVisible( true ); - } - - /* - * PRIVATE METHODS - */ - - private void selectTrack() - { - final ImagePlus imp = WindowManager.getCurrentImage(); - final ModelEditActions actions = editorMap.get( imp ); - if ( null == actions ) - return; - - actions.selectTrack(); - } - - private void semiAutoTracking() - { - final ImagePlus imp = WindowManager.getCurrentImage(); - final ModelEditActions actions = editorMap.get( imp ); - if ( null == actions ) - return; - - actions.semiAutoTracking( params.qualityThreshold, params.distanceTolerance, params.nFrames ); - } - - private void selectTrackDownward() - { - final ImagePlus imp = WindowManager.getCurrentImage(); - final ModelEditActions actions = editorMap.get( imp ); - if ( null == actions ) - return; - - actions.selectTrackDownward(); - } - - private void selectTrackUpward() - { - - final ImagePlus imp = WindowManager.getCurrentImage(); - final ModelEditActions actions = editorMap.get( imp ); - if ( null == actions ) - return; - - actions.selectTrackUpward(); - } - - /* - * INNER CLASSES - */ - - static class SpotEditToolParams - { - - /* - * Semi-auto tracking parameters - */ - /** - * The fraction of the initial quality above which we keep new spots. - * The highest, the more intolerant. - */ - double qualityThreshold = 0.5; - - /** - * How close must be the new spot found to be accepted, in radius units. - */ - double distanceTolerance = 2d; - - /** - * We process at most nFrames. Make it 0 or negative to have no bounds. - */ - int nFrames = 10; - - /** - * By how many frames to jump when we do step-wide time browsing. - */ - int stepwiseTimeBrowsing = 1; - - @Override - public String toString() - { - return super.toString() + ": " + "QualityThreshold = " + qualityThreshold + ", DistanceTolerance = " + distanceTolerance + ", nFrames = " + nFrames; - } - } - - private class MyLogger extends Logger - { - - private Logger logger() - { - if ( configPanel.isVisible() ) - return configPanel.getLogger(); - - return Logger.IJTOOLBAR_LOGGER; - } - - @Override - public void log( final String message, final Color color ) - { - logger().log( message, color ); - } - - @Override - public void error( final String message ) - { - logger().error( message ); - } - - @Override - public void setProgress( final double val ) - { - logger().setProgress( val ); - } - - @Override - public void setStatus( final String status ) - { - logger().setStatus( status ); - } - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditToolConfigPanel.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditToolConfigPanel.java deleted file mode 100644 index 21e3a66bf..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditToolConfigPanel.java +++ /dev/null @@ -1,347 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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 static fiji.plugin.trackmate.gui.Fonts.BIG_FONT; -import static fiji.plugin.trackmate.gui.Fonts.FONT; -import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; -import static fiji.plugin.trackmate.gui.Icons.SELECT_TRACK_ICON; -import static fiji.plugin.trackmate.gui.Icons.SELECT_TRACK_ICON_DOWNWARDS; -import static fiji.plugin.trackmate.gui.Icons.SELECT_TRACK_ICON_UPWARDS; -import static fiji.plugin.trackmate.gui.Icons.SPOT_ICON_64x64; -import static fiji.plugin.trackmate.gui.Icons.TRACK_ICON; -import static fiji.plugin.trackmate.gui.Icons.TRACK_ICON_64x64; - -import java.awt.Color; -import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; - -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JFormattedTextField; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextPane; -import javax.swing.ScrollPaneConstants; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; -import javax.swing.WindowConstants; -import javax.swing.border.LineBorder; -import javax.swing.text.AttributeSet; -import javax.swing.text.SimpleAttributeSet; -import javax.swing.text.StyleConstants; -import javax.swing.text.StyleContext; - -import fiji.plugin.trackmate.Logger; -import fiji.plugin.trackmate.visualization.hyperstack.SpotEditTool.SpotEditToolParams; - -public class SpotEditToolConfigPanel extends JFrame -{ - private static final long serialVersionUID = 1L; - - private final Logger logger; - - private final JFormattedTextField jNFQualityThreshold; - - private final JFormattedTextField jNFDistanceTolerance; - - private final JFormattedTextField jNFNFrames; - - - private final JFormattedTextField jNFNStepwiseTime; - - private final SpotEditToolParams params; - - final JButton buttonSelectTrackDown; - - final JButton buttonSelectTrackUp; - - final JButton buttonSemiAutoTracking; - - final JButton buttonSelectTrack; - - public SpotEditToolConfigPanel( final SpotEditToolParams params ) - { - - /* - * Listeners - */ - - this.params = params; - final ActionListener al = new ActionListener() - { - @Override - public void actionPerformed( final ActionEvent e ) - { - updateParamsFromTextFields(); - } - }; - final FocusListener fl = new FocusListener() - { - @Override - public void focusLost( final FocusEvent arg0 ) - { - updateParamsFromTextFields(); - } - - @Override - public void focusGained( final FocusEvent arg0 ) - {} - }; - - /* - * GUI - */ - - setTitle( "TrackMate tools" ); - setIconImage( TRACK_ICON.getImage() ); - setResizable( false ); - getContentPane().setLayout( new BoxLayout( getContentPane(), BoxLayout.X_AXIS ) ); - - final JPanel mainPanel = new JPanel(); - getContentPane().add( mainPanel ); - mainPanel.setLayout( null ); - - final JLabel lblTitle = new JLabel( "TrackMate tools" ); - lblTitle.setBounds( 6, 6, 395, 33 ); - lblTitle.setFont( BIG_FONT ); - lblTitle.setIcon( TRACK_ICON_64x64 ); - mainPanel.add( lblTitle ); - - final JPanel panelSemiAutoParams = new JPanel(); - panelSemiAutoParams.setBorder( new LineBorder( new Color( 252, 117, 0 ), 1, false ) ); - panelSemiAutoParams.setBounds( 6, 51, 192, 142 ); - mainPanel.add( panelSemiAutoParams ); - panelSemiAutoParams.setLayout( null ); - - final JLabel lblSemiAutoTracking = new JLabel( "Semi-automatic tracking" ); - lblSemiAutoTracking.setBounds( 6, 6, 180, 16 ); - lblSemiAutoTracking.setFont( FONT.deriveFont( Font.BOLD ) ); - panelSemiAutoParams.add( lblSemiAutoTracking ); - - final JLabel lblQualityThreshold = new JLabel( "Quality threshold" ); - lblQualityThreshold.setToolTipText( "" + - "The fraction of the initial spot quality
    " + - "found spots must have to be considered for linking.
    " + - "The higher, the more stringent." ); - lblQualityThreshold.setBounds( 6, 66, 119, 16 ); - lblQualityThreshold.setFont( SMALL_FONT ); - panelSemiAutoParams.add( lblQualityThreshold ); - - jNFQualityThreshold = new JFormattedTextField( params.qualityThreshold ); - jNFQualityThreshold.setHorizontalAlignment( SwingConstants.CENTER ); - jNFQualityThreshold.setFont( SMALL_FONT ); - jNFQualityThreshold.setBounds( 137, 64, 49, 18 ); - jNFQualityThreshold.addActionListener( al ); - jNFQualityThreshold.addFocusListener( fl ); - - panelSemiAutoParams.add( jNFQualityThreshold ); - - final JLabel lblDistanceTolerance = new JLabel( "Distance tolerance" ); - lblDistanceTolerance.setToolTipText( "" + - "The maximal distance above which found spots are rejected,
    " + - "expressed in units of the initial spot radius." ); - lblDistanceTolerance.setBounds( 6, 86, 119, 16 ); - lblDistanceTolerance.setFont( SMALL_FONT ); - panelSemiAutoParams.add( lblDistanceTolerance ); - - jNFDistanceTolerance = new JFormattedTextField( params.distanceTolerance ); - jNFDistanceTolerance.setHorizontalAlignment( SwingConstants.CENTER ); - jNFDistanceTolerance.setFont( SMALL_FONT ); - jNFDistanceTolerance.setBounds( 137, 84, 49, 18 ); - jNFDistanceTolerance.addActionListener( al ); - jNFDistanceTolerance.addFocusListener( fl ); - panelSemiAutoParams.add( jNFDistanceTolerance ); - - final JLabel lblNFrames = new JLabel( "Max nFrames" ); - lblNFrames.setToolTipText( "How many frames to process at max.
    Make it 0 or negative for no limit." ); - lblNFrames.setBounds( 6, 104, 119, 16 ); - lblNFrames.setFont( SMALL_FONT ); - panelSemiAutoParams.add( lblNFrames ); - - jNFNFrames = new JFormattedTextField( params.nFrames ); - jNFNFrames.setBounds( 137, 104, 49, 18 ); - jNFNFrames.setHorizontalAlignment( SwingConstants.CENTER ); - jNFNFrames.setFont( SMALL_FONT ); - jNFNFrames.addActionListener( al ); - jNFNFrames.addFocusListener( fl ); - panelSemiAutoParams.add( jNFNFrames ); - - buttonSemiAutoTracking = new JButton( SPOT_ICON_64x64 ); - buttonSemiAutoTracking.setBounds( 6, 31, 33, 23 ); - panelSemiAutoParams.add( buttonSemiAutoTracking ); - - final JLabel labelSemiAutoTracking = new JLabel( "Semi-automatic tracking" ); - labelSemiAutoTracking.setToolTipText( "Launch semi-automatic tracking on selected spots." ); - labelSemiAutoTracking.setFont( SMALL_FONT ); - labelSemiAutoTracking.setBounds( 49, 31, 137, 23 ); - panelSemiAutoParams.add( labelSemiAutoTracking ); - - - final JScrollPane scrollPane = new JScrollPane(); - scrollPane.setHorizontalScrollBarPolicy( ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); - scrollPane.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS ); - scrollPane.setBounds( 210, 51, 264, 328 ); - mainPanel.add( scrollPane ); - - final JTextPane textPane = new JTextPane(); - textPane.setFont( SMALL_FONT ); - textPane.setEditable( false ); - textPane.setBackground( this.getBackground() ); - scrollPane.setViewportView( textPane ); - - final JPanel panelButtons = new JPanel(); - panelButtons.setBounds( 6, 262, 192, 117 ); - panelButtons.setBorder( new LineBorder( new Color( 252, 117, 0 ), 1, false ) ); - mainPanel.add( panelButtons ); - panelButtons.setLayout( null ); - - final JLabel lblSelectionTools = new JLabel( "Selection tools" ); - lblSelectionTools.setFont( FONT.deriveFont( Font.BOLD ) ); - lblSelectionTools.setBounds( 6, 11, 172, 14 ); - panelButtons.add( lblSelectionTools ); - - buttonSelectTrack = new JButton( SELECT_TRACK_ICON ); - buttonSelectTrack.setBounds( 10, 36, 33, 23 ); - panelButtons.add( buttonSelectTrack ); - - final JLabel lblSelectTrack = new JLabel( "Select track" ); - lblSelectTrack.setBounds( 53, 36, 129, 23 ); - lblSelectTrack.setFont( SMALL_FONT ); - lblSelectTrack.setToolTipText( "Select the whole tracks selected spots belong to." ); - panelButtons.add( lblSelectTrack ); - - buttonSelectTrackUp = new JButton( SELECT_TRACK_ICON_UPWARDS ); - buttonSelectTrackUp.setBounds( 10, 61, 33, 23 ); - panelButtons.add( buttonSelectTrackUp ); - - final JLabel lblSelectTrackUpward = new JLabel( "Select track upward" ); - lblSelectTrackUpward.setBounds( 53, 61, 129, 23 ); - lblSelectTrackUpward.setFont( SMALL_FONT ); - lblSelectTrackUpward.setToolTipText( "" + - "Select the whole tracks selected spots
    " + - "belong to, backward in time." ); - panelButtons.add( lblSelectTrackUpward ); - - buttonSelectTrackDown = new JButton( SELECT_TRACK_ICON_DOWNWARDS ); - buttonSelectTrackDown.setBounds( 10, 86, 33, 23 ); - panelButtons.add( buttonSelectTrackDown ); - - final JLabel lblSelectTrackDown = new JLabel( "Select track downward" ); - lblSelectTrackDown.setBounds( 53, 86, 129, 23 ); - lblSelectTrackDown.setFont( SMALL_FONT ); - lblSelectTrackDown.setToolTipText( "" + - "Select the whole tracks selected spots
    " + - "belong to, forward in time." ); - panelButtons.add( lblSelectTrackDown ); - - final JPanel panel = new JPanel(); - panel.setBorder( new LineBorder( new Color( 252, 117, 0 ) ) ); - panel.setBounds( 6, 201, 192, 53 ); - mainPanel.add( panel ); - panel.setLayout( null ); - - final JLabel lblNavigationTools = new JLabel( "Navigation tools" ); - lblNavigationTools.setBounds( 6, 6, 172, 14 ); - lblNavigationTools.setFont( FONT.deriveFont( Font.BOLD ) ); - panel.add( lblNavigationTools ); - - jNFNStepwiseTime = new JFormattedTextField( params.stepwiseTimeBrowsing ); - jNFNStepwiseTime.setBounds( 137, 26, 49, 18 ); - jNFNStepwiseTime.setHorizontalAlignment( SwingConstants.CENTER ); - jNFNStepwiseTime.setFont( SMALL_FONT ); - jNFNStepwiseTime.addActionListener( al ); - jNFNStepwiseTime.addFocusListener( fl ); - panel.add( jNFNStepwiseTime ); - - final JLabel lblJumpByb = new JLabel( "Stepwise time browsing" ); - lblJumpByb.setBounds( 10, 29, 120, 14 ); - lblJumpByb.setFont( SMALL_FONT ); - panel.add( lblJumpByb ); - - logger = new Logger() - { - - @Override - public void error( final String message ) - { - log( message, Logger.ERROR_COLOR ); - } - - @Override - public void log( final String message, final Color color ) - { - SwingUtilities.invokeLater( new Runnable() - { - @Override - public void run() - { - textPane.setEditable( true ); - final StyleContext sc = StyleContext.getDefaultStyleContext(); - final AttributeSet aset = sc.addAttribute( SimpleAttributeSet.EMPTY, StyleConstants.Foreground, color ); - final int len = textPane.getDocument().getLength(); - textPane.setCaretPosition( len ); - textPane.setCharacterAttributes( aset, false ); - textPane.replaceSelection( message ); - textPane.setEditable( false ); - } - } ); - } - - @Override - public void setStatus( final String status ) - { - log( status, Logger.GREEN_COLOR ); - } - - @Override - public void setProgress( final double val ) - {} - }; - - setSize( 487, 418 ); - setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE ); - } - - /** - * Returns the {@link Logger} that outputs on this config panel. - * - * @return the {@link Logger} instance of this panel. - */ - public Logger getLogger() - { - return logger; - } - - private void updateParamsFromTextFields() - { - params.distanceTolerance = ( ( Number ) jNFDistanceTolerance.getValue() ).doubleValue(); - params.qualityThreshold = ( ( Number ) jNFQualityThreshold.getValue() ).doubleValue(); - params.nFrames = ( ( Number ) jNFNFrames.getValue() ).intValue(); - params.stepwiseTimeBrowsing = ( ( Number ) jNFNStepwiseTime.getValue() ).intValue(); - } -} From def7ae68d6756f97b156ec49a2ea5ff874439cdd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 18:20:27 +0200 Subject: [PATCH 285/371] Put all key config contexts and scope in one file. --- .../hyperstack/HyperStackDisplayer.java | 9 +--- .../behaviours/SpotEditActions.java | 5 +-- .../behaviours/SpotEditBehaviours.java | 5 +-- .../behaviours/TrackMateConfigDialog.java | 13 ++++-- .../visualization/ui/KeyConfigContexts.java | 41 +++++++++++++++++++ .../ui/TrackMateKeymapManager.java | 5 +-- .../visualization/ui/package-info.java | 5 +++ 7 files changed, 62 insertions(+), 21 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/ui/KeyConfigContexts.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/ui/package-info.java 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 e77669cff..73a1dec3b 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -35,6 +35,7 @@ import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SpotEditActions; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SpotEditBehaviours; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.TrackMateConfigDialog; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import ij.ImagePlus; import ij.gui.Overlay; import ij.gui.Roi; @@ -50,12 +51,6 @@ public class HyperStackDisplayer extends AbstractTrackMateModelView public static final String KEY = "HYPERSTACKDISPLAYER"; - /** - * The key configuration context for actions and behaviours specific to this - * displayer. - */ - public static final String KEY_CONFIG_CONTEXT = "trackmate-main-view"; - /* * CONSTRUCTORS */ @@ -174,7 +169,7 @@ public void render() * UI behaviours and actions */ - final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, KEY_CONFIG_CONTEXT ); + final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, KeyConfigContexts.HYPERSTACK_DISPLAYER ); SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); SpotEditActions.install( adapter.actions(), model, selectionModel, imp ); // Select spots with freehand ROI. diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java index c036f8832..2e5e6a5d8 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java @@ -14,8 +14,7 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.util.TrackNavigator; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import ij.ImagePlus; public class SpotEditActions @@ -127,7 +126,7 @@ public static class Descriptions extends CommandDescriptionProvider { public Descriptions() { - super( TrackMateKeymapManager.KEY_CONFIG_SCOPE, HyperStackDisplayer.KEY_CONFIG_CONTEXT ); + super( KeyConfigContexts.KEY_CONFIG_SCOPE, KeyConfigContexts.HYPERSTACK_DISPLAYER ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java index 5fc25653f..7adf939b8 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java @@ -13,8 +13,7 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotBase; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import ij.ImagePlus; import net.imglib2.RealLocalizable; @@ -331,7 +330,7 @@ public static class Descriptions extends CommandDescriptionProvider { public Descriptions() { - super( TrackMateKeymapManager.KEY_CONFIG_SCOPE, HyperStackDisplayer.KEY_CONFIG_CONTEXT ); + super( KeyConfigContexts.KEY_CONFIG_SCOPE, KeyConfigContexts.HYPERSTACK_DISPLAYER ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java index db7300e06..1f1b7a0d7 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java @@ -1,5 +1,12 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.ALL_SPOTS_TABLE; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.HYPERSTACK_DISPLAYER; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.KEY_CONFIG_SCOPE; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACKMATE; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACKSCHEME; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACK_TABLE; + import java.awt.Frame; import org.scijava.plugin.Plugin; @@ -14,8 +21,6 @@ import bdv.ui.keymap.KeymapManager; import bdv.ui.keymap.KeymapSettingsPage; import fiji.plugin.trackmate.gui.GuiUtils; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; public class TrackMateConfigDialog { @@ -23,7 +28,7 @@ public class TrackMateConfigDialog public static void prefDialog( final Frame frame, final Keymap keymap, final KeymapManager keymapManager, final Actions actions ) { final PreferencesDialog preferencesDialog = new PreferencesDialog( frame, keymap, - new String[] { HyperStackDisplayer.KEY_CONFIG_CONTEXT } ); + new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE } ); GuiUtils.positionWindow( preferencesDialog, frame ); BigDataViewerActions.toggleDialogAction( actions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); preferencesDialog.addPage( new KeymapSettingsPage( "Keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); @@ -34,7 +39,7 @@ public static class Descriptions extends CommandDescriptionProvider { public Descriptions() { - super( TrackMateKeymapManager.KEY_CONFIG_SCOPE, HyperStackDisplayer.KEY_CONFIG_CONTEXT ); + super( KEY_CONFIG_SCOPE, TRACKMATE ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/KeyConfigContexts.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/KeyConfigContexts.java new file mode 100644 index 000000000..b9b80c79b --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/KeyConfigContexts.java @@ -0,0 +1,41 @@ +package fiji.plugin.trackmate.visualization.ui; + +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider.Scope; + +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; + +public interface KeyConfigContexts +{ + + /** + * The scope of the TrackMate app. + */ + Scope KEY_CONFIG_SCOPE = new Scope( "TrackMate" ); + + /** + * The action or behaviour applies to the whole app. + */ + String TRACKMATE = "trackmate"; + + /** + * The action or behaviour applies to the {@link HyperStackDisplayer} view + * (the main view). + */ + String HYPERSTACK_DISPLAYER = "trackmate-main-view"; + + /** + * The action or behaviour applies to TrackScheme views. + */ + String TRACKSCHEME = "trackscheme"; + + /** + * The action or behaviour applies to the all spot table views. + */ + String ALL_SPOTS_TABLE = "all-spots-table"; + + /** + * The action or behaviour applies to the track table views. + */ + String TRACK_TABLE = "track-table"; + +} \ No newline at end of file diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java index fc95cc4de..f04fb7355 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java @@ -1,7 +1,6 @@ package fiji.plugin.trackmate.visualization.ui; import org.scijava.Context; -import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider.Scope; import org.scijava.ui.behaviour.io.gui.CommandDescriptionsBuilder; import bdv.ui.keymap.KeymapManager; @@ -10,15 +9,13 @@ public class TrackMateKeymapManager extends KeymapManager { - public static final Scope KEY_CONFIG_SCOPE = new Scope( "TrackMate" ); - @Override public synchronized void discoverCommandDescriptions() { final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); final Context context = TMUtils.getContext(); context.inject( builder ); - builder.discoverProviders( KEY_CONFIG_SCOPE ); + builder.discoverProviders( KeyConfigContexts.KEY_CONFIG_SCOPE ); setCommandDescriptions( builder.build() ); } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/package-info.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/package-info.java new file mode 100644 index 000000000..19e6a9912 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains the classes used in conjunction with the ui-behaviours + * framework, and that are view agnostic. + */ +package fiji.plugin.trackmate.visualization.ui; From 67a40416f2277b0e6aa0c04ae9636ee79d3a6dcb Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 18:54:00 +0200 Subject: [PATCH 286/371] Extract general actions to a class. These actions do not depend on whether they are run from a specific view, and can be reused elsewhere: undo / redo, delete selection, navigations, .... --- .../hyperstack/HyperStackDisplayer.java | 6 +- .../HyperStackDisplayerActions.java | 64 +++++++++++++++++++ .../TrackMateActions.java} | 42 ++---------- 3 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java rename src/main/java/fiji/plugin/trackmate/visualization/{hyperstack/behaviours/SpotEditActions.java => ui/TrackMateActions.java} (72%) 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 73a1dec3b..049fb6980 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -30,12 +30,13 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; import fiji.plugin.trackmate.visualization.ViewUtils; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.HyperStackDisplayerActions; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.ImagePlusBehavioursAdapter; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SelectSpotsWithRoiListener; -import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SpotEditActions; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SpotEditBehaviours; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.TrackMateConfigDialog; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; +import fiji.plugin.trackmate.visualization.ui.TrackMateActions; import ij.ImagePlus; import ij.gui.Overlay; import ij.gui.Roi; @@ -171,7 +172,8 @@ public void render() final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, KeyConfigContexts.HYPERSTACK_DISPLAYER ); SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); - SpotEditActions.install( adapter.actions(), model, selectionModel, imp ); + HyperStackDisplayerActions.install( adapter.actions(), model, selectionModel, imp ); + TrackMateActions.install( adapter.actions(), model, selectionModel ); // Select spots with freehand ROI. SelectSpotsWithRoiListener.install( model, selectionModel, imp ); // Pref dialog. diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java new file mode 100644 index 000000000..61d96c091 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java @@ -0,0 +1,64 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import org.scijava.plugin.Plugin; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; +import org.scijava.ui.behaviour.util.Actions; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; +import ij.ImagePlus; + +public class HyperStackDisplayerActions +{ + private static final String TOGGLE_AUTO_LINKING = "toggle auto-linking"; + private static final String[] TOGGLE_AUTO_LINKING_KEYS = new String[] { "ctrl L" }; + + private static final String NEXT_TIMEPOINT = "next timepoint"; + private static final String PREVIOUS_TIMEPOINT = "previous timepoint"; + private static final String[] NEXT_TIMEPOINT_KEYS = new String[] { "RIGHT" }; + private static final String[] PREVIOUS_TIMEPOINT_KEYS = new String[] { "LEFT" }; + + private static final String SEMI_AUTOMATIC_TRACKING = "semi-automatic tracking"; + private static final String[] SEMI_AUTOMATIC_TRACKING_KEYS = new String[] { "shift A" }; + + private static final String DO_NOTHING_ACTION = "do nothing"; + private static final String[] DO_NOTHING_ACTION_KEYS = new String[] { "W" }; + + public static final void install( final Actions actions, final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + // Toggle auto-linking + actions.runnableAction( () -> SpotEditBehaviours.autoLinkingmode = !SpotEditBehaviours.autoLinkingmode, TOGGLE_AUTO_LINKING, TOGGLE_AUTO_LINKING_KEYS ); + + // Avoid closing the window when pressing W + actions.runnableAction( () -> {}, DO_NOTHING_ACTION, DO_NOTHING_ACTION_KEYS ); + + // Change timepoint + actions.runnableAction( () -> imp.setT( imp.getT() + SemiAutoTracking.params.stepwiseTimeBrowsing ), NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS ); + actions.runnableAction( () -> imp.setT( imp.getT() - SemiAutoTracking.params.stepwiseTimeBrowsing ), PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS ); + + // Semi-automatic tracking + final SemiAutoTracking semiAutoTracking = new SemiAutoTracking( model, selectionModel, imp ); + actions.runnableAction( () -> semiAutoTracking.run(), SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS ); + } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class Descriptions extends CommandDescriptionProvider + { + public Descriptions() + { + super( KeyConfigContexts.KEY_CONFIG_SCOPE, KeyConfigContexts.HYPERSTACK_DISPLAYER ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( TOGGLE_AUTO_LINKING, TOGGLE_AUTO_LINKING_KEYS, "Toggle the auto-linking mode." ); + descriptions.add( NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS, "Go to the next timepoint." ); + descriptions.add( PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS, "Go to the previous timepoint." ); + descriptions.add( SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS, "Run semi-automatic tracking on the selected spots." ); + descriptions.add( DO_NOTHING_ACTION, DO_NOTHING_ACTION_KEYS, "Do nothing. This is to avoid closing the window when pressing W." ); + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java similarity index 72% rename from src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java rename to src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java index 2e5e6a5d8..8c2df8836 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java @@ -1,4 +1,4 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; +package fiji.plugin.trackmate.visualization.ui; import java.awt.Toolkit; import java.awt.event.InputEvent; @@ -14,10 +14,8 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.util.TrackNavigator; -import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; -import ij.ImagePlus; -public class SpotEditActions +public class TrackMateActions { private static final String UNDO_ACTION = "undo"; @@ -47,17 +45,6 @@ public class SpotEditActions private static final String DELETE_SELECTION = "delete selection"; private static final String[] DELETE_SELECTION_KEYS = new String[] { "BACK_SPACE", "DELETE" }; - private static final String TOGGLE_AUTO_LINKING = "toggle auto-linking"; - private static final String[] TOGGLE_AUTO_LINKING_KEYS = new String[] { "ctrl L" }; - - private static final String NEXT_TIMEPOINT = "next timepoint"; - private static final String PREVIOUS_TIMEPOINT = "previous timepoint"; - private static final String[] NEXT_TIMEPOINT_KEYS = new String[] { "RIGHT" }; - private static final String[] PREVIOUS_TIMEPOINT_KEYS = new String[] { "LEFT" }; - - private static final String SEMI_AUTOMATIC_TRACKING = "semi-automatic tracking"; - private static final String[] SEMI_AUTOMATIC_TRACKING_KEYS = new String[] { "shift A" }; - static { final int menuMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); @@ -66,7 +53,7 @@ public class SpotEditActions REDO_ACTION_KEYS = new String[] { modifier + " Y", modifier + " shift Z" }; } - public static final void install( final Actions actions, final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + public static final void install( final Actions actions, final Model model, final SelectionModel selectionModel ) { final TrackNavigator trackNavigator = new TrackNavigator( model, selectionModel ); @@ -84,22 +71,8 @@ public static final void install( final Actions actions, final Model model, fina actions.runnableAction( () -> trackNavigator.previousTrack(), NAVIGATE_TO_PREVIOUS_TRACK, NAVIGATE_TO_PREVIOUS_TRACK_KEYS ); actions.runnableAction( () -> trackNavigator.nextTrack(), NAVIGATE_TO_NEXT_TRACK, NAVIGATE_TO_NEXT_TRACK_KEYS ); - // Delete + // Delete selection actions.runnableAction( () -> deleteSelection( model, selectionModel ), DELETE_SELECTION, DELETE_SELECTION_KEYS ); - - // Toggle auto-linking - actions.runnableAction( () -> SpotEditBehaviours.autoLinkingmode = !SpotEditBehaviours.autoLinkingmode, TOGGLE_AUTO_LINKING, TOGGLE_AUTO_LINKING_KEYS ); - - // Avoid closing the window when pressing W - actions.runnableAction( () -> {}, "do nothing", new String[] { "W" } ); - - // Change timepoint - actions.runnableAction( () -> imp.setT( imp.getT() + SemiAutoTracking.params.stepwiseTimeBrowsing ), NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS ); - actions.runnableAction( () -> imp.setT( imp.getT() - SemiAutoTracking.params.stepwiseTimeBrowsing ), PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS ); - - // Semi-automatic tracking - final SemiAutoTracking semiAutoTracking = new SemiAutoTracking( model, selectionModel, imp ); - actions.runnableAction( () -> semiAutoTracking.run(), SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS ); } private static void deleteSelection( final Model model, final SelectionModel selectionModel ) @@ -126,7 +99,7 @@ public static class Descriptions extends CommandDescriptionProvider { public Descriptions() { - super( KeyConfigContexts.KEY_CONFIG_SCOPE, KeyConfigContexts.HYPERSTACK_DISPLAYER ); + super( KeyConfigContexts.KEY_CONFIG_SCOPE, KeyConfigContexts.TRACKMATE ); } @Override @@ -145,11 +118,6 @@ public void getCommandDescriptions( final CommandDescriptions descriptions ) descriptions.add( NAVIGATE_TO_NEXT_TRACK, NAVIGATE_TO_NEXT_TRACK_KEYS, "Navigate to the next track." ); descriptions.add( DELETE_SELECTION, DELETE_SELECTION_KEYS, "Delete the selected spots and edges." ); - descriptions.add( TOGGLE_AUTO_LINKING, TOGGLE_AUTO_LINKING_KEYS, "Toggle the auto-linking mode." ); - descriptions.add( NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS, "Go to the next timepoint." ); - descriptions.add( PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS, "Go to the previous timepoint." ); - descriptions.add( SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS, "Run semi-automatic tracking on the selected spots." ); - descriptions.add( "do nothing", new String[] { "W" }, "Do nothing. This is to avoid closing the window when pressing W." ); } } } From 08c5cacee03da03364a081e3484054be99a67a23 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 1 Aug 2026 19:03:30 +0200 Subject: [PATCH 287/371] Serialize keymaps. --- .../visualization/hyperstack/HyperStackDisplayer.java | 2 +- .../behaviours/ImagePlusBehavioursAdapter.java | 10 +++++----- .../visualization/ui/TrackMateKeymapManager.java | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) 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 049fb6980..1a2a73f08 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -170,7 +170,7 @@ public void render() * UI behaviours and actions */ - final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, KeyConfigContexts.HYPERSTACK_DISPLAYER ); + final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, new String[] { KeyConfigContexts.HYPERSTACK_DISPLAYER, KeyConfigContexts.TRACKMATE } ); SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); HyperStackDisplayerActions.install( adapter.actions(), model, selectionModel, imp ); TrackMateActions.install( adapter.actions(), model, selectionModel ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java index 88a673dcc..44066aea8 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java @@ -60,7 +60,7 @@ public class ImagePlusBehavioursAdapter private final Behaviours behaviours; - public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keymapManager, final String keyConfigContext ) + public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keymapManager, final String[] keyConfigContexts ) { final ImageCanvas canvas = imp.getCanvas(); canvas.setFocusable( true ); @@ -72,14 +72,14 @@ public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keym final InputTriggerConfig config = keymapManager.getForwardSelectedKeymap().getConfig(); // Behaviours - this.behaviours = new Behaviours( config, keyConfigContext ); - behaviours.install( behaviourBindings, keyConfigContext + "-beaviors" ); + this.behaviours = new Behaviours( config, keyConfigContexts ); + behaviours.install( behaviourBindings, keyConfigContexts[ 0 ] + "-behaviours" ); // Actions final InputMap inputMap = actionBindings.getConcatenatedInputMap(); final ActionMap actionMap = actionBindings.getConcatenatedActionMap(); - this.actions = new Actions( config, keyConfigContext ); - actions.install( actionBindings, keyConfigContext + "-actions" ); + this.actions = new Actions( config, keyConfigContexts ); + actions.install( actionBindings, keyConfigContexts[ 0 ] + "-actions" ); final Keymap keymap = keymapManager.getForwardSelectedKeymap(); keymap.updateListeners().add( () -> { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java index f04fb7355..511b1ae63 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java @@ -1,5 +1,7 @@ package fiji.plugin.trackmate.visualization.ui; +import java.io.File; + import org.scijava.Context; import org.scijava.ui.behaviour.io.gui.CommandDescriptionsBuilder; @@ -9,6 +11,14 @@ public class TrackMateKeymapManager extends KeymapManager { + private static final String KEYMAP_HOME = new File( System.getProperty( "user.home" ), ".trackmate" ).getAbsolutePath(); + + public TrackMateKeymapManager() + { + super( KEYMAP_HOME ); + } + + @Override public synchronized void discoverCommandDescriptions() { From 5aef0d3e46f55d6174ddfe4b148320ec245df852 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 09:46:48 +0200 Subject: [PATCH 288/371] Also remove the old ModelEditActions --- .../hyperstack/ModelEditActions.java | 557 ------------------ 1 file changed, 557 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java deleted file mode 100644 index 8fae59984..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/ModelEditActions.java +++ /dev/null @@ -1,557 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.MouseInfo; -import java.awt.Point; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.Locale; -import java.util.Set; - -import javax.swing.SwingUtilities; - -import org.jgrapht.graph.DefaultWeightedEdge; - -import fiji.plugin.trackmate.Logger; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotBase; -import fiji.plugin.trackmate.detection.semiauto.SemiAutoTracker; -import fiji.plugin.trackmate.util.ModelTools; -import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.util.Threads; -import fiji.plugin.trackmate.util.TrackNavigator; -import ij.IJ; -import ij.ImagePlus; -import ij.Prefs; -import ij.gui.FreehandRoi; -import ij.gui.ImageCanvas; - -public class ModelEditActions -{ - - /** - * Fall back default radius when the settings does not give a default radius - * to use. - */ - static final double FALL_BACK_RADIUS = 5.; - - private static final double COARSE_STEP = 2; - - private static final double FINE_STEP = 0.2f; - - private final Model model; - - private final SelectionModel selectionModel; - - private final Logger logger; - - private final ImagePlus imp; - - private Spot quickEditedSpot; - - private double previousRadius = FALL_BACK_RADIUS; - - private FreehandRoi roiedit; - - private final TrackNavigator trackNavigator; - - public ModelEditActions( final ImagePlus imp, final Model model, final SelectionModel selectionModel, final Logger logger ) - { - this.imp = imp; - this.model = model; - this.selectionModel = selectionModel; - this.logger = logger; - this.trackNavigator = new TrackNavigator( model, selectionModel ); - } - - private Spot makeSpot( Point mouseLocation ) - { - final ImageCanvas canvas = imp.getCanvas(); - if ( mouseLocation == null ) - { - mouseLocation = MouseInfo.getPointerInfo().getLocation(); - SwingUtilities.convertPointFromScreen( mouseLocation, canvas ); - } - final double[] calibration = TMUtils.getSpatialCalibration( imp ); - return new SpotBase( - ( -0.5 + canvas.offScreenXD( mouseLocation.x ) ) * calibration[ 0 ], - ( -0.5 + canvas.offScreenYD( mouseLocation.y ) ) * calibration[ 1 ], - ( imp.getSlice() - 1 ) * calibration[ 2 ], - FALL_BACK_RADIUS, - -1. ); - } - - private Spot getSpotAtMouseLocation() - { - final Spot clickLocation = makeSpot( null ); - final int frame = imp.getFrame() - 1; - return model.getSpots().getSpotAt( clickLocation, frame, true ); - } - - private void updateStatusBar( final Spot spot, final String units ) - { - if ( null == spot ) - return; - String statusString = ""; - if ( null == spot.getName() || spot.getName().equals( "" ) ) - statusString = String.format( Locale.US, "Spot ID%d, x = %.1f, y = %.1f, z = %.1f, r = %.1f %s", spot.ID(), spot.getFeature( Spot.POSITION_X ), spot.getFeature( Spot.POSITION_Y ), spot.getFeature( Spot.POSITION_Z ), spot.getFeature( Spot.RADIUS ), units ); - else - statusString = String.format( Locale.US, "Spot %s, x = %.1f, y = %.1f, z = %.1f, r = %.1f %s", spot.getName(), spot.getFeature( Spot.POSITION_X ), spot.getFeature( Spot.POSITION_Y ), spot.getFeature( Spot.POSITION_Z ), spot.getFeature( Spot.RADIUS ), units ); - IJ.showStatus( statusString ); - } - - public final void deleteSpotSelection() - { - final ArrayList< Spot > spotSelection = new ArrayList<>( selectionModel.getSpotSelection() ); - final ArrayList< DefaultWeightedEdge > edgeSelection = new ArrayList<>( selectionModel.getEdgeSelection() ); - model.beginUpdate(); - try - { - selectionModel.clearSelection(); - for ( final DefaultWeightedEdge edge : edgeSelection ) - { - model.removeEdge( edge ); - logger.log( "Removed edge " + edge + ".\n" ); - } - for ( final Spot spot : spotSelection ) - { - model.removeSpot( spot ); - logger.log( "Removed spot " + spot + ".\n" ); - } - } - finally - { - model.endUpdate(); - } - } - - public void semiAutoTracking( final double qualityThreshold, final double distanceTolerance, final int nFrames ) - { - logger.log( "Semi-automatic tracking.\n" ); - @SuppressWarnings( "rawtypes" ) - final SemiAutoTracker autotracker = new SemiAutoTracker( model, selectionModel, imp, logger ); - autotracker.setParameters( qualityThreshold, distanceTolerance, nFrames ); - autotracker.setNumThreads( Prefs.getThreads() / 2 ); - Threads.run( "TrackMate semi-automated tracking thread", () -> { - final boolean ok = autotracker.checkInput() && autotracker.process(); - if ( !ok ) - logger.error( autotracker.getErrorMessage() ); - } ); - } - - public void addSpot( final boolean autoLinkingmode ) - { - final double radius = previousRadius; - final Spot newSpot = makeSpot( null ); - final double dt = imp.getCalibration().frameInterval; - final int frame = imp.getFrame() - 1; - newSpot.putFeature( Spot.POSITION_T, frame * dt ); - newSpot.putFeature( Spot.FRAME, Double.valueOf( frame ) ); - newSpot.putFeature( Spot.RADIUS, radius ); - newSpot.putFeature( Spot.QUALITY, -1d ); - - model.beginUpdate(); - try - { - model.addSpotTo( newSpot, frame ); - logger.log( "Added spot " + newSpot + " to frame " + frame + ".\n" ); - } - finally - { - model.endUpdate(); - } - - /* - * If we are in auto-link mode, we create an edge with spot in - * selection, if there is just one and if it is in a previous frame - */ - if ( autoLinkingmode ) - { - final Set< Spot > spotSelection = selectionModel.getSpotSelection(); - if ( spotSelection.size() == 1 ) - { - final Spot source = spotSelection.iterator().next(); - if ( newSpot.diffTo( source, Spot.FRAME ) != 0 ) - { - model.beginUpdate(); - try - { - model.addEdge( source, newSpot, -1 ); - logger.log( "Created a link between " + source + " and " + newSpot + ".\n" ); - } - finally - { - model.endUpdate(); - } - } - } - selectionModel.clearSpotSelection(); - selectionModel.addSpotToSelection( newSpot ); - } - } - - public void deleteSpot() - { - final Spot target = getSpotAtMouseLocation(); - if ( null == target ) - return; - - selectionModel.removeSpotFromSelection( target ); - model.beginUpdate(); - try - { - model.removeSpot( target ); - logger.log( "Removed spot " + target + ".\n" ); - } - finally - { - model.endUpdate(); - } - } - - public void startMoveSpot() - { - if ( null == quickEditedSpot ) - { - quickEditedSpot = getSpotAtMouseLocation(); - model.beginUpdate(); - model.beforeEdit( quickEditedSpot ); - } - } - - public void moveSpot( final Point mouseLocation ) - { - if ( quickEditedSpot == null ) - return; - - final ImageCanvas canvas = imp.getCanvas(); - final double[] calibration = TMUtils.getSpatialCalibration( imp ); - final double x = ( -0.5 + canvas.offScreenXD( mouseLocation.x ) ) * calibration[ 0 ]; - final double y = ( -0.5 + canvas.offScreenYD( mouseLocation.y ) ) * calibration[ 1 ]; - final double z = ( imp.getSlice() - 1 ) * calibration[ 2 ]; - - quickEditedSpot.putFeature( Spot.POSITION_X, x ); - quickEditedSpot.putFeature( Spot.POSITION_Y, y ); - quickEditedSpot.putFeature( Spot.POSITION_Z, z ); - imp.updateAndDraw(); - } - - public void endMoveSpot() - { - if ( null == quickEditedSpot ) - return; - model.endUpdate(); - quickEditedSpot = null; - } - - public void changeSpotRadius( final boolean increase, final boolean fast ) - { - final Spot target = getSpotAtMouseLocation(); - if ( null == target ) - return; - - // Compute new radius. - final double radius = target.getFeature( Spot.RADIUS ); - 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; - - // Actually scale the spot. - model.beginUpdate(); - try - { - model.beforeEdit( target ); - target.scale( radius / newRadius ); - // Store new value of radius for next spot creation. - previousRadius = newRadius; - // Scale spot - target.putFeature( Spot.RADIUS, newRadius ); - logger.log( String.format( Locale.US, "Changed spot " + target + " radius to %.1f " + model.getSpaceUnits() + ".\n", radius ) ); - } - catch ( final Exception e ) - { - e.printStackTrace(); - } - finally - { - model.endUpdate(); - } - } - - public void toggleLink() - { - final Set< Spot > selectedSpots = selectionModel.getSpotSelection(); - if ( selectedSpots.size() == 2 ) - { - final Iterator< Spot > it = selectedSpots.iterator(); - final Spot sourceTmp = it.next(); - final Spot targetTmp = it.next(); - - final Spot source = sourceTmp.diffTo( targetTmp, Spot.FRAME ) < 0 ? sourceTmp : targetTmp; - final Spot target = sourceTmp.diffTo( targetTmp, Spot.FRAME ) < 0 ? targetTmp : sourceTmp; - - if ( model.getTrackModel().containsEdge( source, target ) ) - { - /* - * Remove it - */ - model.beginUpdate(); - try - { - model.removeEdge( source, target ); - logger.log( "Removed edge between " + source + " and " + target + ".\n" ); - } - finally - { - model.endUpdate(); - } - - } - else - { - /* - * Create a new link - */ - final int ts = source.getFeature( Spot.FRAME ).intValue(); - final int tt = target.getFeature( Spot.FRAME ).intValue(); - - if ( tt != ts ) - { - model.beginUpdate(); - try - { - model.addEdge( source, target, -1 ); - logger.log( "Created an edge between " + source + " and " + target + ".\n" ); - } - finally - { - model.endUpdate(); - } - /* - * To emulate a kind of automatic linking, we put the last - * spot to the selection, so several spots can be tracked in - * a row without having to de-select one - */ - final Spot single = ( tt > ts ) ? target : source; - selectionModel.clearSpotSelection(); - selectionModel.addSpotToSelection( single ); - } - else - { - logger.error( "Cannot create an edge between two spots belonging to the same frame.\n" ); - } - } - - } - else - { - logger.error( "Expected selection to contain 2 spots, found " + selectedSpots.size() + ".\n" ); - } - } - - public void stepInTime( final boolean forward, final int stepwiseTimeBrowsing ) - { - // Stepwise time browsing. - final int currentT = imp.getT() - 1; - final int prevStep = ( currentT / stepwiseTimeBrowsing ) * stepwiseTimeBrowsing; - int tp; - if ( forward ) - { - tp = prevStep + stepwiseTimeBrowsing; - } - else - { - if ( currentT == prevStep ) - tp = currentT - stepwiseTimeBrowsing; - else - tp = prevStep; - } - imp.setT( tp + 1 ); - } - - public void select( final Point point, final boolean addToSelection, final boolean canClearSelection ) - { - // If no target, we clear selection - final Spot target = getSpotAtMouseLocation(); - if ( null == target ) - { - if ( canClearSelection ) - { - selectionModel.clearSelection(); - logger.log( "Cleared selection.\n" ); - } - roiedit = null; - imp.setRoi( roiedit ); - } - else - { - updateStatusBar( target, imp.getCalibration().getUnits() ); - if ( addToSelection ) - { - if ( selectionModel.getSpotSelection().contains( target ) ) - selectionModel.removeSpotFromSelection( target ); - else - selectionModel.addSpotToSelection( target ); - } - else - { - selectionModel.clearSpotSelection(); - selectionModel.addSpotToSelection( target ); - } - } - } - - public void roiEdit( final MouseEvent e ) - { - if ( null == roiedit ) - { - if ( !IJ.spaceBarDown() ) - { - roiedit = new FreehandRoi( e.getX(), e.getY(), imp ) - { - private static final long serialVersionUID = 1L; - - @Override - protected void handleMouseUp( final int screenX, final int screenY ) - { - type = FREEROI; - super.handleMouseUp( screenX, screenY ); - } - }; - imp.setRoi( roiedit ); - } - } - else - { - roiedit.mouseDragged( e ); - } - } - - public void selectInRoi( final MouseEvent e ) - { - if ( null != roiedit ) - { - Threads.run( "SpotEditTool roiedit processing", () -> { - roiedit.mouseReleased( e ); - final int frame = imp.getFrame() - 1; - - final Iterator< Spot > it; - if ( IJ.shiftKeyDown() ) - it = model.getSpots().iterator( true ); - else - it = model.getSpots().iterator( frame, true ); - - final Collection< Spot > added = new ArrayList<>(); - final double calibration[] = TMUtils.getSpatialCalibration( imp ); - - while ( it.hasNext() ) - { - final Spot spot = it.next(); - final double x = spot.getFeature( Spot.POSITION_X ); - final double y = spot.getFeature( Spot.POSITION_Y ); - // In pixel units - final int xp = ( int ) ( x / calibration[ 0 ] + 0.5f ); - final int yp = ( int ) ( y / calibration[ 1 ] + 0.5f ); - - if ( null != roiedit && roiedit.contains( xp, yp ) ) - added.add( spot ); - } - - if ( !added.isEmpty() ) - { - selectionModel.addSpotToSelection( added ); - if ( added.size() == 1 ) - logger.log( "Added one spot to selection.\n" ); - else - logger.log( "Added " + added.size() + " spots to selection.\n" ); - } - roiedit = null; - } ); - } - } - - public void selectTrackDownward() - { - ModelTools.selectTrackDownward( selectionModel ); - } - - public void selectTrackUpward() - { - ModelTools.selectTrackUpward( selectionModel ); - } - - public void selectTrack() - { - ModelTools.selectTrack( selectionModel ); - } - - public void navigateToChild() - { - trackNavigator.nextInTime(); - } - - public void navigateToParent() - { - trackNavigator.previousInTime(); - } - - public void navigateToNextSibling() - { - trackNavigator.nextSibling(); - } - - public void navigateToPreviousSibling() - { - trackNavigator.previousSibling(); - } - - public void navigateToNextTrack() - { - trackNavigator.nextTrack(); - } - - public void navigateToPreviousTrack() - { - trackNavigator.previousTrack(); - } - - public void undo() - { - model.undo(); - } - - public void redo() - { - model.redo(); - } -} From 5ca11c21fd971a1c2ee4764087cd224d407d40d3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 09:52:01 +0200 Subject: [PATCH 289/371] Remove the editingSpot from the overlay. We don't use it anymore. Everything is painted normally or painted in special overlays elsewhere. --- .../visualization/hyperstack/SpotOverlay.java | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) 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 9b20ef711..5fb3be85f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -60,8 +60,6 @@ public class SpotOverlay extends Roi private static final long serialVersionUID = 1L; - protected Spot editingSpot; - protected final double[] calibration; protected FontMetrics fm; @@ -145,9 +143,6 @@ public void drawOverlay( final Graphics g ) // Track display mode only displays selection. for ( final Spot spot : spotSelection ) { - if ( spot == editingSpot ) - continue; - final int sFrame = spot.getFeature( Spot.FRAME ).intValue(); if ( sFrame != frame ) continue; @@ -169,7 +164,7 @@ public void drawOverlay( final Graphics g ) { final Spot spot = iterator.next(); - if ( editingSpot == spot || ( spotSelection != null && spotSelection.contains( spot ) ) ) + if ( spotSelection != null && spotSelection.contains( spot ) ) continue; final Color color = colorGenerator.color( spot ); @@ -189,9 +184,6 @@ public void drawOverlay( final Graphics g ) g2d.setColor( displaySettings.getHighlightColor() ); for ( final Spot spot : spotSelection ) { - if ( spot == editingSpot ) - continue; - final int sFrame = spot.getFeature( Spot.FRAME ).intValue(); if ( sFrame != frame ) continue; @@ -203,32 +195,6 @@ public void drawOverlay( final Graphics g ) drawExtraLayer( g2d, frame ); - /* - * Deal with editing spot - we always draw it with its center at the - * current z, current t (it moves along with the current slice). - */ - if ( null != editingSpot ) - { - g2d.setColor( displaySettings.getHighlightColor() ); - g2d.setStroke( new BasicStroke( - ( float ) displaySettings.getLineThickness(), - BasicStroke.CAP_ROUND, - BasicStroke.JOIN_ROUND, - 1.0f, - new float[] { 5f, 5f }, 0 ) ); - final double x = editingSpot.getFeature( Spot.POSITION_X ); - final double y = editingSpot.getFeature( Spot.POSITION_Y ); - final double radius = editingSpot.getFeature( Spot.RADIUS ) / calibration[ 0 ] * lMag; - // In pixel units - final double xp = x / calibration[ 0 ] + 0.5d; - final double yp = y / calibration[ 1 ] + 0.5d; - // Scale to image zoom - final double xs = ( xp - xcorner ) * lMag; - final double ys = ( yp - ycorner ) * lMag; - final double radiusRatio = displaySettings.getSpotDisplayRadius(); - g2d.drawOval( ( int ) Math.round( xs - radius * radiusRatio ), ( int ) Math.round( ys - radius * radiusRatio ), ( int ) Math.round( 2 * radius * radiusRatio ), ( int ) Math.round( 2 * radius * radiusRatio ) ); - } - // Restore graphic device original settings g2d.setTransform( originalTransform ); g2d.setComposite( originalComposite ); From 02f44df3353c3b0abe3f64a00e2198e75db98dff Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 11:09:33 +0200 Subject: [PATCH 290/371] Remove the solo undo / redo keybinding. We will use the global one registered in the TrackMateActions class. --- .../plugin/trackmate/TrackMateRunner.java | 2 -- .../visualization/TrackMateModelView.java | 29 ------------------- .../visualization/bvv/TrackMateBVV.java | 8 ----- .../table/AllSpotsTableView.java | 3 -- .../visualization/table/BranchTableView.java | 3 -- .../visualization/table/TrackTableView.java | 3 -- 6 files changed, 48 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java b/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java index 6296ed145..5bea1322b 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java @@ -410,8 +410,6 @@ else if ( macroOptions.containsKey( ARG_INPUT_IMAGE_PATH ) ) // Wizard. final WizardSequence sequence = createSequence( trackmate, selectionModel, displaySettings ); final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); - // Undo / redo - TrackMateModelView.registerUndoShortcut( frame, model ); frame.setIconImage( TRACKMATE_ICON.getImage() ); GuiUtils.positionWindow( frame, imp.getWindow() ); frame.setVisible( true ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java index c6f6597d9..591afb6c3 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java @@ -21,16 +21,6 @@ */ package fiji.plugin.trackmate.visualization; -import java.awt.Toolkit; -import java.awt.event.KeyEvent; - -import javax.swing.JComponent; -import javax.swing.JFrame; -import javax.swing.JRootPane; -import javax.swing.KeyStroke; - -import org.scijava.ui.behaviour.util.RunnableAction; - import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; @@ -85,23 +75,4 @@ public interface TrackMateModelView */ public String getKey(); - /* - * Utilities - */ - - public static void registerUndoShortcut( final JFrame frame, final Model model ) - { - final JRootPane root = frame.getRootPane(); - - // Ctrl on Windows/Linux, Command on macOS - final int menuMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); - final KeyStroke undoKey = KeyStroke.getKeyStroke( KeyEvent.VK_Z, menuMask ); - final KeyStroke redoKey = KeyStroke.getKeyStroke( KeyEvent.VK_Z, menuMask | KeyEvent.SHIFT_DOWN_MASK ); - - root.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW ).put( undoKey, "undo" ); - root.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW ).put( redoKey, "redo" ); - - root.getActionMap().put( "undo", new RunnableAction( "undo", () -> model.undo() ) ); - root.getActionMap().put( "redo", new RunnableAction( "redo", () -> model.redo() ) ); - } } 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 552493433..27140275c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -26,9 +26,6 @@ import java.util.Map; import java.util.Map.Entry; -import javax.swing.JFrame; -import javax.swing.SwingUtilities; - import org.joml.Matrix4f; import bdv.viewer.animate.TranslationAnimator; @@ -43,7 +40,6 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; -import fiji.plugin.trackmate.visualization.TrackMateModelView; import ij.ImagePlus; import net.imglib2.RealLocalizable; import net.imglib2.realtransform.AffineTransform3D; @@ -100,10 +96,6 @@ public void render() it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm, selectionModel.getSpotSelection().contains( s ) ) ); } } ); - - // Undo / redo - final JFrame frame = ( JFrame ) SwingUtilities.getWindowAncestor( viewer ); - TrackMateModelView.registerUndoShortcut( frame, model ); } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java index 98e37f3da..e4beedde9 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java @@ -129,9 +129,6 @@ public AllSpotsTableView( final Model model, final SelectionModel selectionModel getContentPane().add( mainPanel ); pack(); - // Register key bindings for undo and redo. - TrackMateModelView.registerUndoShortcut( this, model ); - /* * Listeners. */ diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java index 9428c32e5..05ebfd767 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java @@ -111,9 +111,6 @@ public BranchTableView( final Model model, final SelectionModel selectionModel, toolbar.add( Box.createHorizontalGlue() ); mainPanel.add( toolbar, BorderLayout.NORTH ); - // Undo/redo. - TrackMateModelView.registerUndoShortcut( this, model ); - getContentPane().add( mainPanel ); pack(); } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java index 999a05342..3db26a84f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java @@ -157,9 +157,6 @@ public TrackTableView( final Model model, final SelectionModel selectionModel, f getContentPane().add( mainPanel ); pack(); - // Undo / redo - TrackMateModelView.registerUndoShortcut( this, model ); - /* * Listeners. */ From 275320546db678104a0b32065b5bdb9d9d89024b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 11:10:19 +0200 Subject: [PATCH 291/371] Put the TrackMateKeymapManager singleton instance in TrackMateKeymapManager --- .../visualization/hyperstack/HyperStackDisplayer.java | 2 ++ .../trackmate/visualization/ui/TrackMateKeymapManager.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) 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 1a2a73f08..3eca1e462 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -37,6 +37,7 @@ import fiji.plugin.trackmate.visualization.hyperstack.behaviours.TrackMateConfigDialog; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import fiji.plugin.trackmate.visualization.ui.TrackMateActions; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; import ij.ImagePlus; import ij.gui.Overlay; import ij.gui.Roi; @@ -170,6 +171,7 @@ public void render() * UI behaviours and actions */ + final TrackMateKeymapManager keymapManager = TrackMateKeymapManager.keymapManager; final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, new String[] { KeyConfigContexts.HYPERSTACK_DISPLAYER, KeyConfigContexts.TRACKMATE } ); SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); HyperStackDisplayerActions.install( adapter.actions(), model, selectionModel, imp ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java index 511b1ae63..7afc7575f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java @@ -13,12 +13,13 @@ public class TrackMateKeymapManager extends KeymapManager private static final String KEYMAP_HOME = new File( System.getProperty( "user.home" ), ".trackmate" ).getAbsolutePath(); + public static final TrackMateKeymapManager keymapManager = new TrackMateKeymapManager(); + public TrackMateKeymapManager() { super( KEYMAP_HOME ); } - @Override public synchronized void discoverCommandDescriptions() { From 6662d3ce9b2839d19572a6d130dfad731b505f90 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 11:14:30 +0200 Subject: [PATCH 292/371] Add a runOnClose() facility to the AbstractTrackMateModelViews --- .../AbstractTrackMateModelView.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java index 5d9ae819d..dac5dea14 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java @@ -21,6 +21,7 @@ */ package fiji.plugin.trackmate.visualization; +import java.util.ArrayList; import java.util.Map; import fiji.plugin.trackmate.Model; @@ -30,7 +31,6 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; /** * An abstract class for TrackMate views. @@ -51,11 +51,7 @@ public abstract class AbstractTrackMateModelView implements SelectionChangeListe protected final DisplaySettings displaySettings; - /* - * STATIC FIELD - */ - - protected static final TrackMateKeymapManager keymapManager = new TrackMateKeymapManager(); + protected final ArrayList< Runnable > runOnClose; /* * PROTECTED CONSTRUCTOR @@ -66,14 +62,39 @@ protected AbstractTrackMateModelView( final Model model, final SelectionModel se this.selectionModel = selectionModel; this.model = model; this.displaySettings = displaySettings; + runOnClose = new ArrayList<>(); + model.addModelChangeListener( this ); selectionModel.addSelectionChangeListener( this ); + onClose( () -> { + model.removeModelChangeListener( this ); + selectionModel.removeSelectionChangeListener( this ); + } ); } /* * PUBLIC METHODS */ + + /** + * Adds the specified {@link Runnable} to the list of runnables to execute + * when this view is closed. + * + * @param runnable + * the {@link Runnable} to add. + */ + public synchronized void onClose( final Runnable runnable ) + { + runOnClose.add( runnable ); + } + + protected synchronized void close() + { + runOnClose.forEach( Runnable::run ); + runOnClose.clear(); + } + /** * This needs to be overridden for concrete implementation to display * selection. From e8bd3440212b388ba22da063a8f80912db312265 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 11:15:15 +0200 Subject: [PATCH 293/371] Abstract class for views based on a JFrame. With facilities to register a behaviours and a actions instances. --- .../AbstractTrackMateModelJFrameView.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java new file mode 100644 index 000000000..b7bd566f2 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java @@ -0,0 +1,121 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2026 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; + +import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +import org.scijava.ui.behaviour.MouseAndKeyHandler; +import org.scijava.ui.behaviour.util.Actions; +import org.scijava.ui.behaviour.util.Behaviours; +import org.scijava.ui.behaviour.util.InputActionBindings; +import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; + +import bdv.ui.keymap.Keymap; +import bdv.ui.keymap.Keymap.UpdateListener; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; + +/** + * An abstract class for TrackMate views that display content in a + * {@link JFrame}. + * + * @author Jean-Yves Tinevez + */ +public abstract class AbstractTrackMateModelJFrameView extends AbstractTrackMateModelView +{ + + private final InputActionBindings keybindings; + + private final TriggerBehaviourBindings triggerbindings; + + private final MouseAndKeyHandler mouseAndKeyHandler; + + protected final Actions actions; + + protected final Behaviours behaviours; + + protected AbstractTrackMateModelJFrameView( final Model model, final SelectionModel selectionModel, final DisplaySettings displaySettings, final String... keyConfigContexts ) + { + super( model, selectionModel, displaySettings ); + final Set< String > c = new LinkedHashSet<>( Arrays.asList( KeyConfigContexts.TRACKMATE ) ); + c.addAll( Arrays.asList( keyConfigContexts ) ); + final String[] kccs = c.toArray( new String[] {} ); + + this.keybindings = new InputActionBindings(); + this.triggerbindings = new TriggerBehaviourBindings(); + + final Keymap keymap = TrackMateKeymapManager.keymapManager.getForwardSelectedKeymap(); + + this.actions = new Actions( keymap.getConfig(), kccs ); + actions.install( keybindings, "view" ); + + this.behaviours = new Behaviours( keymap.getConfig(), kccs ); + behaviours.install( triggerbindings, "view" ); + + final UpdateListener updateListener = () -> { + behaviours.updateKeyConfig( keymap.getConfig() ); + actions.updateKeyConfig( keymap.getConfig() ); + }; + keymap.updateListeners().add( updateListener ); + onClose( () -> keymap.updateListeners().remove( updateListener ) ); + + this.mouseAndKeyHandler = new MouseAndKeyHandler(); + mouseAndKeyHandler.setInputMap( triggerbindings.getConcatenatedInputTriggerMap() ); + mouseAndKeyHandler.setBehaviourMap( triggerbindings.getConcatenatedBehaviourMap() ); + + } + + protected void setWindow( final Window frame ) + { + frame.addWindowListener( new WindowAdapter() + { + @Override + public void windowClosing( final WindowEvent e ) + { + close(); + } + } ); + } + + protected void attachKeybindings( final JComponent component ) + { + SwingUtilities.replaceUIActionMap( component, keybindings.getConcatenatedActionMap() ); + SwingUtilities.replaceUIInputMap( component, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); + component.addKeyListener( mouseAndKeyHandler ); + component.addMouseListener( mouseAndKeyHandler ); + component.addMouseMotionListener( mouseAndKeyHandler ); + component.addMouseWheelListener( mouseAndKeyHandler ); + component.addFocusListener( mouseAndKeyHandler ); + } +} From b05eacd3899ae19c2632b4b00509e9fd70bbfae8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 11:29:18 +0200 Subject: [PATCH 294/371] Rewrite the TrackScheme actions with the ui-behaviour framework. --- .../trackscheme/TrackScheme.java | 18 +- .../trackscheme/TrackSchemeActions.java | 500 ------------------ .../trackscheme/TrackSchemeFrame.java | 9 - .../TrackSchemeKeyboardHandler.java | 198 ------- .../behaviours/AbstractTrackSchemeAction.java | 49 ++ .../behaviours/EditNameAction.java | 113 ++++ .../trackscheme/behaviours/HomingActions.java | 141 +++++ .../trackscheme/behaviours/PanAction.java | 37 ++ .../behaviours/TrackSchemeActions.java | 68 +++ 9 files changed, 423 insertions(+), 710 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/AbstractTrackSchemeAction.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/EditNameAction.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/HomingActions.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/PanAction.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/TrackSchemeActions.java 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 7ac53f325..bf6a3e86d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java @@ -62,10 +62,14 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.visualization.AbstractTrackMateModelJFrameView; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; +import fiji.plugin.trackmate.visualization.trackscheme.behaviours.TrackSchemeActions; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; +import fiji.plugin.trackmate.visualization.ui.TrackMateActions; import ij.ImagePlus; -public class TrackScheme extends AbstractTrackMateModelView +public class TrackScheme extends AbstractTrackMateModelJFrameView { public static final String INFO_TEXT = "" + "TrackScheme displays the tracking results as track lanes,
    " @@ -163,8 +167,9 @@ public class TrackScheme extends AbstractTrackMateModelView public TrackScheme( final Model model, final SelectionModel selectionModel, final DisplaySettings displaySettings ) { - super( model, selectionModel, displaySettings ); + super( model, selectionModel, displaySettings, KeyConfigContexts.TRACKSCHEME ); this.gui = new TrackSchemeFrame( this, displaySettings ); + setWindow( gui ); final String title = "TrackScheme"; gui.setTitle( title ); gui.setSize( DEFAULT_SIZE ); @@ -298,7 +303,6 @@ private JGraphXAdapter createGraph() */ private mxICell updateCellOf( final Spot spot ) { - mxICell cell = graph.getCellFor( spot ); graph.getModel().beginUpdate(); try @@ -740,6 +744,9 @@ public void run() @Override public void render() { + if ( graph != null ) + return; + final long start = System.currentTimeMillis(); // Graph to mirror model this.graph = createGraph(); @@ -785,6 +792,11 @@ public void run() gui.graphComponent.zoomOut(); gui.graphComponent.zoomOut(); + // Actions and behaviours + attachKeybindings( gui.graphComponent ); + TrackSchemeActions.install( actions, model, gui.graphComponent ); + TrackMateActions.install( actions, model, selectionModel ); + gui.logger.setProgress( 0 ); final long end = System.currentTimeMillis(); gui.logger.log( String.format( "TrackScheme rendering done in %.1f s.", ( end - start ) / 1000d ) ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java deleted file mode 100644 index 54ee9b253..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeActions.java +++ /dev/null @@ -1,500 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.trackscheme; - -import static fiji.plugin.trackmate.gui.Icons.ARROW_DOWNLEFT_ICON; -import static fiji.plugin.trackmate.gui.Icons.ARROW_DOWNRIGHT_ICON; -import static fiji.plugin.trackmate.gui.Icons.ARROW_DOWN_ICON; -import static fiji.plugin.trackmate.gui.Icons.ARROW_LEFT_ICON; -import static fiji.plugin.trackmate.gui.Icons.ARROW_RIGHT_ICON; -import static fiji.plugin.trackmate.gui.Icons.ARROW_UPLEFT_ICON; -import static fiji.plugin.trackmate.gui.Icons.ARROW_UPRIGHT_ICON; -import static fiji.plugin.trackmate.gui.Icons.ARROW_UP_ICON; -import static fiji.plugin.trackmate.gui.Icons.EDIT_ICON; -import static fiji.plugin.trackmate.gui.Icons.END_ICON; -import static fiji.plugin.trackmate.gui.Icons.HOME_ICON; -import static fiji.plugin.trackmate.gui.Icons.RESET_ZOOM_ICON; -import static fiji.plugin.trackmate.gui.Icons.ZOOM_IN_ICON; -import static fiji.plugin.trackmate.gui.Icons.ZOOM_OUT_ICON; - -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.geom.Point2D; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.AbstractAction; -import javax.swing.Action; -import javax.swing.Icon; - -import com.mxgraph.model.mxCell; -import com.mxgraph.model.mxICell; -import com.mxgraph.swing.util.mxGraphActions; -import com.mxgraph.util.mxEvent; -import com.mxgraph.util.mxEventObject; -import com.mxgraph.util.mxEventSource.mxIEventListener; -import com.mxgraph.view.mxGraph; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.Spot; - -public class TrackSchemeActions -{ - - /** - * When panning with the keyboard, by how much pixels to move. - */ - private static final int PAN_AMOUNT = 100; - - private static Action zoomInAction; - - static - { - zoomInAction = mxGraphActions.getZoomInAction(); - zoomInAction.putValue( Action.SMALL_ICON, ZOOM_IN_ICON ); - } - - private static Action zoomOutAction; - - static - { - zoomOutAction = mxGraphActions.getZoomOutAction(); - zoomOutAction.putValue( Action.SMALL_ICON, ZOOM_OUT_ICON ); - } - - private TrackSchemeActions() - {} - - public static Action getEditAction( final Model model, final TrackSchemeGraphComponent graphComponent ) - { - return new EditAction( "edit", EDIT_ICON, model, graphComponent ); - } - - public static Action getHomeAction( final TrackSchemeGraphComponent graphComponent ) - { - return new HomeAction( "home", HOME_ICON, graphComponent ); - } - - public static Action getEndAction( final TrackSchemeGraphComponent graphComponent ) - { - return new EndAction( "end", END_ICON, graphComponent ); - } - - public static Action getResetZoomAction( final TrackSchemeGraphComponent graphComponent ) - { - return new ResetZoomAction( "resetZoom", RESET_ZOOM_ICON, graphComponent ); - } - - public static Action getZoomInAction( final TrackSchemeGraphComponent graphComponent ) - { - return new AbstractAction( "zoomIn", ZOOM_IN_ICON ) - { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed( final ActionEvent e ) - { - graphComponent.zoomIn(); - } - }; - } - - public static Action getZoomOutAction( final TrackSchemeGraphComponent graphComponent ) - { - return new AbstractAction( "zoomOut", ZOOM_OUT_ICON ) - { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed( final ActionEvent e ) - { - graphComponent.zoomOut(); - } - }; - } - - public static Action getSelectNoneAction() - { - return mxGraphActions.getSelectNoneAction(); - } - - public static Action getSelectAllAction() - { - return mxGraphActions.getSelectAllAction(); - } - - public static Action getPanDownAction( final TrackSchemeGraphComponent graphComponent ) - { - return new PanAction( "panDown", ARROW_DOWN_ICON, graphComponent, 0, PAN_AMOUNT ); - } - - public static Action getPanLeftAction( final TrackSchemeGraphComponent graphComponent ) - { - return new PanAction( "panLeft", ARROW_LEFT_ICON, graphComponent, -PAN_AMOUNT, 0 ); - } - - public static Action getPanRightAction( final TrackSchemeGraphComponent graphComponent ) - { - return new PanAction( "panRight", ARROW_RIGHT_ICON, graphComponent, PAN_AMOUNT, 0 ); - } - - public static Action getPanUpAction( final TrackSchemeGraphComponent graphComponent ) - { - return new PanAction( "panUp", ARROW_UP_ICON, graphComponent, 0, -PAN_AMOUNT ); - } - - public static Action getPanDownLeftAction( final TrackSchemeGraphComponent graphComponent ) - { - return new PanAction( "panDownLeft", ARROW_DOWNLEFT_ICON, graphComponent, -PAN_AMOUNT, PAN_AMOUNT ); - } - - public static Action getPanDownRightAction( final TrackSchemeGraphComponent graphComponent ) - { - return new PanAction( "panDownRight", ARROW_DOWNRIGHT_ICON, graphComponent, PAN_AMOUNT, PAN_AMOUNT ); - } - - public static Action getPanUpLeftAction( final TrackSchemeGraphComponent graphComponent ) - { - return new PanAction( "panUpLeft", ARROW_UPLEFT_ICON, graphComponent, -PAN_AMOUNT, -PAN_AMOUNT ); - } - - public static Action getPanUpRightAction( final TrackSchemeGraphComponent graphComponent ) - { - return new PanAction( "panUpRight", ARROW_UPRIGHT_ICON, graphComponent, PAN_AMOUNT, -PAN_AMOUNT ); - } - - /* - * ACTION CLASSES - */ - - private static class PanAction extends AbstractAction - { - - private static final long serialVersionUID = 1L; - - private final int amountx; - - private final int amounty; - - private final TrackSchemeGraphComponent graphComponent; - - public PanAction( final String name, final Icon icon, final TrackSchemeGraphComponent graphComponent, final int amountx, final int amounty ) - { - super( name, icon ); - this.graphComponent = graphComponent; - this.amountx = amountx; - this.amounty = amounty; - } - - @Override - public void actionPerformed( final ActionEvent e ) - { - final Rectangle r = graphComponent.getViewport().getViewRect(); - final int right = r.x + ( ( amountx < 0 ) ? 0 : r.width ) + amountx; - final int bottom = r.y + ( ( amounty < 0 ) ? 0 : r.height ) + amounty; - graphComponent.getGraphControl().scrollRectToVisible( new Rectangle( right, bottom, 0, 0 ) ); - } - } - - private static class ResetZoomAction extends AbstractAction - { - - private static final long serialVersionUID = 1L; - - private final TrackSchemeGraphComponent graphComponent; - - public ResetZoomAction( final String name, final Icon icon, final TrackSchemeGraphComponent graphComponent ) - { - super( name, icon ); - this.graphComponent = graphComponent; - } - - @Override - public void actionPerformed( final ActionEvent e ) - { - graphComponent.zoomTo( 1.0, false ); - } - - } - - /** - * Centers the view to the first cell in selection. - * - * @author Jean-Yves Tinevez <jeanyves.tinevez@gmail.com> Sep 12, 2013 - * - */ - private static class HomeAction extends AbstractAction - { - - private static final long serialVersionUID = 1L; - - private final TrackSchemeGraphComponent graphComponent; - - public HomeAction( final String name, final Icon icon, final TrackSchemeGraphComponent graphComponent ) - { - super( name, icon ); - this.graphComponent = graphComponent; - } - - @Override - public void actionPerformed( final ActionEvent e ) - { - - mxCell cell = null; - final JGraphXAdapter graph = graphComponent.getGraph(); - final List< mxCell > vertices = getSelectionVertices( graph ); - if ( !vertices.isEmpty() ) - { - int minFrame = Integer.MAX_VALUE; - for ( final mxCell mxCell : vertices ) - { - final int frame = graph.getSpotFor( mxCell ).getFeature( Spot.FRAME ).intValue(); - if ( frame < minFrame ) - { - minFrame = frame; - cell = mxCell; - } - } - } - else - { - final List< mxCell > edges = getSelectionEdges( graph ); - if ( !edges.isEmpty() ) - { - int minFrame = Integer.MAX_VALUE; - for ( final mxCell mxCell : edges ) - { - final mxICell target = mxCell.getTarget(); - final int frame = graph.getSpotFor( target ).getFeature( Spot.FRAME ).intValue(); - if ( frame < minFrame ) - { - minFrame = frame; - cell = mxCell; - } - } - cell = edges.get( edges.size() - 1 ); - } - else - { - return; - } - } - graphComponent.scrollCellToVisible( cell, true ); - } - } - - /** - * Centers the view to the last cell in selection, sorted by frame number. - * - * @author Jean-Yves Tinevez <jeanyves.tinevez@gmail.com> Sep 12, 2013 - * - */ - private static class EndAction extends AbstractAction - { - - private static final long serialVersionUID = 1L; - - private final TrackSchemeGraphComponent graphComponent; - - public EndAction( final String name, final Icon icon, final TrackSchemeGraphComponent graphComponent ) - { - super( name, icon ); - this.graphComponent = graphComponent; - } - - @Override - public void actionPerformed( final ActionEvent e ) - { - mxCell cell = null; - final JGraphXAdapter graph = graphComponent.getGraph(); - final List< mxCell > vertices = getSelectionVertices( graph ); - - if ( !vertices.isEmpty() ) - { - int maxFrame = Integer.MIN_VALUE; - for ( final mxCell mxCell : vertices ) - { - final int frame = graph.getSpotFor( mxCell ).getFeature( Spot.FRAME ).intValue(); - if ( frame > maxFrame ) - { - maxFrame = frame; - cell = mxCell; - } - } - } - else - { - final List< mxCell > edges = getSelectionEdges( graph ); - if ( !edges.isEmpty() ) - { - int maxFrame = Integer.MIN_VALUE; - for ( final mxCell mxCell : edges ) - { - final mxICell target = mxCell.getTarget(); - final int frame = graph.getSpotFor( target ).getFeature( Spot.FRAME ).intValue(); - if ( frame > maxFrame ) - { - maxFrame = frame; - cell = mxCell; - } - } - cell = edges.get( edges.size() - 1 ); - } - else - { - return; - } - } - graphComponent.scrollCellToVisible( cell, true ); - } - } - - public static class EditAction extends AbstractAction - { - - private static final long serialVersionUID = 1L; - - private final TrackSchemeGraphComponent graphComponent; - - private final Model model; - - public EditAction( final String name, final Icon icon, final Model model, final TrackSchemeGraphComponent graphComponent ) - { - super( name, icon ); - this.model = model; - this.graphComponent = graphComponent; - } - - @Override - public void actionPerformed( final ActionEvent e ) - { - multiEditSpotName( graphComponent, e ); - } - - private void multiEditSpotName( final TrackSchemeGraphComponent lGraphComponent, final ActionEvent triggerEvent ) - { - /* - * We want to display the editing window in the cell is the closer - * to where the user clicked. That is not perfect, because we can - * imagine the click is made for from the selected cells, and that - * the editing window will not even be displayed on the screen. No - * idea for that yet, because JGraphX is expecting to receive a cell - * as location for the editing window. - */ - final JGraphXAdapter graph = lGraphComponent.getGraph(); - final List< mxCell > vertices = getSelectionVertices( graph ); - if ( vertices.isEmpty() ) - { return; } - - final Point mousePosition = lGraphComponent.getMousePosition(); - final mxCell tc; - if ( null != mousePosition ) - tc = getClosestCell( vertices, mousePosition ); - else - tc = vertices.get( 0 ); - vertices.remove( tc ); - - lGraphComponent.startEditingAtCell( tc, triggerEvent ); - lGraphComponent.addListener( mxEvent.LABEL_CHANGED, new mxIEventListener() - { - - @Override - public void invoke( final Object sender, final mxEventObject evt ) - { - model.beginUpdate(); - try - { - for ( final mxCell cell : vertices ) - { - cell.setValue( tc.getValue() ); - final Spot spot = graph.getSpotFor( cell ); - model.beforeEdit( spot ); // name change undoable - spot.setName( tc.getValue().toString() ); - } - lGraphComponent.refresh(); - lGraphComponent.removeListener( this ); - } - finally - { - model.endUpdate(); - } - } - } ); - } - - /** - * Return, from the given list of cell, the one which is the closer to - * the point of this instance. - * - * @param point - */ - private mxCell getClosestCell( final Iterable< mxCell > vertices, final Point2D point ) - { - double min_dist = Double.POSITIVE_INFINITY; - mxCell target_cell = null; - for ( final mxCell cell : vertices ) - { - final Point location = cell.getGeometry().getPoint(); - final double dist = location.distanceSq( point ); - if ( dist < min_dist ) - { - min_dist = dist; - target_cell = cell; - } - } - return target_cell; - } - } - - /* - * PRIVATE STATIC METHODS - */ - - private static List< mxCell > getSelectionVertices( final mxGraph graph ) - { - // Build selection categories - final Object[] selection = graph.getSelectionCells(); - final ArrayList< mxCell > vertices = new ArrayList<>(); - for ( final Object obj : selection ) - { - final mxCell cell = ( mxCell ) obj; - if ( cell.isVertex() ) - vertices.add( cell ); - } - return vertices; - } - - private static List< mxCell > getSelectionEdges( final mxGraph graph ) - { - // Build selection categories - final Object[] selection = graph.getSelectionCells(); - final ArrayList< mxCell > edges = new ArrayList<>(); - for ( final Object obj : selection ) - { - final mxCell cell = ( mxCell ) obj; - if ( cell.isEdge() ) - edges.add( cell ); - } - return edges; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java index 38e50666e..a90096ddf 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java @@ -41,8 +41,6 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.util.TrackNavigator; -import fiji.plugin.trackmate.visualization.TrackMateModelView; public class TrackSchemeFrame extends JFrame { @@ -114,13 +112,6 @@ public void init( final JGraphXAdapter lGraph ) final JSplitPane splitPane = new JSplitPane( JSplitPane.HORIZONTAL_SPLIT, inner, graphComponent ); splitPane.setDividerLocation( 170 ); getContentPane().add( splitPane, BorderLayout.CENTER ); - - final TrackSchemeKeyboardHandler keyboardHandler = new TrackSchemeKeyboardHandler( trackScheme.getModel(), graphComponent, new TrackNavigator( trackScheme.getModel(), trackScheme.getSelectionModel() ) ); - keyboardHandler.installKeyboardActions( graphComponent ); - keyboardHandler.installKeyboardActions( infoPane ); - - // Undo / redo - TrackMateModelView.registerUndoShortcut( this, trackScheme.getModel() ); } /* diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java deleted file mode 100644 index aa63abadd..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeKeyboardHandler.java +++ /dev/null @@ -1,198 +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.visualization.trackscheme; - -import java.awt.event.ActionEvent; -import java.awt.event.KeyEvent; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.InputMap; -import javax.swing.JComponent; -import javax.swing.KeyStroke; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; - -import com.mxgraph.swing.util.mxGraphActions; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.util.TrackNavigator; - -public class TrackSchemeKeyboardHandler -{ - - private final TrackNavigator navigator; - - private final TrackSchemeGraphComponent graphComponent; - - private final Model model; - - public TrackSchemeKeyboardHandler( final Model model, final TrackSchemeGraphComponent graphComponent, final TrackNavigator navigator ) - { - this.model = model; - this.graphComponent = graphComponent; - this.navigator = navigator; - } - - public void installKeyboardActions( final JComponent component ) - { - final InputMap inputMap = getInputMap( JComponent.WHEN_FOCUSED ); - SwingUtilities.replaceUIInputMap( component, JComponent.WHEN_FOCUSED, inputMap ); - SwingUtilities.replaceUIActionMap( component, createActionMap() ); - } - - protected InputMap getInputMap( final int condition ) - { - final InputMap map; - if ( condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ) - map = ( InputMap ) UIManager.get( "ScrollPane.ancestorInputMap" ); - else - map = new InputMap(); - map.put( KeyStroke.getKeyStroke( "F2" ), "edit" ); - map.put( KeyStroke.getKeyStroke( "DELETE" ), "delete" ); - - map.put( KeyStroke.getKeyStroke( "HOME" ), "home" ); - map.put( KeyStroke.getKeyStroke( "END" ), "end" ); - - map.put( KeyStroke.getKeyStroke( "ADD" ), "zoomIn" ); - map.put( KeyStroke.getKeyStroke( "EQUALS" ), "zoomIn" ); - map.put( KeyStroke.getKeyStroke( "SUBTRACT" ), "zoomOut" ); - map.put( KeyStroke.getKeyStroke( "MINUS" ), "zoomOut" ); - map.put( KeyStroke.getKeyStroke( "shift EQUALS" ), "resetZoom" ); - - map.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD4, 0 ), "panLeft" ); - map.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD6, 0 ), "panRight" ); - map.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD8, 0 ), "panUp" ); - map.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD2, 0 ), "panDown" ); - map.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD9, 0 ), "panUpRight" ); - map.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD3, 0 ), "panDownRight" ); - map.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD1, 0 ), "panDownLeft" ); - map.put( KeyStroke.getKeyStroke( KeyEvent.VK_NUMPAD7, 0 ), "panUpLeft" ); - - map.put( KeyStroke.getKeyStroke( "control A" ), "selectAll" ); - map.put( KeyStroke.getKeyStroke( "control shift A" ), "selectNone" ); - - map.put( KeyStroke.getKeyStroke( "UP" ), "selectPreviousInTime" ); - map.put( KeyStroke.getKeyStroke( "DOWN" ), "selectNextInTime" ); - map.put( KeyStroke.getKeyStroke( "RIGHT" ), "selectNextSibling" ); - map.put( KeyStroke.getKeyStroke( "LEFT" ), "selectPreviousSibling" ); - map.put( KeyStroke.getKeyStroke( "PAGE_DOWN" ), "selectNextTrack" ); - map.put( KeyStroke.getKeyStroke( "PAGE_UP" ), "selectPreviousTrack" ); - - return map; - } - - /** - * Returns the mapping between JTree's input map and JGraph's actions. - * - * @return the action map. - */ - protected ActionMap createActionMap() - { - final ActionMap map = ( ActionMap ) UIManager.get( "ScrollPane.actionMap" ); - - map.put( "edit", TrackSchemeActions.getEditAction( model, graphComponent ) ); - map.put( "delete", mxGraphActions.getDeleteAction() ); - - map.put( "home", TrackSchemeActions.getHomeAction( graphComponent ) ); - map.put( "end", TrackSchemeActions.getEndAction( graphComponent ) ); - - map.put( "zoomIn", TrackSchemeActions.getZoomInAction( graphComponent ) ); - map.put( "zoomOut", TrackSchemeActions.getZoomOutAction( graphComponent ) ); - map.put( "resetZoom", TrackSchemeActions.getResetZoomAction( graphComponent ) ); - - map.put( "panUp", TrackSchemeActions.getPanUpAction( graphComponent ) ); - map.put( "panDown", TrackSchemeActions.getPanDownAction( graphComponent ) ); - map.put( "panLeft", TrackSchemeActions.getPanLeftAction( graphComponent ) ); - map.put( "panRight", TrackSchemeActions.getPanRightAction( graphComponent ) ); - map.put( "panUpLeft", TrackSchemeActions.getPanUpLeftAction( graphComponent ) ); - map.put( "panDownLeft", TrackSchemeActions.getPanDownLeftAction( graphComponent ) ); - map.put( "panUpRight", TrackSchemeActions.getPanUpRightAction( graphComponent ) ); - map.put( "panDownRight", TrackSchemeActions.getPanDownRightAction( graphComponent ) ); - - map.put( "selectNone", TrackSchemeActions.getSelectNoneAction() ); - map.put( "selectAll", TrackSchemeActions.getSelectAllAction() ); - - map.put( "selectPreviousInTime", new AbstractAction() - { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed( final ActionEvent arg0 ) - { - navigator.previousInTime(); - } - } ); - map.put( "selectNextInTime", new AbstractAction() - { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed( final ActionEvent arg0 ) - { - navigator.nextInTime(); - } - } ); - map.put( "selectNextSibling", new AbstractAction() - { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed( final ActionEvent arg0 ) - { - navigator.nextSibling(); - } - } ); - map.put( "selectPreviousSibling", new AbstractAction() - { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed( final ActionEvent arg0 ) - { - navigator.previousSibling(); - } - } ); - map.put( "selectNextTrack", new AbstractAction() - { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed( final ActionEvent arg0 ) - { - navigator.nextTrack(); - } - } ); - map.put( "selectPreviousTrack", new AbstractAction() - { - private static final long serialVersionUID = 1L; - - @Override - public void actionPerformed( final ActionEvent arg0 ) - { - navigator.previousTrack(); - } - } ); - return map; - } - -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/AbstractTrackSchemeAction.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/AbstractTrackSchemeAction.java new file mode 100644 index 000000000..c6e31533d --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/AbstractTrackSchemeAction.java @@ -0,0 +1,49 @@ +package fiji.plugin.trackmate.visualization.trackscheme.behaviours; + +import java.util.ArrayList; +import java.util.List; + +import org.scijava.ui.behaviour.util.AbstractNamedAction; + +import com.mxgraph.model.mxCell; +import com.mxgraph.view.mxGraph; + +public abstract class AbstractTrackSchemeAction extends AbstractNamedAction +{ + + private static final long serialVersionUID = 1L; + + protected AbstractTrackSchemeAction( final String name ) + { + super( name ); + } + + protected List< mxCell > getSelectionVertices( final mxGraph graph ) + { + // Build selection categories + final Object[] selection = graph.getSelectionCells(); + final ArrayList< mxCell > vertices = new ArrayList<>(); + for ( final Object obj : selection ) + { + final mxCell cell = ( mxCell ) obj; + if ( cell.isVertex() ) + vertices.add( cell ); + } + return vertices; + } + + protected List< mxCell > getSelectionEdges( final mxGraph graph ) + { + // Build selection categories + final Object[] selection = graph.getSelectionCells(); + final ArrayList< mxCell > edges = new ArrayList<>(); + for ( final Object obj : selection ) + { + final mxCell cell = ( mxCell ) obj; + if ( cell.isEdge() ) + edges.add( cell ); + } + return edges; + } + +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/EditNameAction.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/EditNameAction.java new file mode 100644 index 000000000..b1e3069bb --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/EditNameAction.java @@ -0,0 +1,113 @@ +package fiji.plugin.trackmate.visualization.trackscheme.behaviours; + +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.geom.Point2D; +import java.util.List; + +import com.mxgraph.model.mxCell; +import com.mxgraph.util.mxEvent; +import com.mxgraph.util.mxEventObject; +import com.mxgraph.util.mxEventSource.mxIEventListener; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.visualization.trackscheme.JGraphXAdapter; +import fiji.plugin.trackmate.visualization.trackscheme.TrackSchemeGraphComponent; + +public class EditNameAction extends AbstractTrackSchemeAction +{ + + private static final long serialVersionUID = 1L; + + private final TrackSchemeGraphComponent graphComponent; + + private final Model model; + + public EditNameAction( final Model model, final TrackSchemeGraphComponent graphComponent ) + { + super( TrackSchemeActions.EDIT_NAME ); + this.model = model; + this.graphComponent = graphComponent; + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + multiEditSpotName( graphComponent, e ); + } + + private void multiEditSpotName( final TrackSchemeGraphComponent lGraphComponent, final ActionEvent triggerEvent ) + { + /* + * We want to display the editing window in the cell is the closer to + * where the user clicked. That is not perfect, because we can imagine + * the click is made for from the selected cells, and that the editing + * window will not even be displayed on the screen. No idea for that + * yet, because JGraphX is expecting to receive a cell as location for + * the editing window. + */ + final JGraphXAdapter graph = lGraphComponent.getGraph(); + final List< mxCell > vertices = getSelectionVertices( graph ); + if ( vertices.isEmpty() ) + return; + + final Point mousePosition = lGraphComponent.getMousePosition(); + final mxCell tc; + if ( null != mousePosition ) + tc = getClosestCell( vertices, mousePosition ); + else + tc = vertices.get( 0 ); + vertices.remove( tc ); + + lGraphComponent.startEditingAtCell( tc, triggerEvent ); + lGraphComponent.addListener( mxEvent.LABEL_CHANGED, new mxIEventListener() + { + + @Override + public void invoke( final Object sender, final mxEventObject evt ) + { + model.beginUpdate(); + try + { + for ( final mxCell cell : vertices ) + { + cell.setValue( tc.getValue() ); + final Spot spot = graph.getSpotFor( cell ); + model.beforeEdit( spot ); // name change undoable + spot.setName( tc.getValue().toString() ); + } + lGraphComponent.refresh(); + lGraphComponent.removeListener( this ); + } + finally + { + model.endUpdate(); + } + } + } ); + } + + /** + * Return, from the given list of cell, the one which is the closer to the + * point of this instance. + * + * @param point + */ + private mxCell getClosestCell( final Iterable< mxCell > vertices, final Point2D point ) + { + double min_dist = Double.POSITIVE_INFINITY; + mxCell target_cell = null; + for ( final mxCell cell : vertices ) + { + final Point location = cell.getGeometry().getPoint(); + final double dist = location.distanceSq( point ); + if ( dist < min_dist ) + { + min_dist = dist; + target_cell = cell; + } + } + return target_cell; + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/HomingActions.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/HomingActions.java new file mode 100644 index 000000000..3ecf73a46 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/HomingActions.java @@ -0,0 +1,141 @@ +package fiji.plugin.trackmate.visualization.trackscheme.behaviours; + +import java.awt.event.ActionEvent; +import java.util.List; + +import com.mxgraph.model.mxCell; +import com.mxgraph.model.mxICell; + +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.visualization.trackscheme.JGraphXAdapter; +import fiji.plugin.trackmate.visualization.trackscheme.TrackSchemeGraphComponent; + +public class HomingActions +{ + + /** + * Centers the view to the first cell in selection, sorted by frame number. + */ + public static class HomeAction extends AbstractTrackSchemeAction + { + + private static final long serialVersionUID = 1L; + + private final TrackSchemeGraphComponent graphComponent; + + public HomeAction( final TrackSchemeGraphComponent graphComponent ) + { + super( TrackSchemeActions.CENTER_ON_FIRST_SELECTED ); + this.graphComponent = graphComponent; + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + + mxCell cell = null; + final JGraphXAdapter graph = graphComponent.getGraph(); + final List< mxCell > vertices = getSelectionVertices( graph ); + if ( !vertices.isEmpty() ) + { + int minFrame = Integer.MAX_VALUE; + for ( final mxCell mxCell : vertices ) + { + final int frame = graph.getSpotFor( mxCell ).getFeature( Spot.FRAME ).intValue(); + if ( frame < minFrame ) + { + minFrame = frame; + cell = mxCell; + } + } + } + else + { + final List< mxCell > edges = getSelectionEdges( graph ); + if ( !edges.isEmpty() ) + { + int minFrame = Integer.MAX_VALUE; + for ( final mxCell mxCell : edges ) + { + final mxICell target = mxCell.getTarget(); + final int frame = graph.getSpotFor( target ).getFeature( Spot.FRAME ).intValue(); + if ( frame < minFrame ) + { + minFrame = frame; + cell = mxCell; + } + } + cell = edges.get( edges.size() - 1 ); + } + else + { + return; + } + } + graphComponent.scrollCellToVisible( cell, true ); + } + } + + /** + * Centers the view to the last cell in selection, sorted by frame number. + */ + public static class EndAction extends AbstractTrackSchemeAction + { + + private static final long serialVersionUID = 1L; + + private final TrackSchemeGraphComponent graphComponent; + + public EndAction( final TrackSchemeGraphComponent graphComponent ) + { + super( TrackSchemeActions.CENTER_ON_LAST_SELECTED ); + this.graphComponent = graphComponent; + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + mxCell cell = null; + final JGraphXAdapter graph = graphComponent.getGraph(); + final List< mxCell > vertices = getSelectionVertices( graph ); + + if ( !vertices.isEmpty() ) + { + int maxFrame = Integer.MIN_VALUE; + for ( final mxCell mxCell : vertices ) + { + final int frame = graph.getSpotFor( mxCell ).getFeature( Spot.FRAME ).intValue(); + if ( frame > maxFrame ) + { + maxFrame = frame; + cell = mxCell; + } + } + } + else + { + final List< mxCell > edges = getSelectionEdges( graph ); + if ( !edges.isEmpty() ) + { + int maxFrame = Integer.MIN_VALUE; + for ( final mxCell mxCell : edges ) + { + final mxICell target = mxCell.getTarget(); + final int frame = graph.getSpotFor( target ).getFeature( Spot.FRAME ).intValue(); + if ( frame > maxFrame ) + { + maxFrame = frame; + cell = mxCell; + } + } + cell = edges.get( edges.size() - 1 ); + } + else + { + return; + } + } + graphComponent.scrollCellToVisible( cell, true ); + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/PanAction.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/PanAction.java new file mode 100644 index 000000000..3a0a32020 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/PanAction.java @@ -0,0 +1,37 @@ +package fiji.plugin.trackmate.visualization.trackscheme.behaviours; + +import java.awt.Rectangle; +import java.awt.event.ActionEvent; + +import org.scijava.ui.behaviour.util.AbstractNamedAction; + +import fiji.plugin.trackmate.visualization.trackscheme.TrackSchemeGraphComponent; + +public class PanAction extends AbstractNamedAction +{ + + private static final long serialVersionUID = 1L; + + private final int amountx; + + private final int amounty; + + private final TrackSchemeGraphComponent graphComponent; + + public PanAction( final String name, final int amountx, final int amounty, final TrackSchemeGraphComponent graphComponent ) + { + super( name ); + this.graphComponent = graphComponent; + this.amountx = amountx; + this.amounty = amounty; + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + final Rectangle r = graphComponent.getViewport().getViewRect(); + final int right = r.x + ( ( amountx < 0 ) ? 0 : r.width ) + amountx; + final int bottom = r.y + ( ( amounty < 0 ) ? 0 : r.height ) + amounty; + graphComponent.getGraphControl().scrollRectToVisible( new Rectangle( right, bottom, 0, 0 ) ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/TrackSchemeActions.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/TrackSchemeActions.java new file mode 100644 index 000000000..11c68838f --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/TrackSchemeActions.java @@ -0,0 +1,68 @@ +package fiji.plugin.trackmate.visualization.trackscheme.behaviours; + +import org.scijava.ui.behaviour.util.Actions; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.visualization.trackscheme.TrackSchemeGraphComponent; + +public class TrackSchemeActions +{ + + /** + * When panning with the keyboard, by how much pixels to move. + */ + private static final int PAN_AMOUNT = 100; + + static final String EDIT_NAME = "edit name"; + private static final String[] EDIT_NAME_KEYS = new String[] { "F2" }; + + private static final String ZOOM_IN = "zoom in"; + private static final String[] ZOOM_IN_KEYS = new String[] { "ADD", "EQUALS" }; + private static final String ZOOM_OUT = "zoom out"; + private static final String[] ZOOM_OUT_KEYS = new String[] { "SUBTRACT", "MINUS" }; + private static final String RESET_ZOOM = "reset zoom"; + private static final String[] RESET_ZOOM_KEYS = new String[] { "shift EQUALS", "R" }; + + static final String CENTER_ON_FIRST_SELECTED = "center on first selected"; + private static final String[] CENTER_ON_FIRST_SELECTED_KEYS = new String[] { "HOME", "C" }; + static final String CENTER_ON_LAST_SELECTED = "center on last selected"; + private static final String[] CENTER_ON_LAST_SELECTED_KEYS = new String[] { "END", "shift C" }; + + private static final String PAN_LEFT = "pan left"; + private static final String[] PAN_LEFT_KEYS = new String[] { "NUMPAD4" }; + private static final String PAN_RIGHT = "pan right"; + private static final String[] PAN_RIGHT_KEYS = new String[] { "NUMPAD6" }; + private static final String PAN_UP = "pan up"; + private static final String[] PAN_UP_KEYS = new String[] { "NUMPAD8" }; + private static final String PAN_DOWN = "pan down"; + private static final String[] PAN_DOWN_KEYS = new String[] { "NUMPAD2" }; + private static final String PAN_UP_RIGHT = "pan up right"; + private static final String[] PAN_UP_RIGHT_KEYS = new String[] { "NUMPAD9" }; + private static final String PAN_DOWN_RIGHT = "pan down right"; + private static final String[] PAN_DOWN_RIGHT_KEYS = new String[] { "NUMPAD3" }; + private static final String PAN_DOWN_LEFT = "pan down left"; + private static final String[] PAN_DOWN_LEFT_KEYS = new String[] { "NUMPAD1" }; + private static final String PAN_UP_LEFT = "pan up left"; + private static final String[] PAN_UP_LEFT_KEYS = new String[] { "NUMPAD7" }; + + public static final void install( final Actions actions, final Model model, final TrackSchemeGraphComponent graphComponent ) + { + actions.namedAction( new EditNameAction( model, graphComponent ), EDIT_NAME_KEYS ); + + actions.namedAction( new HomingActions.HomeAction( graphComponent ), CENTER_ON_FIRST_SELECTED_KEYS ); + actions.namedAction( new HomingActions.EndAction( graphComponent ), CENTER_ON_LAST_SELECTED_KEYS ); + + actions.runnableAction( () -> graphComponent.zoomIn(), ZOOM_IN, ZOOM_IN_KEYS ); + actions.runnableAction( () -> graphComponent.zoomOut(), ZOOM_OUT, ZOOM_OUT_KEYS ); + actions.runnableAction( () -> graphComponent.zoomActual(), RESET_ZOOM, RESET_ZOOM_KEYS ); + + actions.namedAction( new PanAction( PAN_LEFT, -PAN_AMOUNT, 0, graphComponent ), PAN_LEFT_KEYS ); + actions.namedAction( new PanAction( PAN_RIGHT, PAN_AMOUNT, 0, graphComponent ), PAN_RIGHT_KEYS ); + actions.namedAction( new PanAction( PAN_UP, 0, -PAN_AMOUNT, graphComponent ), PAN_UP_KEYS ); + actions.namedAction( new PanAction( PAN_DOWN, 0, PAN_AMOUNT, graphComponent ), PAN_DOWN_KEYS ); + actions.namedAction( new PanAction( PAN_UP_RIGHT, PAN_AMOUNT, -PAN_AMOUNT, graphComponent ), PAN_UP_RIGHT_KEYS ); + actions.namedAction( new PanAction( PAN_DOWN_RIGHT, PAN_AMOUNT, PAN_AMOUNT, graphComponent ), PAN_DOWN_RIGHT_KEYS ); + actions.namedAction( new PanAction( PAN_DOWN_LEFT, -PAN_AMOUNT, PAN_AMOUNT, graphComponent ), PAN_DOWN_LEFT_KEYS ); + actions.namedAction( new PanAction( PAN_UP_LEFT, -PAN_AMOUNT, -PAN_AMOUNT, graphComponent ), PAN_UP_LEFT_KEYS ); + } +} From 1a5d981ae70e04c77b53195078801db153cb7667 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 11:46:22 +0200 Subject: [PATCH 295/371] Add command descriptions for TrackScheme actions. --- .../behaviours/TrackSchemeActions.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/TrackSchemeActions.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/TrackSchemeActions.java index 11c68838f..49d346e0d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/TrackSchemeActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/behaviours/TrackSchemeActions.java @@ -1,9 +1,13 @@ package fiji.plugin.trackmate.visualization.trackscheme.behaviours; +import org.scijava.plugin.Plugin; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; import org.scijava.ui.behaviour.util.Actions; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.visualization.trackscheme.TrackSchemeGraphComponent; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; public class TrackSchemeActions { @@ -65,4 +69,32 @@ public static final void install( final Actions actions, final Model model, fina actions.namedAction( new PanAction( PAN_DOWN_LEFT, -PAN_AMOUNT, PAN_AMOUNT, graphComponent ), PAN_DOWN_LEFT_KEYS ); actions.namedAction( new PanAction( PAN_UP_LEFT, -PAN_AMOUNT, -PAN_AMOUNT, graphComponent ), PAN_UP_LEFT_KEYS ); } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class Descriptions extends CommandDescriptionProvider + { + public Descriptions() + { + super( KeyConfigContexts.KEY_CONFIG_SCOPE, KeyConfigContexts.TRACKSCHEME ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( EDIT_NAME, EDIT_NAME_KEYS, "Edit the name of the selected spots." ); + descriptions.add( ZOOM_IN, ZOOM_IN_KEYS, "Zoom in." ); + descriptions.add( ZOOM_OUT, ZOOM_OUT_KEYS, "Zoom out." ); + descriptions.add( RESET_ZOOM, RESET_ZOOM_KEYS, "Reset zoom." ); + descriptions.add( CENTER_ON_FIRST_SELECTED, CENTER_ON_FIRST_SELECTED_KEYS, "Center the view on the first selected spot." ); + descriptions.add( CENTER_ON_LAST_SELECTED, CENTER_ON_LAST_SELECTED_KEYS, "Center the view on the last selected spot." ); + descriptions.add( PAN_LEFT, PAN_LEFT_KEYS, "Pan left." ); + descriptions.add( PAN_RIGHT, PAN_RIGHT_KEYS, "Pan right." ); + descriptions.add( PAN_UP, PAN_UP_KEYS, "Pan up." ); + descriptions.add( PAN_DOWN, PAN_DOWN_KEYS, "Pan down." ); + descriptions.add( PAN_UP_RIGHT, PAN_UP_RIGHT_KEYS, "Pan up and right." ); + descriptions.add( PAN_DOWN_RIGHT, PAN_DOWN_RIGHT_KEYS, "Pan down and right." ); + descriptions.add( PAN_DOWN_LEFT, PAN_DOWN_LEFT_KEYS, "Pan down and left." ); + descriptions.add( PAN_UP_LEFT, PAN_UP_LEFT_KEYS, "Pan up and left." ); + } + } } From 39bbf473a45a88c1ca905d08e3e9fc78889a5bc5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 11:46:52 +0200 Subject: [PATCH 296/371] Move the TrackMateConfigDialog to a common package. --- .../hyperstack/HyperStackDisplayer.java | 30 +++++++++++-------- .../TrackMateConfigDialog.java | 7 +++-- 2 files changed, 22 insertions(+), 15 deletions(-) rename src/main/java/fiji/plugin/trackmate/visualization/{hyperstack/behaviours => ui}/TrackMateConfigDialog.java (88%) 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 3eca1e462..74e0c1e2e 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -21,7 +21,6 @@ */ package fiji.plugin.trackmate.visualization.hyperstack; -import bdv.ui.keymap.Keymap; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.SelectionChangeEvent; @@ -34,9 +33,9 @@ import fiji.plugin.trackmate.visualization.hyperstack.behaviours.ImagePlusBehavioursAdapter; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SelectSpotsWithRoiListener; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SpotEditBehaviours; -import fiji.plugin.trackmate.visualization.hyperstack.behaviours.TrackMateConfigDialog; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import fiji.plugin.trackmate.visualization.ui.TrackMateActions; +import fiji.plugin.trackmate.visualization.ui.TrackMateConfigDialog; import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; import ij.ImagePlus; import ij.gui.Overlay; @@ -171,16 +170,23 @@ public void render() * UI behaviours and actions */ - final TrackMateKeymapManager keymapManager = TrackMateKeymapManager.keymapManager; - final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, new String[] { KeyConfigContexts.HYPERSTACK_DISPLAYER, KeyConfigContexts.TRACKMATE } ); - SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); - HyperStackDisplayerActions.install( adapter.actions(), model, selectionModel, imp ); - TrackMateActions.install( adapter.actions(), model, selectionModel ); - // Select spots with freehand ROI. - SelectSpotsWithRoiListener.install( model, selectionModel, imp ); - // Pref dialog. - final Keymap keymap = keymapManager.getForwardSelectedKeymap(); - TrackMateConfigDialog.prefDialog( imp.getWindow(), keymap, keymapManager, adapter.actions() ); + try + { + final TrackMateKeymapManager keymapManager = TrackMateKeymapManager.keymapManager; + final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, new String[] { KeyConfigContexts.HYPERSTACK_DISPLAYER, KeyConfigContexts.TRACKMATE } ); +// adapter.actions().runnableAction( () -> System.out.println( "TROLOLO" ), "refresh", new String[] { "R" } ); + SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); + HyperStackDisplayerActions.install( adapter.actions(), model, selectionModel, imp ); + TrackMateActions.install( adapter.actions(), model, selectionModel ); + // Select spots with freehand ROI. + SelectSpotsWithRoiListener.install( model, selectionModel, imp ); + // Pref dialog. + TrackMateConfigDialog.prefDialog( imp.getWindow(), adapter.actions() ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + } } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateConfigDialog.java similarity index 88% rename from src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java rename to src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateConfigDialog.java index 1f1b7a0d7..668c39a0a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/TrackMateConfigDialog.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateConfigDialog.java @@ -1,4 +1,4 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; +package fiji.plugin.trackmate.visualization.ui; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.ALL_SPOTS_TABLE; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.HYPERSTACK_DISPLAYER; @@ -18,15 +18,16 @@ import bdv.tools.CloseWindowActions; import bdv.tools.PreferencesDialog; import bdv.ui.keymap.Keymap; -import bdv.ui.keymap.KeymapManager; import bdv.ui.keymap.KeymapSettingsPage; import fiji.plugin.trackmate.gui.GuiUtils; public class TrackMateConfigDialog { - public static void prefDialog( final Frame frame, final Keymap keymap, final KeymapManager keymapManager, final Actions actions ) + public static void prefDialog( final Frame frame, final Actions actions ) { + final TrackMateKeymapManager keymapManager = TrackMateKeymapManager.keymapManager; + final Keymap keymap = keymapManager.getForwardSelectedKeymap(); final PreferencesDialog preferencesDialog = new PreferencesDialog( frame, keymap, new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE } ); GuiUtils.positionWindow( preferencesDialog, frame ); From 8660c4b76bd0e2aba744370d3461388176a9087d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 2 Aug 2026 11:47:03 +0200 Subject: [PATCH 297/371] Add pref dialog action to TrackScheme. --- .../trackmate/visualization/trackscheme/TrackScheme.java | 3 +++ 1 file changed, 3 insertions(+) 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 bf6a3e86d..1c1975b4f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java @@ -67,6 +67,7 @@ import fiji.plugin.trackmate.visualization.trackscheme.behaviours.TrackSchemeActions; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import fiji.plugin.trackmate.visualization.ui.TrackMateActions; +import fiji.plugin.trackmate.visualization.ui.TrackMateConfigDialog; import ij.ImagePlus; public class TrackScheme extends AbstractTrackMateModelJFrameView @@ -796,6 +797,8 @@ public void run() attachKeybindings( gui.graphComponent ); TrackSchemeActions.install( actions, model, gui.graphComponent ); TrackMateActions.install( actions, model, selectionModel ); + // Pref dialog. + TrackMateConfigDialog.prefDialog( gui, actions ); gui.logger.setProgress( 0 ); final long end = System.currentTimeMillis(); From 8d0515af094ac5d6bd3975527640af2f76f8ffa4 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 16:23:32 +0200 Subject: [PATCH 298/371] A GUI model class to store all info required by the full app. Of course, inspired by Mastodon, and prompted by the need to manage windows globally and in a unified manner. --- .../fiji/plugin/trackmate/gui/GuiModel.java | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/GuiModel.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java new file mode 100644 index 000000000..6e98a7879 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java @@ -0,0 +1,211 @@ +package fiji.plugin.trackmate.gui; + +import org.scijava.object.ObjectService; +import org.scijava.ui.behaviour.KeyPressedManager; +import org.scijava.ui.behaviour.util.Actions; + +import bdv.ui.keymap.Keymap; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; +import fiji.plugin.trackmate.visualization.ui.TrackMateActions; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; +import ij.ImagePlus; +import ij.Prefs; + +/** + * GUI model for TrackMate views. + */ +public class GuiModel +{ + + private final Model model; + + private final SelectionModel selectionModel; + + private final DisplaySettings displaySettings; + + private final Actions globalActions; + + private final KeyPressedManager keyPressedManager; + + private final TrackMateKeymapManager keymapManager; + + private final Settings settings; + + private final TrackMate trackmate; + + private final WindowManager windowManager; + + /** + * Creates a new GuiModel. + * + * @param model + * the data model. + * @param settings + * the settings objects. Viewer that can display an image will + * use the {@link Settings#imp} field to do so. + * @param displaySettings + * the display settings to use for the views. + */ + public GuiModel( final Model model, final Settings settings, final DisplaySettings displaySettings ) + { + this.model = model; + this.settings = settings; + this.selectionModel = new SelectionModel( model ); + this.displaySettings = displaySettings; + this.trackmate = createTrackMate( model, settings ); + + // Keymap and actions + this.keyPressedManager = new KeyPressedManager(); + this.keymapManager = new TrackMateKeymapManager(); + final Keymap keymap = keymapManager.getForwardSelectedKeymap(); + keymap.updateListeners().add( () -> getGlobalActions().updateKeyConfig( keymap.getConfig() ) ); + this.globalActions = new Actions( keymapManager.getForwardSelectedKeymap().getConfig(), KeyConfigContexts.TRACKMATE ); + TrackMateActions.install( globalActions, model, selectionModel ); + + // Window manager + this.windowManager = new WindowManager( this ); + } + + /** + * Creates a new GuiModel with default display settings. + * + * @param model + * the data model. + * @param settings + * the settings objects. Viewer that can display an image will + * use the {@link Settings#imp} field to do so. + */ + public GuiModel( final Model model, final Settings settings ) + { + this( model, settings, DisplaySettings.defaultStyle().copy() ); + } + + /** + * Creates a new GuiModel with default settings and display settings, and + * without an image. + * + * @param model + * the data model. + */ + public GuiModel( final Model model ) + { + this( model, new Settings() ); + } + + /** + * Creates a new GuiModel with default settings, and without an image. + * + * @param model + * the data model. + * @param displaySettings + * the display settings to use for the views. + */ + public GuiModel( final Model model, final DisplaySettings displaySettings ) + { + this( model, new Settings(), displaySettings ); + } + + /** + * Creates a new GuiModel with default display settings, and default + * settings set to use the given image. + * + * @param model + * the data model. + * @param imp + * the image to use in the settings. + */ + public GuiModel( final Model model, final ImagePlus imp ) + { + this( model, new Settings( 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 ) + { + /* + * Since we are now sure that we will be working on this model with this + * settings, we need to pass to the model the units from the settings. + */ + final String spaceUnits = settings.imp.getCalibration().getXUnit(); + final String timeUnits = settings.imp.getCalibration().getTimeUnit(); + model.setPhysicalUnits( spaceUnits, timeUnits ); + + final TrackMate trackmate = new TrackMate( model, settings ); + final ObjectService objectService = TMUtils.getContext().service( ObjectService.class ); + if ( objectService != null ) + objectService.addObject( trackmate ); + + // Set the num of threads from IJ prefs. + trackmate.setNumThreads( Prefs.getThreads() ); + + return trackmate; + } + + /** + * Actions that operates on the whole TrackMate session. + *

    + * For instance, saving, importing, creating a new view, showing the + * preference window, etc. + * + * @return the global actions. + */ + public Actions getGlobalActions() + { + return globalActions; + } + + public KeyPressedManager getKeyPressedManager() + { + return keyPressedManager; + } + + public TrackMateKeymapManager getKeymapManager() + { + return keymapManager; + } + + public Model getModel() + { + return model; + } + + public Settings getSettings() + { + return settings; + } + + public SelectionModel getSelectionModel() + { + return selectionModel; + } + + public DisplaySettings getDisplaySettings() + { + return displaySettings; + } + + public TrackMate getTrackMate() + { + return trackmate; + } + + public WindowManager getWindowManager() + { + return windowManager; + } +} From b13ad32f69ae5a5004333bae9df565358e1acc28 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 16:24:31 +0200 Subject: [PATCH 299/371] A WindowManager class. Again inspired by Mastodon. This class knows how to create any view and can manage the ones it created. The notion of views will include the spot editor. --- .../plugin/trackmate/gui/WindowManager.java | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/WindowManager.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java new file mode 100644 index 000000000..d8992e3a0 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java @@ -0,0 +1,197 @@ +package fiji.plugin.trackmate.gui; + +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.ALL_SPOTS_TABLE; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.HYPERSTACK_DISPLAYER; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.KEY_CONFIG_SCOPE; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACKMATE; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACKSCHEME; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACK_TABLE; + +import java.awt.Window; +import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.swing.SwingUtilities; + +import org.scijava.plugin.Plugin; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; +import org.scijava.ui.behaviour.util.Actions; + +import bdv.BigDataViewerActions; +import bdv.tools.CloseWindowActions; +import bdv.tools.PreferencesDialog; +import bdv.ui.keymap.Keymap; +import bdv.ui.keymap.KeymapSettingsPage; +import bdv.util.InvokeOnEDT; +import bvv.vistools.BvvHandle; +import fiji.plugin.trackmate.gui.editor.LabkitLauncher; +import fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame; +import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.TrackMateModelView; +import fiji.plugin.trackmate.visualization.bvv.TrackMateBVV; +import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import fiji.plugin.trackmate.visualization.table.AllSpotsTableView; +import fiji.plugin.trackmate.visualization.table.TrackTableView; +import fiji.plugin.trackmate.visualization.trackscheme.SpotImageUpdater; +import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; +import ij.ImagePlus; + +public class WindowManager +{ + + private final GuiModel guiModel; + + private final List< TrackMateModelView > views = new ArrayList<>(); + + private final List< Window > windows = new ArrayList<>(); + + public WindowManager( final GuiModel guiModel ) + { + this.guiModel = guiModel; + + // Unwrap + final TrackMateKeymapManager keymapManager = guiModel.getKeymapManager(); + final Keymap keymap = keymapManager.getForwardSelectedKeymap(); + final Actions globalActions = guiModel.getGlobalActions(); + + // Preferences dialog + final PreferencesDialog preferencesDialog = new PreferencesDialog( null, keymap, new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE } ); + BigDataViewerActions.toggleDialogAction( globalActions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); + preferencesDialog.addPage( new KeymapSettingsPage( "Keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); + } + + public HyperStackDisplayer createHyperStackDisplayer() + { + final HyperStackDisplayer displayer = new HyperStackDisplayer( guiModel ); + registerView( displayer ); + displayer.render(); + return displayer; + } + + public TMLabKitFrame createSpotEditor( final boolean singleTimepoint ) + { + final ImagePlus imp = guiModel.getSettings().imp; + int timepoint; + if ( imp == null ) + timepoint = -1; + else + timepoint = singleTimepoint ? imp.getFrame() - 1 : -1; + final TMLabKitFrame frame = LabkitLauncher.launch( guiModel, timepoint ); + registerWindow( frame ); + return frame; + } + + public TrackScheme createTrackScheme() + { + final TrackScheme trackscheme = new TrackScheme( guiModel ); + final SpotImageUpdater thumbnailUpdater = new SpotImageUpdater( guiModel.getSettings() ); + trackscheme.setSpotImageUpdater( thumbnailUpdater ); + registerView( trackscheme ); + trackscheme.render(); + return trackscheme; + } + + public TrackMateBVV< ? > createBVV() + { + final ImagePlus imp = guiModel.getSettings().imp; + if ( imp != null ) + { + final TrackMateBVV< ? > tbvv = new TrackMateBVV<>( guiModel, imp ); + registerView( tbvv ); + tbvv.render(); + final BvvHandle bvvHandle = tbvv.getBvvHandle(); + SwingUtilities.getWindowAncestor( bvvHandle.getViewerPanel() ).setLocationRelativeTo( null ); + return tbvv; + } + return null; + } + + public AllSpotsTableView createAllSpotsTable() + { + final String imageFileName = TMUtils.getImagePathWithoutExtension( guiModel.getSettings() ); + final AllSpotsTableView view = new AllSpotsTableView( guiModel, imageFileName ); + registerView( view ); + view.render(); + return view; + } + + public TrackTableView createTrackTable() + { + final String imageFileName = TMUtils.getImagePathWithoutExtension( guiModel.getSettings() ); + final TrackTableView view = new TrackTableView( guiModel, imageFileName ); + registerView( view ); + view.render(); + return view; + } + + /** + * Registers a TrackMate view created by this window manager. + * + * @param view + * the view to register. + */ + private void registerView( final TrackMateModelView view ) + { + views.add( view ); + } + + /** + * Registers a frame created by this window manager. This is useful for + * windows and dialogs that are not TrackMateModelView. + * + * @param frame + * the frame to register. + */ + private void registerWindow( final Window window ) + { + windows.add( window ); + } + + @Plugin( type = CommandDescriptionProvider.class ) + public static class Descriptions extends CommandDescriptionProvider + { + public Descriptions() + { + super( KEY_CONFIG_SCOPE, TRACKMATE ); + } + + @Override + public void getCommandDescriptions( final CommandDescriptions descriptions ) + { + descriptions.add( CloseWindowActions.CLOSE_DIALOG, CloseWindowActions.CLOSE_DIALOG_KEYS, "Close the active dialog." ); + descriptions.add( BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS, "Open the preferences dialog." ); + } + } + + /** + * Close all opened views and dialogs. + */ + public void closeAll() + { + final ArrayList< Window > windowsToClose = new ArrayList<>(); + views.forEach( v -> windowsToClose.add( v.getWindow() ) ); + windowsToClose.addAll( windows ); + try + { + InvokeOnEDT.invokeAndWait( + () -> windowsToClose.stream() + .filter( Objects::nonNull ) + .forEach( window -> window + .dispatchEvent( new WindowEvent( window, WindowEvent.WINDOW_CLOSING ) ) ) ); + } + catch ( final InvocationTargetException e ) + { + e.printStackTrace(); + } + catch ( final InterruptedException e ) + { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + } +} From dd551d5dacb9c398efb5dc83a2c409d94d6f324f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 16:25:21 +0200 Subject: [PATCH 300/371] Use the new GuiModel and WindowManager in TrackMate. --- .../plugin/trackmate/LoadTrackMatePlugIn.java | 48 ++-- .../trackmate/ManualTrackingPlugIn.java | 26 +- .../plugin/trackmate/TrackMatePlugIn.java | 62 +--- .../plugin/trackmate/TrackMateRunner.java | 21 +- .../trackmate/action/CTCExporterAction.java | 10 +- .../action/CaptureOverlayAction.java | 14 +- .../action/ComputeDistanceToRoiAction.java | 16 +- .../action/ExportAllSpotsStatsAction.java | 13 +- .../action/ExportStatsTablesAction.java | 13 +- .../trackmate/action/ExportTracksToXML.java | 13 +- .../action/ExtractTrackStackAction.java | 29 +- .../trackmate/action/IJRoiExporter.java | 10 +- .../action/ISBIChallengeExporter.java | 13 +- .../trackmate/action/IcyTrackExporter.java | 18 +- .../trackmate/action/LabelImgExporter.java | 81 +----- .../trackmate/action/MergeFileAction.java | 10 +- .../trackmate/action/MeshSeriesExporter.java | 10 +- .../trackmate/action/MotilityLabExporter.java | 34 +-- .../action/PlotNSpotsVsTimeAction.java | 14 +- .../action/RecomputeFeatureAction.java | 7 +- .../action/ResetSpotTimeFeatureAction.java | 10 +- .../trackmate/action/TrackBranchAnalysis.java | 13 +- .../trackmate/action/TrackMateAction.java | 16 +- .../action/TrimNotVisibleAction.java | 10 +- .../action/autonaming/AutoNamingAction.java | 8 +- .../autonaming/AutoNamingController.java | 23 +- .../action/closegaps/CloseGapsAction.java | 8 +- .../closegaps/CloseGapsByDetection.java | 45 --- .../action/fit/SpotFitterController.java | 29 +- .../action/fit/SpotGaussianFittingAction.java | 8 +- .../action/meshtools/MeshSmootherAction.java | 10 +- .../trackmate/detection/Process2DZ.java | 2 +- .../features/EdgeFeatureGrapher.java | 36 +-- .../features/SpotFeatureGrapher.java | 36 +-- .../features/TrackFeatureGrapher.java | 36 +-- .../gui/components/ActionChooserPanel.java | 12 +- .../gui/components/ConfigureViewsPanel.java | 206 +++++++++++--- .../gui/components/GrapherPanel.java | 102 +++---- .../trackmate/gui/editor/LabkitLauncher.java | 89 +----- .../gui/wizard/TrackMateWizardSequence.java | 268 +++++------------- .../trackmate/gui/wizard/WizardSequence.java | 17 -- .../descriptors/ActionChooserDescriptor.java | 8 +- .../descriptors/ChooseDetectorDescriptor.java | 37 +-- .../descriptors/ChooseTrackerDescriptor.java | 42 +-- .../descriptors/ConfigureViewsDescriptor.java | 24 +- .../ExecuteDetectionDescriptor.java | 19 +- .../ExecuteTrackingDescriptor.java | 22 +- .../wizard/descriptors/GrapherDescriptor.java | 18 +- .../descriptors/InitFilterDescriptor.java | 16 +- .../wizard/descriptors/SaveDescriptor.java | 24 +- .../descriptors/SpotFilterDescriptor.java | 46 +-- .../descriptors/TrackFilterDescriptor.java | 49 ++-- .../fiji/plugin/trackmate/io/TmXmlReader.java | 82 ------ .../AbstractTrackMateModelJFrameView.java | 23 +- .../AbstractTrackMateModelView.java | 29 +- .../visualization/TrackMateModelView.java | 16 +- .../trackmate/visualization/ViewUtils.java | 2 +- .../visualization/bvv/TrackMateBVV.java | 43 ++- .../hyperstack/HyperStackDisplayer.java | 89 +++--- .../visualization/hyperstack/SpotOverlay.java | 1 - .../HyperStackDisplayerActions.java | 6 +- .../ImagePlusBehavioursAdapter.java | 15 +- .../table/AllSpotsTableView.java | 99 +++---- .../visualization/table/BranchTableView.java | 55 ++-- .../visualization/table/TrackTableView.java | 87 +++--- .../trackscheme/TrackScheme.java | 81 +++--- .../trackscheme/TrackSchemeFrame.java | 4 +- .../TrackSchemeGraphComponent.java | 14 +- .../trackscheme/TrackSchemePopupMenu.java | 12 +- .../trackscheme/TrackSchemeToolbar.java | 2 +- .../java/fiji/plugin/trackmate/TestCopy.java | 13 +- .../plugin/trackmate/TestTrackMatePlugin.java | 21 +- .../action/SpotGaussianFitterExample.java | 17 +- .../detection/HessianDetectorTestDrive1.java | 11 +- .../graph/ConvexBranchDecompositionDebug.java | 7 +- .../ConcurrentSpotTestDrive.java | 2 +- .../HyperStackDisplayerTestDrive.java | 11 +- .../interactivetests/NNTrackerTest.java | 9 +- .../SpotFeatureGrapherExample.java | 13 +- .../interactivetests/TrackLayoutTest.java | 8 +- .../TrackSchemeTestDrive.java | 6 +- .../plugin/trackmate/mesh/DebugZSlicer.java | 10 +- .../plugin/trackmate/mesh/DefaultMesh.java | 15 +- .../plugin/trackmate/mesh/DemoContour.java | 8 +- .../plugin/trackmate/mesh/DemoHollowMesh.java | 16 +- .../trackmate/mesh/DemoPixelIteration.java | 16 +- .../trackmate/mesh/ExportMeshForDemo.java | 7 +- .../kalman/KalmanTrackerInteractiveTest.java | 23 +- .../kalman/KalmanTrackerInteractiveTest2.java | 13 +- .../kalman/KalmanTrackerInteractiveTest3.java | 13 +- .../sparselap/SparseLAPTrackerExample.java | 16 +- .../table/TrackMateTableExample.java | 4 +- 92 files changed, 1103 insertions(+), 1567 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java b/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java index c22e79f20..478fcb51f 100644 --- a/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java @@ -34,6 +34,7 @@ import fiji.plugin.trackmate.features.edges.EdgeAnalyzer; import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactoryBase; import fiji.plugin.trackmate.features.track.TrackAnalyzer; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.components.LogPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -45,9 +46,7 @@ import fiji.plugin.trackmate.io.SettingsPersistence; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.visualization.TrackMateModelView; import fiji.plugin.trackmate.visualization.ViewUtils; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; @@ -135,7 +134,7 @@ public void run( final String filePath ) ImagePlus imp = reader.readImage(); if ( null == imp ) - imp = ViewUtils.makeEmpytImagePlus( model ); + imp = ViewUtils.makeEmptyImagePlus( model ); /* * Read settings. @@ -180,20 +179,17 @@ public void run( final String filePath ) analyzer.getFeatureDimensions(), analyzer.getIsIntFeature() ); + // Display settings. + final DisplaySettings displaySettings = reader.getDisplaySettings(); + /* - * Create TrackMate. + * Create GuiModel. */ - final TrackMate trackmate = createTrackMate( model, settings ); + final GuiModel guiModel = new GuiModel( model, settings, displaySettings ); // Hook actions - postRead( trackmate ); - - // Display settings. - final DisplaySettings displaySettings = reader.getDisplaySettings(); - - // Selection model. - final SelectionModel selectionModel = new SelectionModel( model ); + postRead( guiModel ); if ( !reader.isReadingOk() ) { @@ -202,8 +198,7 @@ public void run( final String filePath ) } // Main view. - final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, settings.imp, displaySettings ); - displayer.render(); + guiModel.getWindowManager().createHyperStackDisplayer(); // GUI state String panelIdentifier = reader.getGUIState(); @@ -212,7 +207,7 @@ public void run( final String filePath ) panelIdentifier = ConfigureViewsDescriptor.KEY; // Wizard. - final WizardSequence sequence = createSequence( trackmate, selectionModel, displaySettings ); + final WizardSequence sequence = createSequence( guiModel ); sequence.setCurrent( panelIdentifier ); final JFrame frame = sequence.run( "TrackMate on " + settings.imp.getShortTitle() ); frame.setIconImage( TRACKMATE_ICON.getImage() ); @@ -220,8 +215,6 @@ public void run( final String filePath ) frame.setVisible( true ); final Dimension size = frame.getSize(); frame.setSize( size.width, size.height + 1 ); - // Undo / redo - TrackMateModelView.registerUndoShortcut( frame, model ); // Text final LogPanelDescriptor2 logDescriptor = ( LogPanelDescriptor2 ) sequence.logDescriptor(); @@ -270,13 +263,13 @@ public void run( final String filePath ) /** * Hook for subclassers:
    - * The {@link TrackMate} object is loaded and properly configured. This + * The {@link guiModel} object is loaded and properly configured. This * method is called just before the controller and GUI are launched. * * @param trackmate - * the {@link TrackMate} instance that was fledged after loading. + * the {@link guiModel} instance that was fledged after loading. */ - protected void postRead( final TrackMate trackmate ) + protected void postRead( final GuiModel guiModel ) {} /** @@ -299,13 +292,20 @@ protected TmXmlReader createReader( final File lFile ) public static void main( final String[] args ) { - GuiUtils.setSystemLookAndFeel(); - ImageJ.main( args ); - final LoadTrackMatePlugIn plugIn = new LoadTrackMatePlugIn(); + try + { + GuiUtils.setSystemLookAndFeel(); + ImageJ.main( args ); + final LoadTrackMatePlugIn plugIn = new LoadTrackMatePlugIn(); // 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" ); + } + catch ( final Throwable t ) + { + t.printStackTrace(); + } } } diff --git a/src/main/java/fiji/plugin/trackmate/ManualTrackingPlugIn.java b/src/main/java/fiji/plugin/trackmate/ManualTrackingPlugIn.java index 354f4d7cb..5d90e0210 100644 --- a/src/main/java/fiji/plugin/trackmate/ManualTrackingPlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/ManualTrackingPlugIn.java @@ -22,7 +22,7 @@ package fiji.plugin.trackmate; import fiji.plugin.trackmate.detection.ManualDetectorFactory; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.wizard.WizardSequence; import fiji.plugin.trackmate.gui.wizard.descriptors.ConfigureViewsDescriptor; import fiji.plugin.trackmate.tracking.manual.ManualTrackerFactory; @@ -33,9 +33,17 @@ public class ManualTrackingPlugIn extends TrackMatePlugIn { @Override - protected WizardSequence createSequence( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings ) + protected WizardSequence createSequence( final GuiModel guiModel ) { - final WizardSequence sequence = super.createSequence( trackmate, selectionModel, displaySettings ); + // Trigger computation of features so that they analyzers are declared + // in the model. + final TrackMate trackmate = guiModel.getTrackMate(); + trackmate.computeSpotFeatures( false ); + trackmate.computeEdgeFeatures( false ); + trackmate.computeTrackFeatures( false ); + + // Create sequence and position it on the ConfigureViewsDescriptor. + final WizardSequence sequence = super.createSequence( guiModel ); sequence.setCurrent( ConfigureViewsDescriptor.KEY ); return sequence; } @@ -54,18 +62,6 @@ protected Settings createSettings( final ImagePlus imp ) return lSettings; } - @Override - protected TrackMate createTrackMate( final Model model, final Settings settings ) - { - final TrackMate trackmate = super.createTrackMate( model, settings ); - // Trigger computation of features so that they analyzers are declared - // in the model. - trackmate.computeSpotFeatures( false ); - trackmate.computeEdgeFeatures( false ); - trackmate.computeTrackFeatures( false ); - return trackmate; - } - public static void main( final String[] args ) { ImageJ.main( args ); diff --git a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java index 855a70716..173dcf2e6 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMatePlugIn.java @@ -27,8 +27,7 @@ import javax.swing.JFrame; -import org.scijava.object.ObjectService; - +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; @@ -37,13 +36,9 @@ import fiji.plugin.trackmate.gui.wizard.TrackMateWizardSequence; import fiji.plugin.trackmate.gui.wizard.WizardSequence; import fiji.plugin.trackmate.io.SettingsPersistence; -import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.visualization.TrackMateModelView; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; -import ij.Prefs; import ij.WindowManager; import ij.plugin.PlugIn; @@ -98,19 +93,15 @@ else if ( imp.getType() == ImagePlus.COLOR_RGB ) // Main objects. final Settings settings = createSettings( imp ); final Model model = createModel( imp ); - final TrackMate trackmate = createTrackMate( model, settings ); - final SelectionModel selectionModel = new SelectionModel( model ); final DisplaySettings displaySettings = createDisplaySettings(); + final GuiModel guiModel = new GuiModel( model, settings, displaySettings ); // Main view. - final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, imp, displaySettings ); - displayer.render(); + guiModel.getWindowManager().createHyperStackDisplayer(); // Wizard. - final WizardSequence sequence = createSequence( trackmate, selectionModel, displaySettings ); + final WizardSequence sequence = createSequence( guiModel ); final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); - // Undo / redo - TrackMateModelView.registerUndoShortcut( frame, model ); frame.setIconImage( TRACKMATE_ICON.getImage() ); GuiUtils.positionWindow( frame, imp.getWindow() ); frame.setVisible( true ); @@ -130,18 +121,16 @@ else if ( imp.getType() == ImagePlus.COLOR_RGB ) * Hook for subclassers:
    * Will create and position the sequence that will be played by the wizard * launched by this plugin. + * + * @param guiModel + * the {@link GuiModel} that will be used to store the data of + * the wizard. * - * @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 ) + protected WizardSequence createSequence( final GuiModel guiModel ) { - return new TrackMateWizardSequence( trackmate, selectionModel, displaySettings ); + return new TrackMateWizardSequence( guiModel ); } /** @@ -182,37 +171,6 @@ protected Settings createSettings( final ImagePlus imp ) return settings; } - /** - * 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 ) - { - /* - * Since we are now sure that we will be working on this model with this - * settings, we need to pass to the model the units from the settings. - */ - final String spaceUnits = settings.imp.getCalibration().getXUnit(); - final String timeUnits = settings.imp.getCalibration().getTimeUnit(); - model.setPhysicalUnits( spaceUnits, timeUnits ); - - final TrackMate trackmate = new TrackMate( model, settings ); - final ObjectService objectService = TMUtils.getContext().service( ObjectService.class ); - if ( objectService != null ) - objectService.addObject( trackmate ); - - // Set the num of threads from IJ prefs. - trackmate.setNumThreads( Prefs.getThreads() ); - - return trackmate; - } - protected DisplaySettings createDisplaySettings() { return DisplaySettingsIO.readUserDefault().copy( "CurrentDisplaySettings" ); diff --git a/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java b/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java index 5bea1322b..365f2f0f6 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java +++ b/src/main/java/fiji/plugin/trackmate/TrackMateRunner.java @@ -40,6 +40,7 @@ import fiji.plugin.trackmate.detection.DetectorKeys; import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.features.track.TrackBranchingAnalyzer; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.components.LogPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -297,15 +298,14 @@ else if ( macroOptions.containsKey( ARG_INPUT_IMAGE_PATH ) ) } /* - * Instantiate TrackMate. + * Instantiate GUImodel. */ final Settings settings = createSettings( imp ); final Model model = createModel( imp ); - final SelectionModel selectionModel = new SelectionModel( model ); model.setLogger( logger ); - final TrackMate trackmate = createTrackMate( model, settings ); final DisplaySettings displaySettings = createDisplaySettings(); + final GuiModel guiModel = new GuiModel( model, settings, displaySettings ); /* * Configure default settings. @@ -403,12 +403,12 @@ else if ( macroOptions.containsKey( ARG_INPUT_IMAGE_PATH ) ) imp.show(); } GuiUtils.userCheckImpDimensions( imp ); + // Main view. - final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, imp, displaySettings ); - displayer.render(); + guiModel.getWindowManager().createHyperStackDisplayer(); // Wizard. - final WizardSequence sequence = createSequence( trackmate, selectionModel, displaySettings ); + final WizardSequence sequence = createSequence( guiModel ); final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); frame.setIconImage( TRACKMATE_ICON.getImage() ); GuiUtils.positionWindow( frame, imp.getWindow() ); @@ -416,6 +416,7 @@ else if ( macroOptions.containsKey( ARG_INPUT_IMAGE_PATH ) ) return; } + final TrackMate trackmate = guiModel.getTrackMate(); final String welcomeMessage = TrackMate.PLUGIN_NAME_STR + " v" + TrackMate.PLUGIN_NAME_VERSION + " started on:\n" + TMUtils.getCurrentTimeString() + '\n'; logger.log( welcomeMessage ); if ( !trackmate.checkInput() || !trackmate.process() ) @@ -435,8 +436,8 @@ else if ( macroOptions.containsKey( ARG_INPUT_IMAGE_PATH ) ) final TmXmlWriter writer = new TmXmlWriter( save_path, logger ); writer.appendLog( logger.toString() ); - writer.appendModel( trackmate.getModel() ); - writer.appendSettings( trackmate.getSettings() ); + writer.appendModel( model ); + writer.appendSettings( settings ); try { @@ -502,11 +503,11 @@ else if ( macroOptions.containsKey( ARG_INPUT_IMAGE_PATH ) ) */ // Main view. - final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, imp, displaySettings ); + final TrackMateModelView displayer = new HyperStackDisplayer( guiModel ); displayer.render(); // Wizard. - final WizardSequence sequence = createSequence( trackmate, selectionModel, displaySettings ); + final WizardSequence sequence = createSequence( guiModel ); sequence.setCurrent( ConfigureViewsDescriptor.KEY ); final JFrame frame = sequence.run( "TrackMate on " + imp.getShortTitle() ); frame.setIconImage( TRACKMATE_ICON.getImage() ); diff --git a/src/main/java/fiji/plugin/trackmate/action/CTCExporterAction.java b/src/main/java/fiji/plugin/trackmate/action/CTCExporterAction.java index 56b634c41..b58f4b07c 100644 --- a/src/main/java/fiji/plugin/trackmate/action/CTCExporterAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/CTCExporterAction.java @@ -32,10 +32,8 @@ import org.scijava.plugin.Plugin; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.action.CTCExporter.ExportType; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import ij.ImagePlus; import ij.gui.GenericDialog; @@ -53,11 +51,11 @@ public class CTCExporterAction extends AbstractTMAction ""; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { final GenericDialog dialog = new GenericDialog( "CTC exporter", parent ); - final ImagePlus imp = trackmate.getSettings().imp; + final ImagePlus imp = guiModel.getSettings().imp; String defaultPath; if ( imp == null || imp.getOriginalFileInfo() == null ) defaultPath = System.getProperty( "user.home" ); @@ -80,7 +78,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo try { - CTCExporter.exportAll( exportRootFolder, trackmate, ExportType.values()[ choiceIndex ], logger ); + CTCExporter.exportAll( exportRootFolder, guiModel.getTrackMate(), ExportType.values()[ choiceIndex ], logger ); } catch ( final IOException e ) { diff --git a/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java b/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java index 87e939a33..9fa72de6d 100644 --- a/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/CaptureOverlayAction.java @@ -34,9 +34,9 @@ import org.scijava.plugin.Plugin; import fiji.plugin.trackmate.Logger; -import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.ViewUtils; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; @@ -74,9 +74,9 @@ public class CaptureOverlayAction extends AbstractTMAction private static boolean whiteBackground = false; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame gui ) + public void execute( final GuiModel guiModel, final Frame gui ) { - final ImagePlus imp = trackmate.getSettings().imp; + final ImagePlus imp = guiModel.getSettings().imp; if ( firstFrame < 0 ) firstFrame = 1; @@ -127,7 +127,9 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo imp2.updateAndRepaintWindow(); } // Add overlay to it. - final HyperStackDisplayer displayer = new HyperStackDisplayer( trackmate.getModel(), new SelectionModel( trackmate.getModel() ), imp2, displaySettings ); + final Settings settings2 = new Settings( imp2 ); + final GuiModel guiModel2 = new GuiModel( guiModel.getModel(), settings2, guiModel.getDisplaySettings() ); + final HyperStackDisplayer displayer = guiModel2.getWindowManager().createHyperStackDisplayer(); displayer.render(); final ImagePlus capture = capture( imp2, firstFrame, lastFrame, logger ); imp2.close(); @@ -135,7 +137,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo } else { - final ImagePlus capture = capture( trackmate, firstFrame, lastFrame, logger ); + final ImagePlus capture = capture( guiModel.getTrackMate(), firstFrame, lastFrame, logger ); capture.show(); } diff --git a/src/main/java/fiji/plugin/trackmate/action/ComputeDistanceToRoiAction.java b/src/main/java/fiji/plugin/trackmate/action/ComputeDistanceToRoiAction.java index 4dcbf1534..f413035d4 100644 --- a/src/main/java/fiji/plugin/trackmate/action/ComputeDistanceToRoiAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/ComputeDistanceToRoiAction.java @@ -37,14 +37,12 @@ import fiji.plugin.trackmate.Dimension; import fiji.plugin.trackmate.FeatureModel; 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.features.spot.SpotAnalyzer; import fiji.plugin.trackmate.features.spot.SpotAnalyzerFactory; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; import ij.gui.Roi; import ij.plugin.frame.RoiManager; @@ -76,11 +74,7 @@ public class ComputeDistanceToRoiAction extends AbstractTMAction private static final String KEY = "COMPUTE_DIST_TO_ROI"; @Override - public void execute( - final TrackMate trackmate, - final SelectionModel selectionModel, - final DisplaySettings displaySettings, - final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { logger.log( "Computing distance from visible spots to closest ROI.\n" ); // Get Roi Manager. @@ -92,9 +86,9 @@ public void execute( } // Get spatial calibration. final double[] calibration; - if ( trackmate.getSettings() != null ) + if ( guiModel.getSettings() != null ) { - final Settings settings = trackmate.getSettings(); + final Settings settings = guiModel.getSettings(); if ( settings.imp != null ) { calibration = TMUtils.getSpatialCalibration( settings.imp ); @@ -113,7 +107,7 @@ public void execute( } // Compute - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); computeDistance( model, rm, calibration ); logger.log( "Done.\n" ); } diff --git a/src/main/java/fiji/plugin/trackmate/action/ExportAllSpotsStatsAction.java b/src/main/java/fiji/plugin/trackmate/action/ExportAllSpotsStatsAction.java index 89c9f6ea3..048ab2f98 100644 --- a/src/main/java/fiji/plugin/trackmate/action/ExportAllSpotsStatsAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/ExportAllSpotsStatsAction.java @@ -29,10 +29,7 @@ import org.scijava.plugin.Plugin; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.table.AllSpotsTableView; @@ -50,14 +47,14 @@ public class ExportAllSpotsStatsAction extends AbstractTMAction + ""; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - createSpotsTable( trackmate.getModel(), selectionModel, displaySettings, TMUtils.getImagePathWithoutExtension( trackmate.getSettings() ) ).render(); + createSpotsTable( guiModel, TMUtils.getImagePathWithoutExtension( guiModel.getSettings() ) ).render(); } - public static final AllSpotsTableView createSpotsTable( final Model model, final SelectionModel selectionModel, final DisplaySettings displaySettings, final String imageFileName ) + public static final AllSpotsTableView createSpotsTable( final GuiModel guiModel, final String imageFileName ) { - return new AllSpotsTableView( model, selectionModel, displaySettings, imageFileName ); + return new AllSpotsTableView( guiModel, imageFileName ); } // Invisible because called on the view config panel. diff --git a/src/main/java/fiji/plugin/trackmate/action/ExportStatsTablesAction.java b/src/main/java/fiji/plugin/trackmate/action/ExportStatsTablesAction.java index f73b7c1e7..59138f387 100644 --- a/src/main/java/fiji/plugin/trackmate/action/ExportStatsTablesAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/ExportStatsTablesAction.java @@ -29,10 +29,7 @@ import org.scijava.plugin.Plugin; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.table.TrackTableView; @@ -56,14 +53,14 @@ public class ExportStatsTablesAction extends AbstractTMAction + ""; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - createTrackTables( trackmate.getModel(), selectionModel, displaySettings, TMUtils.getImagePathWithoutExtension( trackmate.getSettings() ) ).render(); + createTrackTables( guiModel, TMUtils.getImagePathWithoutExtension( guiModel.getSettings() ) ).render(); } - public static TrackTableView createTrackTables( final Model model, final SelectionModel selectionModel, final DisplaySettings displaySettings, final String imageFileName ) + public static TrackTableView createTrackTables( final GuiModel guiModel, final String imageFileName ) { - return new TrackTableView( model, selectionModel, displaySettings, imageFileName ); + return new TrackTableView( guiModel, imageFileName ); } // Invisible because called on the view config panel. diff --git a/src/main/java/fiji/plugin/trackmate/action/ExportTracksToXML.java b/src/main/java/fiji/plugin/trackmate/action/ExportTracksToXML.java index 11ff46f52..98be4814f 100644 --- a/src/main/java/fiji/plugin/trackmate/action/ExportTracksToXML.java +++ b/src/main/java/fiji/plugin/trackmate/action/ExportTracksToXML.java @@ -41,11 +41,10 @@ import fiji.plugin.trackmate.Logger; 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.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.io.IOUtils; import fiji.plugin.trackmate.util.TMUtils; @@ -95,10 +94,10 @@ public static void export( final Model model, final Settings settings, final Fil } @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { logger.log( "Exporting tracks to simple XML format.\n" ); - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); final int ntracks = model.getTrackModel().nTracks( true ); if ( ntracks == 0 ) { @@ -107,12 +106,12 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo } logger.log( " Preparing XML data.\n" ); - final Element root = marshall( model, trackmate.getSettings(), logger ); + final Element root = marshall( model, guiModel.getSettings(), logger ); File folder; try { - folder = new File( trackmate.getSettings().imp.getOriginalFileInfo().directory ); + folder = new File( guiModel.getSettings().imp.getOriginalFileInfo().directory ); } catch ( final NullPointerException npe ) { @@ -122,7 +121,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo File file; try { - String filename = trackmate.getSettings().imageFileName; + String filename = guiModel.getSettings().imageFileName; final int dot = filename.indexOf( "." ); filename = dot < 0 ? filename : filename.substring( 0, dot ); file = new File( folder.getPath() + File.separator + filename + "_Tracks.xml" ); diff --git a/src/main/java/fiji/plugin/trackmate/action/ExtractTrackStackAction.java b/src/main/java/fiji/plugin/trackmate/action/ExtractTrackStackAction.java index 5d3e9c4fe..3b63495fb 100644 --- a/src/main/java/fiji/plugin/trackmate/action/ExtractTrackStackAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/ExtractTrackStackAction.java @@ -42,8 +42,7 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.trackscheme.SpotIconGrabber; import ij.CompositeImage; @@ -92,11 +91,7 @@ public class ExtractTrackStackAction extends AbstractTMAction private static final float RESIZE_FACTOR = 1.5f; @Override - public void execute( - final TrackMate trackmate, - final SelectionModel selectionModel, - final DisplaySettings displaySettings, - final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { // Show dialog. final GenericDialog dialog = new GenericDialog( "Extract track stack", parent ); @@ -119,7 +114,9 @@ public void execute( logger.log( "Capturing " + ( do3d ? "3D" : "2D" ) + " track stack.\n" ); - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); final Set< Spot > selection = selectionModel.getSpotSelection(); final int nspots = selection.size(); if ( nspots != 2 ) @@ -146,7 +143,7 @@ public void execute( selectionModel.addEdgeToSelection( edges ); // Get stack. - final ImagePlus imp = trackStack( trackmate, spot, do3d, logger ); + final ImagePlus imp = trackStack( model, settings, spot, do3d, logger ); imp.show(); imp.setZ( imp.getNSlices() / 2 + 1 ); imp.resetDisplayRange(); @@ -188,7 +185,7 @@ public void execute( selectionModel.addEdgeToSelection( edges ); // Get stack. - final ImagePlus imp = trackStack( trackmate, start1, end1, do3d, logger ); + final ImagePlus imp = trackStack( model, settings, start1, end1, do3d, logger ); imp.show(); imp.setZ( imp.getNSlices() / 2 + 1 ); imp.resetDisplayRange(); @@ -196,28 +193,28 @@ public void execute( } public static final ImagePlus trackStack( - final TrackMate trackmate, + final Model model, + final Settings settings, final Spot spot, final boolean do3d, final Logger logger ) { - final Model model = trackmate.getModel(); final Integer trackID = model.getTrackModel().trackIDOf( spot ); final List< Spot > spots = new ArrayList<>( model.getTrackModel().trackSpots( trackID ) ); Collections.sort( spots, Spot.frameComparator ); final Spot start = spots.get( 0 ); final Spot end = spots.get( spots.size() - 1 ); - return trackStack( trackmate, start, end, do3d, logger ); + return trackStack( model, settings, start, end, do3d, logger ); } public static final ImagePlus trackStack( - final TrackMate trackmate, + final Model model, + final Settings settings, final Spot start, final Spot end, final boolean do3d, final Logger logger ) { - final Model model = trackmate.getModel(); final Spot start1; final Spot end1; if ( start.getFeature( Spot.POSITION_T ) > end.getFeature( Spot.POSITION_T ) ) @@ -265,7 +262,7 @@ public static final ImagePlus trackStack( // Sort spot by ascending frame number final TreeSet< Spot > sortedSpots = new TreeSet<>( Spot.timeComparator ); sortedSpots.addAll( path ); - return trackStack( trackmate.getSettings(), path, radius, do3d, logger ); + return trackStack( settings, path, radius, do3d, logger ); } @SuppressWarnings( { "unchecked", "rawtypes" } ) diff --git a/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java index 177bd5e30..dd88ff363 100644 --- a/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/IJRoiExporter.java @@ -32,10 +32,9 @@ import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotRoi; -import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import ij.ImagePlus; import ij.gui.GenericDialog; import ij.gui.OvalRoi; @@ -131,7 +130,7 @@ public static class IJRoiExporterAction extends AbstractTMAction { @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { // Show dialog. final GenericDialog dialog = new GenericDialog( "Export spots to IJ ROIs", parent ); @@ -146,10 +145,11 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo return; // Execute. + final SelectionModel selectionModel = guiModel.getSelectionModel(); final Iterable< Spot > spots; final int choice = Arrays.asList( choices ).indexOf( dialog.getNextRadioButton() ); if ( choice == 0 ) - spots = trackmate.getModel().getSpots().iterable( true ); + spots = guiModel.getModel().getSpots().iterable( true ); else if ( choice == 1 ) spots = selectionModel.getSpotSelection(); else @@ -160,7 +160,7 @@ else if ( choice == 1 ) spots = selectionModel.getSpotSelection(); } - final IJRoiExporter exporter = new IJRoiExporter( trackmate.getSettings().imp, logger ); + final IJRoiExporter exporter = new IJRoiExporter( guiModel.getSettings().imp, logger ); exporter.export( spots ); } } diff --git a/src/main/java/fiji/plugin/trackmate/action/ISBIChallengeExporter.java b/src/main/java/fiji/plugin/trackmate/action/ISBIChallengeExporter.java index c065990d6..7fc68000a 100644 --- a/src/main/java/fiji/plugin/trackmate/action/ISBIChallengeExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/ISBIChallengeExporter.java @@ -44,11 +44,9 @@ import fiji.plugin.trackmate.Logger; 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.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.io.IOUtils; public class ISBIChallengeExporter extends AbstractTMAction @@ -68,15 +66,16 @@ public class ISBIChallengeExporter extends AbstractTMAction ""; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { logger.log( "Exporting tracks to ISBI challenge file format.\n" ); - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); File file; final File folder = new File( System.getProperty( "user.dir" ) ).getParentFile().getParentFile(); try { - String filename = trackmate.getSettings().imageFileName; + String filename = settings.imageFileName; filename = filename.substring( 0, filename.indexOf( "." ) ); file = new File( folder.getPath() + File.separator + filename + "_ISBI.xml" ); } @@ -91,7 +90,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo return; } - exportToFile( model, trackmate.getSettings(), file, logger ); + exportToFile( model, settings, file, logger ); } public static void exportToFile( final Model model, final Settings settings, final File file ) diff --git a/src/main/java/fiji/plugin/trackmate/action/IcyTrackExporter.java b/src/main/java/fiji/plugin/trackmate/action/IcyTrackExporter.java index 7a69943df..8a3ef33fd 100644 --- a/src/main/java/fiji/plugin/trackmate/action/IcyTrackExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/IcyTrackExporter.java @@ -31,9 +31,7 @@ import org.scijava.plugin.Plugin; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.io.IOUtils; import fiji.plugin.trackmate.io.IcyTrackFormatWriter; @@ -51,10 +49,10 @@ public class IcyTrackExporter extends AbstractTMAction private static final String KEY = "ICY_EXPORTER"; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { logger.log( "Exporting tracks to Icy format.\n" ); - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); final int ntracks = model.getTrackModel().nTracks( true ); if ( ntracks == 0 ) { @@ -65,7 +63,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo File folder; try { - folder = new File( trackmate.getSettings().imp.getOriginalFileInfo().directory ); + folder = new File( guiModel.getSettings().imp.getOriginalFileInfo().directory ); } catch ( final NullPointerException npe ) { @@ -75,7 +73,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo File file; try { - String filename = trackmate.getSettings().imageFileName; + String filename = guiModel.getSettings().imageFileName; final int dotLoca = filename.indexOf( "." ); if ( dotLoca > 0 ) filename = filename.substring( 0, dotLoca ); @@ -95,9 +93,9 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo logger.log( " Writing to file.\n" ); final double[] calibration = new double[ 3 ]; - calibration[ 0 ] = trackmate.getSettings().dx; - calibration[ 1 ] = trackmate.getSettings().dy; - calibration[ 2 ] = trackmate.getSettings().dz; + calibration[ 0 ] = guiModel.getSettings().dx; + calibration[ 1 ] = guiModel.getSettings().dy; + calibration[ 2 ] = guiModel.getSettings().dz; final IcyTrackFormatWriter writer = new IcyTrackFormatWriter( file, model, calibration ); if ( !writer.checkInput() || !writer.process() ) diff --git a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java index 68e8d0c49..f468ad51a 100644 --- a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java @@ -35,12 +35,10 @@ 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.TrackMate; import fiji.plugin.trackmate.TrackModel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.GlasbeyLut; import ij.ImagePlus; @@ -78,7 +76,7 @@ public class LabelImgExporter extends AbstractTMAction public static final String NAME = "Export label image"; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame gui ) + public void execute( final GuiModel guiModel, final Frame gui ) { /* * Ask use for option. @@ -116,78 +114,9 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo * Generate label image. */ - createLabelImagePlus( trackmate, exportSpotsAsDots, exportTracksOnly, labelIdPainting, logger ).show(); - } - - /** - * Creates a new label {@link ImagePlus} where the spots of the specified - * model are painted with their shape, with their track ID as pixel value. - * - * @param trackmate - * the trackmate instance from which we takes the spots to paint. - * The label image will have the same calibration, name and - * dimension from the input image stored in the trackmate - * settings. The output label image will have the same size that - * 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. 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 - * belonging to a track will be painted with a unique ID, - * different from the track IDs and different for each spot. - * @param labeIdPainting - * specifies how to paint the label ID of spots. - * - * @return a new {@link ImagePlus}. - */ - public static final ImagePlus createLabelImagePlus( - final TrackMate trackmate, - final boolean exportSpotsAsDots, - final boolean exportTracksOnly, - final LabelIdPainting labeIdPainting - ) - { - return createLabelImagePlus( trackmate, exportSpotsAsDots, exportTracksOnly, labeIdPainting, Logger.VOID_LOGGER ); - } - - /** - * Creates a new label {@link ImagePlus} where the spots of the specified - * model are painted with their shape, with their track ID as pixel value. - * - * @param trackmate - * the trackmate instance from which we takes the spots to paint. - * The label image will have the same calibration, name and - * dimension from the input image stored in the trackmate - * settings. The output label image will have the same size that - * 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. 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 - * belonging to a track will be painted with a unique ID, - * different from the track IDs and different for each spot. - * @param labelIdPainting - * specifies how to paint the label ID of spots. - * @param logger - * a {@link Logger} instance, to report progress of the export - * process. - * - * @return a new {@link ImagePlus}. - */ - public static final ImagePlus createLabelImagePlus( - final TrackMate trackmate, - final boolean exportSpotsAsDots, - final boolean exportTracksOnly, - final LabelIdPainting labelIdPainting, - final Logger logger ) - { - return createLabelImagePlus( trackmate.getModel(), trackmate.getSettings().imp, exportSpotsAsDots, exportTracksOnly, labelIdPainting, logger ); + final Model model = guiModel.getModel(); + final ImagePlus imp = guiModel.getSettings().imp; + createLabelImagePlus( model, imp, exportSpotsAsDots, exportTracksOnly, labelIdPainting, logger ).show(); } /** diff --git a/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java b/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java index ec9e22128..5b690883d 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java @@ -37,11 +37,9 @@ import org.scijava.plugin.Plugin; 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.gui.GuiModel; import fiji.plugin.trackmate.io.IOUtils; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.util.TMUtils; @@ -67,9 +65,9 @@ public class MergeFileAction extends AbstractTMAction + ""; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - File file = TMUtils.proposeTrackMateSaveFile( trackmate.getSettings(), logger ); + File file = TMUtils.proposeTrackMateSaveFile( guiModel.getSettings(), logger ); if ( null == file ) { final File folder = new File( System.getProperty( "user.dir" ) ).getParentFile().getParentFile(); @@ -92,7 +90,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo // Model final Model modelToMerge = reader.getModel(); - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); final int nNewTracks = modelToMerge.getTrackModel().nTracks( true ); int progress = 0; diff --git a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java index 21027b385..8562d747d 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/MeshSeriesExporter.java @@ -36,12 +36,10 @@ 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.gui.GuiModel; import fiji.plugin.trackmate.io.IOUtils; import net.imglib2.mesh.Mesh; import net.imglib2.mesh.Meshes; @@ -70,15 +68,15 @@ public class MeshSeriesExporter extends AbstractTMAction ""; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { logger.log( "Exporting spot 3D meshes to a file series.\n" ); - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); File file; final File folder = new File( System.getProperty( "user.dir" ) ).getParentFile().getParentFile(); try { - String filename = trackmate.getSettings().imageFileName; + String filename = guiModel.getSettings().imageFileName; int i = filename.indexOf( "." ); if ( i < 0 ) i = filename.length(); diff --git a/src/main/java/fiji/plugin/trackmate/action/MotilityLabExporter.java b/src/main/java/fiji/plugin/trackmate/action/MotilityLabExporter.java index 4a7760b1b..58026a127 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MotilityLabExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/MotilityLabExporter.java @@ -28,7 +28,6 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.Frame; -import java.io.File; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; @@ -53,16 +52,9 @@ import org.scijava.plugin.Plugin; 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.TrackModel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; -import fiji.plugin.trackmate.io.TmXmlReader; -import ij.ImageJ; -import ij.ImagePlus; +import fiji.plugin.trackmate.gui.GuiModel; public class MotilityLabExporter extends AbstractTMAction { @@ -96,13 +88,9 @@ public class MotilityLabExporter extends AbstractTMAction Double.class ); @Override - public void execute( - final TrackMate trackmate, - final SelectionModel selectionModel, - final DisplaySettings displaySettings, - final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - final JPanel panel = createSpotTable( trackmate.getModel() ); + final JPanel panel = createSpotTable( guiModel.getModel() ); final JFrame frame = new JFrame( "TrackMate MotilityLab table export" ); frame.setIconImage( TRACK_TABLES_ICON.getImage() ); @@ -304,20 +292,4 @@ public ImageIcon getIcon() return TRACK_TABLES_ICON; } } - - public static void main( final String[] args ) - { - ImageJ.main( args ); - - final TmXmlReader reader = new TmXmlReader( new File("samples/MAX_Merged.xml" )); - final Model model = reader.getModel(); - final ImagePlus imp = reader.readImage(); - final Settings settings = reader.readSettings( imp ); - new MotilityLabExporter().execute( - new TrackMate( model, settings ), - new SelectionModel( model ), - DisplaySettingsIO.readUserDefault(), - null ); - } - } diff --git a/src/main/java/fiji/plugin/trackmate/action/PlotNSpotsVsTimeAction.java b/src/main/java/fiji/plugin/trackmate/action/PlotNSpotsVsTimeAction.java index 65333699c..f035156fd 100644 --- a/src/main/java/fiji/plugin/trackmate/action/PlotNSpotsVsTimeAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/PlotNSpotsVsTimeAction.java @@ -43,9 +43,9 @@ import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; -import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.AbstractFeatureGrapher; import fiji.plugin.trackmate.features.ModelDataset; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -63,15 +63,11 @@ public class PlotNSpotsVsTimeAction extends AbstractTMAction ""; @Override - public void execute( - final TrackMate trackmate, - final SelectionModel selectionModel, - final DisplaySettings displaySettings, - final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { // Collect data - final Model model = trackmate.getModel(); - final Settings settings = trackmate.getSettings(); + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); final SpotCollection spots = model.getSpots(); final int maxFrame = spots.keySet().stream().mapToInt( Integer::intValue ).max().getAsInt(); @@ -83,6 +79,8 @@ public void execute( nSpots[ frame ] = spots.getNSpots( frame, true ); time[ frame ] = frame * settings.dt; } + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); final NSpotPerFrameDataset dataset = new NSpotPerFrameDataset( model, selectionModel, displaySettings, time, nSpots ); final String yFeature = "N spots"; final Map< String, Dimension > dimMap = new HashMap<>( 2 ); diff --git a/src/main/java/fiji/plugin/trackmate/action/RecomputeFeatureAction.java b/src/main/java/fiji/plugin/trackmate/action/RecomputeFeatureAction.java index b5f9f9c34..9af450a88 100644 --- a/src/main/java/fiji/plugin/trackmate/action/RecomputeFeatureAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/RecomputeFeatureAction.java @@ -31,11 +31,10 @@ import fiji.plugin.trackmate.Logger; 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.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; public class RecomputeFeatureAction extends AbstractTMAction { @@ -52,9 +51,9 @@ public class RecomputeFeatureAction extends AbstractTMAction @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - recompute( trackmate, logger ); + recompute( guiModel.getTrackMate(), logger ); } @Plugin( type = TrackMateActionFactory.class ) diff --git a/src/main/java/fiji/plugin/trackmate/action/ResetSpotTimeFeatureAction.java b/src/main/java/fiji/plugin/trackmate/action/ResetSpotTimeFeatureAction.java index 2e320e4cd..1bfabf9d7 100644 --- a/src/main/java/fiji/plugin/trackmate/action/ResetSpotTimeFeatureAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/ResetSpotTimeFeatureAction.java @@ -34,11 +34,9 @@ import org.scijava.plugin.Plugin; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; -import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; public class ResetSpotTimeFeatureAction extends AbstractTMAction { @@ -53,14 +51,14 @@ public class ResetSpotTimeFeatureAction extends AbstractTMAction { @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { logger.log("Reset spot time.\n"); - double dt = trackmate.getSettings().dt; + double dt = guiModel.getSettings().dt; if (dt == 0) { dt = 1; } - final SpotCollection spots = trackmate.getModel().getSpots(); + final SpotCollection spots = guiModel.getModel().getSpots(); final Set frames = spots.keySet(); for(final int frame : frames) { for (final Iterator iterator = spots.iterator(frame, true); iterator.hasNext();) { diff --git a/src/main/java/fiji/plugin/trackmate/action/TrackBranchAnalysis.java b/src/main/java/fiji/plugin/trackmate/action/TrackBranchAnalysis.java index ea50ab8c0..64202281c 100644 --- a/src/main/java/fiji/plugin/trackmate/action/TrackBranchAnalysis.java +++ b/src/main/java/fiji/plugin/trackmate/action/TrackBranchAnalysis.java @@ -29,10 +29,7 @@ import org.scijava.plugin.Plugin; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.table.BranchTableView; @@ -56,14 +53,14 @@ public class TrackBranchAnalysis extends AbstractTMAction public static final String DOC_URL = "https://imagej.net/plugins/trackmate/actions/branch-hierarchy-analysis"; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - createBranchTable( trackmate.getModel(), selectionModel, TMUtils.getImagePathWithoutExtension( trackmate.getSettings() ) ).render(); + createBranchTable( guiModel, TMUtils.getImagePathWithoutExtension( guiModel.getSettings() ) ).render(); } - public static final BranchTableView createBranchTable( final Model model, final SelectionModel selectionModel, final String imageFileName ) + public static final BranchTableView createBranchTable( final GuiModel guiModel, final String imageFileName ) { - return new BranchTableView( model, selectionModel, imageFileName ); + return new BranchTableView( guiModel, imageFileName ); } @Plugin( type = TrackMateActionFactory.class, enabled = true ) diff --git a/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java b/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java index d9c6dc335..a9f658129 100644 --- a/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/TrackMateAction.java @@ -24,9 +24,8 @@ import java.awt.Frame; import fiji.plugin.trackmate.Logger; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; /** * This interface describe a track mate action, that can be run on a @@ -38,19 +37,14 @@ public interface TrackMateAction { /** - * Executes this action within an application specified by the parameters. + * Executes this action. * - * @param trackmate - * the {@link TrackMate} instance to use to execute the action. - * @param selectionModel - * the {@link SelectionModel} currently used in the application, - * @param displaySettings - * the {@link DisplaySettings} used to render the views in the - * application. + * @param guiModel + * the {@link GuiModel} that contains the required data. * @param parent * the user-interface parent window. */ - public void execute( TrackMate trackmate, SelectionModel selectionModel, DisplaySettings displaySettings, Frame parent ); + public void execute( GuiModel guiModel, Frame parent ); /** * Sets the logger that will receive logs when this action is executed. diff --git a/src/main/java/fiji/plugin/trackmate/action/TrimNotVisibleAction.java b/src/main/java/fiji/plugin/trackmate/action/TrimNotVisibleAction.java index 45f7c9fad..9be77bfb8 100644 --- a/src/main/java/fiji/plugin/trackmate/action/TrimNotVisibleAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/TrimNotVisibleAction.java @@ -32,12 +32,10 @@ import org.scijava.plugin.Plugin; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotCollection; -import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.TrackModel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; public class TrimNotVisibleAction extends AbstractTMAction { @@ -60,13 +58,13 @@ public class TrimNotVisibleAction extends AbstractTMAction public static final String NAME = "Trim non-visible data"; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); final TrackModel tm = model.getTrackModel(); final SpotCollection spots = new SpotCollection(); - spots.setNumThreads( trackmate.getNumThreads() ); + spots.setNumThreads( guiModel.getTrackMate().getNumThreads() ); final Collection< Spot > toRemove = new ArrayList<>(); for ( final Integer trackID : tm.unsortedTrackIDs( false ) ) diff --git a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingAction.java b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingAction.java index 64621a1b4..55db72782 100644 --- a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingAction.java @@ -25,13 +25,11 @@ import org.scijava.plugin.Plugin; -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.GuiModel; import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; public class AutoNamingAction extends AbstractTMAction { @@ -41,9 +39,9 @@ public class AutoNamingAction extends AbstractTMAction + "All spot names are changed. Can be undone."; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final java.awt.Frame parent ) + public void execute( final GuiModel guiModel, final java.awt.Frame parent ) { - final AutoNamingController controller = new AutoNamingController( trackmate, logger ); + final AutoNamingController controller = new AutoNamingController( guiModel, logger ); controller.show(); } diff --git a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingController.java b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingController.java index f19e8852b..6bd26dff5 100644 --- a/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingController.java +++ b/src/main/java/fiji/plugin/trackmate/action/autonaming/AutoNamingController.java @@ -28,24 +28,26 @@ import javax.swing.JLabel; import fiji.plugin.trackmate.Logger; -import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; import fiji.plugin.trackmate.util.Threads; +import ij.ImagePlus; public class AutoNamingController { - private final TrackMate trackmate; - private final AutoNamingPanel gui; private final Logger logger; - public AutoNamingController( final TrackMate trackmate, final Logger logger ) + private final GuiModel guiModel; + + public AutoNamingController( final GuiModel guiModel, final Logger logger ) { - this.trackmate = trackmate; + this.guiModel = guiModel; this.logger = logger; final Collection< AutoNamingRule > namingRules = new ArrayList<>( 3 ); @@ -60,6 +62,7 @@ public AutoNamingController( final TrackMate trackmate, final Logger logger ) private void run( final AutoNamingRule autoNaming ) { + final Model model = guiModel.getModel(); final EverythingDisablerAndReenabler disabler = new EverythingDisablerAndReenabler( gui, new Class[] { JLabel.class } ); disabler.disable(); Threads.run( "TrackMateAutoNamingThread", () -> @@ -68,8 +71,8 @@ private void run( final AutoNamingRule autoNaming ) { logger.log( "Applying naming rule: " + autoNaming.toString() + ".\n" ); logger.setStatus( "Spot auto-naming" ); - AutoNamingPerformer.autoNameSpots( trackmate.getModel(), autoNaming ); - trackmate.getModel().notifyFeaturesComputed(); + AutoNamingPerformer.autoNameSpots( model, autoNaming ); + model.notifyFeaturesComputed(); logger.log( "Spot auto-naming done.\n" ); } finally @@ -88,7 +91,11 @@ public void show() frame.setIconImage( Icons.TRACK_SCHEME_ICON.getImage() ); frame.setSize( 500, 400 ); frame.getContentPane().add( gui ); - GuiUtils.positionWindow( frame, trackmate.getSettings().imp.getCanvas() ); + final ImagePlus imp = guiModel.getSettings().imp; + if ( imp != null ) + GuiUtils.positionWindow( frame, imp.getCanvas() ); + else + frame.setLocationRelativeTo( null ); frame.setVisible( true ); } } diff --git a/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsAction.java b/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsAction.java index 484f1852c..91016e44f 100644 --- a/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsAction.java @@ -27,13 +27,11 @@ import org.scijava.plugin.Plugin; -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.GuiModel; import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; public class CloseGapsAction extends AbstractTMAction { @@ -61,9 +59,9 @@ public class CloseGapsAction extends AbstractTMAction public static final String DOC_URL = "https://imagej.net/plugins/trackmate/actions/close-gaps-action"; @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - final CloseGapsController controller = new CloseGapsController( trackmate, logger ); + final CloseGapsController controller = new CloseGapsController( guiModel.getTrackMate(), logger ); controller.show(); } diff --git a/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsByDetection.java b/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsByDetection.java index 1c455621a..637ee88bf 100644 --- a/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsByDetection.java +++ b/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsByDetection.java @@ -21,7 +21,6 @@ */ package fiji.plugin.trackmate.action.closegaps; -import java.io.File; import java.util.ArrayDeque; import java.util.Collections; import java.util.List; @@ -30,21 +29,10 @@ import fiji.plugin.trackmate.Logger; 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.TrackModel; -import fiji.plugin.trackmate.detection.DetectorKeys; -import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; -import fiji.plugin.trackmate.io.TmXmlReader; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; -import ij.ImageJ; -import ij.ImagePlus; import net.imglib2.util.Util; /** @@ -188,37 +176,4 @@ public String toString() { return NAME; } - - public static void main( final String[] args ) - { - ImageJ.main( args ); - -// final String filePath = "/Users/tinevez/Desktop/GaelleGapClosingWeirdBug/Simple2-211116MovieFDBYFP_Movie0-01-Scene-64-TR114.xml"; - final String filePath = "/Users/tinevez/Desktop/GaelleGapClosingWeirdBug/211116 Movie FDB YFP_Movie 0-01-Scene-64-TR114-zeroed.xml"; - final TmXmlReader reader = new TmXmlReader( new File( filePath ) ); - if ( !reader.isReadingOk() ) - { - System.err.println( reader.getErrorMessage() ); - return; - } - final Model model = reader.getModel(); - final ImagePlus imp = reader.readImage(); - final Settings settings = reader.readSettings( imp ); - final TrackMate trackmate = new TrackMate( model, settings ); - trackmate.setNumThreads( 5 ); - - settings.detectorSettings.put( DetectorKeys.KEY_RADIUS, 8. ); - - final CloseGapsByDetection gapCloser = new CloseGapsByDetection(); - gapCloser.getParameters().get( 0 ).value = 2.; - gapCloser.execute( trackmate, Logger.DEFAULT_LOGGER ); - - final SelectionModel selectionModel = new SelectionModel( model ); - final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); - ds.setSpotColorBy( TrackMateObject.TRACKS, TrackIndexAnalyzer.TRACK_INDEX ); - ds.setTrackColorBy( TrackMateObject.TRACKS, TrackIndexAnalyzer.TRACK_INDEX ); - - new TrackScheme( model, selectionModel, ds ).render(); - new HyperStackDisplayer( model, selectionModel, settings.imp, ds ).render(); - } } diff --git a/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterController.java b/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterController.java index fa356d065..d1d04c9cd 100644 --- a/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterController.java +++ b/src/main/java/fiji/plugin/trackmate/action/fit/SpotFitterController.java @@ -36,6 +36,7 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; @@ -45,24 +46,21 @@ public class SpotFitterController { - private final TrackMate trackmate; - - private final SelectionModel selectionModel; - private final SpotFitterPanel gui; private final Logger logger; private final Map< Spot, double[] > undo; - public SpotFitterController( final TrackMate trackmate, final SelectionModel selectionModel, final Logger logger ) + private final GuiModel guiModel; + + public SpotFitterController( final GuiModel guiModel, final Logger logger ) { - this.trackmate = trackmate; - this.selectionModel = selectionModel; + this.guiModel = guiModel; this.logger = logger; this.undo = new HashMap<>(); - final Settings settings = trackmate.getSettings(); + final Settings settings = guiModel.getSettings(); final List< String > fits = getAvailableFits( DetectionUtils.is2D( settings.imp ) ); final List< String > docs = getDocs( DetectionUtils.is2D( settings.imp ) ); this.gui = new SpotFitterPanel( fits, docs, settings.imp.getNChannels() ); @@ -94,13 +92,14 @@ private void undo() } logger.setProgress( 0. ); // Recompute features. + final TrackMate trackmate = guiModel.getTrackMate(); trackmate.computeSpotFeatures( true ); trackmate.computeEdgeFeatures( true ); trackmate.computeTrackFeatures( true ); logger.log( "Undoing done.\n" ); // Notify changes happened. - trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ) ) ); + guiModel.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ) ) ); } finally { @@ -117,7 +116,7 @@ private void fit() { try { - final ImagePlus imp = trackmate.getSettings().imp; + final ImagePlus imp = guiModel.getSettings().imp; // 1-based to 0-based. final int channel = gui.getSelectedChannel() - 1; final int index = gui.getSelectedFitIndex(); @@ -141,12 +140,13 @@ else if ( index == 1 ) else throw new IllegalArgumentException( "Index points to an unknown fit model: " + index ); } - fitter.setNumThreads( trackmate.getNumThreads() ); + fitter.setNumThreads( guiModel.getTrackMate().getNumThreads() ); // Get spots to fit. + final SelectionModel selectionModel = guiModel.getSelectionModel(); final Iterable< Spot > spots; if ( gui.rdbtnAll.isSelected() ) - spots = trackmate.getModel().getSpots().iterable( true ); + spots = guiModel.getModel().getSpots().iterable( true ); else if ( gui.rdbtnSelection.isSelected() ) spots = selectionModel.getSpotSelection(); else @@ -173,12 +173,13 @@ else if ( gui.rdbtnSelection.isSelected() ) fitter.process( spots, logger ); // Recompute features. + final TrackMate trackmate = guiModel.getTrackMate(); trackmate.computeSpotFeatures( true ); trackmate.computeEdgeFeatures( true ); trackmate.computeTrackFeatures( true ); // Notify changes happened. - trackmate.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ) ) ); + guiModel.getModel().getModelChangeListener().forEach( l -> l.modelChanged( new ModelChangeEvent( this, ModelChangeEvent.MODEL_MODIFIED ) ) ); } finally { @@ -196,7 +197,7 @@ public void show() frame.setIconImage( Icons.SPOT_ICON.getImage() ); frame.setSize( 300, 300 ); frame.getContentPane().add( gui ); - GuiUtils.positionWindow( frame, trackmate.getSettings().imp.getCanvas() ); + GuiUtils.positionWindow( frame, guiModel.getSettings().imp.getCanvas() ); frame.setVisible( true ); } diff --git a/src/main/java/fiji/plugin/trackmate/action/fit/SpotGaussianFittingAction.java b/src/main/java/fiji/plugin/trackmate/action/fit/SpotGaussianFittingAction.java index 075bc917a..0d0089162 100644 --- a/src/main/java/fiji/plugin/trackmate/action/fit/SpotGaussianFittingAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/fit/SpotGaussianFittingAction.java @@ -25,21 +25,19 @@ import org.scijava.plugin.Plugin; -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.GuiModel; import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; public class SpotGaussianFittingAction extends AbstractTMAction { @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final java.awt.Frame parent ) + public void execute( final GuiModel guiModel, final java.awt.Frame parent ) { - final SpotFitterController controller = new SpotFitterController( trackmate, selectionModel, logger ); + final SpotFitterController controller = new SpotFitterController( guiModel, logger ); controller.show(); } 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 4fdc0965a..1fe9c209b 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherAction.java @@ -27,22 +27,20 @@ import org.scijava.plugin.Plugin; -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.GuiModel; import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; public class MeshSmootherAction extends AbstractTMAction { @Override - public void execute( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings, final Frame parent ) + public void execute( final GuiModel guiModel, final Frame parent ) { - final MeshSmootherController controller = new MeshSmootherController( trackmate.getModel(), selectionModel, logger ); - controller.setNumThreads( trackmate.getNumThreads() ); + final MeshSmootherController controller = new MeshSmootherController( guiModel.getModel(), guiModel.getSelectionModel(), logger ); + controller.setNumThreads( guiModel.getTrackMate().getNumThreads() ); controller.show( parent ); } diff --git a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java index 8bb62d906..b3f764d3e 100644 --- a/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java +++ b/src/main/java/fiji/plugin/trackmate/detection/Process2DZ.java @@ -192,7 +192,7 @@ public boolean process() } // Get 2D+T masks - final ImagePlus lblImp = LabelImgExporter.createLabelImagePlus( trackmate, false, true, LabelIdPainting.LABEL_IS_TRACK_ID ); + final ImagePlus lblImp = LabelImgExporter.createLabelImagePlus( trackmate.getModel(), imp, false, true, LabelIdPainting.LABEL_IS_TRACK_ID ); /* * Exposes tracked labels as a 3D image and segment them again with diff --git a/src/main/java/fiji/plugin/trackmate/features/EdgeFeatureGrapher.java b/src/main/java/fiji/plugin/trackmate/features/EdgeFeatureGrapher.java index bfad53b4d..ab313c8f5 100644 --- a/src/main/java/fiji/plugin/trackmate/features/EdgeFeatureGrapher.java +++ b/src/main/java/fiji/plugin/trackmate/features/EdgeFeatureGrapher.java @@ -25,44 +25,34 @@ import org.jgrapht.graph.DefaultWeightedEdge; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; public class EdgeFeatureGrapher extends AbstractFeatureGrapher { private final List< DefaultWeightedEdge > edges; - private final Model model; - - private final SelectionModel selectionModel; - - private final DisplaySettings ds; - private final boolean addLines; + private final GuiModel guiModel; + public EdgeFeatureGrapher( + final GuiModel guiModel, final List< DefaultWeightedEdge > edges, final String xFeature, final List< String > yFeatures, - final Model model, - final SelectionModel selectionModel, - final DisplaySettings displaySettings, final boolean addLines ) { super( xFeature, yFeatures, - model.getFeatureModel().getEdgeFeatureDimensions().get( xFeature ), - model.getFeatureModel().getEdgeFeatureDimensions(), - model.getFeatureModel().getEdgeFeatureNames(), - model.getSpaceUnits(), - model.getTimeUnits() ); + guiModel.getModel().getFeatureModel().getEdgeFeatureDimensions().get( xFeature ), + guiModel.getModel().getFeatureModel().getEdgeFeatureDimensions(), + guiModel.getModel().getFeatureModel().getEdgeFeatureNames(), + guiModel.getModel().getSpaceUnits(), + guiModel.getModel().getTimeUnits() ); + this.guiModel = guiModel; this.edges = edges; - this.model = model; - this.selectionModel = selectionModel; - this.ds = displaySettings; this.addLines = addLines; } @@ -70,9 +60,9 @@ public EdgeFeatureGrapher( protected ModelDataset buildMainDataSet( final List< String > targetYFeatures ) { return new EdgeCollectionDataset( - model, - selectionModel, - ds, + guiModel.getModel(), + guiModel.getSelectionModel(), + guiModel.getDisplaySettings(), xFeature, targetYFeatures, edges, diff --git a/src/main/java/fiji/plugin/trackmate/features/SpotFeatureGrapher.java b/src/main/java/fiji/plugin/trackmate/features/SpotFeatureGrapher.java index 52da8386e..b4e18040b 100644 --- a/src/main/java/fiji/plugin/trackmate/features/SpotFeatureGrapher.java +++ b/src/main/java/fiji/plugin/trackmate/features/SpotFeatureGrapher.java @@ -23,45 +23,35 @@ 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.gui.GuiModel; public class SpotFeatureGrapher extends AbstractFeatureGrapher { private final List< Spot > spots; - private final SelectionModel selectionModel; - - private final Model model; - - private final DisplaySettings ds; - private final boolean addLines; + private final GuiModel guiModel; + public SpotFeatureGrapher( + final GuiModel guiModel, final List< Spot > spots, final String xFeature, final List< String > yFeatures, - final Model model, - final SelectionModel selectionModel, - final DisplaySettings displaySettings, final boolean addLines ) { super( xFeature, yFeatures, - model.getFeatureModel().getSpotFeatureDimensions().get( xFeature ), - model.getFeatureModel().getSpotFeatureDimensions(), - model.getFeatureModel().getSpotFeatureNames(), - model.getSpaceUnits(), - model.getTimeUnits() ); + guiModel.getModel().getFeatureModel().getSpotFeatureDimensions().get( xFeature ), + guiModel.getModel().getFeatureModel().getSpotFeatureDimensions(), + guiModel.getModel().getFeatureModel().getSpotFeatureNames(), + guiModel.getModel().getSpaceUnits(), + guiModel.getModel().getTimeUnits() ); + this.guiModel = guiModel; this.spots = spots; - this.model = model; - this.selectionModel = selectionModel; - this.ds = displaySettings; this.addLines = addLines; } @@ -69,9 +59,9 @@ public SpotFeatureGrapher( protected ModelDataset buildMainDataSet( final List< String > targetYFeatures ) { return new SpotCollectionDataset( - model, - selectionModel, - ds, + guiModel.getModel(), + guiModel.getSelectionModel(), + guiModel.getDisplaySettings(), xFeature, targetYFeatures, spots, diff --git a/src/main/java/fiji/plugin/trackmate/features/TrackFeatureGrapher.java b/src/main/java/fiji/plugin/trackmate/features/TrackFeatureGrapher.java index 05a8440ff..2964243bc 100644 --- a/src/main/java/fiji/plugin/trackmate/features/TrackFeatureGrapher.java +++ b/src/main/java/fiji/plugin/trackmate/features/TrackFeatureGrapher.java @@ -23,50 +23,40 @@ import java.util.List; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; public class TrackFeatureGrapher extends AbstractFeatureGrapher { private final List< Integer > trackIDs; - private final Model model; - - private final SelectionModel selectionModel; - - private final DisplaySettings ds; + private final GuiModel guiModel; public TrackFeatureGrapher( + final GuiModel guiModel, final List< Integer > trackIDs, final String xFeature, - final List< String > yFeatures, - final Model model, - final SelectionModel selectionModel, - final DisplaySettings displaySettings ) + final List< String > yFeatures ) { super( xFeature, yFeatures, - model.getFeatureModel().getTrackFeatureDimensions().get( xFeature ), - model.getFeatureModel().getTrackFeatureDimensions(), - model.getFeatureModel().getTrackFeatureNames(), - model.getSpaceUnits(), - model.getTimeUnits() ); + guiModel.getModel().getFeatureModel().getTrackFeatureDimensions().get( xFeature ), + guiModel.getModel().getFeatureModel().getTrackFeatureDimensions(), + guiModel.getModel().getFeatureModel().getTrackFeatureNames(), + guiModel.getModel().getSpaceUnits(), + guiModel.getModel().getTimeUnits() ); + this.guiModel = guiModel; this.trackIDs = trackIDs; - this.model = model; - this.selectionModel = selectionModel; - this.ds = displaySettings; } @Override protected ModelDataset buildMainDataSet( final List< String > targetYFeatures ) { return new TrackCollectionDataset( - model, - selectionModel, - ds, + guiModel.getModel(), + guiModel.getSelectionModel(), + guiModel.getDisplaySettings(), xFeature, targetYFeatures, trackIDs ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/ActionChooserPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/ActionChooserPanel.java index 36860ba25..81c4eee4a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ActionChooserPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ActionChooserPanel.java @@ -35,12 +35,10 @@ import javax.swing.SwingUtilities; import fiji.plugin.trackmate.Logger; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.action.CaptureOverlayAction; import fiji.plugin.trackmate.action.TrackMateAction; import fiji.plugin.trackmate.action.TrackMateActionFactory; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.providers.ActionProvider; import fiji.plugin.trackmate.util.Threads; @@ -49,7 +47,7 @@ public class ActionChooserPanel extends ModuleChooserPanel< TrackMateActionFacto private static final long serialVersionUID = 1L; - public ActionChooserPanel( final ActionProvider actionProvider, final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings ) + public ActionChooserPanel( final ActionProvider actionProvider, final GuiModel guiModel ) { super( actionProvider, "action", CaptureOverlayAction.KEY ); @@ -94,11 +92,7 @@ public void actionPerformed( final ActionEvent e ) else { action.setLogger( logger ); - action.execute( - trackmate, - selectionModel, - displaySettings, - ( JFrame ) SwingUtilities.getWindowAncestor( ActionChooserPanel.this ) ); + action.execute( guiModel, ( JFrame ) SwingUtilities.getWindowAncestor( ActionChooserPanel.this ) ); } } finally 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 9e3805f48..c4e789bf8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -24,7 +24,11 @@ import static fiji.plugin.trackmate.gui.Fonts.BIG_FONT; import static fiji.plugin.trackmate.gui.Fonts.FONT; import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; +import static fiji.plugin.trackmate.gui.Icons.BVV_ICON; import static fiji.plugin.trackmate.gui.Icons.EDIT_SETTINGS_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; import java.awt.Color; import java.awt.Component; @@ -34,9 +38,10 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; +import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import javax.swing.Action; +import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; @@ -44,19 +49,27 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JRootPane; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; import javax.swing.border.LineBorder; +import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; +import fiji.plugin.trackmate.gui.WindowManager; 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.TMUtils; +import fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame; +import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; +import fiji.plugin.trackmate.util.Threads; import fiji.plugin.trackmate.util.WrapLayout; +import ij.ImagePlus; /** * A configuration panel used to tune the aspect of spots and tracks in multiple @@ -70,22 +83,20 @@ public class ConfigureViewsPanel extends JPanel private static final long serialVersionUID = 1L; - private static final Color BORDER_COLOR = new java.awt.Color( 192, 192, 192 ); + private static final Color BORDER_COLOR = new Color( 192, 192, 192 ); + + private final WindowManager windowManager; + + private final GuiModel guiModel; /* * CONSTRUCTOR */ - public ConfigureViewsPanel( - final DisplaySettings ds, - final FeatureDisplaySelector featureSelector, - final String spaceUnits, - final Action launchBVVAction, - final Action launchTrackSchemeAction, - final Action showTrackTablesAction, - final Action showSpotTableAction, - final Action launchLabKitAction ) + public ConfigureViewsPanel( final GuiModel guiModel, final FeatureDisplaySelector featureSelector ) { + this.guiModel = guiModel; + this.windowManager = guiModel.getWindowManager(); this.setPreferredSize( new Dimension( 300, 521 ) ); this.setSize( 300, 500 ); @@ -114,6 +125,7 @@ public ConfigureViewsPanel( * Settings editor. */ + final DisplaySettings ds = guiModel.getDisplaySettings(); final JFrame editor = ConfigTrackMateDisplaySettings.editor( ds, "Configure the display settings used in this current session.", "TrackMate display settings" ); @@ -348,6 +360,7 @@ public ConfigureViewsPanel( spinnerDrawingZDepth.setFont( SMALL_FONT ); panelDrawingZDepth.add( spinnerDrawingZDepth ); + final String spaceUnits = guiModel.getModel().getSpaceUnits(); final JLabel lblDrawingZDepthUnits = new JLabel( spaceUnits ); lblDrawingZDepthUnits.setFont( SMALL_FONT ); panelDrawingZDepth.add( lblDrawingZDepthUnits ); @@ -360,45 +373,31 @@ public ConfigureViewsPanel( panelButtons.setLayout( new WrapLayout() ); // BVV button. - final JButton btnShowBVV = new JButton( launchBVVAction ); + final JButton btnShowBVV = new JButton( new LaunchBVVAction() ); panelButtons.add( btnShowBVV ); btnShowBVV.setFont( FONT ); // TrackScheme button. - final JButton btnShowTrackScheme = new JButton( launchTrackSchemeAction ); + final JButton btnShowTrackScheme = new JButton( new LaunchTrackSchemeAction() ); panelButtons.add( btnShowTrackScheme ); btnShowTrackScheme.setFont( FONT ); // Do analysis button. - final JButton btnShowTrackTables = new JButton( showTrackTablesAction ); + final JButton btnShowTrackTables = new JButton( new ShowTrackTablesAction() ); panelButtons.add( btnShowTrackTables ); btnShowTrackTables.setFont( FONT ); - final JButton btnShowSpotTable = new JButton( showSpotTableAction ); + final JButton btnShowSpotTable = new JButton( new ShowSpotTableAction() ); panelButtons.add( btnShowSpotTable ); btnShowSpotTable.setFont( FONT ); // Labkit button. - // Is labkit available? - if ( TMUtils.isClassPresent( "sc.fiji.labkit.ui.LabkitFrame" ) && launchLabKitAction.isEnabled() ) - { - final JButton btnLabKit = new JButton( launchLabKitAction ); - btnLabKit.setFont( FONT ); - btnLabKit.setText( "Launch spot editor" ); - btnLabKit.setIcon( GuiUtils.scaleImage( Icons.SEGMENTATION_EDITOR_ICON_64x64, 16, 16 ) ); - btnLabKit.setToolTipText( "" - + "Launch the Labkit editor to edit spot segmentation
    " - + "on the time-point currently displayed in the main
    " - + "view." - + "

    " - + "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 ); - } + final JButton btnLabKit = new JButton( new LaunchSpotEditorAction() ); + btnLabKit.setFont( FONT ); + btnLabKit.setText( "Launch spot editor" ); + btnLabKit.setIcon( GuiUtils.scaleImage( Icons.SEGMENTATION_EDITOR_ICON_64x64, 16, 16 ) ); + btnLabKit.setToolTipText( SPOT_EDITOR_TOOLTIP ); + panelButtons.add( btnLabKit ); panelButtons.setSize( new Dimension( 300, 1 ) ); final GridBagConstraints gbcPanelButtons = new GridBagConstraints(); @@ -489,4 +488,139 @@ private static final void setEnabled( final Container container, final boolean e setEnabled( ( Container ) component, enabled ); } } + + /* + * Actions that launches the different views. + */ + + private class LaunchSpotEditorAction extends AbstractAction + { + private static final long serialVersionUID = 1L; + + private LaunchSpotEditorAction() + { + super( "Launch spot editor", Icons.SEGMENTATION_EDITOR_ICON_64x64 ); + putValue( SHORT_DESCRIPTION, "Launch the Labkit editor to edit spot segmentation." ); + // TODO when we go for 3D editor. + setEnabled( DetectionUtils.is2D( guiModel.getSettings().imp ) ); + } + + @Override + public void actionPerformed( final ActionEvent ae ) + { + Threads.run( "Launching spot editor thread", () -> { + final JRootPane parent = SwingUtilities.getRootPane( ( Component ) ae.getSource() ); + final EverythingDisablerAndReenabler disabler = new EverythingDisablerAndReenabler( parent, new Class[] { JLabel.class } ); + disabler.disable(); + try + { + // Is shift pressed? + final int mod = ae.getModifiers(); + final boolean shiftPressed = ( mod & ActionEvent.SHIFT_MASK ) > 0; + final TMLabKitFrame spotEditor = windowManager.createSpotEditor( !shiftPressed ); + spotEditor.onCloseListeners().addListener( disabler::reenable ); + } + catch ( final Exception e ) + { + e.printStackTrace(); + disabler.reenable(); + } + } ); + } + } + + 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 = guiModel.getSettings().imp; + final boolean enabled = ( imp != null ) && !DetectionUtils.is2D( imp ); + setEnabled( enabled ); + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + Threads.run( "Launching BVV thread", () -> windowManager.createBVV() ); + } + } + + private class LaunchTrackSchemeAction extends AbstractAction + { + private static final long serialVersionUID = 1L; + + private LaunchTrackSchemeAction() + { + super( "TrackScheme", TRACK_SCHEME_ICON_16x16 ); + putValue( SHORT_DESCRIPTION, TRACKSCHEME_BUTTON_TOOLTIP ); + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + Threads.run( "Launching TrackScheme thread", () -> windowManager.createTrackScheme() ); + } + } + + private class ShowTrackTablesAction extends AbstractAction + { + private static final long serialVersionUID = 1L; + + private ShowTrackTablesAction() + { + super( "Tracks", TRACK_TABLES_ICON ); + putValue( SHORT_DESCRIPTION, TRACK_TABLES_BUTTON_TOOLTIP ); + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + Threads.run( "Launching tracks table thread", () -> windowManager.createTrackTable() ); + } + } + + private class ShowSpotTableAction extends AbstractAction + { + private static final long serialVersionUID = 1L; + + private ShowSpotTableAction() + { + super( "Spots", SPOT_TABLE_ICON ); + putValue( SHORT_DESCRIPTION, SPOT_TABLE_BUTTON_TOOLTIP ); + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + Threads.run( "Launching all spots table thread", () -> windowManager.createAllSpotsTable() ); + } + } + + private static final String SPOT_TABLE_BUTTON_TOOLTIP = "Export the features of all spots to ImageJ tables."; + + 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 static final String TRACK_TABLES_BUTTON_TOOLTIP = "" + + "Export the features of all tracks, edges and all
    " + + "spots belonging to a track to ImageJ tables." + + ""; + + private static final String SPOT_EDITOR_TOOLTIP = "" + + "Launch the Labkit editor to edit spot segmentation
    " + + "on the time-point currently displayed in the main
    " + + "view." + + "

    " + + "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."; + } diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/GrapherPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/GrapherPanel.java index e994471ae..586dd6e83 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/GrapherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/GrapherPanel.java @@ -46,27 +46,26 @@ import org.jgrapht.graph.DefaultWeightedEdge; +import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.EdgeFeatureGrapher; import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.features.SpotFeatureGrapher; import fiji.plugin.trackmate.features.TrackFeatureGrapher; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; import fiji.plugin.trackmate.util.Threads; +import ij.ImagePlus; public class GrapherPanel extends JPanel { private static final long serialVersionUID = 1L; - private final TrackMate trackmate; - private final JPanel panelSpot; private final JPanel panelEdges; @@ -79,10 +78,6 @@ public class GrapherPanel extends JPanel private final FeaturePlotSelectionPanel trackFeatureSelectionPanel; - private final DisplaySettings displaySettings; - - private final SelectionModel selectionModel; - private final JPanel panelSelection; private final JRadioButton rdbtnAll; @@ -93,36 +88,35 @@ public class GrapherPanel extends JPanel private final JCheckBox chkboxConnectDots; + private final GuiModel guiModel; + /* * CONSTRUCTOR */ - public GrapherPanel( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings ) + public GrapherPanel( final GuiModel guiModel ) { - this.trackmate = trackmate; - this.selectionModel = selectionModel; - this.displaySettings = displaySettings; + this.guiModel = guiModel; setLayout( new BorderLayout( 0, 0 ) ); - final JTabbedPane tabbedPane = new JTabbedPane( SwingConstants.TOP ); add( tabbedPane, BorderLayout.CENTER ); - panelSpot = new JPanel(); + this.panelSpot = new JPanel(); tabbedPane.addTab( "Spots", SPOT_ICON_64x64, panelSpot, null ); panelSpot.setLayout( new BorderLayout( 0, 0 ) ); - panelEdges = new JPanel(); + this.panelEdges = new JPanel(); tabbedPane.addTab( "Links", EDGE_ICON_64x64, panelEdges, null ); panelEdges.setLayout( new BorderLayout( 0, 0 ) ); - panelTracks = new JPanel(); + this.panelTracks = new JPanel(); tabbedPane.addTab( "Tracks", TRACK_ICON_64x64, panelTracks, null ); panelTracks.setLayout( new BorderLayout( 0, 0 ) ); - final Map< String, String > spotFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.SPOTS, trackmate.getModel(), trackmate.getSettings() ); + final Map< String, String > spotFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.SPOTS, guiModel.getModel(), guiModel.getSettings() ); final Set< String > spotFeatures = spotFeatureNames.keySet(); - spotFeatureSelectionPanel = new FeaturePlotSelectionPanel( + this.spotFeatureSelectionPanel = new FeaturePlotSelectionPanel( "T", "Mean intensity ch1", spotFeatures, @@ -132,9 +126,9 @@ public GrapherPanel( final TrackMate trackmate, final SelectionModel selectionMo // regen edge features panelEdges.removeAll(); - final Map< String, String > edgeFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.EDGES, trackmate.getModel(), trackmate.getSettings() ); + final Map< String, String > edgeFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.EDGES, guiModel.getModel(), guiModel.getSettings() ); final Set< String > edgeFeatures = edgeFeatureNames.keySet(); - edgeFeatureSelectionPanel = new FeaturePlotSelectionPanel( + this.edgeFeatureSelectionPanel = new FeaturePlotSelectionPanel( "Edge time", "Speed", edgeFeatures, @@ -144,9 +138,9 @@ public GrapherPanel( final TrackMate trackmate, final SelectionModel selectionMo // regen trak features panelTracks.removeAll(); - final Map< String, String > trackFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.TRACKS, trackmate.getModel(), trackmate.getSettings() ); + final Map< String, String > trackFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.TRACKS, guiModel.getModel(), guiModel.getSettings() ); final Set< String > trackFeatures = trackFeatureNames.keySet(); - trackFeatureSelectionPanel = new FeaturePlotSelectionPanel( + this.trackFeatureSelectionPanel = new FeaturePlotSelectionPanel( "Track index", "Number of spots in track", trackFeatures, @@ -154,19 +148,19 @@ public GrapherPanel( final TrackMate trackmate, final SelectionModel selectionMo ( xKey, yKeys ) -> Threads.run( () -> plotTrackFeatures( xKey, yKeys ) ) ); panelTracks.add( trackFeatureSelectionPanel ); - panelSelection = new JPanel(); + this.panelSelection = new JPanel(); panelSelection.setLayout( new BoxLayout( panelSelection, BoxLayout.LINE_AXIS ) ); add( panelSelection, BorderLayout.SOUTH ); - rdbtnAll = new JRadioButton( "All" ); + this.rdbtnAll = new JRadioButton( "All" ); rdbtnAll.setFont( rdbtnAll.getFont().deriveFont( rdbtnAll.getFont().getSize() - 2f ) ); panelSelection.add( rdbtnAll ); - rdbtnSelection = new JRadioButton( "Selection" ); + this.rdbtnSelection = new JRadioButton( "Selection" ); rdbtnSelection.setFont( rdbtnSelection.getFont().deriveFont( rdbtnSelection.getFont().getSize() - 2f ) ); panelSelection.add( rdbtnSelection ); - rdbtnTracks = new JRadioButton( "Tracks of selection" ); + this.rdbtnTracks = new JRadioButton( "Tracks of selection" ); rdbtnTracks.setFont( rdbtnTracks.getFont().deriveFont( rdbtnTracks.getFont().getSize() - 2f ) ); panelSelection.add( rdbtnTracks ); @@ -178,7 +172,7 @@ public GrapherPanel( final TrackMate trackmate, final SelectionModel selectionMo panelSelection.add( new JSeparator( SwingConstants.VERTICAL ) ); - chkboxConnectDots = new JCheckBox( "Connect" ); + this.chkboxConnectDots = new JCheckBox( "Connect" ); chkboxConnectDots.setFont( chkboxConnectDots.getFont().deriveFont( chkboxConnectDots.getFont().getSize() - 2f ) ); chkboxConnectDots.setSelected( true ); panelSelection.add( chkboxConnectDots ); @@ -201,6 +195,9 @@ public FeaturePlotSelectionPanel getTrackFeatureSelectionPanel() private void plotSpotFeatures( final String xFeature, final List< String > yFeatures ) { + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( this, new Class[] { JLabel.class } ); enabler.disable(); try @@ -208,9 +205,9 @@ private void plotSpotFeatures( final String xFeature, final List< String > yFeat final List< Spot > spots; if ( rdbtnAll.isSelected() ) { - spots = new ArrayList<>( trackmate.getModel().getSpots().getNSpots( true ) ); - for ( final Integer trackID : trackmate.getModel().getTrackModel().trackIDs( true ) ) - spots.addAll( trackmate.getModel().getTrackModel().trackSpots( trackID ) ); + spots = new ArrayList<>( model.getSpots().getNSpots( true ) ); + for ( final Integer trackID : model.getTrackModel().trackIDs( true ) ) + spots.addAll( model.getTrackModel().trackSpots( trackID ) ); } else if ( rdbtnSelection.isSelected() ) { @@ -226,16 +223,17 @@ else if ( rdbtnSelection.isSelected() ) final boolean addLines = chkboxConnectDots.isSelected(); final SpotFeatureGrapher grapher = new SpotFeatureGrapher( + guiModel, spots, xFeature, yFeatures, - trackmate.getModel(), - selectionModel, - displaySettings, addLines ); final JFrame frame = grapher.render(); frame.setIconImage( Icons.PLOT_ICON.getImage() ); - frame.setTitle( trackmate.getSettings().imp.getShortTitle() + " spot features" ); + + final ImagePlus imp = guiModel.getSettings().imp; + final String title = imp != null ? imp.getShortTitle() : "TrackMate"; + frame.setTitle( title + " spot features" ); GuiUtils.positionWindow( frame, SwingUtilities.getWindowAncestor( this ) ); frame.setVisible( true ); } @@ -247,6 +245,9 @@ else if ( rdbtnSelection.isSelected() ) private void plotEdgeFeatures( final String xFeature, final List< String > yFeatures ) { + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( this, new Class[] { JLabel.class } ); enabler.disable(); try @@ -255,8 +256,8 @@ private void plotEdgeFeatures( final String xFeature, final List< String > yFeat if ( rdbtnAll.isSelected() ) { edges = new ArrayList<>(); - for ( final Integer trackID : trackmate.getModel().getTrackModel().trackIDs( true ) ) - edges.addAll( trackmate.getModel().getTrackModel().trackEdges( trackID ) ); + for ( final Integer trackID : model.getTrackModel().trackIDs( true ) ) + edges.addAll( model.getTrackModel().trackEdges( trackID ) ); } else if ( rdbtnSelection.isSelected() ) { @@ -272,16 +273,17 @@ else if ( rdbtnSelection.isSelected() ) final boolean addLines = chkboxConnectDots.isSelected(); final EdgeFeatureGrapher grapher = new EdgeFeatureGrapher( + guiModel, edges, xFeature, yFeatures, - trackmate.getModel(), - selectionModel, - displaySettings, addLines ); final JFrame frame = grapher.render(); frame.setIconImage( Icons.PLOT_ICON.getImage() ); - frame.setTitle( trackmate.getSettings().imp.getShortTitle() + " edge features" ); + + final ImagePlus imp = guiModel.getSettings().imp; + final String title = imp != null ? imp.getShortTitle() : "TrackMate"; + frame.setTitle( title + " edge features" ); GuiUtils.positionWindow( frame, SwingUtilities.getWindowAncestor( this ) ); frame.setVisible( true ); edgeFeatureSelectionPanel.setEnabled( true ); @@ -294,6 +296,9 @@ else if ( rdbtnSelection.isSelected() ) private void plotTrackFeatures( final String xFeature, final List< String > yFeatures ) { + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final EverythingDisablerAndReenabler enabler = new EverythingDisablerAndReenabler( this, new Class[] { JLabel.class } ); enabler.disable(); try @@ -301,28 +306,29 @@ private void plotTrackFeatures( final String xFeature, final List< String > yFea final List< Integer > trackIDs; if ( rdbtnAll.isSelected() ) { - trackIDs = new ArrayList<>( trackmate.getModel().getTrackModel().unsortedTrackIDs( true ) ); + trackIDs = new ArrayList<>( model.getTrackModel().unsortedTrackIDs( true ) ); } else { final Set< Integer > set = new HashSet<>(); for ( final Spot spot : selectionModel.getSpotSelection() ) - set.add( trackmate.getModel().getTrackModel().trackIDOf( spot ) ); + set.add( model.getTrackModel().trackIDOf( spot ) ); for ( final DefaultWeightedEdge edge : selectionModel.getEdgeSelection() ) - set.add( trackmate.getModel().getTrackModel().trackIDOf( edge ) ); + set.add( model.getTrackModel().trackIDOf( edge ) ); trackIDs = new ArrayList< >( set ); } final TrackFeatureGrapher grapher = new TrackFeatureGrapher( + guiModel, trackIDs, xFeature, - yFeatures, - trackmate.getModel(), - selectionModel, - displaySettings ); + yFeatures ); final JFrame frame = grapher.render(); frame.setIconImage( Icons.PLOT_ICON.getImage() ); - frame.setTitle( trackmate.getSettings().imp.getShortTitle() + " track features" ); + + final ImagePlus imp = guiModel.getSettings().imp; + final String title = imp != null ? imp.getShortTitle() : "TrackMate"; + frame.setTitle( title + " track features" ); GuiUtils.positionWindow( frame, SwingUtilities.getWindowAncestor( this ) ); frame.setVisible( true ); } 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 c9dbe822c..b06308b5e 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -21,38 +21,28 @@ */ package fiji.plugin.trackmate.gui.editor; -import java.awt.Component; -import java.awt.event.ActionEvent; import java.io.File; import javax.swing.JCheckBox; import javax.swing.JFrame; -import javax.swing.JLabel; import javax.swing.JOptionPane; -import javax.swing.JRootPane; import javax.swing.JSeparator; -import javax.swing.SwingUtilities; import org.scijava.Context; -import org.scijava.ui.behaviour.util.AbstractNamedAction; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.detection.DetectionUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame; import fiji.plugin.trackmate.gui.editor.labkit.model.TMLabKitModel; import fiji.plugin.trackmate.io.TmXmlReader; -import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler; import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.visualization.TrackMateModelView; import fiji.plugin.trackmate.visualization.ViewUtils; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImagePlus; +import ij.gui.Roi; import net.imagej.axis.Axes; import net.imagej.axis.CalibratedAxis; import net.imglib2.Interval; @@ -61,25 +51,24 @@ public class LabkitLauncher { - private static final boolean ENABLE_SPOT_EDITOR = true; - private static boolean simplify = true; - public static final TMLabKitFrame launch( final TrackMate trackmate, final DisplaySettings displaySettings, final int timepoint ) + public static final TMLabKitFrame launch( final GuiModel guiModel, final int timepoint ) { // Input model. - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); // Input image. - ImagePlus imp = trackmate.getSettings().imp; + ImagePlus imp = guiModel.getSettings().imp; if ( null == imp ) - imp = ViewUtils.makeEmpytImagePlus( model ); + imp = ViewUtils.makeEmptyImagePlus( model ); // ROI & interval. final Interval interval = TMUtils.createROIInterval( imp ); // Create the LabKit model. final Context context = TMUtils.getContext(); + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); final TMLabKitModel lbModel = TMLabKitModel.create( model, imp, interval, displaySettings, timepoint, context ); // Create the UI for editing. @@ -157,61 +146,10 @@ public void run() }.start(); } - public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate, final DisplaySettings ds ) - { - final AbstractNamedAction action = 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 - { - // Is shift pressed? - final int mod = ae.getModifiers(); - final boolean shiftPressed = ( mod & ActionEvent.SHIFT_MASK ) > 0; - final boolean singleTimepoint = !shiftPressed; - final ImagePlus imp = trackmate.getSettings().imp; - int timepoint; - if ( imp == null ) - timepoint = -1; - else - timepoint = singleTimepoint ? imp.getFrame() - 1 : -1; - - final TMLabKitFrame labKitFrame = LabkitLauncher.launch( trackmate, ds, timepoint ); - labKitFrame.onCloseListeners().addListener( disabler::reenable ); - } - catch ( final Exception e ) - { - e.printStackTrace(); - disabler.reenable(); - } - }; - }.start(); - } - }; // Disable if the image is not 2D. - if ( !DetectionUtils.is2D( trackmate.getSettings().imp ) ) - action.setEnabled( false ); - else - action.setEnabled( ENABLE_SPOT_EDITOR ); - return action; - } - public static void main( final String[] args ) { -// final String filename = "samples/MAX_Merged.xml"; - final String filename = "samples/221031_Stat_Stage55_561nm_part1Conf_crop_f4.xml"; + final String filename = "samples/MAX_Merged.xml"; +// final String filename = "samples/221031_Stat_Stage55_561nm_part1Conf_crop_f4.xml"; final TmXmlReader reader = new TmXmlReader( new File( filename ) ); if ( !reader.isReadingOk() ) { @@ -221,17 +159,16 @@ public static void main( final String[] args ) final Model model = reader.getModel(); final ImagePlus imp = reader.readImage(); + imp.setRoi( new Roi( 10, 30, 100, 100 ) ); final Settings settings = reader.readSettings( imp ); final DisplaySettings ds = reader.getDisplaySettings(); - final TrackMate trackmate = new TrackMate( model, settings ); - final SelectionModel selectionModel = new SelectionModel( model ); + final GuiModel guiModel = new GuiModel( model, settings, ds ); // Main view. - final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, settings.imp, ds ); - displayer.render(); + guiModel.getWindowManager().createHyperStackDisplayer(); imp.setSlice( 7 ); // Editor - LabkitLauncher.launch( trackmate, ds, 6 ); + LabkitLauncher.launch( guiModel, -1 ); } } 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 ed1021592..0066fa346 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -21,44 +21,29 @@ */ 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; - -import java.awt.Component; -import java.awt.event.ActionEvent; +import java.awt.Window; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.swing.AbstractAction; -import javax.swing.JLabel; -import javax.swing.JRootPane; -import javax.swing.SwingUtilities; +import javax.swing.JFrame; -import bvv.vistools.BvvHandle; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.TrackMate; -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; import fiji.plugin.trackmate.features.ModelFeatureUpdater; -import fiji.plugin.trackmate.gui.GuiUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.ConfigurationPanel; 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; @@ -80,21 +65,10 @@ 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; -import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; -import ij.ImagePlus; - -public class TrackMateWizardSequence implements WizardSequence -{ - - private final TrackMate trackmate; - - private final SelectionModel selectionModel; +import fiji.plugin.trackmate.visualization.AbstractTrackMateModelJFrameView; - private final DisplaySettings displaySettings; +public class TrackMateWizardSequence extends AbstractTrackMateModelJFrameView implements WizardSequence +{ private WizardPanelDescriptor current; @@ -128,13 +102,15 @@ public class TrackMateWizardSequence implements WizardSequence private final SaveDescriptor saveDescriptor; - public TrackMateWizardSequence( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings ) + private JFrame frame; + + public TrackMateWizardSequence( final GuiModel guiModel ) { - this.trackmate = trackmate; - this.selectionModel = selectionModel; - this.displaySettings = displaySettings; - final Settings settings = trackmate.getSettings(); - final Model model = trackmate.getModel(); + super( guiModel ); + final Settings settings = guiModel.getSettings(); + final Model model = guiModel.getModel(); + final TrackMate trackmate = guiModel.getTrackMate(); + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); // Listen to changes in the model and update features accordingly. final ModelFeatureUpdater modelFeatureUpdater = new ModelFeatureUpdater( model, settings ); @@ -151,37 +127,23 @@ public TrackMateWizardSequence( final TrackMate trackmate, final SelectionModel logDescriptor = new LogPanelDescriptor2( logPanel ); startDialogDescriptor = new StartDialogDescriptor( settings, logger ); - chooseDetectorDescriptor = new ChooseDetectorDescriptor( new DetectorProvider(), trackmate ); - executeDetectionDescriptor = new ExecuteDetectionDescriptor( trackmate, logPanel ); - initFilterDescriptor = new InitFilterDescriptor( trackmate, initialFilter ); - spotFilterDescriptor = new SpotFilterDescriptor( trackmate, spotFilters, featureSelector ); - chooseTrackerDescriptor = new ChooseTrackerDescriptor( new TrackerProvider(), trackmate, displaySettings ); - executeTrackingDescriptor = new ExecuteTrackingDescriptor( trackmate, logPanel, displaySettings ); - trackFilterDescriptor = new TrackFilterDescriptor( trackmate, trackFilters, featureSelector, displaySettings ); - configureViewsDescriptor = new ConfigureViewsDescriptor( - displaySettings, - featureSelector, - new LaunchBVVAction(), - 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 ); + chooseDetectorDescriptor = new ChooseDetectorDescriptor( new DetectorProvider(), guiModel ); + executeDetectionDescriptor = new ExecuteDetectionDescriptor( guiModel, logPanel ); + initFilterDescriptor = new InitFilterDescriptor( guiModel, initialFilter ); + spotFilterDescriptor = new SpotFilterDescriptor( guiModel, spotFilters, featureSelector ); + chooseTrackerDescriptor = new ChooseTrackerDescriptor( new TrackerProvider(), guiModel ); + executeTrackingDescriptor = new ExecuteTrackingDescriptor( guiModel, logPanel ); + trackFilterDescriptor = new TrackFilterDescriptor( guiModel, trackFilters, featureSelector ); + configureViewsDescriptor = new ConfigureViewsDescriptor( guiModel, featureSelector ); + grapherDescriptor = new GrapherDescriptor( guiModel ); + actionChooserDescriptor = new ActionChooserDescriptor( new ActionProvider(), guiModel ); + saveDescriptor = new SaveDescriptor( guiModel, this ); this.next = getForwardSequence(); this.previous = getBackwardSequence(); current = startDialogDescriptor; } - @Override - public void onClose() - { - trackmate.getModel().setLogger( Logger.IJ_LOGGER ); - } - @Override public WizardPanelDescriptor next() { @@ -324,8 +286,10 @@ public void setCurrent( final String panelIdentifier ) */ private SpotDetectorDescriptor getDetectorConfigDescriptor() { - final SpotDetectorFactoryBase< ? > detectorFactory = trackmate.getSettings().detectorFactory; + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + final SpotDetectorFactoryBase< ? > detectorFactory = settings.detectorFactory; /* * Special case: are we dealing with the manual detector? If yes, no * config, no detection. @@ -345,7 +309,7 @@ private SpotDetectorDescriptor getDetectorConfigDescriptor() * descriptor. */ // From settings. - final Map< String, Object > oldSettings1 = new HashMap<>( trackmate.getSettings().detectorSettings ); + final Map< String, Object > oldSettings1 = new HashMap<>( settings.detectorSettings ); // From previous panel. final Map< String, Object > oldSettings2 = new HashMap<>(); final WizardPanelDescriptor previousDescriptor = next.get( chooseDetectorDescriptor ); @@ -366,10 +330,10 @@ private SpotDetectorDescriptor getDetectorConfigDescriptor() defaultSettings.put( skey, previousValue ); } - final ConfigurationPanel detectorConfigurationPanel = detectorFactory.getDetectorConfigurationPanel( trackmate.getSettings(), trackmate.getModel() ); + final ConfigurationPanel detectorConfigurationPanel = detectorFactory.getDetectorConfigurationPanel( settings, model ); detectorConfigurationPanel.setSettings( defaultSettings ); - trackmate.getSettings().detectorSettings = defaultSettings; - final SpotDetectorDescriptor configDescriptor = new SpotDetectorDescriptor( trackmate.getSettings(), detectorConfigurationPanel, trackmate.getModel().getLogger() ); + settings.detectorSettings = defaultSettings; + final SpotDetectorDescriptor configDescriptor = new SpotDetectorDescriptor( settings, detectorConfigurationPanel, model.getLogger() ); // Position sequence next and previous. next.put( chooseDetectorDescriptor, configDescriptor ); @@ -390,7 +354,9 @@ private SpotDetectorDescriptor getDetectorConfigDescriptor() */ private SpotTrackerDescriptor getTrackerConfigDescriptor() { - final SpotTrackerFactory trackerFactory = trackmate.getSettings().trackerFactory; + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + final SpotTrackerFactory trackerFactory = settings.trackerFactory; /* * Special case: are we dealing with the manual tracker? If yes, no @@ -409,7 +375,7 @@ private SpotTrackerDescriptor getTrackerConfigDescriptor() * descriptor. */ // From settings. - final Map< String, Object > oldSettings1 = new HashMap<>( trackmate.getSettings().trackerSettings ); + final Map< String, Object > oldSettings1 = new HashMap<>( settings.trackerSettings ); // From previous panel. final Map< String, Object > oldSettings2 = new HashMap<>(); final WizardPanelDescriptor previousDescriptor = next.get( chooseTrackerDescriptor ); @@ -433,17 +399,15 @@ private SpotTrackerDescriptor getTrackerConfigDescriptor() final ConfigurationPanel trackerConfigurationPanel; if (trackerFactory instanceof SpotImageTrackerFactory) { - trackerConfigurationPanel = ((SpotImageTrackerFactory)trackerFactory).getTrackerConfigurationPanel( - trackmate.getModel(), trackmate.getSettings().imp ); + trackerConfigurationPanel = ( ( SpotImageTrackerFactory ) trackerFactory ).getTrackerConfigurationPanel( model, settings.imp ); } else { - trackerConfigurationPanel= trackerFactory.getTrackerConfigurationPanel( - trackmate.getModel() ); + trackerConfigurationPanel = trackerFactory.getTrackerConfigurationPanel( model ); } trackerConfigurationPanel.setSettings( defaultSettings ); - trackmate.getSettings().trackerSettings = defaultSettings; - final SpotTrackerDescriptor configDescriptor = new SpotTrackerDescriptor( trackmate.getSettings(), trackerConfigurationPanel, trackmate.getModel().getLogger() ); + settings.trackerSettings = defaultSettings; + final SpotTrackerDescriptor configDescriptor = new SpotTrackerDescriptor( settings, trackerConfigurationPanel, model.getLogger() ); // Position sequence next and previous. next.put( chooseTrackerDescriptor, configDescriptor ); @@ -455,135 +419,47 @@ private SpotTrackerDescriptor getTrackerConfigDescriptor() return configDescriptor; } - private static final String TRACK_TABLES_BUTTON_TOOLTIP = "" - + "Export the features of all tracks, edges and all
    " - + "spots belonging to a track to ImageJ tables." - + ""; - - private static final String SPOT_TABLE_BUTTON_TOOLTIP = "Export the features of all spots to ImageJ tables."; - - 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 + @Override + public JFrame run( final String title ) { - 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 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 ) - { - final TrackMateBVV< ? > tbvv = new TrackMateBVV<>( model, selectionModel, imp, displaySettings ); - tbvv.render(); - final BvvHandle bvvHandle = tbvv.getBvvHandle(); - GuiUtils.positionWindow( SwingUtilities.getWindowAncestor( bvvHandle.getViewerPanel() ), c ); - } - } - catch ( final Exception e ) - { - e.printStackTrace(); - } - finally - { - enabler.reenable(); - } - } - }.start(); - } + this.frame = WizardSequence.super.run( title ); + setWindow( frame ); + onClose( () -> { + guiModel.getModel().setLogger( Logger.VOID_LOGGER ); + guiModel.getWindowManager().closeAll(); + } ); + return frame; } - private class LaunchTrackSchemeAction extends AbstractAction - { - private static final long serialVersionUID = 1L; - - private LaunchTrackSchemeAction() - { - super( "TrackScheme", TRACK_SCHEME_ICON_16x16 ); - putValue( SHORT_DESCRIPTION, TRACKSCHEME_BUTTON_TOOLTIP ); - } - - @Override - public void actionPerformed( final ActionEvent e ) - { - Threads.run( "Launching TrackScheme thread", () -> - { - final TrackScheme trackscheme = new TrackScheme( trackmate.getModel(), selectionModel, displaySettings ); - final SpotImageUpdater thumbnailUpdater = new SpotImageUpdater( trackmate.getSettings() ); - trackscheme.setSpotImageUpdater( thumbnailUpdater ); - trackscheme.render(); - } ); - } - } + @Override + public void render() + {} - private class ShowTrackTablesAction extends AbstractAction - { - private static final long serialVersionUID = 1L; + @Override + public void refresh() + {} - private ShowTrackTablesAction() - { - super( "Tracks", TRACK_TABLES_ICON ); - putValue( SHORT_DESCRIPTION, TRACK_TABLES_BUTTON_TOOLTIP ); - } + @Override + public void clear() + {} - @Override - public void actionPerformed( final ActionEvent e ) - { - showTables( false ); - } - } + @Override + public void centerViewOn( final Spot spot ) + {} - private class ShowSpotTableAction extends AbstractAction + @Override + public String getKey() { - private static final long serialVersionUID = 1L; - - private ShowSpotTableAction() - { - super( "Spots", SPOT_TABLE_ICON ); - putValue( SHORT_DESCRIPTION, SPOT_TABLE_BUTTON_TOOLTIP ); - } - - @Override - public void actionPerformed( final ActionEvent e ) - { - showTables( true ); - } + return "TRACKMATE_WIZARD"; } - private void showTables( final boolean showSpotTable ) + @Override + public Window getWindow() { - Threads.run( "TrackMate table thread.", () -> - { - AbstractTMAction action; - if ( showSpotTable ) - action = new ExportAllSpotsStatsAction(); - else - action = new ExportStatsTablesAction(); - - action.execute( trackmate, selectionModel, displaySettings, null ); - } ); + return frame; } + + @Override + public void modelChanged( final ModelChangeEvent event ) + {} } 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 51660bf66..b0814d3ff 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/WizardSequence.java @@ -21,9 +21,6 @@ */ package fiji.plugin.trackmate.gui.wizard; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; - import javax.swing.JFrame; /** @@ -51,23 +48,9 @@ public default JFrame run( final String title ) frame.setSize( 350, 560 ); frame.setTitle( title ); controller.init(); - frame.addWindowListener( new WindowAdapter() - { - @Override - public void windowClosing( final WindowEvent e ) - { - onClose(); - }; - } ); return frame; } - /** - * Method called when the wizard is closed. - */ - public default void onClose() - {} - /** * Returns the descriptor currently displayed. * diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ActionChooserDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ActionChooserDescriptor.java index b40125068..475bfd11b 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ActionChooserDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ActionChooserDescriptor.java @@ -21,10 +21,8 @@ */ package fiji.plugin.trackmate.gui.wizard.descriptors; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.ActionChooserPanel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; import fiji.plugin.trackmate.providers.ActionProvider; @@ -33,9 +31,9 @@ public class ActionChooserDescriptor extends WizardPanelDescriptor private static final String KEY = "Actions"; - public ActionChooserDescriptor( final ActionProvider actionProvider, final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings ) + public ActionChooserDescriptor( final ActionProvider actionProvider, final GuiModel guiModel ) { super( KEY ); - this.targetPanel = new ActionChooserPanel( actionProvider, trackmate, selectionModel, displaySettings ); + this.targetPanel = new ActionChooserPanel( actionProvider, guiModel ); } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ChooseDetectorDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ChooseDetectorDescriptor.java index 753430c0c..f86a99394 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ChooseDetectorDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ChooseDetectorDescriptor.java @@ -23,9 +23,11 @@ import java.util.Map; -import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.detection.LogDetectorFactory; import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.ModuleChooserPanel; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; import fiji.plugin.trackmate.io.SettingsPersistence; @@ -36,19 +38,19 @@ public class ChooseDetectorDescriptor extends WizardPanelDescriptor private static final String KEY = "ChooseDetector"; - private final TrackMate trackmate; - private final DetectorProvider detectorProvider; - public ChooseDetectorDescriptor( final DetectorProvider detectorProvider, final TrackMate trackmate ) + private final GuiModel guiModel; + + public ChooseDetectorDescriptor( final DetectorProvider detectorProvider, final GuiModel guiModel ) { super( KEY ); - this.trackmate = trackmate; this.detectorProvider = detectorProvider; + this.guiModel = guiModel; String selectedDetector = LogDetectorFactory.DETECTOR_KEY; // default - if ( null != trackmate.getSettings().detectorFactory ) - selectedDetector = trackmate.getSettings().detectorFactory.getKey(); + if ( null != guiModel.getSettings().detectorFactory ) + selectedDetector = guiModel.getSettings().detectorFactory.getKey(); this.targetPanel = new ModuleChooserPanel<>( detectorProvider, "detector", selectedDetector ); } @@ -56,8 +58,8 @@ public ChooseDetectorDescriptor( final DetectorProvider detectorProvider, final private void setCurrentChoiceFromPlugin() { String key = LogDetectorFactory.DETECTOR_KEY; // back to default - if ( null != trackmate.getSettings().detectorFactory ) - key = trackmate.getSettings().detectorFactory.getKey(); + if ( null != guiModel.getSettings().detectorFactory ) + key = guiModel.getSettings().detectorFactory.getKey(); @SuppressWarnings( { "rawtypes", "unchecked" } ) final ModuleChooserPanel< SpotDetectorFactoryBase > component = ( fiji.plugin.trackmate.gui.components.ModuleChooserPanel< SpotDetectorFactoryBase > ) targetPanel; @@ -73,6 +75,9 @@ public void displayingPanel() @Override public void aboutToHidePanel() { + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + // Configure the detector provider with choice made in panel @SuppressWarnings( { "rawtypes", "unchecked" } ) final ModuleChooserPanel< SpotDetectorFactoryBase > component = ( fiji.plugin.trackmate.gui.components.ModuleChooserPanel< SpotDetectorFactoryBase > ) targetPanel; @@ -83,37 +88,37 @@ public void aboutToHidePanel() if ( null == factory ) { - trackmate.getModel().getLogger().error( "[ChooseDetectorDescriptor] Cannot find detector named " + detectorKey + " in current TrackMate modules." ); + model.getLogger().error( "[ChooseDetectorDescriptor] Cannot find detector named " + detectorKey + " in current TrackMate modules." ); return; } - trackmate.getSettings().detectorFactory = factory; + settings.detectorFactory = factory; /* * Compare current settings with default ones, and substitute default * ones only if the old ones are absent or not compatible with it. */ - final Map< String, Object > currentSettings = trackmate.getSettings().detectorSettings; + final Map< String, Object > currentSettings = settings.detectorSettings; if ( factory.checkSettings( currentSettings ) != null ) { final String error = factory.checkSettings( currentSettings ); if ( error == null ) { - trackmate.getSettings().detectorSettings = currentSettings; + settings.detectorSettings = currentSettings; } else { final Map< String, Object > defaultSettings = factory.getDefaultSettings(); - trackmate.getSettings().detectorSettings = defaultSettings; + settings.detectorSettings = defaultSettings; } } // Settings persistence. - SettingsPersistence.saveLastUsedSettings( trackmate.getSettings(), trackmate.getModel().getLogger() ); + SettingsPersistence.saveLastUsedSettings( settings, model.getLogger() ); } @Override public Runnable getBackwardRunnable() { - return () -> trackmate.getModel().clearSpots( true ); + return () -> guiModel.getModel().clearSpots( true ); } } 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 1466a4b1d..011272f69 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 @@ -23,8 +23,10 @@ import java.util.Map; -import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.features.FeatureUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.ModuleChooserPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; @@ -39,25 +41,20 @@ public class ChooseTrackerDescriptor extends WizardPanelDescriptor private static final String KEY = "ChooseTracker"; - private final TrackMate trackmate; - private final TrackerProvider trackerProvider; - private final DisplaySettings displaySettings; + private final GuiModel guiModel; - public ChooseTrackerDescriptor( - final TrackerProvider trackerProvider, - final TrackMate trackmate, - final DisplaySettings displaySettings ) + public ChooseTrackerDescriptor( final TrackerProvider trackerProvider, final GuiModel guiModel ) { super( KEY ); - this.trackmate = trackmate; this.trackerProvider = trackerProvider; - this.displaySettings = displaySettings; + this.guiModel = guiModel; + final Settings settings = guiModel.getSettings(); String selectedTracker = SimpleSparseLAPTrackerFactory.THIS2_TRACKER_KEY; // default - if ( null != trackmate.getSettings().trackerFactory ) - selectedTracker = trackmate.getSettings().trackerFactory.getKey(); + if ( null != settings.trackerFactory ) + selectedTracker = settings.trackerFactory.getKey(); this.targetPanel = new ModuleChooserPanel<>( trackerProvider, "tracker", selectedTracker ); } @@ -65,8 +62,8 @@ public ChooseTrackerDescriptor( private void setCurrentChoiceFromPlugin() { String key = SimpleSparseLAPTrackerFactory.THIS2_TRACKER_KEY; // default - if ( null != trackmate.getSettings().trackerFactory ) - key = trackmate.getSettings().trackerFactory.getKey(); + if ( null != guiModel.getSettings().trackerFactory ) + key = guiModel.getSettings().trackerFactory.getKey(); @SuppressWarnings( "unchecked" ) final ModuleChooserPanel< SpotTrackerFactory > component = ( fiji.plugin.trackmate.gui.components.ModuleChooserPanel< SpotTrackerFactory > ) targetPanel; @@ -82,6 +79,9 @@ public void displayingPanel() @Override public void aboutToHidePanel() { + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + // Configure the detector provider with choice made in panel @SuppressWarnings( "unchecked" ) final ModuleChooserPanel< SpotTrackerFactory > component = ( fiji.plugin.trackmate.gui.components.ModuleChooserPanel< SpotTrackerFactory > ) targetPanel; @@ -92,24 +92,24 @@ public void aboutToHidePanel() if ( null == factory ) { - trackmate.getModel().getLogger().error( "[ChooseTrackerDescriptor] Cannot find tracker named " + trackerKey + " in current TrackMate modules." ); + model.getLogger().error( "[ChooseTrackerDescriptor] Cannot find tracker named " + trackerKey + " in current TrackMate modules." ); return; } - trackmate.getSettings().trackerFactory = factory; + settings.trackerFactory = factory; /* * Compare current settings with default ones, and substitute default * ones only if the old ones are absent or not compatible with it. */ - final Map< String, Object > currentSettings = trackmate.getSettings().trackerSettings; + final Map< String, Object > currentSettings = settings.trackerSettings; if ( factory.checkSettings( currentSettings ) != null ) { final Map< String, Object > defaultSettings = factory.getDefaultSettings(); - trackmate.getSettings().trackerSettings = defaultSettings; + settings.trackerSettings = defaultSettings; } // Settings persistence. - SettingsPersistence.saveLastUsedSettings( trackmate.getSettings(), trackmate.getModel().getLogger() ); + SettingsPersistence.saveLastUsedSettings( settings, model.getLogger() ); } @Override @@ -117,10 +117,12 @@ public Runnable getBackwardRunnable() { // Delete tracks and put back default coloring if needed. return () -> { + final Model model = guiModel.getModel(); + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); if ( displaySettings.getSpotColorByType() == TrackMateObject.TRACKS || displaySettings.getSpotColorByType() == TrackMateObject.EDGES ) displaySettings.setSpotColorBy( TrackMateObject.DEFAULT, FeatureUtils.USE_UNIFORM_COLOR_KEY ); - trackmate.getModel().clearTracks( true ); + model.clearTracks( true ); }; } } 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 d1a624e19..0acb2e4a8 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 @@ -21,11 +21,9 @@ */ package fiji.plugin.trackmate.gui.wizard.descriptors; -import javax.swing.Action; - +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.ConfigureViewsPanel; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; public class ConfigureViewsDescriptor extends WizardPanelDescriptor @@ -33,25 +31,9 @@ public class ConfigureViewsDescriptor extends WizardPanelDescriptor public static final String KEY = "ConfigureViews"; - public ConfigureViewsDescriptor( - final DisplaySettings ds, - final FeatureDisplaySelector featureSelector, - final Action launchBVVAction, - final Action launchTrackSchemeAction, - final Action showTrackTablesAction, - final Action showSpotTableAction, - final Action launchLabkitAction, - final String spaceUnits ) + public ConfigureViewsDescriptor( final GuiModel guiModel, final FeatureDisplaySelector featureSelector ) { super( KEY ); - this.targetPanel = new ConfigureViewsPanel( - ds, - featureSelector, - spaceUnits, - launchBVVAction, - launchTrackSchemeAction, - showTrackTablesAction, - showSpotTableAction, - launchLabkitAction ); + this.targetPanel = new ConfigureViewsPanel( guiModel, featureSelector ); } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ExecuteDetectionDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ExecuteDetectionDescriptor.java index a91d97dda..3cf7b3833 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ExecuteDetectionDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ExecuteDetectionDescriptor.java @@ -23,8 +23,10 @@ import org.scijava.Cancelable; +import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.LogPanel; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; @@ -33,12 +35,12 @@ public class ExecuteDetectionDescriptor extends WizardPanelDescriptor public static final String KEY = "ExecuteDetection"; - private final TrackMate trackmate; + private final GuiModel guiModel; - public ExecuteDetectionDescriptor( final TrackMate trackmate, final LogPanel logPanel ) + public ExecuteDetectionDescriptor( final GuiModel guiModel, final LogPanel logPanel ) { super( KEY ); - this.trackmate = trackmate; + this.guiModel = guiModel; this.targetPanel = logPanel; } @@ -46,23 +48,24 @@ public ExecuteDetectionDescriptor( final TrackMate trackmate, final LogPanel log public Runnable getForwardRunnable() { return () -> { - + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + final TrackMate trackmate = guiModel.getTrackMate(); // Read ROI from imp NOW. - final Settings settings = trackmate.getSettings(); settings.setRoi( settings.imp.getRoi() ); // Exec detection. final long start = System.currentTimeMillis(); final boolean ok = trackmate.execDetection(); if ( !ok ) - trackmate.getModel().getLogger().error( trackmate.getErrorMessage() + '\n' ); + model.getLogger().error( trackmate.getErrorMessage() + '\n' ); final long end = System.currentTimeMillis(); - trackmate.getModel().getLogger().log( String.format( "Detection done in %.1f s.\n", ( end - start ) / 1e3f ) ); + model.getLogger().log( String.format( "Detection done in %.1f s.\n", ( end - start ) / 1e3f ) ); }; } @Override public Cancelable getCancelable() { - return trackmate; + return guiModel.getTrackMate(); } } 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 62773e219..219113149 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 @@ -26,10 +26,12 @@ import org.scijava.Cancelable; import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.Model; 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.GuiModel; import fiji.plugin.trackmate.gui.components.LogPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; @@ -40,31 +42,30 @@ public class ExecuteTrackingDescriptor extends WizardPanelDescriptor public static final String KEY = "ExecuteTracking"; - private final TrackMate trackmate; + private final GuiModel guiModel; - private final DisplaySettings displaySettings; - - public ExecuteTrackingDescriptor( final TrackMate trackmate, final LogPanel logPanel, final DisplaySettings displaySettings ) + public ExecuteTrackingDescriptor( final GuiModel guiModel, final LogPanel logPanel ) { super( KEY ); - this.trackmate = trackmate; + this.guiModel = guiModel; this.targetPanel = logPanel; - this.displaySettings = displaySettings; } @Override public Runnable getForwardRunnable() { return () -> { + final TrackMate trackmate = guiModel.getTrackMate(); + final Model model = guiModel.getModel(); final long start = System.currentTimeMillis(); final boolean ok = trackmate.execTracking(); if ( !ok ) - trackmate.getModel().getLogger().error( trackmate.getErrorMessage() + '\n' ); + model.getLogger().error( trackmate.getErrorMessage() + '\n' ); final long end = System.currentTimeMillis(); - final Logger logger = trackmate.getModel().getLogger(); + final Logger logger = model.getLogger(); logger.log( String.format( "Tracking done in %.1f s.\n", ( end - start ) / 1e3f ) ); - final TrackModel trackModel = trackmate.getModel().getTrackModel(); + final TrackModel trackModel = model.getTrackModel(); final int nTracks = trackModel.nTracks( false ); final IntSummaryStatistics stats = trackModel.unsortedTrackIDs( false ).stream() .mapToInt( id -> trackModel.trackSpots( id ).size() ) @@ -75,6 +76,7 @@ public Runnable getForwardRunnable() logger.log( String.format( " - max size: %d spots.\n", stats.getMax() ) ); // Possibly tweak display settings: color spots by track id. + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); if ( displaySettings.getSpotColorByType() == TrackMateObject.DEFAULT ) if ( displaySettings.getSpotColorByFeature().equals( FeatureUtils.USE_UNIFORM_COLOR_KEY ) ) displaySettings.setSpotColorBy( TrackMateObject.TRACKS, TrackIndexAnalyzer.TRACK_INDEX ); @@ -84,6 +86,6 @@ public Runnable getForwardRunnable() @Override public Cancelable getCancelable() { - return trackmate; + return guiModel.getTrackMate(); } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/GrapherDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/GrapherDescriptor.java index 6379d4551..adab1258f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/GrapherDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/GrapherDescriptor.java @@ -24,12 +24,10 @@ import java.util.Map; import java.util.Set; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.FeatureUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.FeaturePlotSelectionPanel; import fiji.plugin.trackmate.gui.components.GrapherPanel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; @@ -38,13 +36,13 @@ public class GrapherDescriptor extends WizardPanelDescriptor private static final String KEY = "GraphFeatures"; - private final TrackMate trackmate; + private final GuiModel guiModel; - public GrapherDescriptor( final TrackMate trackmate, final SelectionModel selectionModel, final DisplaySettings displaySettings ) + public GrapherDescriptor( final GuiModel guiModel ) { super( KEY ); - this.trackmate = trackmate; - this.targetPanel = new GrapherPanel( trackmate, selectionModel, displaySettings ); + this.guiModel = guiModel; + this.targetPanel = new GrapherPanel( guiModel ); } @Override @@ -53,17 +51,17 @@ public void aboutToDisplayPanel() // Regen features. final GrapherPanel panel = ( GrapherPanel ) targetPanel; - final Map< String, String > spotFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.SPOTS, trackmate.getModel(), trackmate.getSettings() ); + final Map< String, String > spotFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.SPOTS, guiModel.getModel(), guiModel.getSettings() ); final Set< String > spotFeatures = spotFeatureNames.keySet(); final FeaturePlotSelectionPanel spotFeatureSelectionPanel = panel.getSpotFeatureSelectionPanel(); spotFeatureSelectionPanel.setFeatures( spotFeatures, spotFeatureNames ); - final Map< String, String > edgeFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.EDGES, trackmate.getModel(), trackmate.getSettings() ); + final Map< String, String > edgeFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.EDGES, guiModel.getModel(), guiModel.getSettings() ); final Set< String > edgeFeatures = edgeFeatureNames.keySet(); final FeaturePlotSelectionPanel edgeFeatureSelectionPanel = panel.getEdgeFeatureSelectionPanel(); edgeFeatureSelectionPanel.setFeatures( edgeFeatures, edgeFeatureNames ); - final Map< String, String > trackFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.TRACKS, trackmate.getModel(), trackmate.getSettings() ); + final Map< String, String > trackFeatureNames = FeatureUtils.collectFeatureKeys( TrackMateObject.TRACKS, guiModel.getModel(), guiModel.getSettings() ); final Set< String > trackFeatures = trackFeatureNames.keySet(); final FeaturePlotSelectionPanel trackFeatureSelectionPanel = panel.getTrackFeatureSelectionPanel(); trackFeatureSelectionPanel.setFeatures( trackFeatures, trackFeatureNames ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/InitFilterDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/InitFilterDescriptor.java index 4c98c973a..934829447 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/InitFilterDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/InitFilterDescriptor.java @@ -25,9 +25,9 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.features.FeatureUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.InitFilterPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; @@ -38,14 +38,14 @@ public class InitFilterDescriptor extends WizardPanelDescriptor public static final String KEY = "InitialFiltering"; - private final TrackMate trackmate; + private final GuiModel guiModel; - public InitFilterDescriptor( final TrackMate trackmate, final FeatureFilter filter ) + public InitFilterDescriptor( final GuiModel guiModel, final FeatureFilter filter ) { super( KEY ); - this.trackmate = trackmate; + this.guiModel = guiModel; final Function< String, double[] > valuesCollector = key -> FeatureUtils.collectFeatureValues( - Spot.QUALITY, TrackMateObject.SPOTS, trackmate.getModel(), false ); + Spot.QUALITY, TrackMateObject.SPOTS, guiModel.getModel(), false ); this.targetPanel = new InitFilterPanel( filter, valuesCollector ); } @@ -58,7 +58,7 @@ public Runnable getForwardRunnable() @Override public void run() { - trackmate.getModel().getLogger().log( "\nComputing spot quality histogram...\n", Logger.BLUE_COLOR ); + guiModel.getModel().getLogger().log( "\nComputing spot quality histogram...\n", Logger.BLUE_COLOR ); final InitFilterPanel component = ( InitFilterPanel ) targetPanel; component.refresh(); } @@ -69,9 +69,9 @@ public void run() public void aboutToHidePanel() { final InitFilterPanel component = ( InitFilterPanel ) targetPanel; - trackmate.getSettings().initialSpotFilterValue = component.getFeatureThreshold().value; + guiModel.getSettings().initialSpotFilterValue = component.getFeatureThreshold().value; // Settings persistence. - SettingsPersistence.saveLastUsedSettings( trackmate.getSettings(), trackmate.getModel().getLogger() ); + SettingsPersistence.saveLastUsedSettings( guiModel.getSettings(), guiModel.getModel().getLogger() ); } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SaveDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SaveDescriptor.java index ca0275740..919467fda 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SaveDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/SaveDescriptor.java @@ -31,8 +31,8 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.TrackMatePlugIn; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.LogPanel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.wizard.WizardPanelDescriptor; import fiji.plugin.trackmate.gui.wizard.WizardSequence; import fiji.plugin.trackmate.io.IOUtils; @@ -44,18 +44,15 @@ public class SaveDescriptor extends WizardPanelDescriptor private static final String KEY = "Saving"; - private final TrackMate trackmate; - - private final DisplaySettings displaySettings; - private final WizardSequence sequence; - public SaveDescriptor( final TrackMate trackmate, final DisplaySettings displaySettings, final WizardSequence sequence ) + private final GuiModel guiModel; + + public SaveDescriptor( final GuiModel guiModel, final WizardSequence sequence ) { super( KEY ); + this.guiModel = guiModel; this.targetPanel = sequence.logDescriptor().getPanelComponent(); - this.trackmate = trackmate; - this.displaySettings = displaySettings; this.sequence = sequence; } @@ -70,14 +67,15 @@ public void displayingPanel() if ( TrackMatePlugIn.lastLoadedFile != null && TrackMatePlugIn.lastLoadedFile.exists() ) file = TrackMatePlugIn.lastLoadedFile; else - file = TMUtils.proposeTrackMateSaveFile( trackmate.getSettings(), logger ); + file = TMUtils.proposeTrackMateSaveFile( guiModel.getSettings(), logger ); /* * If we are to save tracks, we better ensures that track and edge * features are there, even if we have to enforce it. */ - if ( trackmate.getModel().getTrackModel().nTracks( false ) > 0 ) + if ( guiModel.getModel().getTrackModel().nTracks( false ) > 0 ) { + final TrackMate trackmate = guiModel.getTrackMate(); trackmate.computeEdgeFeatures( true ); trackmate.computeTrackFeatures( true ); } @@ -97,10 +95,10 @@ public void displayingPanel() final TmXmlWriter writer = new TmXmlWriter( file, logger ); writer.appendLog( logPanel.getTextContent() ); - writer.appendModel( trackmate.getModel() ); - writer.appendSettings( trackmate.getSettings() ); + writer.appendModel( guiModel.getModel() ); + writer.appendSettings( guiModel.getSettings() ); writer.appendGUIState( sequence.current().getPanelDescriptorIdentifier() ); - writer.appendDisplaySettings( displaySettings ); + writer.appendDisplaySettings( guiModel.getDisplaySettings() ); try { 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 2151b08a2..4037a22fc 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 @@ -34,6 +34,7 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.FeatureFilter; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.FilterGuiPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; @@ -48,18 +49,18 @@ public class SpotFilterDescriptor extends WizardPanelDescriptor private static final String KEY = "SpotFilter"; - private final TrackMate trackmate; + private final GuiModel guiModel; public SpotFilterDescriptor( - final TrackMate trackmate, + final GuiModel guiModel, final List< FeatureFilter > filters, final FeatureDisplaySelector featureSelector ) { super( KEY ); - this.trackmate = trackmate; + this.guiModel = guiModel; final FilterGuiPanel component = new FilterGuiPanel( - trackmate.getModel(), - trackmate.getSettings(), + guiModel.getModel(), + guiModel.getSettings(), TrackMateObject.SPOTS, filters, Spot.QUALITY, @@ -72,7 +73,8 @@ public SpotFilterDescriptor( private void filterSpots() { final FilterGuiPanel component = ( FilterGuiPanel ) targetPanel; - trackmate.getSettings().setSpotFilters( component.getFeatureFilters() ); + guiModel.getSettings().setSpotFilters( component.getFeatureFilters() ); + final TrackMate trackmate = guiModel.getTrackMate(); trackmate.execSpotFiltering( false ); } @@ -88,11 +90,12 @@ public void run() disabler.disable(); try { - - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + final TrackMate trackmate = guiModel.getTrackMate(); final Logger logger = model.getLogger(); final String str = "Initial thresholding with a quality threshold above " - + String.format( "%.1f", trackmate.getSettings().initialSpotFilterValue ) + + String.format( "%.1f", settings.initialSpotFilterValue ) + " ...\n"; logger.log( str, Logger.BLUE_COLOR ); final int ntotal = model.getSpots().getNSpots( false ); @@ -106,7 +109,6 @@ public void run() */ 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(); @@ -128,11 +130,11 @@ public void run() // Calculate features final long start = System.currentTimeMillis(); - final Logger oldLogger = trackmate.getModel().getLogger(); - trackmate.getModel().setLogger( panel.getLogger() ); + final Logger oldLogger = model.getLogger(); + model.setLogger( panel.getLogger() ); trackmate.computeSpotFeatures( true ); final long end = System.currentTimeMillis(); - trackmate.getModel().setLogger( oldLogger ); + model.setLogger( oldLogger ); if ( trackmate.isCanceled() ) logger.log( "Spot feature calculation canceled.\nSome spots will have missing feature values.\n" ); logger.log( String.format( "Calculating features done in %.1f s.\n", ( end - start ) / 1e3f ) ); @@ -154,19 +156,21 @@ public void run() public void displayingPanel() { final FilterGuiPanel component = ( FilterGuiPanel ) targetPanel; - trackmate.getSettings().setSpotFilters( component.getFeatureFilters() ); - trackmate.execSpotFiltering( false ); + guiModel.getSettings().setSpotFilters( component.getFeatureFilters() ); + guiModel.getTrackMate().execSpotFiltering( false ); } @Override public void aboutToHidePanel() { - final Logger logger = trackmate.getModel().getLogger(); + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + final TrackMate trackmate = guiModel.getTrackMate(); + final Logger logger = model.getLogger(); logger.log( "\nPerforming spot filtering on the following features:\n", Logger.BLUE_COLOR ); - final Model model = trackmate.getModel(); final FilterGuiPanel component = ( FilterGuiPanel ) targetPanel; final List< FeatureFilter > featureFilters = component.getFeatureFilters(); - trackmate.getSettings().setSpotFilters( featureFilters ); + settings.setSpotFilters( featureFilters ); trackmate.execSpotFiltering( false ); final int ntotal = model.getSpots().getNSpots( false ); @@ -178,7 +182,7 @@ public void aboutToHidePanel() { for ( final FeatureFilter ft : featureFilters ) { - String str = " - on " + trackmate.getModel().getFeatureModel().getSpotFeatureNames().get( ft.feature ); + String str = " - on " + model.getFeatureModel().getSpotFeatureNames().get( ft.feature ); if ( ft.isAbove ) str += " above "; else @@ -192,12 +196,12 @@ public void aboutToHidePanel() } // Settings persistence. - SettingsPersistence.saveLastUsedSettings( trackmate.getSettings(), logger ); + SettingsPersistence.saveLastUsedSettings( settings, logger ); } @Override public Cancelable getCancelable() { - return trackmate; + return guiModel.getTrackMate(); } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/TrackFilterDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/TrackFilterDescriptor.java index 99b9f1a48..88337e608 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/TrackFilterDescriptor.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/TrackFilterDescriptor.java @@ -28,10 +28,12 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.features.track.TrackBranchingAnalyzer; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.FilterGuiPanel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -45,22 +47,18 @@ public class TrackFilterDescriptor extends WizardPanelDescriptor private static final String KEY = "TrackFilter"; - private final TrackMate trackmate; - - private final DisplaySettings displaySettings; + private final GuiModel guiModel; public TrackFilterDescriptor( - final TrackMate trackmate, + final GuiModel guiModel, final List< FeatureFilter > filters, - final FeatureDisplaySelector featureSelector, - final DisplaySettings displaySettings ) + final FeatureDisplaySelector featureSelector ) { super( KEY ); - this.trackmate = trackmate; - this.displaySettings = displaySettings; + this.guiModel = guiModel; final FilterGuiPanel component = new FilterGuiPanel( - trackmate.getModel(), - trackmate.getSettings(), + guiModel.getModel(), + guiModel.getSettings(), TrackMateObject.TRACKS, filters, TrackBranchingAnalyzer.NUMBER_SPOTS, @@ -73,8 +71,8 @@ public TrackFilterDescriptor( private void filterTracks() { final FilterGuiPanel component = ( FilterGuiPanel ) targetPanel; - trackmate.getSettings().setTrackFilters( component.getFeatureFilters() ); - trackmate.execTrackFiltering( false ); + guiModel.getSettings().setTrackFilters( component.getFeatureFilters() ); + guiModel.getTrackMate().execTrackFiltering( false ); } @Override @@ -89,7 +87,8 @@ public void run() disabler.disable(); try { - final Model model = trackmate.getModel(); + final Model model = guiModel.getModel(); + final TrackMate trackmate = guiModel.getTrackMate(); final Logger logger = model.getLogger(); /* @@ -106,18 +105,19 @@ public void run() logger.log( "\n" ); // Calculate features final long start = System.currentTimeMillis(); - final Logger oldLogger = trackmate.getModel().getLogger(); - trackmate.getModel().setLogger( panel.getLogger() ); + final Logger oldLogger = model.getLogger(); + model.setLogger( panel.getLogger() ); trackmate.computeEdgeFeatures( true ); trackmate.computeTrackFeatures( true ); final long end = System.currentTimeMillis(); - trackmate.getModel().setLogger( oldLogger ); + model.setLogger( oldLogger ); if ( trackmate.isCanceled() ) logger.log( "Spot feature calculation canceled.\nSome spots will have missing feature values.\n" ); logger.log( String.format( "Calculating features done in %.1f s.\n", ( end - start ) / 1e3f ) ); panel.showProgressBar( false ); // Default color spots by track index. + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); displaySettings.setSpotColorBy( TrackMateObject.TRACKS, FeatureUtils.USE_TRACK_INDEX_COLOR_KEY ); // Refresh component. @@ -136,19 +136,22 @@ public void run() public void displayingPanel() { final FilterGuiPanel component = ( FilterGuiPanel ) targetPanel; - trackmate.getSettings().setTrackFilters( component.getFeatureFilters() ); - trackmate.execTrackFiltering( false ); + guiModel.getSettings().setTrackFilters( component.getFeatureFilters() ); + guiModel.getTrackMate().execTrackFiltering( false ); } @Override public void aboutToHidePanel() { - final Logger logger = trackmate.getModel().getLogger(); + final Model model = guiModel.getModel(); + final Settings settings = guiModel.getSettings(); + final TrackMate trackmate = guiModel.getTrackMate(); + + final Logger logger = model.getLogger(); logger.log( "\nPerforming track filtering on the following features:\n", Logger.BLUE_COLOR ); - final Model model = trackmate.getModel(); final FilterGuiPanel component = ( FilterGuiPanel ) targetPanel; final List< FeatureFilter > featureFilters = component.getFeatureFilters(); - trackmate.getSettings().setTrackFilters( featureFilters ); + settings.setTrackFilters( featureFilters ); trackmate.execTrackFiltering( false ); final int ntotal = model.getTrackModel().nTracks( false ); @@ -160,7 +163,7 @@ public void aboutToHidePanel() { for ( final FeatureFilter ft : featureFilters ) { - String str = " - on " + trackmate.getModel().getFeatureModel().getTrackFeatureNames().get( ft.feature ); + String str = " - on " + model.getFeatureModel().getTrackFeatureNames().get( ft.feature ); if ( ft.isAbove ) str += " above "; else @@ -174,6 +177,6 @@ public void aboutToHidePanel() } // Settings persistence. - SettingsPersistence.saveLastUsedSettings( trackmate.getSettings(), logger ); + SettingsPersistence.saveLastUsedSettings( settings, logger ); } } diff --git a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java index a2aa7b087..9703b9ce5 100644 --- a/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java +++ b/src/main/java/fiji/plugin/trackmate/io/TmXmlReader.java @@ -52,8 +52,6 @@ import static fiji.plugin.trackmate.io.TmXmlKeys.FRAME_ATTRIBUTE_NAME; import static fiji.plugin.trackmate.io.TmXmlKeys.GUI_STATE_ATTRIBUTE; import static fiji.plugin.trackmate.io.TmXmlKeys.GUI_STATE_ELEMENT_KEY; -import static fiji.plugin.trackmate.io.TmXmlKeys.GUI_VIEW_ATTRIBUTE; -import static fiji.plugin.trackmate.io.TmXmlKeys.GUI_VIEW_ELEMENT_KEY; import static fiji.plugin.trackmate.io.TmXmlKeys.IMAGE_ELEMENT_KEY; import static fiji.plugin.trackmate.io.TmXmlKeys.IMAGE_FILENAME_ATTRIBUTE_NAME; import static fiji.plugin.trackmate.io.TmXmlKeys.IMAGE_FOLDER_ATTRIBUTE_NAME; @@ -124,7 +122,6 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Logger.StringBuilderLogger; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotBase; @@ -148,11 +145,7 @@ import fiji.plugin.trackmate.providers.SpotAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackAnalyzerProvider; import fiji.plugin.trackmate.providers.TrackerProvider; -import fiji.plugin.trackmate.providers.ViewProvider; import fiji.plugin.trackmate.tracking.SpotTrackerFactory; -import fiji.plugin.trackmate.visualization.TrackMateModelView; -import fiji.plugin.trackmate.visualization.ViewFactory; -import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; import ij.IJ; import ij.ImagePlus; import net.imglib2.mesh.Mesh; @@ -282,81 +275,6 @@ public DisplaySettings getDisplaySettings() return DisplaySettingsIO.fromJson( dsel.getText() ); } - /** - * Returns the collection of views that were saved in this file. The views - * returned are not rendered yet. - * - * @param provider - * the {@link ViewProvider} to instantiate the view. Each saved - * view must be known by the specified provider. - * @param model - * the model to display in the views. - * @param settings - * the settings to build the views. - * @param selectionModel - * the {@link SelectionModel} model that will be shared with the - * new views. - * @param displaySettings - * the display settings to pass to the view. - * @return the collection of views. - * @see TrackMateModelView#render() - */ - public Collection< TrackMateModelView > getViews( - final ViewProvider provider, - final Model model, - final Settings settings, - final SelectionModel selectionModel, - final DisplaySettings displaySettings ) - { - final Element guiel = root.getChild( GUI_STATE_ELEMENT_KEY ); - if ( null != guiel ) - { - - final List< Element > children = guiel.getChildren( GUI_VIEW_ELEMENT_KEY ); - final Collection< TrackMateModelView > views = new ArrayList<>( children.size() ); - - for ( final Element child : children ) - { - final String viewKey = child.getAttributeValue( GUI_VIEW_ATTRIBUTE ); - if ( null == viewKey ) - { - logger.error( "Could not find view key attribute for element " + child + ".\n" ); - ok = false; - } - else - { - // Do not instantiate TrackScheme if found in the file. - if ( viewKey.equals( TrackScheme.KEY ) ) - continue; - - final ViewFactory factory = provider.getFactory( viewKey ); - if ( null == factory ) - { - logger.error( "Unknown view factory for key " + viewKey + ".\n" ); - ok = false; - continue; - } - - final TrackMateModelView view = factory.create( model, settings, selectionModel, displaySettings ); - if ( null == view ) - { - logger.error( "Unknown view for key " + viewKey + ".\n" ); - ok = false; - } - else - { - views.add( view ); - } - } - } - return views; - } - - logger.error( "Could not find GUI state element.\n" ); - ok = false; - return new ArrayList<>(); - } - /** * Returns the model saved in the file, or null if a saved * model cannot be found in the xml file. diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java index b7bd566f2..1b90892e0 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java @@ -21,7 +21,6 @@ */ package fiji.plugin.trackmate.visualization; -import java.awt.Window; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Arrays; @@ -37,12 +36,12 @@ import org.scijava.ui.behaviour.util.Behaviours; import org.scijava.ui.behaviour.util.InputActionBindings; import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; +import org.scijava.ui.behaviour.util.WrappedActionMap; +import org.scijava.ui.behaviour.util.WrappedInputMap; import bdv.ui.keymap.Keymap; import bdv.ui.keymap.Keymap.UpdateListener; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; @@ -65,9 +64,9 @@ public abstract class AbstractTrackMateModelJFrameView extends AbstractTrackMate protected final Behaviours behaviours; - protected AbstractTrackMateModelJFrameView( final Model model, final SelectionModel selectionModel, final DisplaySettings displaySettings, final String... keyConfigContexts ) + protected AbstractTrackMateModelJFrameView( final GuiModel guiModel, final String... keyConfigContexts ) { - super( model, selectionModel, displaySettings ); + super( guiModel ); final Set< String > c = new LinkedHashSet<>( Arrays.asList( KeyConfigContexts.TRACKMATE ) ); c.addAll( Arrays.asList( keyConfigContexts ) ); final String[] kccs = c.toArray( new String[] {} ); @@ -94,9 +93,16 @@ protected AbstractTrackMateModelJFrameView( final Model model, final SelectionMo mouseAndKeyHandler.setInputMap( triggerbindings.getConcatenatedInputTriggerMap() ); mouseAndKeyHandler.setBehaviourMap( triggerbindings.getConcatenatedBehaviourMap() ); + // Register global actions, if any. + final Actions globalActions = guiModel.getGlobalActions(); + if ( globalActions != null ) + { + keybindings.addActionMap( "global", new WrappedActionMap( globalActions.getActionMap() ) ); + keybindings.addInputMap( "global", new WrappedInputMap( globalActions.getInputMap() ) ); + } } - protected void setWindow( final Window frame ) + protected void setWindow( final JFrame frame ) { frame.addWindowListener( new WindowAdapter() { @@ -106,9 +112,10 @@ public void windowClosing( final WindowEvent e ) close(); } } ); + attachKeybindings( frame.getRootPane() ); } - protected void attachKeybindings( final JComponent component ) + private void attachKeybindings( final JComponent component ) { SwingUtilities.replaceUIActionMap( component, keybindings.getConcatenatedActionMap() ); SwingUtilities.replaceUIInputMap( component, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java index dac5dea14..1a04ba9e5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelView.java @@ -24,13 +24,17 @@ import java.util.ArrayList; import java.util.Map; +import javax.swing.SwingUtilities; + import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeListener; import fiji.plugin.trackmate.SelectionChangeEvent; import fiji.plugin.trackmate.SelectionChangeListener; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; /** * An abstract class for TrackMate views. @@ -44,31 +48,30 @@ public abstract class AbstractTrackMateModelView implements SelectionChangeListe * FIELDS */ - /** The model displayed by this class. */ - protected final Model model; - - protected final SelectionModel selectionModel; - - protected final DisplaySettings displaySettings; - protected final ArrayList< Runnable > runOnClose; + protected final GuiModel guiModel; + /* * PROTECTED CONSTRUCTOR */ - protected AbstractTrackMateModelView( final Model model, final SelectionModel selectionModel, final DisplaySettings displaySettings ) + protected AbstractTrackMateModelView( final GuiModel guiModel ) { - this.selectionModel = selectionModel; - this.model = model; - this.displaySettings = displaySettings; + this.guiModel = guiModel; runOnClose = new ArrayList<>(); + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); model.addModelChangeListener( this ); selectionModel.addSelectionChangeListener( this ); + final UpdateListener refresher = () -> SwingUtilities.invokeLater( this::refresh ); + displaySettings.listeners().add( refresher ); onClose( () -> { model.removeModelChangeListener( this ); selectionModel.removeSelectionChangeListener( this ); + displaySettings.listeners().remove( refresher ); } ); } @@ -116,8 +119,8 @@ public void selectionChanged( final SelectionChangeEvent event ) } @Override - public Model getModel() + public GuiModel getGuiModel() { - return model; + return guiModel; } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java index 591afb6c3..327cdbe76 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/TrackMateModelView.java @@ -21,8 +21,12 @@ */ package fiji.plugin.trackmate.visualization; -import fiji.plugin.trackmate.Model; +import java.awt.Window; + +import javax.swing.text.ViewFactory; + import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.gui.GuiModel; public interface TrackMateModelView { @@ -58,11 +62,11 @@ public interface TrackMateModelView public void centerViewOn( final Spot spot ); /** - * Returns the model displayed in this view. + * Returns the GUI model used in this view. * * @return the model. */ - public Model getModel(); + public GuiModel getGuiModel(); /** * Returns the unique key that identifies this view. @@ -75,4 +79,10 @@ public interface TrackMateModelView */ public String getKey(); + /** + * Returns the window that contains this view, if any. + * + * @return the window. + */ + public Window getWindow(); } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ViewUtils.java b/src/main/java/fiji/plugin/trackmate/visualization/ViewUtils.java index 723054a4d..095b2d447 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ViewUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ViewUtils.java @@ -65,7 +65,7 @@ public static final ImagePlus makeEmptyImagePlus( final int width, final int hei return imp; } - public static final ImagePlus makeEmpytImagePlus( final Model model ) + public static final ImagePlus makeEmptyImagePlus( final Model model ) { double maxX = 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 27140275c..93fdc87b7 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -22,10 +22,13 @@ package fiji.plugin.trackmate.visualization.bvv; import java.awt.Color; +import java.awt.Window; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import javax.swing.SwingUtilities; + import org.joml.Matrix4f; import bdv.viewer.animate.TranslationAnimator; @@ -34,10 +37,13 @@ import bvv.vistools.BvvHandle; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; +import fiji.plugin.trackmate.SelectionChangeListener; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.features.FeatureUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import ij.ImagePlus; @@ -56,16 +62,26 @@ public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelV private final Map< Spot, StupidMesh > meshMap; - public TrackMateBVV( final Model model, final SelectionModel selectionModel, final ImagePlus imp, final DisplaySettings displaySettings ) + public TrackMateBVV( final GuiModel guiModel, final ImagePlus imp ) { - super( model, selectionModel, displaySettings ); + super( guiModel ); this.imp = imp; this.meshMap = new HashMap<>(); + + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); final Iterable< Spot > it = model.getSpots().iterable( true ); it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ) ); updateColor(); - displaySettings.listeners().add( this::updateColor ); - selectionModel.addSelectionChangeListener( e -> refresh() ); + final UpdateListener colorUpdater = () -> updateColor(); + displaySettings.listeners().add( colorUpdater ); + final SelectionChangeListener refresher = e -> refresh(); + selectionModel.addSelectionChangeListener( refresher ); + onClose( () -> { + displaySettings.listeners().remove( colorUpdater ); + selectionModel.removeSelectionChangeListener( refresher ); + } ); } /** @@ -85,15 +101,15 @@ public void render() this.handle = BVVUtils.createViewer( imp ); final VolumeViewerPanel viewer = handle.getViewerPanel(); viewer.setRenderScene( ( gl, data ) -> { - if ( displaySettings.isSpotVisible() ) + if ( guiModel.getDisplaySettings().isSpotVisible() ) { final Matrix4f pvm = new Matrix4f( data.getPv() ); 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 ); - it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm, selectionModel.getSpotSelection().contains( s ) ) ); + final Iterable< Spot > it = guiModel.getModel().getSpots().iterable( t, true ); + it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm, guiModel.getSelectionModel().getSpotSelection().contains( s ) ) ); } } ); } @@ -193,7 +209,7 @@ public void modelChanged( final ModelChangeEvent event ) private void updateColor() { - final FeatureColorGenerator< Spot > spotColorGenerator = FeatureUtils.createSpotColorGenerator( model, displaySettings ); + final FeatureColorGenerator< Spot > spotColorGenerator = FeatureUtils.createSpotColorGenerator( guiModel.getModel(), guiModel.getDisplaySettings() ); for ( final Entry< Spot, StupidMesh > entry : meshMap.entrySet() ) { final StupidMesh sm = entry.getValue(); @@ -201,10 +217,17 @@ private void updateColor() continue; final Color color = spotColorGenerator.color( entry.getKey() ); - final float alpha = ( float ) displaySettings.getSpotTransparencyAlpha(); + final float alpha = ( float ) guiModel.getDisplaySettings().getSpotTransparencyAlpha(); sm.setColor( color, alpha ); - sm.setSelectionColor( displaySettings.getHighlightColor(), alpha ); + sm.setSelectionColor( guiModel.getDisplaySettings().getHighlightColor(), alpha ); } refresh(); } + + @Override + public Window getWindow() + { + final VolumeViewerPanel viewerPanel = handle.getViewerPanel(); + return SwingUtilities.getWindowAncestor( viewerPanel ); + } } 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 74e0c1e2e..9eb5cc4a7 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -21,12 +21,20 @@ */ package fiji.plugin.trackmate.visualization.hyperstack; +import java.awt.Window; + +import org.scijava.ui.behaviour.util.Actions; +import org.scijava.ui.behaviour.util.WrappedActionMap; +import org.scijava.ui.behaviour.util.WrappedInputMap; + import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.SelectionChangeEvent; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; import fiji.plugin.trackmate.visualization.ViewUtils; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.HyperStackDisplayerActions; @@ -34,8 +42,6 @@ import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SelectSpotsWithRoiListener; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.SpotEditBehaviours; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; -import fiji.plugin.trackmate.visualization.ui.TrackMateActions; -import fiji.plugin.trackmate.visualization.ui.TrackMateConfigDialog; import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; import ij.ImagePlus; import ij.gui.Overlay; @@ -46,9 +52,9 @@ public class HyperStackDisplayer extends AbstractTrackMateModelView protected final ImagePlus imp; - protected SpotOverlay spotOverlay; + protected final SpotOverlay spotOverlay; - protected TrackOverlay trackOverlay; + protected final TrackOverlay trackOverlay; public static final String KEY = "HYPERSTACKDISPLAYER"; @@ -56,54 +62,27 @@ public class HyperStackDisplayer extends AbstractTrackMateModelView * CONSTRUCTORS */ - public HyperStackDisplayer( final Model model, final SelectionModel selectionModel, final ImagePlus imp, final DisplaySettings displaySettings ) + public HyperStackDisplayer( final GuiModel guiModel ) { - super( model, selectionModel, displaySettings ); - if ( null != imp ) - this.imp = imp; + super( guiModel ); + if ( null != guiModel.getSettings().imp ) + this.imp = guiModel.getSettings().imp; else - this.imp = ViewUtils.makeEmpytImagePlus( model ); + this.imp = ViewUtils.makeEmptyImagePlus( guiModel.getModel() ); - this.spotOverlay = createSpotOverlay( displaySettings ); - this.trackOverlay = createTrackOverlay( displaySettings ); - displaySettings.listeners().add( () -> refresh() ); - } + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); + this.spotOverlay = new SpotOverlay( guiModel.getModel(), imp, guiModel.getDisplaySettings() ); + this.trackOverlay = new TrackOverlay( guiModel.getModel(), imp, guiModel.getDisplaySettings() ); - public HyperStackDisplayer( final Model model, final SelectionModel selectionModel, final DisplaySettings displaySettings ) - { - this( model, selectionModel, null, displaySettings ); + final UpdateListener refresher = () -> refresh(); + displaySettings.listeners().add( refresher ); + onClose( () -> displaySettings.listeners().remove( refresher ) ); } /* * PROTECTED METHODS */ - /** - * Hook for subclassers. Instantiate here the overlay you want to use for - * the spots. - * - * @param displaySettings - * the display settings. - * @return the spot overlay - */ - protected SpotOverlay createSpotOverlay( final DisplaySettings displaySettings ) - { - return new SpotOverlay( model, imp, displaySettings ); - } - - /** - * Hook for subclassers. Instantiate here the overlay you want to use for - * the spots. - * - * @param displaySettings - * the display settings. - * @return the track overlay - */ - protected TrackOverlay createTrackOverlay( final DisplaySettings displaySettings ) - { - return new TrackOverlay( model, imp, displaySettings ); - } - /* * PUBLIC METHODS */ @@ -137,8 +116,8 @@ public void modelChanged( final ModelChangeEvent event ) public void selectionChanged( final SelectionChangeEvent event ) { // Highlight selection - trackOverlay.setHighlight( selectionModel.getEdgeSelection() ); - spotOverlay.setSpotSelection( selectionModel.getSpotSelection() ); + trackOverlay.setHighlight( guiModel.getSelectionModel().getEdgeSelection() ); + spotOverlay.setSpotSelection( guiModel.getSelectionModel().getSpotSelection() ); // Center on last spot super.selectionChanged( event ); // Redraw @@ -172,16 +151,19 @@ public void render() try { - final TrackMateKeymapManager keymapManager = TrackMateKeymapManager.keymapManager; + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final TrackMateKeymapManager keymapManager = guiModel.getKeymapManager(); final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, new String[] { KeyConfigContexts.HYPERSTACK_DISPLAYER, KeyConfigContexts.TRACKMATE } ); -// adapter.actions().runnableAction( () -> System.out.println( "TROLOLO" ), "refresh", new String[] { "R" } ); SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); HyperStackDisplayerActions.install( adapter.actions(), model, selectionModel, imp ); - TrackMateActions.install( adapter.actions(), model, selectionModel ); // Select spots with freehand ROI. SelectSpotsWithRoiListener.install( model, selectionModel, imp ); - // Pref dialog. - TrackMateConfigDialog.prefDialog( imp.getWindow(), adapter.actions() ); + // Global actions. + final Actions globalActions = guiModel.getGlobalActions(); + adapter.keybindings().addActionMap( "global", new WrappedActionMap( globalActions.getActionMap() ) ); + adapter.keybindings().addInputMap( "global", new WrappedInputMap( globalActions.getInputMap() ) ); + } catch ( final Exception e ) { @@ -214,14 +196,15 @@ public void addOverlay( final Roi overlay ) imp.getOverlay().add( overlay ); } - public SelectionModel getSelectionModel() + @Override + public String getKey() { - return selectionModel; + return KEY; } @Override - public String getKey() + public Window getWindow() { - return KEY; + return imp.getWindow(); } } 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 5fb3be85f..4e6c5e342 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotOverlay.java @@ -155,7 +155,6 @@ public void drawOverlay( final Graphics g ) g2d.setColor( color ); drawSpot( g2d, spot, zslice, xcorner, ycorner, lMag, filled ); } - } else { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java index 61d96c091..660fe1227 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java @@ -17,8 +17,8 @@ public class HyperStackDisplayerActions private static final String NEXT_TIMEPOINT = "next timepoint"; private static final String PREVIOUS_TIMEPOINT = "previous timepoint"; - private static final String[] NEXT_TIMEPOINT_KEYS = new String[] { "RIGHT" }; - private static final String[] PREVIOUS_TIMEPOINT_KEYS = new String[] { "LEFT" }; + private static final String[] NEXT_TIMEPOINT_KEYS = new String[] { "G" }; + private static final String[] PREVIOUS_TIMEPOINT_KEYS = new String[] { "F" }; private static final String SEMI_AUTOMATIC_TRACKING = "semi-automatic tracking"; private static final String[] SEMI_AUTOMATIC_TRACKING_KEYS = new String[] { "shift A" }; @@ -41,6 +41,8 @@ public static final void install( final Actions actions, final Model model, fina // Semi-automatic tracking final SemiAutoTracking semiAutoTracking = new SemiAutoTracking( model, selectionModel, imp ); actions.runnableAction( () -> semiAutoTracking.run(), SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS ); + + actions.runnableAction( () -> System.out.println( "[HyperStackDisplayer] TROLOLO" ), "refresh", new String[] { "R" } ); // DEBUG } @Plugin( type = CommandDescriptionProvider.class ) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java index 44066aea8..3bcf7c6ab 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java @@ -60,13 +60,15 @@ public class ImagePlusBehavioursAdapter private final Behaviours behaviours; + private InputActionBindings keybindings; + public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keymapManager, final String[] keyConfigContexts ) { final ImageCanvas canvas = imp.getCanvas(); canvas.setFocusable( true ); // Initialize configuration and binding registries - final InputActionBindings actionBindings = new InputActionBindings(); + this.keybindings = new InputActionBindings(); final TriggerBehaviourBindings behaviourBindings = new TriggerBehaviourBindings(); keymapManager.discoverCommandDescriptions(); final InputTriggerConfig config = keymapManager.getForwardSelectedKeymap().getConfig(); @@ -76,10 +78,10 @@ public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keym behaviours.install( behaviourBindings, keyConfigContexts[ 0 ] + "-behaviours" ); // Actions - final InputMap inputMap = actionBindings.getConcatenatedInputMap(); - final ActionMap actionMap = actionBindings.getConcatenatedActionMap(); + final InputMap inputMap = keybindings.getConcatenatedInputMap(); + final ActionMap actionMap = keybindings.getConcatenatedActionMap(); this.actions = new Actions( config, keyConfigContexts ); - actions.install( actionBindings, keyConfigContexts[ 0 ] + "-actions" ); + actions.install( keybindings, keyConfigContexts[ 0 ] + "-actions" ); final Keymap keymap = keymapManager.getForwardSelectedKeymap(); keymap.updateListeners().add( () -> { @@ -160,6 +162,11 @@ public Behaviours behaviours() return behaviours; } + public InputActionBindings keybindings() + { + return keybindings; + } + /** * A proxy that intercepts mouse and key events, and routes them to the * appropriate actions and behaviours. It also consumes the events if they diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java index e4beedde9..0332761d9 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/AllSpotsTableView.java @@ -22,11 +22,10 @@ package fiji.plugin.trackmate.visualization.table; import static fiji.plugin.trackmate.gui.Icons.CSV_ICON; -import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; import java.awt.BorderLayout; import java.awt.Color; -import java.awt.event.WindowAdapter; +import java.awt.Window; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -54,57 +53,58 @@ import fiji.plugin.trackmate.Dimension; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; -import fiji.plugin.trackmate.ModelChangeListener; import fiji.plugin.trackmate.SelectionChangeEvent; -import fiji.plugin.trackmate.SelectionChangeListener; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.features.manual.ManualSpotColorAnalyzerFactory; +import fiji.plugin.trackmate.gui.GuiModel; +import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; import fiji.plugin.trackmate.util.FileChooser; import fiji.plugin.trackmate.util.FileChooser.DialogType; import fiji.plugin.trackmate.util.FileChooser.SelectionMode; +import fiji.plugin.trackmate.visualization.AbstractTrackMateModelJFrameView; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; -import fiji.plugin.trackmate.visualization.TrackMateModelView; import fiji.plugin.trackmate.visualization.trackscheme.utils.SearchBar; -public class AllSpotsTableView extends JFrame implements TrackMateModelView, ModelChangeListener, SelectionChangeListener +public class AllSpotsTableView extends AbstractTrackMateModelJFrameView { - private static final long serialVersionUID = 1L; - private static final String KEY = "SPOT_TABLE"; private String selectedFile; - private final Model model; - private final TablePanel< Spot > spotTable; private final AtomicBoolean ignoreSelectionChange = new AtomicBoolean( false ); - private final SelectionModel selectionModel; + private final JFrame frame; - public AllSpotsTableView( final Model model, final SelectionModel selectionModel, final DisplaySettings ds, final String imageFileName ) + public AllSpotsTableView( final GuiModel guiModel, final String imageFileName ) { - super( "All spots table" ); - setIconImage( TRACKMATE_ICON.getImage() ); - this.model = model; - this.selectionModel = selectionModel; + super( guiModel ); this.selectedFile = imageFileName + "_allspots.csv"; /* * GUI. */ + // Frame. + this.frame = new JFrame( "All Spots Table" ); + frame.setIconImage( Icons.TRACKMATE_ICON.getImage() ); + + setWindow( frame ); + + // Main panel. final JPanel mainPanel = new JPanel(); mainPanel.setLayout( new BorderLayout() ); + frame.getContentPane().add( mainPanel ); // Table. + final Model model = guiModel.getModel(); + final DisplaySettings ds = guiModel.getDisplaySettings(); this.spotTable = createSpotTable( model, ds ); - mainPanel.add( spotTable.getPanel(), BorderLayout.CENTER ); // Tool bar. @@ -125,38 +125,20 @@ public AllSpotsTableView( final Model model, final SelectionModel selectionModel } ); toolbar.add( tglColoring ); mainPanel.add( toolbar, BorderLayout.NORTH ); + frame.pack(); - getContentPane().add( mainPanel ); - pack(); - - /* - * Listeners. - */ - - spotTable.getTable().getSelectionModel().addListSelectionListener( - new SpotTableSelectionListener() ); + // Listeners. + spotTable.getTable().getSelectionModel().addListSelectionListener( new SpotTableSelectionListener() ); - final UpdateListener refresher = () -> refresh(); - ds.listeners().add( refresher ); - selectionModel.addSelectionChangeListener( this ); - model.addModelChangeListener( this ); - addWindowListener( new WindowAdapter() - { - @Override - public void windowClosing( final java.awt.event.WindowEvent e ) - { - selectionModel.removeSelectionChangeListener( AllSpotsTableView.this ); - model.removeModelChangeListener( AllSpotsTableView.this ); - ds.listeners().remove( refresher ); - }; - } ); + // Actions + actions.runnableAction( () -> System.out.println( "TROLOLO" ), "trololo", "R" ); // DEBUG } public void exportToCsv() { final File file = FileChooser.chooseFile( - this, + frame, selectedFile, new FileNameExtensionFilter( "CSV files", "csv" ), "Export table to CSV", @@ -177,7 +159,7 @@ public void exportToCsv( final String csvFile ) } catch ( final IOException e ) { - model.getLogger().error( "Problem exporting to file " + guiModel.getModel().getLogger().error( "Problem exporting to file " + csvFile + "\n" + e.getMessage() ); } } @@ -271,14 +253,14 @@ else if ( feature.equals( SPOT_ID ) ) @Override public void render() { - setLocationRelativeTo( null ); - setVisible( true ); + frame.setLocationRelativeTo( null ); + frame.setVisible( true ); } @Override public void refresh() { - repaint(); + frame.repaint(); } @Override @@ -291,7 +273,7 @@ public void modelChanged( final ModelChangeEvent event ) } final List< Spot > spots = new ArrayList<>(); - for ( final Spot spot : model.getSpots().iterable( true ) ) + for ( final Spot spot : guiModel.getModel().getSpots().iterable( true ) ) spots.add( spot ); spotTable.setObjects( spots ); @@ -309,7 +291,7 @@ public void selectionChanged( final SelectionChangeEvent event ) ignoreSelectionChange.set( true ); // Vertices table. - final Set< Spot > selectedVertices = selectionModel.getSpotSelection(); + final Set< Spot > selectedVertices = guiModel.getSelectionModel().getSpotSelection(); final JTable vt = spotTable.getTable(); vt.getSelectionModel().clearSelection(); for ( final Spot spot : selectedVertices ) @@ -340,22 +322,12 @@ public void centerViewOn( final Spot spot ) spotTable.scrollToObject( spot ); } - @Override - public Model getModel() - { - return model; - } - @Override public String getKey() { return KEY; } - @Override - public void clear() - {} - /** * Forward spot table selection to selection model. */ @@ -375,6 +347,7 @@ public void valueChanged( final ListSelectionEvent event ) for ( final int row : selectedRows ) toSelect.add( spotTable.getObjectForViewRow( row ) ); + final SelectionModel selectionModel = guiModel.getSelectionModel(); selectionModel.clearSelection(); selectionModel.addSpotToSelection( toSelect ); refresh(); @@ -382,4 +355,14 @@ public void valueChanged( final ListSelectionEvent event ) ignoreSelectionChange.set( false ); } } + + @Override + public void clear() + {} + + @Override + public Window getWindow() + { + return frame; + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java index 05ebfd767..2a656ea5a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/BranchTableView.java @@ -22,10 +22,10 @@ package fiji.plugin.trackmate.visualization.table; import static fiji.plugin.trackmate.gui.Icons.CSV_ICON; -import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Window; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -58,36 +58,37 @@ import fiji.plugin.trackmate.Dimension; 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.graph.ConvexBranchesDecomposition; import fiji.plugin.trackmate.graph.ConvexBranchesDecomposition.TrackBranchDecomposition; import fiji.plugin.trackmate.graph.TimeDirectedNeighborIndex; +import fiji.plugin.trackmate.gui.GuiModel; +import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.util.FileChooser; import fiji.plugin.trackmate.util.FileChooser.DialogType; import fiji.plugin.trackmate.util.FileChooser.SelectionMode; +import fiji.plugin.trackmate.visualization.AbstractTrackMateModelJFrameView; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; -import fiji.plugin.trackmate.visualization.TrackMateModelView; -public class BranchTableView extends JFrame implements TrackMateModelView +public class BranchTableView extends AbstractTrackMateModelJFrameView { - private static final long serialVersionUID = 1L; - private static final String KEY = "SPOT_TABLE"; private String selectedFile = System.getProperty( "user.home" ) + File.separator + "branches.csv"; - private final Model model; - private final TablePanel< Branch > branchTable; - public BranchTableView( final Model model, final SelectionModel selectionModel, final String imageFileName ) + private final JFrame frame; + + public BranchTableView( final GuiModel guiModel, final String imageFileName ) { - super( "Branch table" ); - setIconImage( TRACKMATE_ICON.getImage() ); - this.model = model; + super( guiModel ); this.selectedFile = imageFileName + "_branches.csv"; + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); /* * GUI. @@ -111,8 +112,10 @@ public BranchTableView( final Model model, final SelectionModel selectionModel, toolbar.add( Box.createHorizontalGlue() ); mainPanel.add( toolbar, BorderLayout.NORTH ); - getContentPane().add( mainPanel ); - pack(); + this.frame = new JFrame( "Branch table" ); + frame.setIconImage( Icons.TRACKMATE_ICON.getImage() ); + frame.getContentPane().add( mainPanel ); + frame.pack(); } public TablePanel< Branch > getBranchTable() @@ -123,7 +126,7 @@ public TablePanel< Branch > getBranchTable() public void exportToCsv() { final File file = FileChooser.chooseFile( - this, + frame, selectedFile, new FileNameExtensionFilter( "CSV files", "csv" ), "Export table to CSV", @@ -144,7 +147,7 @@ public void exportToCsv( final String csvFile ) } catch ( final IOException e ) { - model.getLogger().error( "Problem exporting to file " + guiModel.getModel().getLogger().error( "Problem exporting to file " + csvFile + "\n" + e.getMessage() ); } } @@ -321,26 +324,20 @@ public static final TablePanel< Branch > createBranchTable( final Model model, f @Override public void render() { - setLocationRelativeTo( null ); - setVisible( true ); + frame.setLocationRelativeTo( null ); + frame.setVisible( true ); } @Override public void refresh() { - repaint(); + frame.repaint(); } @Override public void centerViewOn( final Spot spot ) {} - @Override - public Model getModel() - { - return model; - } - @Override public String getKey() { @@ -586,4 +583,14 @@ public int compareTo( final Branch o ) BRANCH_FEATURES_ISINTS.put( MEAN_PREDECESSORS_DELAY, Boolean.FALSE ); BRANCH_FEATURES_DIMENSIONS.put( MEAN_PREDECESSORS_DELAY, Dimension.TIME ); } + + @Override + public void modelChanged( final ModelChangeEvent event ) + {} + + @Override + public Window getWindow() + { + return frame; + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java index 3db26a84f..9432c44f8 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java @@ -22,11 +22,10 @@ package fiji.plugin.trackmate.visualization.table; import static fiji.plugin.trackmate.gui.Icons.CSV_ICON; -import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; import java.awt.BorderLayout; import java.awt.Color; -import java.awt.event.WindowAdapter; +import java.awt.Window; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -58,32 +57,28 @@ import fiji.plugin.trackmate.Dimension; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; -import fiji.plugin.trackmate.ModelChangeListener; import fiji.plugin.trackmate.SelectionChangeEvent; -import fiji.plugin.trackmate.SelectionChangeListener; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.features.manual.ManualEdgeColorAnalyzer; import fiji.plugin.trackmate.features.manual.ManualSpotColorAnalyzerFactory; +import fiji.plugin.trackmate.gui.GuiModel; +import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; import fiji.plugin.trackmate.util.FileChooser; import fiji.plugin.trackmate.util.FileChooser.DialogType; import fiji.plugin.trackmate.util.FileChooser.SelectionMode; +import fiji.plugin.trackmate.visualization.AbstractTrackMateModelJFrameView; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; -import fiji.plugin.trackmate.visualization.TrackMateModelView; import fiji.plugin.trackmate.visualization.trackscheme.utils.SearchBar; -public class TrackTableView extends JFrame implements TrackMateModelView, ModelChangeListener, SelectionChangeListener +public class TrackTableView extends AbstractTrackMateModelJFrameView { - private static final long serialVersionUID = 1L; - private static final String KEY = "TRACK_TABLES"; - private final Model model; - private final TablePanel< Spot > spotTable; private final TablePanel< DefaultWeightedEdge > edgeTable; @@ -92,17 +87,17 @@ public class TrackTableView extends JFrame implements TrackMateModelView, ModelC private final AtomicBoolean ignoreSelectionChange = new AtomicBoolean( false ); - private final SelectionModel selectionModel; - private String imagePath; - public TrackTableView( final Model model, final SelectionModel selectionModel, final DisplaySettings ds, final String imagePath ) + private final JFrame frame; + + public TrackTableView( final GuiModel guiModel, final String imagePath ) { - super( "Track tables" ); + super( guiModel ); this.imagePath = imagePath; - setIconImage( TRACKMATE_ICON.getImage() ); - this.model = model; - this.selectionModel = selectionModel; + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final DisplaySettings ds = guiModel.getDisplaySettings(); /* * GUI. @@ -117,12 +112,9 @@ public TrackTableView( final Model model, final SelectionModel selectionModel, f this.trackTable = createTrackTable( model, ds ); // Listeners. - spotTable.getTable().getSelectionModel().addListSelectionListener( - new SpotTableSelectionListener() ); - edgeTable.getTable().getSelectionModel().addListSelectionListener( - new EdgeTableSelectionListener() ); - trackTable.getTable().getSelectionModel().addListSelectionListener( - new TrackTableSelectionListener() ); + spotTable.getTable().getSelectionModel().addListSelectionListener( new SpotTableSelectionListener() ); + edgeTable.getTable().getSelectionModel().addListSelectionListener( new EdgeTableSelectionListener() ); + trackTable.getTable().getSelectionModel().addListSelectionListener( new TrackTableSelectionListener() ); // Tabbed pane. final JTabbedPane tabbedPane = new JTabbedPane( JTabbedPane.LEFT ); @@ -154,8 +146,11 @@ public TrackTableView( final Model model, final SelectionModel selectionModel, f toolbar.add( tglColoring ); mainPanel.add( toolbar, BorderLayout.NORTH ); - getContentPane().add( mainPanel ); - pack(); + this.frame = new JFrame( "Track Tables" ); + frame.setIconImage( Icons.TRACKMATE_ICON.getImage() ); + setWindow( frame ); + frame.getContentPane().add( mainPanel ); + frame.pack(); /* * Listeners. @@ -165,15 +160,10 @@ public TrackTableView( final Model model, final SelectionModel selectionModel, f ds.listeners().add( refresher ); selectionModel.addSelectionChangeListener( this ); model.addModelChangeListener( this ); - addWindowListener( new WindowAdapter() - { - @Override - public void windowClosing( final java.awt.event.WindowEvent e ) - { - selectionModel.removeSelectionChangeListener( TrackTableView.this ); - model.removeModelChangeListener( TrackTableView.this ); - ds.listeners().remove( refresher ); - }; + onClose( () -> { + ds.listeners().remove( refresher ); + selectionModel.removeSelectionChangeListener( this ); + model.removeModelChangeListener( this ); } ); } @@ -204,7 +194,7 @@ private void exportToCsv( final int index ) } final File file = FileChooser.chooseFile( - this, + frame, selectedFile, new FileNameExtensionFilter( "CSV files", "csv" ), "Export table to CSV", @@ -220,7 +210,7 @@ private void exportToCsv( final int index ) } catch ( final IOException e ) { - model.getLogger().error( "Problem exporting to file " + guiModel.getModel().getLogger().error( "Problem exporting to file " + file + "\n" + e.getMessage() ); } imagePath = selectedFile; @@ -421,14 +411,14 @@ else if ( feature.equals( SPOT_ID ) ) @Override public void render() { - setLocationRelativeTo( null ); - setVisible( true ); + frame.setLocationRelativeTo( null ); + frame.setVisible( true ); } @Override public void refresh() { - repaint(); + frame.repaint(); } @Override @@ -439,7 +429,7 @@ public void modelChanged( final ModelChangeEvent event ) refresh(); return; } - + final Model model = guiModel.getModel(); final List< Spot > spots = new ArrayList<>(); for ( final Integer trackID : model.getTrackModel().unsortedTrackIDs( true ) ) spots.addAll( model.getTrackModel().trackSpots( trackID ) ); @@ -467,6 +457,7 @@ public void selectionChanged( final SelectionChangeEvent event ) ignoreSelectionChange.set( true ); // Vertices table. + final SelectionModel selectionModel = guiModel.getSelectionModel(); final Set< Spot > selectedVertices = selectionModel.getSpotSelection(); final JTable vt = spotTable.getTable(); vt.getSelectionModel().clearSelection(); @@ -525,12 +516,6 @@ public void centerViewOn( final Spot spot ) spotTable.scrollToObject( spot ); } - @Override - public Model getModel() - { - return model; - } - @Override public String getKey() { @@ -575,6 +560,7 @@ public void valueChanged( final ListSelectionEvent event ) for ( final int row : selectedRows ) toSelect.add( spotTable.getObjectForViewRow( row ) ); + final SelectionModel selectionModel = guiModel.getSelectionModel(); selectionModel.clearSelection(); selectionModel.addSpotToSelection( toSelect ); refresh(); @@ -602,6 +588,7 @@ public void valueChanged( final ListSelectionEvent event ) for ( final int row : selectedRows ) toSelect.add( edgeTable.getObjectForViewRow( row ) ); + final SelectionModel selectionModel = guiModel.getSelectionModel(); selectionModel.clearSelection(); selectionModel.addEdgeToSelection( toSelect ); refresh(); @@ -623,6 +610,8 @@ public void valueChanged( final ListSelectionEvent event ) return; ignoreSelectionChange.set( true ); + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); final Set< Spot > spots = new HashSet<>(); final Set< DefaultWeightedEdge > edges = new HashSet<>(); @@ -643,4 +632,10 @@ public void valueChanged( final ListSelectionEvent event ) } } + + @Override + public Window getWindow() + { + return frame; + } } 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 1c1975b4f..96a07e2c9 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackScheme.java @@ -25,8 +25,7 @@ import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; +import java.awt.Window; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collection; @@ -61,13 +60,11 @@ import fiji.plugin.trackmate.SelectionChangeEvent; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelJFrameView; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; import fiji.plugin.trackmate.visualization.trackscheme.behaviours.TrackSchemeActions; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; -import fiji.plugin.trackmate.visualization.ui.TrackMateActions; -import fiji.plugin.trackmate.visualization.ui.TrackMateConfigDialog; import ij.ImagePlus; public class TrackScheme extends AbstractTrackMateModelJFrameView @@ -166,24 +163,15 @@ public class TrackScheme extends AbstractTrackMateModelJFrameView * CONSTRUCTORS */ - public TrackScheme( final Model model, final SelectionModel selectionModel, final DisplaySettings displaySettings ) + public TrackScheme( final GuiModel guiModel ) { - super( model, selectionModel, displaySettings, KeyConfigContexts.TRACKSCHEME ); - this.gui = new TrackSchemeFrame( this, displaySettings ); + super( guiModel, KeyConfigContexts.TRACKSCHEME ); + this.gui = new TrackSchemeFrame( this, guiModel.getDisplaySettings() ); setWindow( gui ); final String title = "TrackScheme"; gui.setTitle( title ); gui.setSize( DEFAULT_SIZE ); - displaySettings.listeners().add( () -> doTrackStyle() ); - gui.addWindowListener( new WindowAdapter() - { - @Override - public void windowClosing( final WindowEvent e ) - { - model.removeModelChangeListener( TrackScheme.this ); - } - } ); gui.setLocationByPlatform( true ); gui.setLocationRelativeTo( null ); gui.setVisible( true ); @@ -198,11 +186,6 @@ public void setSpotImageUpdater( final SpotImageUpdater spotImageUpdater ) this.spotImageUpdater = spotImageUpdater; } - public SelectionModel getSelectionModel() - { - return selectionModel; - } - /** * Returns the column index that is the first one after all the track * columns. @@ -225,9 +208,7 @@ public int getNextFreeColumn( final int frame ) { Integer columnIndex = rowLengths.get( frame ); if ( null == columnIndex ) - { columnIndex = 2; - } return columnIndex + 1; } @@ -274,7 +255,7 @@ private JGraphXAdapter createGraph() { gui.logger.setStatus( "Creating graph adapter." ); - final JGraphXAdapter lGraph = new JGraphXAdapter( model ); + final JGraphXAdapter lGraph = new JGraphXAdapter( guiModel.getModel() ); lGraph.setAllowLoops( false ); lGraph.setAllowDanglingEdges( false ); lGraph.setCellsCloneable( false ); @@ -326,7 +307,7 @@ private mxICell updateCellOf( final Spot spot ) if ( spotImageUpdater != null && doThumbnailCapture ) { String style = cell.getStyle(); - final double radiusFactor = displaySettings.getSpotDisplayRadius(); + final double radiusFactor = guiModel.getDisplaySettings().getSpotDisplayRadius(); final String imageStr = spotImageUpdater.getImageString( spot, radiusFactor ); style = mxStyleUtils.setStyle( style, mxConstants.STYLE_IMAGE, "data:image/base64," + imageStr ); graph.getModel().setStyle( cell, style ); @@ -361,7 +342,7 @@ private mxICell insertSpotInGraph( final Spot spot, final int targetColumn ) final mxGeometry geometry = new mxGeometry( x, y, DEFAULT_CELL_WIDTH, DEFAULT_CELL_HEIGHT ); cellAdded.setGeometry( geometry ); // Set its style - final double radiusFactor = displaySettings.getSpotDisplayRadius(); + final double radiusFactor = guiModel.getDisplaySettings().getSpotDisplayRadius(); if ( null != spotImageUpdater && doThumbnailCapture ) { final String imageStr = spotImageUpdater.getImageString( spot, radiusFactor ); @@ -378,6 +359,7 @@ private mxICell insertSpotInGraph( final Spot spot, final int targetColumn ) */ private void importTrack( final int trackIndex ) { + final Model model = guiModel.getModel(); model.beginUpdate(); graph.getModel().beginUpdate(); try @@ -423,6 +405,7 @@ protected void addEdgeManually( mxCell cell ) { final mxIGraphModel graphModel = graph.getModel(); cell.setValue( "New" ); + final Model model = guiModel.getModel(); model.beginUpdate(); graphModel.beginUpdate(); try @@ -495,7 +478,7 @@ protected void addEdgeManually( mxCell cell ) { graphModel.endUpdate(); model.endUpdate(); - selectionModel.clearEdgeSelection(); + guiModel.getSelectionModel().clearEdgeSelection(); } } } @@ -512,6 +495,7 @@ public void selectionChanged( final SelectionChangeEvent event ) doFireSelectionChangeEvent = false; + final SelectionModel selectionModel = guiModel.getSelectionModel(); final ArrayList< Object > newSelection = new ArrayList<>( selectionModel.getSpotSelection().size() + selectionModel.getEdgeSelection().size() ); final Iterator< DefaultWeightedEdge > edgeIt = selectionModel.getEdgeSelection().iterator(); while ( edgeIt.hasNext() ) @@ -566,6 +550,7 @@ public void modelChanged( final ModelChangeEvent event ) if ( event.getEventID() != ModelChangeEvent.MODEL_MODIFIED ) return; + final Model model = guiModel.getModel(); graph.getModel().beginUpdate(); try { @@ -758,15 +743,17 @@ public void render() @Override public void run() { + final Model model = guiModel.getModel(); + // Pass graph to GUI gui.logger.setStatus( "Generating GUI components." ); gui.init( graph ); // Init functions that set look and position gui.logger.setStatus( "Creating style manager." ); - TrackScheme.this.stylist = new TrackSchemeStylist( model, graph, displaySettings ); + TrackScheme.this.stylist = new TrackSchemeStylist( guiModel.getModel(), graph, guiModel.getDisplaySettings() ); gui.logger.setStatus( "Creating layout manager." ); - TrackScheme.this.graphLayout = new TrackSchemeGraphLayout( graph, model, gui.graphComponent ); + TrackScheme.this.graphLayout = new TrackSchemeGraphLayout( graph, guiModel.getModel(), gui.graphComponent ); // Execute style and layout gui.logger.setProgress( 0.75 ); @@ -794,11 +781,9 @@ public void run() gui.graphComponent.zoomOut(); // Actions and behaviours - attachKeybindings( gui.graphComponent ); TrackSchemeActions.install( actions, model, gui.graphComponent ); - TrackMateActions.install( actions, model, selectionModel ); - // Pref dialog. - TrackMateConfigDialog.prefDialog( gui, actions ); + // DEBUG + actions.runnableAction( () -> System.out.println( "[TrackScheme] TROLOLO" ), "trolol", "R" ); gui.logger.setProgress( 0 ); final long end = System.currentTimeMillis(); @@ -810,19 +795,13 @@ public void run() @Override public void refresh() - {} - - @Override - public void clear() { - System.out.println( "[TrackScheme] clear() called" ); + doTrackStyle(); } @Override - public Model getModel() - { - return model; - } + public void clear() + {} /* * PRIVATE METHODS @@ -932,6 +911,7 @@ private void userChangedSelection( final Collection< Object > added, final Colle } doFireSelectionChangeEvent = false; + final SelectionModel selectionModel = guiModel.getSelectionModel(); if ( !edgesToAdd.isEmpty() ) selectionModel.addEdgeToSelection( edgesToAdd ); @@ -996,6 +976,8 @@ else if ( cell.isEdge() ) // Clean model doFireModelChangeEvent = false; + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); model.beginUpdate(); try { @@ -1106,6 +1088,7 @@ public void doTrackStyle() private void createThumbnails() { // Group spots per frame + final Model model = guiModel.getModel(); final Set< Integer > frames = model.getSpots().keySet(); final HashMap< Integer, HashSet< Spot > > spotPerFrame = new HashMap<>( frames.size() ); for ( final Integer frame : frames ) @@ -1124,7 +1107,7 @@ private void createThumbnails() if ( null != spotImageUpdater ) { gui.logger.setStatus( "Collecting spot thumbnails." ); - final double radiusFactor = displaySettings.getSpotDisplayRadius(); + final double radiusFactor = guiModel.getDisplaySettings().getSpotDisplayRadius(); int index = 0; try { @@ -1205,6 +1188,8 @@ public void toggleDisplayDecoration() */ public void linkSpots() { + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); // Sort spots by time final TreeMap< Integer, Spot > spotsInTime = new TreeMap<>(); @@ -1352,7 +1337,7 @@ public void selectTrack( final Collection< mxCell > vertices, final Collection< inspectionEdges.add( dwe ); } // Forward to selection model - selectionModel.selectTrack( inspectionSpots, inspectionEdges, direction ); + guiModel.getSelectionModel().selectTrack( inspectionSpots, inspectionEdges, direction ); } @Override @@ -1360,4 +1345,10 @@ public String getKey() { return KEY; } + + @Override + public Window getWindow() + { + return gui; + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java index a90096ddf..141eb2503 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFrame.java @@ -40,6 +40,7 @@ import com.mxgraph.swing.handler.mxRubberband; import fiji.plugin.trackmate.Logger; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; public class TrackSchemeFrame extends JFrame @@ -100,7 +101,8 @@ public void init( final JGraphXAdapter lGraph ) graphComponent = createGraphComponent(); // Add the info pane - infoPane = new InfoPane( trackScheme.getModel(), trackScheme.getSelectionModel() ); + final GuiModel guiModel = trackScheme.getGuiModel(); + infoPane = new InfoPane( guiModel.getModel(), guiModel.getSelectionModel() ); // Add the graph outline final mxGraphOutline graphOutline = new mxGraphOutline( graphComponent ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeGraphComponent.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeGraphComponent.java index 50017239f..b23dff1ec 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeGraphComponent.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeGraphComponent.java @@ -59,6 +59,7 @@ import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraphView; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; public class TrackSchemeGraphComponent extends mxGraphComponent implements mxIEventListener @@ -514,7 +515,8 @@ public void mouseClicked( final MouseEvent event ) if ( column >= columnWidths.length ) return; - final String oldName = trackScheme.getModel().getTrackModel().name( columnTrackIDs[ column ] ); + final GuiModel guiModel = trackScheme.getGuiModel(); + final String oldName = guiModel.getModel().getTrackModel().name( columnTrackIDs[ column ] ); final Integer trackID = columnTrackIDs[ column ]; int cwidth = columnWidths[ column ] * xcs; @@ -552,10 +554,10 @@ public void actionPerformed( final ActionEvent arg0 ) { // Prevent setting the name of a track that has // been deleted. - if ( trackScheme.getModel().getTrackModel().unsortedTrackIDs( false ).contains( trackID ) ) + if ( guiModel.getModel().getTrackModel().unsortedTrackIDs( false ).contains( trackID ) ) { final String newname = textArea.getText(); - trackScheme.getModel().getTrackModel().setName( trackID, newname ); + guiModel.getModel().getTrackModel().setName( trackID, newname ); } scrollPane.remove( textArea ); ColumnHeader.this.remove( scrollPane ); @@ -597,7 +599,8 @@ public String getToolTipText( final MouseEvent event ) if ( index == 0 ) index = 1; - String columnName = trackScheme.getModel().getTrackModel().name( columnTrackIDs[ index - 1 ] ); + final GuiModel guiModel = trackScheme.getGuiModel(); + String columnName = guiModel.getModel().getTrackModel().name( columnTrackIDs[ index - 1 ] ); if ( null == columnName ) columnName = "Name not set"; @@ -642,7 +645,8 @@ public void paint( final Graphics g ) if ( minx > paintBounds.x + paintBounds.width || maxx < paintBounds.x ) continue; - String columnName = trackScheme.getModel().getTrackModel().name( columnTrackIDs[ i ] ); + final GuiModel guiModel = trackScheme.getGuiModel(); + String columnName = guiModel.getModel().getTrackModel().name( columnTrackIDs[ i ] ); if ( null == columnName ) columnName = "Name not set"; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java index c134b79a3..b13d239b0 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemePopupMenu.java @@ -83,7 +83,7 @@ private void manualColorEdges( final ArrayList< mxCell > edges ) { final DefaultWeightedEdge edge = trackScheme.getGraph().getEdgeFor( mxCell ); final Double value = Double.valueOf( previousColor.getRGB() ); - trackScheme.getModel().getFeatureModel().putEdgeFeature( edge, ManualEdgeColorAnalyzer.FEATURE, value ); + trackScheme.getGuiModel().getModel().getFeatureModel().putEdgeFeature( edge, ManualEdgeColorAnalyzer.FEATURE, value ); } } @@ -152,7 +152,7 @@ private void multiEditSpotName( final ArrayList< mxCell > vertices, final EventO @Override public void invoke( final Object sender, final mxEventObject evt ) { - final Model model = trackScheme.getModel(); + final Model model = trackScheme.getGuiModel().getModel(); model.beginUpdate(); try { @@ -293,7 +293,7 @@ public void actionPerformed( final ActionEvent e ) } // Link - final Action linkAction = new AbstractAction( "Link " + trackScheme.getSelectionModel().getSpotSelection().size() + " spots" ) + final Action linkAction = new AbstractAction( "Link " + trackScheme.getGuiModel().getSelectionModel().getSpotSelection().size() + " spots" ) { @Override public void actionPerformed( final ActionEvent e ) @@ -301,10 +301,8 @@ public void actionPerformed( final ActionEvent e ) linkSpots(); } }; - if ( trackScheme.getSelectionModel().getSpotSelection().size() > 1 ) - { + if ( trackScheme.getGuiModel().getSelectionModel().getSpotSelection().size() > 1 ) add( linkAction ); - } } /* @@ -396,7 +394,7 @@ public void actionPerformed( final ActionEvent e ) for ( final mxCell mxCell : edges ) { final DefaultWeightedEdge edge = trackScheme.getGraph().getEdgeFor( mxCell ); - trackScheme.getModel().getFeatureModel().removeEdgeFeature( edge, ManualEdgeColorAnalyzer.FEATURE ); + trackScheme.getGuiModel().getModel().getFeatureModel().removeEdgeFeature( edge, ManualEdgeColorAnalyzer.FEATURE ); } SwingUtilities.invokeLater( new Runnable() diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeToolbar.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeToolbar.java index fee2e40bc..282744206 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeToolbar.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeToolbar.java @@ -310,7 +310,7 @@ public void actionPerformed( final ActionEvent e ) add( loopDisplayDecorationsButton ); // Separator addSeparator(); - add( new SearchBar( trackScheme.getModel(), trackScheme ) ); + add( new SearchBar( trackScheme.getGuiModel().getModel(), trackScheme ) ); add( Box.createHorizontalGlue() ); final Dimension dim = new Dimension( 100, 30 ); diff --git a/src/test/java/fiji/plugin/trackmate/TestCopy.java b/src/test/java/fiji/plugin/trackmate/TestCopy.java index d2cdcde30..adb689c74 100644 --- a/src/test/java/fiji/plugin/trackmate/TestCopy.java +++ b/src/test/java/fiji/plugin/trackmate/TestCopy.java @@ -27,11 +27,9 @@ import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.wizard.TrackMateWizardSequence; import fiji.plugin.trackmate.io.TmXmlReader; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImageJ; import ij.ImagePlus; @@ -57,13 +55,10 @@ public static void main( final String[] args ) throws ClassNotFoundException, In final ImagePlus imp = reader.readImage(); imp.show(); final Settings settings = reader.readSettings( imp ); + final GuiModel guiModel = new GuiModel( copy, settings ); - final SelectionModel selectionModel = new SelectionModel( copy ); - final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); - final HyperStackDisplayer displayer = new HyperStackDisplayer( copy, selectionModel, imp, ds ); - displayer.render(); - - final TrackMateWizardSequence sequence = new TrackMateWizardSequence( new TrackMate( copy, settings ), selectionModel, ds ); + guiModel.getWindowManager().createHyperStackDisplayer(); + final TrackMateWizardSequence sequence = new TrackMateWizardSequence( guiModel ); sequence.setCurrent( "ConfigureViews" ); final JFrame frame = sequence.run( "Copy model" ); frame.setLocationRelativeTo( imp.getWindow() ); diff --git a/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java b/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java index 5cde8c717..826a3dc6f 100644 --- a/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java +++ b/src/test/java/fiji/plugin/trackmate/TestTrackMatePlugin.java @@ -23,21 +23,26 @@ import org.scijava.Context; +import fiji.plugin.trackmate.gui.GuiModel; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.util.TMUtils; import ij.IJ; import ij.ImagePlus; -class TestTrackMatePlugin extends TrackMatePlugIn { +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 void setUp() + { + final ImagePlus imp = IJ.createImage( "Test Image", 256, 256, 10, 8 ); + final Settings settings = createSettings( imp ); + final Model model = createModel( imp ); + final DisplaySettings ds = createDisplaySettings(); + new GuiModel( model, settings, ds ); } - public Context getLocalContext() { + public Context getLocalContext() + { return TMUtils.getContext(); } } diff --git a/src/test/java/fiji/plugin/trackmate/action/SpotGaussianFitterExample.java b/src/test/java/fiji/plugin/trackmate/action/SpotGaussianFitterExample.java index 53f178239..bf7a36bcc 100644 --- a/src/test/java/fiji/plugin/trackmate/action/SpotGaussianFitterExample.java +++ b/src/test/java/fiji/plugin/trackmate/action/SpotGaussianFitterExample.java @@ -31,16 +31,14 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.action.fit.SpotFitterController; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.wizard.TrackMateWizardSequence; import fiji.plugin.trackmate.io.TmXmlReader; -import fiji.plugin.trackmate.visualization.TrackMateModelView; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImageJ; import ij.ImagePlus; import net.imglib2.type.numeric.RealType; @@ -59,16 +57,15 @@ public static < T extends RealType< T > > void main( final String[] args ) throw final ImagePlus imp = reader.readImage(); imp.show(); final Settings settings = reader.readSettings( imp ); - final TrackMate trackmate = new TrackMate( model, settings ); + final DisplaySettings displaySettings = reader.getDisplaySettings(); + final GuiModel guiModel = new GuiModel( model, settings, displaySettings ); + final TrackMate trackmate = guiModel.getTrackMate(); trackmate.setNumThreads( 1 ); // Main view. - final SelectionModel selectionModel = new SelectionModel( model ); - final DisplaySettings displaySettings = reader.getDisplaySettings(); - final TrackMateModelView displayer = new HyperStackDisplayer( model, selectionModel, settings.imp, displaySettings ); - displayer.render(); + guiModel.getWindowManager().createHyperStackDisplayer(); - final TrackMateWizardSequence sequence = new TrackMateWizardSequence( trackmate, selectionModel, displaySettings ); + final TrackMateWizardSequence sequence = new TrackMateWizardSequence( guiModel ); sequence.setCurrent( "ConfigureViews" ); final JFrame frame = sequence.run( "Test Gauss-fitting action" ); frame.setIconImage( TRACKMATE_ICON.getImage() ); @@ -76,7 +73,7 @@ public static < T extends RealType< T > > void main( final String[] args ) throw frame.setVisible( true ); // Launch fitting controller. - final SpotFitterController controller = new SpotFitterController( trackmate, selectionModel, Logger.DEFAULT_LOGGER ); + final SpotFitterController controller = new SpotFitterController( guiModel, Logger.DEFAULT_LOGGER ); controller.show(); } } diff --git a/src/test/java/fiji/plugin/trackmate/detection/HessianDetectorTestDrive1.java b/src/test/java/fiji/plugin/trackmate/detection/HessianDetectorTestDrive1.java index ccb2ed689..5a11fabd9 100644 --- a/src/test/java/fiji/plugin/trackmate/detection/HessianDetectorTestDrive1.java +++ b/src/test/java/fiji/plugin/trackmate/detection/HessianDetectorTestDrive1.java @@ -24,16 +24,16 @@ import java.util.List; 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.features.FeatureUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; 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; @@ -90,14 +90,13 @@ public static < T extends RealType< T > & NativeType< T > > void main( final Str final Model model = new Model(); model.setPhysicalUnits( imp.getCalibration().getUnit(), imp.getCalibration().getTimeUnit() ); model.setSpots( sc, false ); - final SelectionModel selectionModel = new SelectionModel( model ); final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); final String feature = Spot.QUALITY; ds.setSpotColorBy( TrackMateObject.SPOTS, feature ); final double[] mm = FeatureUtils.autoMinMax( model, TrackMateObject.SPOTS, feature ); ds.setSpotMinMax( mm[ 0 ], mm[ 1 ] ); - final HyperStackDisplayer displayer = new HyperStackDisplayer( model, selectionModel, imp, ds ); - displayer.render(); - displayer.refresh(); + final Settings settings = new Settings( imp ); + final GuiModel guiModel = new GuiModel( model, settings, ds ); + guiModel.getWindowManager().createHyperStackDisplayer(); } } diff --git a/src/test/java/fiji/plugin/trackmate/graph/ConvexBranchDecompositionDebug.java b/src/test/java/fiji/plugin/trackmate/graph/ConvexBranchDecompositionDebug.java index 641af6c30..7cd3554a7 100644 --- a/src/test/java/fiji/plugin/trackmate/graph/ConvexBranchDecompositionDebug.java +++ b/src/test/java/fiji/plugin/trackmate/graph/ConvexBranchDecompositionDebug.java @@ -22,13 +22,12 @@ package fiji.plugin.trackmate.graph; 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; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; public class ConvexBranchDecompositionDebug @@ -71,8 +70,8 @@ public static void main( final String[] args ) model.addEdge( sa3, sa4, -2 ); model.addEdge( sb3, sb4, -2 ); - final SelectionModel sm = new SelectionModel( model ); - final TrackScheme trackScheme = new TrackScheme( model, sm, DisplaySettings.defaultStyle().copy() ); + final GuiModel guiModel = new GuiModel( model ); + final TrackScheme trackScheme = new TrackScheme( guiModel ); trackScheme.render(); final TrackModel tm = model.getTrackModel(); diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/ConcurrentSpotTestDrive.java b/src/test/java/fiji/plugin/trackmate/interactivetests/ConcurrentSpotTestDrive.java index 155f7eb91..e064f022e 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/ConcurrentSpotTestDrive.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/ConcurrentSpotTestDrive.java @@ -68,7 +68,7 @@ public static void main( final String[] args ) trackmate.execDetection(); // Retrieve spots - final SpotCollection spots = trackmate.getModel().getSpots(); + final SpotCollection spots = model.getSpots(); // Parse spots and detect duplicate IDs final int[] IDs = new int[ Spot.IDcounter.get() + 1 ]; diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/HyperStackDisplayerTestDrive.java b/src/test/java/fiji/plugin/trackmate/interactivetests/HyperStackDisplayerTestDrive.java index a817f5e0c..9f6404b1b 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/HyperStackDisplayerTestDrive.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/HyperStackDisplayerTestDrive.java @@ -26,14 +26,13 @@ import org.scijava.util.AppUtils; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.ModelFeatureUpdater; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackDisplayMode; import fiji.plugin.trackmate.io.TmXmlReader; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; import ij.ImagePlus; @@ -51,17 +50,15 @@ public static void main( final String[] args ) final Model model = reader.getModel(); final ImagePlus imp = reader.readImage(); final Settings settings = reader.readSettings( imp ); - final DisplaySettings ds = DisplaySettings.defaultStyle().copy(); ds.setSpotShowName( true ); ds.setTrackDisplayMode( TrackDisplayMode.LOCAL_BACKWARD ); + final GuiModel guiModel = new GuiModel( model, settings, ds ); new ModelFeatureUpdater( model, settings ); - final SelectionModel selectionModel = new SelectionModel( model ); - final HyperStackDisplayer displayer = new HyperStackDisplayer( model, selectionModel, imp, ds ); - displayer.render(); + guiModel.getWindowManager().createHyperStackDisplayer(); - final TrackScheme trackScheme = new TrackScheme( model, selectionModel, ds ); + final TrackScheme trackScheme = new TrackScheme( guiModel ); trackScheme.render(); } } diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/NNTrackerTest.java b/src/test/java/fiji/plugin/trackmate/interactivetests/NNTrackerTest.java index ced3f6dfc..b3b9a28d4 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/NNTrackerTest.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/NNTrackerTest.java @@ -31,13 +31,10 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.TrackMate; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.tracking.kdtree.NearestNeighborTracker; -import fiji.plugin.trackmate.visualization.TrackMateModelView; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImagePlus; public class NNTrackerTest @@ -89,7 +86,7 @@ public static void main( final String args[] ) // Load Image ij.ImageJ.main( args ); - final TrackMateModelView sd2d = new HyperStackDisplayer( model, new SelectionModel( model ), imp, DisplaySettings.defaultStyle().copy() ); - sd2d.render(); + final GuiModel guiModel = new GuiModel( model, imp ); + guiModel.getWindowManager().createHyperStackDisplayer(); } } diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/SpotFeatureGrapherExample.java b/src/test/java/fiji/plugin/trackmate/interactivetests/SpotFeatureGrapherExample.java index 692fbb9cb..f87c7c77b 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/SpotFeatureGrapherExample.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/SpotFeatureGrapherExample.java @@ -33,14 +33,13 @@ import org.scijava.util.AppUtils; 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; import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; @@ -49,12 +48,11 @@ public class SpotFeatureGrapherExample public static void main( final String[] args ) { - // Load objects final File file = new File( AppUtils.getBaseDirectory( TrackMate.class ), "samples/FakeTracks.xml" ); final TmXmlReader reader = new TmXmlReader( file ); final Model model = reader.getModel(); - final SelectionModel selectionModel = new SelectionModel( model ); + final GuiModel guiModel = new GuiModel( model ); final List< String > Y = new ArrayList<>( 1 ); Y.add( Spot.POSITION_T ); @@ -63,12 +61,10 @@ public static void main( final String[] args ) spots.add( it.next() ); final SpotFeatureGrapher grapher = new SpotFeatureGrapher( + guiModel, spots, Spot.POSITION_X, Y, - model, - selectionModel, - DisplaySettings.defaultStyle().copy(), true ); final JFrame frame = grapher.render(); frame.setLocationRelativeTo( null ); @@ -77,9 +73,8 @@ public static void main( final String[] args ) final TrackIndexAnalyzer analyzer = new TrackIndexAnalyzer(); analyzer.process( model.getTrackModel().trackIDs( true ), model ); // needed for trackScheme - final TrackScheme trackScheme = new TrackScheme( model, new SelectionModel( model ), DisplaySettings.defaultStyle().copy() ); + final TrackScheme trackScheme = new TrackScheme( guiModel ); trackScheme.render(); - } /** diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/TrackLayoutTest.java b/src/test/java/fiji/plugin/trackmate/interactivetests/TrackLayoutTest.java index 68294fd63..a83553aa9 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/TrackLayoutTest.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/TrackLayoutTest.java @@ -22,8 +22,7 @@ package fiji.plugin.trackmate.interactivetests; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; public class TrackLayoutTest @@ -31,10 +30,9 @@ public class TrackLayoutTest public static void main( final String[] args ) { - final Model model = GraphTest.getExampleModel(); - - final TrackScheme trackScheme = new TrackScheme( model, new SelectionModel( model ), DisplaySettings.defaultStyle().copy() ); + final GuiModel guiModel = new GuiModel( model ); + final TrackScheme trackScheme = new TrackScheme( guiModel ); trackScheme.render(); } } diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/TrackSchemeTestDrive.java b/src/test/java/fiji/plugin/trackmate/interactivetests/TrackSchemeTestDrive.java index d458ae97c..264c2e124 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/TrackSchemeTestDrive.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/TrackSchemeTestDrive.java @@ -26,9 +26,9 @@ import org.scijava.util.AppUtils; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.edges.EdgeSpeedAnalyzer; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.io.TmXmlReader; @@ -54,8 +54,8 @@ public static void main( final String[] args ) ds.setTrackColorBy( TrackMateObject.EDGES, EdgeSpeedAnalyzer.DISPLACEMENT ); // Instantiate displayer - final SelectionModel sm = new SelectionModel( model ); - final TrackScheme trackscheme = new TrackScheme( model, sm, ds ); + final GuiModel guiModel = new GuiModel( model, ds ); + final TrackScheme trackscheme = new TrackScheme( guiModel ); trackscheme.render(); trackscheme.refresh(); } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java index 7fa4019e5..2ec486e27 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DebugZSlicer.java @@ -24,13 +24,13 @@ import java.io.File; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.gui.GuiModel; 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.CompositeImage; import ij.ImageJ; import ij.ImagePlus; @@ -57,13 +57,13 @@ public static void main( final String[] args ) final ImagePlus imp = reader.readImage(); imp.show(); final double[] calibration = TMUtils.getSpatialCalibration( imp ); + final Settings settings = reader.readSettings( imp ); final Model model = reader.getModel(); - final SelectionModel selection = new SelectionModel( model ); final DisplaySettings ds = reader.getDisplaySettings(); + final GuiModel guiModel = new GuiModel( model, settings, ds ); - final HyperStackDisplayer view = new HyperStackDisplayer( model, selection, imp, ds ); - view.render(); + guiModel.getWindowManager().createHyperStackDisplayer(); imp.setDisplayMode( CompositeImage.GRAYSCALE ); final Spot spot = model.getSpots().iterable( true ).iterator().next(); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java index 74bd896b7..88c67cac5 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DefaultMesh.java @@ -24,12 +24,11 @@ import java.util.List; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.Settings; 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.gui.GuiModel; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImageJ; import ij.ImagePlus; @@ -66,10 +65,8 @@ public static void main( final String[] args ) System.out.println( spot ); } - final SelectionModel selectionModel = new SelectionModel( model ); - final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); - final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, imp, ds ); - view.render(); + final GuiModel guiModel = new GuiModel( model, new Settings( imp ) ); + guiModel.getWindowManager().createHyperStackDisplayer(); } public static void main2( final String[] args ) @@ -99,9 +96,7 @@ public static void main2( final String[] args ) model.endUpdate(); } - final SelectionModel selectionModel = new SelectionModel( model ); - final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); - final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, imp, ds ); + final HyperStackDisplayer view = new HyperStackDisplayer( new GuiModel( model, imp ) ); view.render(); } } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java index fe145ac2a..e9709c226 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoContour.java @@ -24,9 +24,7 @@ import java.io.File; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImageJ; @@ -56,9 +54,7 @@ public static void main( final String[] args ) 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 ); + final HyperStackDisplayer view = new HyperStackDisplayer( new GuiModel( model, imp ) ); view.render(); } } diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java index f51aad467..b75f570e0 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoHollowMesh.java @@ -22,16 +22,13 @@ 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.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.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImageJ; import ij.ImagePlus; import ij.gui.NewImage; @@ -53,16 +50,13 @@ public static void main( final String[] args ) settings.detectorSettings.put( ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD, 120. ); settings.detectorSettings.put( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, false ); - final TrackMate trackmate = new TrackMate( settings ); + final Model model = new Model(); + final GuiModel guiModel = new GuiModel( model, imp ); + final TrackMate trackmate = guiModel.getTrackMate(); 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(); + guiModel.getWindowManager().createHyperStackDisplayer(); } public static ImagePlus makeImg() diff --git a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java index e8d067e66..b310e6d87 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/DemoPixelIteration.java @@ -24,16 +24,13 @@ 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.ThresholdDetectorFactory; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.IJ; import ij.ImageJ; import ij.ImagePlus; @@ -78,11 +75,12 @@ public static < T extends RealType< T > > void main( final String[] args ) settings.detectorSettings.put( ThresholdDetectorFactory.KEY_INTENSITY_THRESHOLD, 100. ); - final TrackMate trackmate = new TrackMate( settings ); + final Model model = new Model(); + final GuiModel guiModel = new GuiModel( model, settings ); + final TrackMate trackmate = guiModel.getTrackMate(); trackmate.setNumThreads( 4 ); trackmate.execDetection(); - final Model model = trackmate.getModel(); final SpotCollection spots = model.getSpots(); spots.setVisible( true ); @@ -111,11 +109,7 @@ public static < T extends RealType< T > > void main( final String[] args ) } } - final SelectionModel sm = new SelectionModel( model ); - final DisplaySettings ds = DisplaySettingsIO.readUserDefault(); - final HyperStackDisplayer view = new HyperStackDisplayer( model, sm, imp, ds ); - view.render(); - + guiModel.getWindowManager().createHyperStackDisplayer(); imp.setSlice( 19 ); imp.resetDisplayRange(); imp.setLut( LUT.createLutFromColor( Color.BLUE ) ); diff --git a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java index 1d019b057..b03d914da 100644 --- a/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java +++ b/src/test/java/fiji/plugin/trackmate/mesh/ExportMeshForDemo.java @@ -31,6 +31,7 @@ import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.detection.MaskDetectorFactory; import fiji.plugin.trackmate.detection.ThresholdDetectorFactory; +import fiji.plugin.trackmate.gui.GuiModel; import ij.IJ; import ij.ImagePlus; import net.imglib2.mesh.io.stl.STLMeshIO; @@ -50,11 +51,11 @@ public static void main( final String[] args ) settings.detectorSettings = settings.detectorFactory.getDefaultSettings(); settings.detectorSettings.put( ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS, false ); - final TrackMate trackmate = new TrackMate( settings ); + final Model model = new Model(); + final GuiModel guiModel = new GuiModel( model, settings ); + final TrackMate trackmate = guiModel.getTrackMate(); trackmate.setNumThreads( 4 ); trackmate.execDetection(); - - final Model model = trackmate.getModel(); final SpotCollection spots = model.getSpots(); spots.setVisible( true ); 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 151491f5d..2d0b4773e 100644 --- a/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest.java +++ b/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest.java @@ -30,17 +30,15 @@ import java.util.Random; 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.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.tracking.TrackerKeys; import fiji.plugin.trackmate.tracking.jaqaman.SparseLAPFrameToFrameTracker; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; import ij.ImageJ; public class KalmanTrackerInteractiveTest @@ -156,12 +154,9 @@ private Model testLAP( final SpotCollection spots ) ds.setTrackColorBy( TrackMateObject.TRACKS, TrackIndexAnalyzer.TRACK_INDEX ); ds.setSpotShowName( true ); - final SelectionModel selectionModel = new SelectionModel( model ); - final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, ds ); - view.render(); - - final TrackScheme trackscheme = new TrackScheme( model, selectionModel, ds ); - trackscheme.render(); + final GuiModel guiModel = new GuiModel( model, ds ); + guiModel.getWindowManager().createHyperStackDisplayer(); + guiModel.getWindowManager().createTrackScheme(); return model; } @@ -202,13 +197,9 @@ private Model test( final SpotCollection spots ) final TrackIndexAnalyzer ta = new TrackIndexAnalyzer(); ta.process( model.getTrackModel().trackIDs( true ), model ); - final SelectionModel selectionModel = new SelectionModel( model ); - final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, ds ); - view.render(); - - final TrackScheme trackscheme = new TrackScheme( model, selectionModel, ds ); - trackscheme.render(); - + final GuiModel guiModel = new GuiModel( model, ds ); + guiModel.getWindowManager().createHyperStackDisplayer(); + guiModel.getWindowManager().createTrackScheme(); return model; } diff --git a/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest2.java b/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest2.java index addc73199..c1c20f9f2 100644 --- a/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest2.java +++ b/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest2.java @@ -28,13 +28,11 @@ 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.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.io.TmXmlReader; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; import ij.ImagePlus; public class KalmanTrackerInteractiveTest2 @@ -79,12 +77,9 @@ public static void main( final String[] args ) } ij.ImageJ.main( args ); - final SelectionModel selectionModel = new SelectionModel( model ); - final DisplaySettings ds = DisplaySettings.defaultStyle().copy(); - ds.setSpotColorBy( TrackMateObject.SPOTS, Spot.QUALITY ); - - final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, imp, ds ); - view.render(); + final GuiModel guiModel = new GuiModel( model, imp ); + guiModel.getDisplaySettings().setSpotColorBy( TrackMateObject.SPOTS, Spot.QUALITY ); + guiModel.getWindowManager().createHyperStackDisplayer(); } } 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 d6dcd5241..f4557da3f 100755 --- a/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest3.java +++ b/src/test/java/fiji/plugin/trackmate/tracking/kalman/KalmanTrackerInteractiveTest3.java @@ -25,15 +25,13 @@ import fiji.plugin.trackmate.Logger; 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.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; import ij.ImageJ; public class KalmanTrackerInteractiveTest3 @@ -87,12 +85,9 @@ private Model test( final SpotCollection spots ) final TrackIndexAnalyzer ta = new TrackIndexAnalyzer(); ta.process( model.getTrackModel().trackIDs( true ), model ); - final SelectionModel selectionModel = new SelectionModel( model ); - final HyperStackDisplayer view = new HyperStackDisplayer( model, selectionModel, ds ); - view.render(); - - final TrackScheme trackscheme = new TrackScheme( model, selectionModel, ds ); - trackscheme.render(); + final GuiModel guiModel = new GuiModel( model ); + guiModel.getWindowManager().createHyperStackDisplayer(); + guiModel.getWindowManager().createTrackScheme(); return model; } diff --git a/src/test/java/fiji/plugin/trackmate/tracking/sparselap/SparseLAPTrackerExample.java b/src/test/java/fiji/plugin/trackmate/tracking/sparselap/SparseLAPTrackerExample.java index 3e7e95a04..206b35883 100755 --- a/src/test/java/fiji/plugin/trackmate/tracking/sparselap/SparseLAPTrackerExample.java +++ b/src/test/java/fiji/plugin/trackmate/tracking/sparselap/SparseLAPTrackerExample.java @@ -53,13 +53,10 @@ import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.io.TmXmlReader; import fiji.plugin.trackmate.tracking.jaqaman.SparseLAPTracker; -import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; -import fiji.plugin.trackmate.visualization.trackscheme.TrackScheme; public class SparseLAPTrackerExample { @@ -103,15 +100,10 @@ public static void main( final String[] args ) model.setTracks( tracker.getResult(), true ); - final DisplaySettings ds = DisplaySettings.defaultStyle().copy(); - ij.ImageJ.main( args ); - final SelectionModel sm = new SelectionModel( model ); - final HyperStackDisplayer view = new HyperStackDisplayer( model, sm, ds ); - view.render(); - final TrackScheme trackScheme = new TrackScheme( model, sm, ds ); - trackScheme.render(); - + final GuiModel guiModel = new GuiModel( model ); + guiModel.getWindowManager().createHyperStackDisplayer(); + guiModel.getWindowManager().createTrackScheme(); } } diff --git a/src/test/java/fiji/plugin/trackmate/visualization/table/TrackMateTableExample.java b/src/test/java/fiji/plugin/trackmate/visualization/table/TrackMateTableExample.java index a9e3bcdc5..6870b2701 100644 --- a/src/test/java/fiji/plugin/trackmate/visualization/table/TrackMateTableExample.java +++ b/src/test/java/fiji/plugin/trackmate/visualization/table/TrackMateTableExample.java @@ -28,6 +28,7 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.io.TmXmlReader; import ij.ImageJ; @@ -47,6 +48,7 @@ public static void main( final String[] args ) throws ClassNotFoundException, In model.getSpots().iterable( 1, true ).forEach( selectionModel::addSpotToSelection ); final String exportFile = System.getProperty( "user.home" ) + File.separator + "test"; - new TrackTableView( model, selectionModel, ds, exportFile ).render(); + final GuiModel guiModel = new GuiModel( model, ds ); + new TrackTableView( guiModel, exportFile ).render(); } } From e6408b2e491aba961233cde3c716aebd51888676 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 16:25:44 +0200 Subject: [PATCH 301/371] Remove the unused ViewFactory framework. --- .../trackmate/providers/ViewProvider.java | 39 ---------- .../trackmate/visualization/ViewFactory.java | 49 ------------ .../HyperStackDisplayerFactory.java | 74 ------------------- .../trackscheme/TrackSchemeFactory.java | 72 ------------------ 4 files changed, 234 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/providers/ViewProvider.java delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/ViewFactory.java delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayerFactory.java delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFactory.java diff --git a/src/main/java/fiji/plugin/trackmate/providers/ViewProvider.java b/src/main/java/fiji/plugin/trackmate/providers/ViewProvider.java deleted file mode 100644 index 31ec8dc10..000000000 --- a/src/main/java/fiji/plugin/trackmate/providers/ViewProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.visualization.ViewFactory; - -public class ViewProvider extends AbstractProvider< ViewFactory > -{ - - public ViewProvider() - { - super( ViewFactory.class ); - } - - public static void main( final String[] args ) - { - final ViewProvider provider = new ViewProvider(); - System.out.println( provider.echo() ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ViewFactory.java b/src/main/java/fiji/plugin/trackmate/visualization/ViewFactory.java deleted file mode 100644 index 1913114a6..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/ViewFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.TrackMateModule; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; - -public interface ViewFactory extends TrackMateModule -{ - - /** - * Returns a new instance of the concrete view. - * - * @param model - * the model to display in the view. - * @param settings - * a {@link Settings} object, which specific implementation might - * use to display the model. - * @param selectionModel - * the {@link SelectionModel} model to share in the created view. - * @param displaySettings - * the display settings to use to paint the view. - * @return a new view of the specified model. - */ - public TrackMateModelView create( final Model model, final Settings settings, final SelectionModel selectionModel, final DisplaySettings displaySettings ); - -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayerFactory.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayerFactory.java deleted file mode 100644 index a1bcd4e6e..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayerFactory.java +++ /dev/null @@ -1,74 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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 javax.swing.ImageIcon; - -import org.scijava.plugin.Plugin; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.visualization.TrackMateModelView; -import fiji.plugin.trackmate.visualization.ViewFactory; -import ij.ImagePlus; - -@Plugin( type = ViewFactory.class ) -public class HyperStackDisplayerFactory implements ViewFactory -{ - - private static final String INFO_TEXT = "" + "This displayer overlays the spots and tracks on the current
    " + "ImageJ hyperstack window.
    " + "

    " + "This displayer allows manual editing of spots, thanks to the spot
    " + "edit tool that appear in ImageJ toolbar." + "

    " + "Double-clicking in a spot toggles the editing mode: The spot can
    " + "be moved around in a XY plane by mouse dragging. To move it in Z
    " + "or in time, simply change the current plane and time-point by
    " + "using the hyperstack sliders. To change its radius, hold the
    " + "alt key down and rotate the mouse-wheel. Holding the
    " + "shift key on top changes it faster. " + "

    " + "Alternatively, keyboard can be used to edit spots:
    " + " - A creates a new spot under the mouse.
    " + " - D deletes the spot under the mouse.
    " + " - Q and E decreases and increases the radius of the spot " + "under the mouse (shift to go faster).
    " + " - Space + mouse drag moves the spot under the mouse.
    " + "

    " + "To toggle links between two spots, select two spots (Shift+Click),
    " + "then press L. " + "

    " + "Shift+L toggle the auto-linking mode on/off.
    " + "If on, every spot created will be automatically linked with the spot
    " + "currently selected, if they are in subsequent frames." + ""; - - private static final String NAME = "HyperStack Displayer"; - - @Override - public TrackMateModelView create( final Model model, final Settings settings, final SelectionModel selectionModel, final DisplaySettings displaySettings ) - { - final ImagePlus imp = ( settings == null ) ? null : settings.imp; - return new HyperStackDisplayer( model, selectionModel, imp, displaySettings ); - } - - @Override - public String getInfoText() - { - return INFO_TEXT; - } - - @Override - public String getName() - { - return NAME; - } - - @Override - public String getKey() - { - return HyperStackDisplayer.KEY; - } - - @Override - public ImageIcon getIcon() - { - return null; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFactory.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFactory.java deleted file mode 100644 index 16201a173..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeFactory.java +++ /dev/null @@ -1,72 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.trackscheme; - -import javax.swing.ImageIcon; - -import org.scijava.plugin.Plugin; - -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; -import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; -import fiji.plugin.trackmate.visualization.TrackMateModelView; -import fiji.plugin.trackmate.visualization.ViewFactory; - -/* - * We annotate the TrackScheme factory to be NOT visible, - * because we do not want it to show in the GUI menu. - */ -@Plugin( type = ViewFactory.class, visible = false ) -public class TrackSchemeFactory implements ViewFactory -{ - - @Override - public TrackMateModelView create( final Model model, final Settings settings, final SelectionModel selectionModel, final DisplaySettings displaySettings ) - { - return new TrackScheme( model, selectionModel, displaySettings ); - } - - @Override - public String getName() - { - return "TrackScheme"; - } - - @Override - public String getKey() - { - return TrackScheme.KEY; - } - - @Override - public ImageIcon getIcon() - { - return null; - } - - @Override - public String getInfoText() - { - return "Not redacted!"; - } -} From 15abd9ff769b8f3d135279a943049309a18d236a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 16:26:02 +0200 Subject: [PATCH 302/371] Remove the unused TrackMateConfigDialog It is now built in the global actions. --- .../ui/TrackMateConfigDialog.java | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateConfigDialog.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateConfigDialog.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateConfigDialog.java deleted file mode 100644 index 668c39a0a..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateConfigDialog.java +++ /dev/null @@ -1,53 +0,0 @@ -package fiji.plugin.trackmate.visualization.ui; - -import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.ALL_SPOTS_TABLE; -import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.HYPERSTACK_DISPLAYER; -import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.KEY_CONFIG_SCOPE; -import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACKMATE; -import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACKSCHEME; -import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACK_TABLE; - -import java.awt.Frame; - -import org.scijava.plugin.Plugin; -import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; -import org.scijava.ui.behaviour.io.gui.CommandDescriptions; -import org.scijava.ui.behaviour.util.Actions; - -import bdv.BigDataViewerActions; -import bdv.tools.CloseWindowActions; -import bdv.tools.PreferencesDialog; -import bdv.ui.keymap.Keymap; -import bdv.ui.keymap.KeymapSettingsPage; -import fiji.plugin.trackmate.gui.GuiUtils; - -public class TrackMateConfigDialog -{ - - public static void prefDialog( final Frame frame, final Actions actions ) - { - final TrackMateKeymapManager keymapManager = TrackMateKeymapManager.keymapManager; - final Keymap keymap = keymapManager.getForwardSelectedKeymap(); - final PreferencesDialog preferencesDialog = new PreferencesDialog( frame, keymap, - new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE } ); - GuiUtils.positionWindow( preferencesDialog, frame ); - BigDataViewerActions.toggleDialogAction( actions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); - preferencesDialog.addPage( new KeymapSettingsPage( "Keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); - } - - @Plugin( type = CommandDescriptionProvider.class ) - public static class Descriptions extends CommandDescriptionProvider - { - public Descriptions() - { - super( KEY_CONFIG_SCOPE, TRACKMATE ); - } - - @Override - public void getCommandDescriptions( final CommandDescriptions descriptions ) - { - descriptions.add( CloseWindowActions.CLOSE_DIALOG, CloseWindowActions.CLOSE_DIALOG_KEYS, "Close the active dialog." ); - descriptions.add( BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS, "Open the preferences dialog." ); - } - } -} From f5e4f911337e5e4fdfaf9907f4aecd9600053f3c Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 17:39:34 +0200 Subject: [PATCH 303/371] A class to intercept user closing an ImagePlus. Used to ask for confirmation. --- .../util/ImpCloseWindowListener.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/util/ImpCloseWindowListener.java diff --git a/src/main/java/fiji/plugin/trackmate/util/ImpCloseWindowListener.java b/src/main/java/fiji/plugin/trackmate/util/ImpCloseWindowListener.java new file mode 100644 index 000000000..2bb9251b6 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/util/ImpCloseWindowListener.java @@ -0,0 +1,111 @@ + +package fiji.plugin.trackmate.util; + +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.function.BooleanSupplier; + +import ij.gui.ImageWindow; + +/** + * Intercepts the ImageJ window closing event and prevents the image from being + * closed when the user clicks on the close button. Then asks for confirmation. + */ +public class ImpCloseWindowListener implements WindowListener +{ + + /** + * Adds a listener to the given ImageWindow that intercepts the close event + * and asks for confirmation before closing the window. If the user + * confirms, the window is closed and the onClosed runnable is executed. + * + * @param window + * the ImageWindow to wrap. + * @param confirmClose + * a BooleanSupplier that returns true if the window should be + * closed, false otherwise. It can be build from a JOptionPane or + * any other confirmation dialog. + * @param onClosed + * what to do when the window is closed. Can be null. + */ + public static void wrap( final ImageWindow window, final BooleanSupplier confirmClose, final Runnable onClosed ) + { + new ImpCloseWindowListener( window, confirmClose, onClosed ); + } + + private final WindowListener nativeListener; + + private final Runnable onClosed; + + private final BooleanSupplier confirmClose; + + private ImpCloseWindowListener( + final ImageWindow win, + final BooleanSupplier confirmClose, + final Runnable onClosed ) + { + this.confirmClose = confirmClose; + this.onClosed = onClosed; + // Identify and remove IJ close listener + WindowListener tmp = null; + final WindowListener[] listeners = win.getWindowListeners(); + for ( final WindowListener listener : listeners ) + { + if ( ImageWindow.class.isAssignableFrom( listener.getClass() ) ) + { + tmp = listener; + win.removeWindowListener( tmp ); + break; + } + } + if ( tmp == null ) + throw new IllegalStateException( "Could not find native ImageWindow listener." ); + this.nativeListener = tmp; + win.addWindowListener( this ); + } + + @Override + public void windowOpened( final WindowEvent e ) + { + nativeListener.windowOpened( e ); + } + + @Override + public void windowClosing( final WindowEvent e ) + { + if ( confirmClose.getAsBoolean() ) + nativeListener.windowClosing( e ); + } + + @Override + public void windowClosed( final WindowEvent e ) + { + nativeListener.windowClosed( e ); + if ( onClosed != null ) + onClosed.run(); + } + + @Override + public void windowIconified( final WindowEvent e ) + { + nativeListener.windowIconified( e ); + } + + @Override + public void windowDeiconified( final WindowEvent e ) + { + nativeListener.windowDeiconified( e ); + } + + @Override + public void windowActivated( final WindowEvent e ) + { + nativeListener.windowActivated( e ); + } + + @Override + public void windowDeactivated( final WindowEvent e ) + { + nativeListener.windowDeactivated( e ); + } +} From 928731e1b57f3fb045068edec0d2e958b17795b1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 17:39:46 +0200 Subject: [PATCH 304/371] A new 64x64 TrackMate icon. --- src/main/java/fiji/plugin/trackmate/gui/Icons.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/gui/Icons.java b/src/main/java/fiji/plugin/trackmate/gui/Icons.java index 328d45f19..0c1d8e5e3 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/Icons.java +++ b/src/main/java/fiji/plugin/trackmate/gui/Icons.java @@ -41,6 +41,8 @@ public class Icons public static final ImageIcon TRACKMATE_ICON_16x16; + public static final ImageIcon TRACKMATE_ICON_64x64; + public static final ImageIcon TRACK_SCHEME_ICON_16x16; public static final ImageIcon SPOT_ICON_64x64; @@ -81,6 +83,10 @@ public class Icons final Image image7 = EDGE_ICON.getImage(); final Image newimg7 = image7.getScaledInstance( 16, 16, java.awt.Image.SCALE_SMOOTH ); EDGE_ICON_16x16 = new ImageIcon( newimg7 ); + + final Image image8 = TRACKMATE_ICON.getImage(); + final Image newimg8 = image8.getScaledInstance( 64, 64, java.awt.Image.SCALE_SMOOTH ); + TRACKMATE_ICON_64x64 = new ImageIcon( newimg8 ); } public static final ImageIcon SPOT_ICON_16x16 = new ImageIcon( Icons.class.getResource( "images/spot_icon_16x16.png" ) ); From bce640e65f9eeba32093df1542c4bfb026d4907b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 17:42:17 +0200 Subject: [PATCH 305/371] Set the main view apart in the WindowManager. There can be only one main view with ImagePlus. Right now I chose to thrown an Exception for debugging. Also, we don't register it in the list of side views. --- .../fiji/plugin/trackmate/gui/WindowManager.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java index d8992e3a0..e47d82ffb 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java @@ -50,6 +50,8 @@ public class WindowManager private final List< Window > windows = new ArrayList<>(); + private HyperStackDisplayer mainView; + public WindowManager( final GuiModel guiModel ) { this.guiModel = guiModel; @@ -67,10 +69,11 @@ public WindowManager( final GuiModel guiModel ) public HyperStackDisplayer createHyperStackDisplayer() { - final HyperStackDisplayer displayer = new HyperStackDisplayer( guiModel ); - registerView( displayer ); - displayer.render(); - return displayer; + if ( mainView != null ) + throw new IllegalStateException( "There can be only one main view." ); + this.mainView = new HyperStackDisplayer( guiModel ); + mainView.render(); + return mainView; } public TMLabKitFrame createSpotEditor( final boolean singleTimepoint ) @@ -141,8 +144,8 @@ private void registerView( final TrackMateModelView view ) } /** - * Registers a frame created by this window manager. This is useful for - * windows and dialogs that are not TrackMateModelView. + * Registers a frame in this window manager. This is useful for windows and + * dialogs that are not TrackMateModelView. * * @param frame * the frame to register. From b72466ad360f0d084aa1624b5764723ba289aa62 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 17:42:43 +0200 Subject: [PATCH 306/371] Hook up onClose() actions on the main view. --- .../hyperstack/HyperStackDisplayer.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) 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 9eb5cc4a7..3878045b4 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -22,6 +22,8 @@ package fiji.plugin.trackmate.visualization.hyperstack; import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import org.scijava.ui.behaviour.util.Actions; import org.scijava.ui.behaviour.util.WrappedActionMap; @@ -79,14 +81,6 @@ public HyperStackDisplayer( final GuiModel guiModel ) onClose( () -> displaySettings.listeners().remove( refresher ) ); } - /* - * PROTECTED METHODS - */ - - /* - * PUBLIC METHODS - */ - /** * Exposes the {@link ImagePlus} on which the model is drawn by this view. * @@ -141,6 +135,15 @@ public void render() if ( !imp.isVisible() ) imp.show(); + imp.getWindow().addWindowListener( new WindowAdapter() + { + @Override + public void windowClosing( final WindowEvent e ) + { + close(); + } + } ); + addOverlay( spotOverlay ); addOverlay( trackOverlay ); imp.updateAndDraw(); From 9a9c1e54d3848e31fdb4c57e6a3887de8c1b79e7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 3 Aug 2026 17:42:58 +0200 Subject: [PATCH 307/371] Ask for confirmation when user closes the main view of the wizard. --- .../gui/wizard/TrackMateWizardSequence.java | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 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 0066fa346..a8ea75cb5 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java +++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java @@ -22,12 +22,18 @@ package fiji.plugin.trackmate.gui.wizard; import java.awt.Window; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.BooleanSupplier; import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.WindowConstants; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; @@ -40,6 +46,7 @@ import fiji.plugin.trackmate.features.FeatureFilter; import fiji.plugin.trackmate.features.ModelFeatureUpdater; import fiji.plugin.trackmate.gui.GuiModel; +import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.components.ConfigurationPanel; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.LogPanel; @@ -65,7 +72,9 @@ import fiji.plugin.trackmate.tracking.SpotImageTrackerFactory; import fiji.plugin.trackmate.tracking.SpotTrackerFactory; import fiji.plugin.trackmate.tracking.manual.ManualTrackerFactory; +import fiji.plugin.trackmate.util.ImpCloseWindowListener; import fiji.plugin.trackmate.visualization.AbstractTrackMateModelJFrameView; +import ij.gui.ImageWindow; public class TrackMateWizardSequence extends AbstractTrackMateModelJFrameView implements WizardSequence { @@ -397,7 +406,7 @@ private SpotTrackerDescriptor getTrackerConfigDescriptor() } final ConfigurationPanel trackerConfigurationPanel; - if (trackerFactory instanceof SpotImageTrackerFactory) + if ( trackerFactory instanceof SpotImageTrackerFactory ) { trackerConfigurationPanel = ( ( SpotImageTrackerFactory ) trackerFactory ).getTrackerConfigurationPanel( model, settings.imp ); } @@ -423,11 +432,48 @@ private SpotTrackerDescriptor getTrackerConfigDescriptor() public JFrame run( final String title ) { this.frame = WizardSequence.super.run( title ); - setWindow( frame ); - onClose( () -> { - guiModel.getModel().setLogger( Logger.VOID_LOGGER ); + final ImageWindow window = guiModel.getSettings().imp.getWindow(); + + // Build a confirmation dialog, add it to the wizard window and the + // image window. + final BooleanSupplier confirmClose = () -> { + final int choice = JOptionPane.showOptionDialog( window, "" + + "This will close the image and\n" + + "terminate this TrackMate session. \n" + + "Close window?", + "End TrackMate session?", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + Icons.TRACKMATE_ICON_64x64, + null, + JOptionPane.NO_OPTION ); + return ( choice == JOptionPane.YES_OPTION ); + }; + final Runnable onClosed = () -> { guiModel.getWindowManager().closeAll(); - } ); + frame.dispose(); + }; + + // Intercept closing the image -> ask for confirmation. + ImpCloseWindowListener.wrap( window, confirmClose, onClosed ); + + // Intercept closing the main window. + frame.setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE ); + final WindowListener closeConfirm = new WindowAdapter() + { + + @Override + public void windowClosing( final WindowEvent e ) + { + if ( confirmClose.getAsBoolean() ) + { + onClosed.run(); + window.dispose(); + } + }; + }; + frame.addWindowListener( closeConfirm ); + setWindow( frame ); return frame; } From 9bf6d9d7bd045f239ce41c4e0443ae0f4059ab53 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 09:45:04 +0200 Subject: [PATCH 308/371] Add the mouse and key proxies to the ImagePlus canvas AND window. --- .../ImagePlusBehavioursAdapter.java | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java index 3bcf7c6ab..f44279adf 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/ImagePlusBehavioursAdapter.java @@ -34,6 +34,7 @@ import gnu.trove.set.TIntSet; import ij.ImagePlus; import ij.gui.ImageCanvas; +import ij.gui.ImageWindow; /** * Adapter class that connects a {@link ImagePlus} to the SciJava Behaviours @@ -65,7 +66,7 @@ public class ImagePlusBehavioursAdapter public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keymapManager, final String[] keyConfigContexts ) { final ImageCanvas canvas = imp.getCanvas(); - canvas.setFocusable( true ); + final ImageWindow window = imp.getWindow(); // Initialize configuration and binding registries this.keybindings = new InputActionBindings(); @@ -100,9 +101,13 @@ public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keym // Put the IJ key listener at the end of the chain, so that we can // intercept events before they reach it. - final KeyListener[] keyListeners = canvas.getKeyListeners(); - for ( final KeyListener keyListener : keyListeners ) + final KeyListener[] canvasKeyListeners = canvas.getKeyListeners(); + for ( final KeyListener keyListener : canvasKeyListeners ) canvas.removeKeyListener( keyListener ); + // The same for the window + final KeyListener[] windowKeyListeners = window.getKeyListeners(); + for ( final KeyListener keyListener : windowKeyListeners ) + window.removeKeyListener( keyListener ); /* * Connect the handler to the AWT component for Mouse handling We need @@ -115,13 +120,17 @@ public ImagePlusBehavioursAdapter( final ImagePlus imp, final KeymapManager keym canvas.addMouseListener( proxy ); canvas.addMouseMotionListener( proxy ); canvas.addMouseWheelListener( proxy ); + window.addKeyListener( proxy ); + window.addMouseListener( proxy ); + window.addMouseMotionListener( proxy ); + window.addMouseWheelListener( proxy ); /* * Direct Key Event Proxy Bridge. Because an AWT Canvas bypasses Swing's * ActionMap dispatch pipeline, we manually intercept the KeyStrokes and * route them to our Action map. */ - canvas.addKeyListener( new KeyAdapter() + final KeyAdapter actionsRoutingProxy = new KeyAdapter() { @Override public void keyPressed( final KeyEvent e ) @@ -145,11 +154,15 @@ public void keyPressed( final KeyEvent e ) } } } - } ); + }; + canvas.addKeyListener( actionsRoutingProxy ); + window.addKeyListener( actionsRoutingProxy ); // Re-add the original ImageJ KeyListener after all proxies - for ( final KeyListener keyListener : keyListeners ) + for ( final KeyListener keyListener : canvasKeyListeners ) canvas.addKeyListener( keyListener ); + for ( final KeyListener keyListener : windowKeyListeners ) + window.addKeyListener( keyListener ); } public Actions actions() @@ -204,6 +217,7 @@ public MouseEventProxy( final MouseAndKeyHandler delegate, final InputTriggerMap catch ( final Exception ex ) { this.getMaskMethod = null; + System.err.println( "[ImagePlusBehavioursAdapter] Failed to extract getMask() method from MouseAndKeyHandler via reflection. Behavioral trigger matching will be disabled." ); // DEBUG } } @@ -220,8 +234,7 @@ private boolean hasMatchingBehaviour( final InputEvent e ) try { // Retrieve the calculated normalization bitmask from the - // handler - // instance + // handler instance final int mask = ( Integer ) getMaskMethod.invoke( delegate, e ); // Keep the primitive TIntSet collection directly without @@ -248,6 +261,7 @@ private boolean hasMatchingBehaviour( final InputEvent e ) } catch ( final Exception ex ) { + System.err.println( "[ImagePlusBehavioursAdapter] Exception occurred while checking for matching behaviour: " + ex.getMessage() ); // DEBUG // Prevent crashes from faulty reflections, letting the event // propagate unconsumed } From bd17af6629158fcddaa208f3c95c6397463fa94a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 10:25:35 +0200 Subject: [PATCH 309/371] GuiModel has fields for the preferences of the spot editor. --- .../fiji/plugin/trackmate/gui/GuiModel.java | 21 +++++++++++++++++++ .../trackmate/gui/editor/LabkitLauncher.java | 17 ++++++--------- ...pManager.java => EditorKeymapManager.java} | 10 ++++----- .../labkit/component/TMLabKitActions.java | 8 ++++--- .../labkit/component/TMLabKitFrame.java | 10 ++++----- .../editor/labkit/model/TMLabKitModel.java | 11 ++++++++++ 6 files changed, 53 insertions(+), 24 deletions(-) rename src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/{TMKeymapManager.java => EditorKeymapManager.java} (91%) diff --git a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java index 6e98a7879..158befb43 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java @@ -1,15 +1,19 @@ package fiji.plugin.trackmate.gui; +import static fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame.EDITOR_KEYMAP_HOME; + import org.scijava.object.ObjectService; import org.scijava.ui.behaviour.KeyPressedManager; import org.scijava.ui.behaviour.util.Actions; +import bdv.ui.appearance.AppearanceManager; import bdv.ui.keymap.Keymap; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.editor.labkit.component.EditorKeymapManager; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import fiji.plugin.trackmate.visualization.ui.TrackMateActions; @@ -23,6 +27,7 @@ public class GuiModel { + private final Model model; private final SelectionModel selectionModel; @@ -35,6 +40,10 @@ public class GuiModel private final TrackMateKeymapManager keymapManager; + private final EditorKeymapManager editorKeymapManager; + + private final AppearanceManager appearanceManager; + private final Settings settings; private final TrackMate trackmate; @@ -61,6 +70,8 @@ public GuiModel( final Model model, final Settings settings, final DisplaySettin this.trackmate = createTrackMate( model, settings ); // Keymap and actions + this.editorKeymapManager = new EditorKeymapManager(); + this.appearanceManager = new AppearanceManager( EDITOR_KEYMAP_HOME ); this.keyPressedManager = new KeyPressedManager(); this.keymapManager = new TrackMateKeymapManager(); final Keymap keymap = keymapManager.getForwardSelectedKeymap(); @@ -179,6 +190,11 @@ public TrackMateKeymapManager getKeymapManager() return keymapManager; } + public EditorKeymapManager getEditorKeymapManager() + { + return editorKeymapManager; + } + public Model getModel() { return model; @@ -208,4 +224,9 @@ public WindowManager getWindowManager() { return windowManager; } + + public AppearanceManager getAppearanceManager() + { + return appearanceManager; + } } 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 b06308b5e..b1edfeb82 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java @@ -28,24 +28,22 @@ import javax.swing.JOptionPane; import javax.swing.JSeparator; -import org.scijava.Context; - +import bdv.ui.appearance.AppearanceManager; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.editor.labkit.component.EditorKeymapManager; import fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame; import fiji.plugin.trackmate.gui.editor.labkit.model.TMLabKitModel; import fiji.plugin.trackmate.io.TmXmlReader; -import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.ViewUtils; import ij.ImagePlus; import ij.gui.Roi; import net.imagej.axis.Axes; import net.imagej.axis.CalibratedAxis; -import net.imglib2.Interval; import sc.fiji.labkit.ui.labeling.Labeling; public class LabkitLauncher @@ -63,16 +61,13 @@ public static final TMLabKitFrame launch( final GuiModel guiModel, final int tim if ( null == imp ) imp = ViewUtils.makeEmptyImagePlus( model ); - // ROI & interval. - final Interval interval = TMUtils.createROIInterval( imp ); - // Create the LabKit model. - final Context context = TMUtils.getContext(); - final DisplaySettings displaySettings = guiModel.getDisplaySettings(); - final TMLabKitModel lbModel = TMLabKitModel.create( model, imp, interval, displaySettings, timepoint, context ); + final TMLabKitModel lbModel = TMLabKitModel.create( guiModel, timepoint ); // Create the UI for editing. - final TMLabKitFrame labkit = new TMLabKitFrame( lbModel ); + final EditorKeymapManager keymapManager = guiModel.getEditorKeymapManager(); + final AppearanceManager appearanceManager = guiModel.getAppearanceManager(); + final TMLabKitFrame labkit = new TMLabKitFrame( lbModel, keymapManager, appearanceManager ); GuiUtils.positionWindow( labkit, imp.getWindow() ); labkit.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMKeymapManager.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/EditorKeymapManager.java similarity index 91% rename from src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMKeymapManager.java rename to src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/EditorKeymapManager.java index d05709b61..38f5a0f64 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMKeymapManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/EditorKeymapManager.java @@ -21,7 +21,7 @@ */ package fiji.plugin.trackmate.gui.editor.labkit.component; -import static fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame.KEYMAP_HOME; +import static fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame.EDITOR_KEYMAP_HOME; import static fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame.KEY_CONFIG_SCOPE; import java.io.IOException; @@ -37,19 +37,19 @@ import bdv.ui.keymap.KeymapManager; import fiji.plugin.trackmate.util.TMUtils; -public class TMKeymapManager extends KeymapManager +public class EditorKeymapManager extends KeymapManager { private static final String DEFAULT_KEYMAP_PATH = "/keymaps/Default-BDV.yaml"; - public TMKeymapManager() + public EditorKeymapManager() { - super( KEYMAP_HOME ); + super( EDITOR_KEYMAP_HOME ); } static Keymap loadBDVKeymap() { - final InputStream inputStream = TMKeymapManager.class.getResourceAsStream( DEFAULT_KEYMAP_PATH ); + final InputStream inputStream = EditorKeymapManager.class.getResourceAsStream( DEFAULT_KEYMAP_PATH ); if ( inputStream == null ) { diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java index 79cee3302..c3883c5b7 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitActions.java @@ -25,6 +25,7 @@ import static fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame.KEY_CONFIG_SCOPE; import java.awt.Font; +import java.awt.Frame; import javax.swing.SwingUtilities; @@ -55,7 +56,7 @@ public class TMLabKitActions public static void install( final Actions actions, final TMLabKitModel model, - final TMLabKitFrame frame, + final Frame frame, final ViewerPanel viewerPanel, final InputActionBindings keybindings, final KeymapManager keymapManager, @@ -68,11 +69,12 @@ public static void install( */ final PreferencesDialog preferencesDialog = new PreferencesDialog( frame, keymap, new String[] { KEY_CONFIG_CONTEXT } ); + preferencesDialog.setTitle( "Editor Preferences" ); fiji.plugin.trackmate.gui.GuiUtils.positionWindow( preferencesDialog, frame ); BigDataViewerActions.toggleDialogAction( actions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); - preferencesDialog.addPage( new KeymapSettingsPage( "Keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); - preferencesDialog.addPage( new AppearanceSettingsPage( "Appearance", appearanceManager ) ); + preferencesDialog.addPage( new KeymapSettingsPage( "Editor keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); + preferencesDialog.addPage( new AppearanceSettingsPage( "Editor appearance", appearanceManager ) ); appearanceManager.appearance().updateListeners().add( frame::repaint ); SwingUtilities.invokeLater( () -> appearanceManager.updateLookAndFeel() ); diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java index 409954a04..c8589ac25 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/component/TMLabKitFrame.java @@ -86,7 +86,7 @@ public class TMLabKitFrame extends JFrame private static final long serialVersionUID = 1L; - static final String KEYMAP_HOME = new File( new File( System.getProperty( "user.home" ), ".trackmate" ), "editor" ).getAbsolutePath(); + public static final String EDITOR_KEYMAP_HOME = new File( new File( System.getProperty( "user.home" ), ".trackmate" ), "editor" ).getAbsolutePath(); static final String KEY_CONFIG_CONTEXT = "trackmate-labkit"; @@ -94,9 +94,11 @@ public class TMLabKitFrame extends JFrame private final Notifier onCloseListeners = new Notifier(); - public TMLabKitFrame( final TMLabKitModel model ) + public TMLabKitFrame( final TMLabKitModel model, final EditorKeymapManager kmp, final AppearanceManager am ) { final TMImageLabelingModel imageLabelingModel = model.imageLabelingModel(); + final EditorKeymapManager keymapManager = ( kmp == null ) ? new EditorKeymapManager() : kmp; + final AppearanceManager appearanceManager = ( am == null ) ? new AppearanceManager( EDITOR_KEYMAP_HOME ) : am; /* * Here we create a specific config for BDV, so that we can use a custom @@ -106,10 +108,9 @@ public TMLabKitFrame( final TMLabKitModel model ) * So the only solution is to initialize the BDV window with a custom * keymap, configured rationally, and leave it as is. */ - final AppearanceManager appearanceManager = new AppearanceManager( KEYMAP_HOME ); final KeymapManager bdvKeymapManager = new KeymapManager(); final Keymap bdvKeymap = bdvKeymapManager.getForwardSelectedKeymap(); - bdvKeymap.set( TMKeymapManager.loadBDVKeymap() ); + bdvKeymap.set( EditorKeymapManager.loadBDVKeymap() ); final BdvOptions options = BdvOptions.options() .inputTriggerConfig( bdvKeymap.getConfig() ) @@ -156,7 +157,6 @@ public TMLabKitFrame( final TMLabKitModel model ) SwingUtilities.replaceUIActionMap( getRootPane(), keybindings.getConcatenatedActionMap() ); SwingUtilities.replaceUIInputMap( getRootPane(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); - final TMKeymapManager keymapManager = new TMKeymapManager(); final InputTriggerConfig inputTriggerConfig = keymapManager.getForwardSelectedKeymap().getConfig(); // Actions instance diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitModel.java b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitModel.java index cef771a46..065c1d4ea 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitModel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/editor/labkit/model/TMLabKitModel.java @@ -33,6 +33,7 @@ import fiji.plugin.trackmate.SpotCollection; import fiji.plugin.trackmate.detection.DetectionUtils; import fiji.plugin.trackmate.features.FeatureUtils; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; import fiji.plugin.trackmate.util.TMUtils; @@ -133,6 +134,16 @@ public boolean isTrained() throw new UnsupportedOperationException( "TrackMate editor does not have segmenting capabilities" ); } + public final static TMLabKitModel create( final GuiModel guiModel, final int timepoint ) + { + final Model model = guiModel.getModel(); + final ImagePlus imp = guiModel.getSettings().imp; + final Interval interval = TMUtils.createROIInterval( imp ); + final DisplaySettings displaySettings = guiModel.getDisplaySettings(); + final Context context = TMUtils.getContext(); + return create( model, imp, interval, displaySettings, timepoint, context ); + } + /** * Creates a LabKit {@link TMLabKitModel} from the specified TrackMate * model. From b60e167eb43253b19f285ca82def84222dc98e96 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 10:25:52 +0200 Subject: [PATCH 310/371] The main preferences dialog can configure the spot editor. --- src/main/java/fiji/plugin/trackmate/gui/WindowManager.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java index e47d82ffb..2a64c5775 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java @@ -24,6 +24,7 @@ import bdv.BigDataViewerActions; import bdv.tools.CloseWindowActions; import bdv.tools.PreferencesDialog; +import bdv.ui.appearance.AppearanceSettingsPage; import bdv.ui.keymap.Keymap; import bdv.ui.keymap.KeymapSettingsPage; import bdv.util.InvokeOnEDT; @@ -63,8 +64,12 @@ public WindowManager( final GuiModel guiModel ) // Preferences dialog final PreferencesDialog preferencesDialog = new PreferencesDialog( null, keymap, new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE } ); + preferencesDialog.setTitle( "TrackMate Preferences" ); + preferencesDialog.setLocationRelativeTo( null ); BigDataViewerActions.toggleDialogAction( globalActions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); - preferencesDialog.addPage( new KeymapSettingsPage( "Keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); + preferencesDialog.addPage( new KeymapSettingsPage( "Global keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); + preferencesDialog.addPage( new KeymapSettingsPage( "Editor keymap", guiModel.getEditorKeymapManager(), guiModel.getEditorKeymapManager().getCommandDescriptions() ) ); + preferencesDialog.addPage( new AppearanceSettingsPage( "Editor appearance", guiModel.getAppearanceManager() ) ); } public HyperStackDisplayer createHyperStackDisplayer() From 3cc7355de538c68ffa9ac561464fbbc8dd937078 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 10:38:52 +0200 Subject: [PATCH 311/371] Tweak the link spot behavior. Use the spot painters to draw the actual shape of spots. --- .../behaviours/LinkSpotsBehaviour.java | 107 ++++++++++++------ 1 file changed, 71 insertions(+), 36 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java index de5b890b3..3b67e7de5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java @@ -1,6 +1,7 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours; import java.awt.BasicStroke; +import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Shape; @@ -11,6 +12,14 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.Spot.SpotVisitor; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.SpotRoi; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.visualization.hyperstack.PaintSpotMesh; +import fiji.plugin.trackmate.visualization.hyperstack.PaintSpotRoi; +import fiji.plugin.trackmate.visualization.hyperstack.PaintSpotSphere; import ij.ImagePlus; import ij.gui.Roi; import net.imglib2.KDTree; @@ -60,6 +69,8 @@ public void init( final int x, final int y ) // Keep track of the source. this.source = spot; overlay.source = source; + overlay.sourcePixelPos[ 0 ] = x; + overlay.sourcePixelPos[ 1 ] = y; // Move to next frame if forward, previous frame if backward. final int targetFrame = backward ? frame - 1 : frame + 1; @@ -150,25 +161,28 @@ private class LinkSpotsOverlay extends Roi private static final long serialVersionUID = 1L; - private static final Stroke sourceStroke = new BasicStroke( 1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, new float[] { 5f, 5f }, 0.0f ); + private static final Stroke sourceStroke = new BasicStroke( 2f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10.0f, new float[] { 5f, 5f }, 0.0f ); - private static final Stroke targetStroke = new BasicStroke( 1f ); + private static final Stroke targetStroke = new BasicStroke( 2f ); private Spot source; + public final int[] sourcePixelPos = new int[ 2 ]; + private Spot target; public final int[] targetPixelPos = new int[ 2 ]; - private final int[] bb = new int[ 4 ]; - private final ArrowShape arrow = new ArrowShape(); private final CrossedLineShape crossedLine = new CrossedLineShape(); + private final SpotPainter painter; + public LinkSpotsOverlay( final ImagePlus imp ) { super( 0, 0, imp ); + this.painter = new SpotPainter(); } @Override @@ -177,28 +191,26 @@ public void drawOverlay( final Graphics g ) if ( source == null ) return; - final int xcorner = ic.offScreenX( 0 ); - final int ycorner = ic.offScreenY( 0 ); - final double magnification = getMagnification(); final Graphics2D g2d = ( Graphics2D ) g; + g2d.setColor( Color.WHITE ); + painter.setGraphics( g2d ); // Source bounding box - boundingBox( source, xcorner, ycorner, magnification ); g2d.setStroke( sourceStroke ); - g2d.drawRect( bb[ 0 ], bb[ 1 ], bb[ 2 ], bb[ 3 ] ); + source.accept( painter ); // Arrow to current pos. if ( backward ) { arrow.x1d = targetPixelPos[ 0 ]; arrow.y1d = targetPixelPos[ 1 ]; - arrow.x2d = bb[ 0 ] + bb[ 2 ] / 2; - arrow.y2d = bb[ 1 ] + bb[ 3 ] / 2; + arrow.x2d = sourcePixelPos[ 0 ]; + arrow.y2d = sourcePixelPos[ 1 ]; } else { - arrow.x1d = bb[ 0 ] + bb[ 2 ] / 2; - arrow.y1d = bb[ 1 ] + bb[ 3 ] / 2; + arrow.x1d = sourcePixelPos[ 0 ]; + arrow.y1d = sourcePixelPos[ 1 ]; arrow.x2d = targetPixelPos[ 0 ]; arrow.y2d = targetPixelPos[ 1 ]; } @@ -214,32 +226,55 @@ public void drawOverlay( final Graphics g ) // Target bounding box if ( target != null ) - { - boundingBox( target, xcorner, ycorner, magnification ); - g2d.drawRect( bb[ 0 ], bb[ 1 ], bb[ 2 ], bb[ 3 ] ); - } + target.accept( painter ); } - private final void boundingBox( - final Spot spot, - final double xcorner, - final double ycorner, - final double magnification ) + private class SpotPainter implements SpotVisitor { - // Pixel coords. - final double xpmin = spot.realMin( 0 ) / calibration[ 0 ] + 0.5f; - final double ypmin = spot.realMin( 1 ) / calibration[ 1 ] + 0.5f; - final double xpmax = spot.realMax( 0 ) / calibration[ 0 ] + 0.5f; - final double ypmax = spot.realMax( 1 ) / calibration[ 1 ] + 0.5f; - // Display window coordinates. - final double xsmin = ( xpmin - xcorner ) * magnification; - final double ysmin = ( ypmin - ycorner ) * magnification; - final double xsmax = ( xpmax - xcorner ) * magnification; - final double ysmax = ( ypmax - ycorner ) * magnification; - bb[ 0 ] = ( int ) Math.round( xsmin ); - bb[ 1 ] = ( int ) Math.round( ysmin ); - bb[ 2 ] = ( int ) Math.round( xsmax - xsmin ); - bb[ 3 ] = ( int ) Math.round( ysmax - ysmin ); + + private final PaintSpotRoi paintSpotRoi; + + private final PaintSpotSphere paintSpotSphere; + + private final PaintSpotMesh paintSpotMesh; + + private final DisplaySettings displaySettings; + + private Graphics2D g2d; + + public SpotPainter() + { + this.displaySettings = DisplaySettings.defaultStyle().copy( "Link spot overlay settings" ); + displaySettings.setSpotUniformColor( Color.WHITE ); + displaySettings.setSpotShowName( true ); + displaySettings.setLineThickness( 2. ); + this.paintSpotSphere = new PaintSpotSphere( imp, calibration, displaySettings ); + this.paintSpotRoi = new PaintSpotRoi( imp, calibration, displaySettings ); + this.paintSpotMesh = new PaintSpotMesh( imp, calibration, displaySettings ); + } + + private void setGraphics( final Graphics2D g2d ) + { + this.g2d = g2d; + } + + @Override + public void visit( final SpotBase spot ) + { + paintSpotSphere.paint( g2d, spot ); + } + + @Override + public void visit( final SpotRoi spot ) + { + paintSpotRoi.paint( g2d, spot ); + } + + @Override + public void visit( final SpotMesh spot ) + { + paintSpotMesh.paint( g2d, spot ); + } } } From 959cfe451e4d68badb282a968c5df2e0e1f0a04f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 11:48:35 +0200 Subject: [PATCH 312/371] A new action to add and or link new spots. Default keybinding 'A' or 'shift A' to do it backward in time. This action does a bit everything: - If there is no spot at click location, create one. The user can position it by holding the key and dragging the mouse around. - If there is a spot, move to the next frame (or previous) and link a new target spot from it. If the mouse enters an existing spot, then it becomes the target of the link. If a link exists already, it can be removed. Greatly inspired by Mastodon. This action does a bit everything, with plenty of null checks, but is super useful in practice to quickly annotate a movie. --- .../behaviours/AddAndLinkSpotBehaviour.java | 167 ++++++++++++++++++ .../behaviours/LinkSpotsBehaviour.java | 85 ++++----- .../behaviours/SpotEditBehaviours.java | 79 +-------- 3 files changed, 218 insertions(+), 113 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AddAndLinkSpotBehaviour.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AddAndLinkSpotBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AddAndLinkSpotBehaviour.java new file mode 100644 index 000000000..d1bce509b --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AddAndLinkSpotBehaviour.java @@ -0,0 +1,167 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; +import ij.ImagePlus; +import net.imglib2.KDTree; +import net.imglib2.RealLocalizable; +import net.imglib2.neighborsearch.NearestNeighborSearchOnKDTree; + +public class AddAndLinkSpotBehaviour extends LinkSpotsBehaviour +{ + + private static final String OVERLAY_NAME = "AddAndLinkSpotActionOverlay"; + + private SpotBase newSpot; + + public AddAndLinkSpotBehaviour( final Model model, final ImagePlus imp, final boolean backward ) + { + super( model, imp, backward ); + } + + @Override + public void init( final int x, final int y ) + { + if ( source != null && target != null ) + return; + + final RealLocalizable pos = toWorldCoords( x, y ); + final Spot spot = getSpotAtMouseLocation( pos ); + final int targetFrame; + if ( spot == null ) + { + // Create a new spot, that we will move around until the user + // releases the mouse button. + this.source = null; + overlay.source = null; + targetFrame = imp.getT() - 1; + this.search = null; + } + else + { + // We a source, link from it. + // Cannot link if at first frame and backward, etc. + final int frame = spot.getFeature( Spot.FRAME ).intValue(); + if ( backward && frame == 0 ) + return; + if ( !backward && frame == imp.getNFrames() - 1 ) + return; + + // Keep track of the source. + this.source = spot; + overlay.source = source; + overlay.sourcePixelPos[ 0 ] = x; + overlay.sourcePixelPos[ 1 ] = y; + + // Move to next frame if forward, previous frame if backward. + targetFrame = backward ? frame - 1 : frame + 1; + imp.setT( targetFrame + 1 ); + + // Build KD-tree of target frame spots. + final Iterable< Spot > targetSpots = model.getSpots().iterable( targetFrame, true ); + final int nTargetSpots = model.getSpots().getNSpots( targetFrame, true ); + final KDTree< Spot > tree = new KDTree< Spot >( nTargetSpots, targetSpots, targetSpots ); + this.search = new NearestNeighborSearchOnKDTree<>( tree ); + } + + // Add a new spot at the mouse location in the target frame. + final double radius = SpotEditBehaviours.ResizeSpotBehaviour.previousRadius; + this.newSpot = new SpotBase( pos, radius, -1. ); + final double dt = imp.getCalibration().frameInterval; + newSpot.putFeature( Spot.POSITION_T, targetFrame * dt ); + newSpot.putFeature( Spot.FRAME, Double.valueOf( targetFrame ) ); + + target = newSpot; + overlay.target = newSpot; + overlay.targetPixelPos[ 0 ] = x; + overlay.targetPixelPos[ 1 ] = y; + imp.getOverlay().add( overlay, OVERLAY_NAME ); + imp.updateAndDraw(); + } + + @Override + public void drag( final int x, final int y ) + { + final RealLocalizable pos = toWorldCoords( x, y ); + if ( search != null ) + { + search.search( pos ); + final Spot closestSpot = search.getSampler().get(); + final double r = closestSpot.getFeature( Spot.RADIUS ); + if ( search.getSquareDistance() < r * r ) + { + target = closestSpot; + final RealLocalizable screenPos = toScreenCoords( target ); + overlay.targetPixelPos[ 0 ] = ( int ) Math.round( screenPos.getDoublePosition( 0 ) ); + overlay.targetPixelPos[ 1 ] = ( int ) Math.round( screenPos.getDoublePosition( 1 ) ); + overlay.target = closestSpot; + + // Is there an existing link between source and target? + overlay.crossedLine.crossed = model.getTrackModel().containsEdge( source, target ); + imp.updateAndDraw(); + return; + } + } + // Simply move the created spot. + newSpot.setPosition( pos.getDoublePosition( 0 ), 0 ); + newSpot.setPosition( pos.getDoublePosition( 1 ), 1 ); + target = newSpot; + overlay.targetPixelPos[ 0 ] = x; + overlay.targetPixelPos[ 1 ] = y; + overlay.target = newSpot; + overlay.crossedLine.crossed = false; + imp.updateAndDraw(); + } + + @Override + public void end( final int x, final int y ) + { + try + { + if ( target == null ) + return; + + model.beginUpdate(); + try + { + if ( target == newSpot ) + { + model.addSpotTo( newSpot, newSpot.getFeature( Spot.FRAME ).intValue() ); + if ( source != null ) + model.addEdge( source, newSpot, -1 ); + } + else + { + // Add or remove link between source and pre-existing + // target. + if ( model.getTrackModel().containsEdge( source, target ) ) + { + model.removeEdge( source, target ); + } + else + { + if ( backward ) + model.addEdge( target, source, -1 ); + else + model.addEdge( source, target, -1 ); + } + } + } + finally + { + model.endUpdate(); + } + } + finally + { + source = null; + target = null; + search = null; + overlay.source = null; + overlay.target = null; + imp.updateAndDraw(); + imp.getOverlay().remove( OVERLAY_NAME ); + } + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java index 3b67e7de5..6bda614f1 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/LinkSpotsBehaviour.java @@ -31,15 +31,15 @@ public class LinkSpotsBehaviour extends AbstractSpotEditBehaviour implements Dra private static final String OVERLAY_NAME = "LinkSpotActionOverlay"; - private final boolean backward; + protected final boolean backward; - private Spot source; + protected Spot source; - private Spot target; + protected Spot target; - private NearestNeighborSearchOnKDTree< Spot > search; + protected NearestNeighborSearchOnKDTree< Spot > search; - private final LinkSpotsOverlay overlay; + protected final LinkSpotsOverlay overlay; public LinkSpotsBehaviour( final Model model, final ImagePlus imp, final boolean backward ) { @@ -156,7 +156,7 @@ public void end( final int x, final int y ) } } - private class LinkSpotsOverlay extends Roi + class LinkSpotsOverlay extends Roi { private static final long serialVersionUID = 1L; @@ -165,17 +165,17 @@ private class LinkSpotsOverlay extends Roi private static final Stroke targetStroke = new BasicStroke( 2f ); - private Spot source; + Spot source; - public final int[] sourcePixelPos = new int[ 2 ]; + final int[] sourcePixelPos = new int[ 2 ]; - private Spot target; + Spot target; - public final int[] targetPixelPos = new int[ 2 ]; + final int[] targetPixelPos = new int[ 2 ]; private final ArrowShape arrow = new ArrowShape(); - private final CrossedLineShape crossedLine = new CrossedLineShape(); + final CrossedLineShape crossedLine = new CrossedLineShape(); private final SpotPainter painter; @@ -188,43 +188,44 @@ public LinkSpotsOverlay( final ImagePlus imp ) @Override public void drawOverlay( final Graphics g ) { - if ( source == null ) - return; - final Graphics2D g2d = ( Graphics2D ) g; g2d.setColor( Color.WHITE ); painter.setGraphics( g2d ); - // Source bounding box - g2d.setStroke( sourceStroke ); - source.accept( painter ); - - // Arrow to current pos. - if ( backward ) + if ( source != null ) { - arrow.x1d = targetPixelPos[ 0 ]; - arrow.y1d = targetPixelPos[ 1 ]; - arrow.x2d = sourcePixelPos[ 0 ]; - arrow.y2d = sourcePixelPos[ 1 ]; - } - else - { - arrow.x1d = sourcePixelPos[ 0 ]; - arrow.y1d = sourcePixelPos[ 1 ]; - arrow.x2d = targetPixelPos[ 0 ]; - arrow.y2d = targetPixelPos[ 1 ]; + // Source outline + g2d.setStroke( sourceStroke ); + source.accept( painter ); + + // Arrow to current pos. + if ( backward ) + { + arrow.x1d = targetPixelPos[ 0 ]; + arrow.y1d = targetPixelPos[ 1 ]; + arrow.x2d = sourcePixelPos[ 0 ]; + arrow.y2d = sourcePixelPos[ 1 ]; + } + else + { + arrow.x1d = sourcePixelPos[ 0 ]; + arrow.y1d = sourcePixelPos[ 1 ]; + arrow.x2d = targetPixelPos[ 0 ]; + arrow.y2d = targetPixelPos[ 1 ]; + } + crossedLine.x1d = arrow.x1d; + crossedLine.y1d = arrow.y1d; + crossedLine.x2d = arrow.x2d; + crossedLine.y2d = arrow.y2d; + + g2d.setStroke( targetStroke ); + g2d.draw( crossedLine.getPath() ); + if ( !crossedLine.crossed ) + g2d.fill( arrow.getPath() ); } - crossedLine.x1d = arrow.x1d; - crossedLine.y1d = arrow.y1d; - crossedLine.x2d = arrow.x2d; - crossedLine.y2d = arrow.y2d; + // Target outline g2d.setStroke( targetStroke ); - g2d.draw( crossedLine.getPath() ); - if ( !crossedLine.crossed ) - g2d.fill( arrow.getPath() ); - - // Target bounding box if ( target != null ) target.accept( painter ); } @@ -420,7 +421,7 @@ private Shape getPath() } } - private static class CrossedLineShape + static class CrossedLineShape { private final Path2D.Double path = new Path2D.Double( Path2D.WIND_NON_ZERO ); @@ -431,7 +432,7 @@ private static class CrossedLineShape private double x1d, y1d, x2d, y2d; - private boolean crossed = false; + boolean crossed = false; private Path2D.Double getPath() { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java index 7adf939b8..eb6f10cbe 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java @@ -1,7 +1,5 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours; -import java.util.Set; - import org.scijava.plugin.Plugin; import org.scijava.ui.behaviour.ClickBehaviour; import org.scijava.ui.behaviour.DragBehaviour; @@ -12,7 +10,6 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import ij.ImagePlus; import net.imglib2.RealLocalizable; @@ -29,6 +26,8 @@ public class SpotEditBehaviours private static final String DELETE_SPOT = "delete spot"; private static final String LINK_SPOTS = "link spots"; private static final String LINK_SPOTS_BACKWARD = "link spots backward"; + private static final String ADD_AND_LINK_SPOTS_FORWARD = "add and link spots forward"; + private static final String ADD_AND_LINK_SPOTS_BACKWARD = "add and link spots backward"; private static final String CLICK_SELECT_SPOT = "click select spot"; private static final String CLICK_SELECT_ADD_SPOT = "click select add spot"; @@ -41,6 +40,8 @@ public class SpotEditBehaviours private static final String[] DELETE_SPOT_KEYS = new String[] { "D" }; private static final String[] LINK_SPOTS_KEYS = new String[] { "L" }; private static final String[] LINK_SPOTS_BACKWARD_KEYS = new String[] { "shift L" }; + private static final String[] ADD_AND_LINK_SPOTS_FORWARD_KEYS = new String[] { "A" }; + private static final String[] ADD_AND_LINK_SPOTS_BACKWARD_KEYS = new String[] { "shift A" }; private static final String[] CLICK_SELECT_SPOT_KEYS = new String[] { "button1" }; private static final String[] CLICK_SELECT_ADD_SPOT_KEYS = new String[] { "shift button1" }; @@ -55,12 +56,14 @@ public static final void install( final Behaviours behaviours, final Model model behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, false ), DECREASE_SPOT_RADIUS, DECREASE_SPOT_RADIUS_KEYS ); behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, true ), DECREASE_SPOT_RADIUS_FAST, DECREASE_SPOT_RADIUS_FAST_KEYS ); - behaviours.behaviour( new AddSpotBehaviour( model, selectionModel, imp ), ADD_SPOT, ADD_SPOT_KEYS ); behaviours.behaviour( new DeleteSpotBehaviour( model, selectionModel, imp ), DELETE_SPOT, DELETE_SPOT_KEYS ); behaviours.behaviour( new LinkSpotsBehaviour( model, imp, false ), LINK_SPOTS, LINK_SPOTS_KEYS ); behaviours.behaviour( new LinkSpotsBehaviour( model, imp, true ), LINK_SPOTS_BACKWARD, LINK_SPOTS_BACKWARD_KEYS ); + behaviours.behaviour( new AddAndLinkSpotBehaviour( model, imp, false ), ADD_AND_LINK_SPOTS_FORWARD, ADD_AND_LINK_SPOTS_FORWARD_KEYS ); + behaviours.behaviour( new AddAndLinkSpotBehaviour( model, imp, true ), ADD_AND_LINK_SPOTS_BACKWARD, ADD_AND_LINK_SPOTS_BACKWARD_KEYS ); + behaviours.behaviour( new ClickSelectSpotBehaviour( model, selectionModel, imp ), CLICK_SELECT_SPOT, CLICK_SELECT_SPOT_KEYS ); behaviours.behaviour( new ClickSelectAddSpotBehaviour( model, selectionModel, imp ), CLICK_SELECT_ADD_SPOT, CLICK_SELECT_ADD_SPOT_KEYS ); } @@ -145,72 +148,6 @@ public void click( final int x, final int y ) } } - private static class AddSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour - { - - private final SelectionModel selectionModel; - - public AddSpotBehaviour( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) - { - super( model, imp ); - this.selectionModel = selectionModel; - } - - @Override - public void click( final int x, final int y ) - { - final RealLocalizable pos = toWorldCoords( x, y ); - // Forbid adding a spot if there is already one at this location. - if ( getSpotAtMouseLocation( pos ) != null ) - return; - - final double radius = ResizeSpotBehaviour.previousRadius; - final SpotBase newSpot = new SpotBase( pos, radius, -1. ); - - final double dt = imp.getCalibration().frameInterval; - final int frame = imp.getFrame() - 1; - newSpot.putFeature( Spot.POSITION_T, frame * dt ); - newSpot.putFeature( Spot.FRAME, Double.valueOf( frame ) ); - - model.beginUpdate(); - try - { - model.addSpotTo( newSpot, frame ); - } - finally - { - model.endUpdate(); - } - - /* - * If we are in auto-link mode, we create an edge with spot in - * selection, if there is just one and if it is in a previous frame - */ - if ( autoLinkingmode ) - { - final Set< Spot > spotSelection = selectionModel.getSpotSelection(); - if ( spotSelection.size() == 1 ) - { - final Spot source = spotSelection.iterator().next(); - if ( newSpot.diffTo( source, Spot.FRAME ) != 0 ) - { - model.beginUpdate(); - try - { - model.addEdge( source, newSpot, -1 ); - } - finally - { - model.endUpdate(); - } - } - } - selectionModel.clearSpotSelection(); - selectionModel.addSpotToSelection( newSpot ); - } - } - } - private static class MoveSpotBehaviour extends AbstractSpotEditBehaviour implements DragBehaviour { @@ -259,7 +196,7 @@ public void end( final int x, final int y ) } } - private static class ResizeSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour + static class ResizeSpotBehaviour extends AbstractSpotEditBehaviour implements ClickBehaviour { /** From d5fbe7c29f34ea0aa2486bd2a2e8fbb19fde57d5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 11:49:03 +0200 Subject: [PATCH 313/371] Change the default shortcut for semi-automatic tracking. So that it does not clash with the add and link spot behaviour. --- .../hyperstack/behaviours/HyperStackDisplayerActions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java index 660fe1227..da8806bf0 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java @@ -21,7 +21,7 @@ public class HyperStackDisplayerActions private static final String[] PREVIOUS_TIMEPOINT_KEYS = new String[] { "F" }; private static final String SEMI_AUTOMATIC_TRACKING = "semi-automatic tracking"; - private static final String[] SEMI_AUTOMATIC_TRACKING_KEYS = new String[] { "shift A" }; + private static final String[] SEMI_AUTOMATIC_TRACKING_KEYS = new String[] { "shift T" }; private static final String DO_NOTHING_ACTION = "do nothing"; private static final String[] DO_NOTHING_ACTION_KEYS = new String[] { "W" }; From f3ea9969108cb2bb5119f64eadfeee0e7ec19512 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 17:38:47 +0200 Subject: [PATCH 314/371] Put back the default add spot action, but make it not mapped. So that users can still use it if they modify the keybindings. --- .../behaviours/SpotEditBehaviours.java | 83 ++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java index eb6f10cbe..21c0ecdd5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditBehaviours.java @@ -1,5 +1,7 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours; +import java.util.Set; + import org.scijava.plugin.Plugin; import org.scijava.ui.behaviour.ClickBehaviour; import org.scijava.ui.behaviour.DragBehaviour; @@ -10,9 +12,11 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import ij.ImagePlus; import net.imglib2.RealLocalizable; +import net.imglib2.util.Util; public class SpotEditBehaviours { @@ -36,7 +40,7 @@ public class SpotEditBehaviours private static final String[] INCREASE_SPOT_RADIUS_FAST_KEYS = new String[] { "shift E" }; private static final String[] DECREASE_SPOT_RADIUS_KEYS = new String[] { "Q" }; private static final String[] DECREASE_SPOT_RADIUS_FAST_KEYS = new String[] { "shift Q" }; - private static final String[] ADD_SPOT_KEYS = new String[] { "A" }; + private static final String[] ADD_SPOT_KEYS = new String[] { "not mapped" }; private static final String[] DELETE_SPOT_KEYS = new String[] { "D" }; private static final String[] LINK_SPOTS_KEYS = new String[] { "L" }; private static final String[] LINK_SPOTS_BACKWARD_KEYS = new String[] { "shift L" }; @@ -56,6 +60,7 @@ public static final void install( final Behaviours behaviours, final Model model behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, false ), DECREASE_SPOT_RADIUS, DECREASE_SPOT_RADIUS_KEYS ); behaviours.behaviour( new ResizeSpotBehaviour( model, imp, false, true ), DECREASE_SPOT_RADIUS_FAST, DECREASE_SPOT_RADIUS_FAST_KEYS ); + behaviours.behaviour( new AddSpotBehaviour( model, selectionModel, imp ), ADD_SPOT, ADD_SPOT_KEYS ); behaviours.behaviour( new DeleteSpotBehaviour( model, selectionModel, imp ), DELETE_SPOT, DELETE_SPOT_KEYS ); behaviours.behaviour( new LinkSpotsBehaviour( model, imp, false ), LINK_SPOTS, LINK_SPOTS_KEYS ); @@ -148,6 +153,80 @@ public void click( final int x, final int y ) } } + private static class AddSpotBehaviour extends AbstractSpotEditBehaviour implements DragBehaviour + { + + private final SelectionModel selectionModel; + + private SpotBase newSpot; + + public AddSpotBehaviour( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + { + super( model, imp ); + this.selectionModel = selectionModel; + } + + @Override + public void init( final int x, final int y ) + { + if ( null != newSpot ) + return; + + final RealLocalizable pos = toWorldCoords( x, y ); + // Forbid adding a spot if there is already one at this location. + if ( getSpotAtMouseLocation( pos ) != null ) + return; + + final double radius = ResizeSpotBehaviour.previousRadius; + this.newSpot = new SpotBase( pos, radius, -1. ); + + final double dt = imp.getCalibration().frameInterval; + final int frame = imp.getFrame() - 1; + newSpot.putFeature( Spot.POSITION_T, frame * dt ); + newSpot.putFeature( Spot.FRAME, Double.valueOf( frame ) ); + + model.beginUpdate(); + model.addSpotTo( newSpot, frame ); + + /* + * If we are in auto-link mode, we create an edge with spot in + * selection, if there is just one and if it is in a previous frame + */ + if ( autoLinkingmode ) + { + final Set< Spot > spotSelection = selectionModel.getSpotSelection(); + if ( spotSelection.size() == 1 ) + { + final Spot source = spotSelection.iterator().next(); + if ( newSpot.diffTo( source, Spot.FRAME ) != 0 ) + model.addEdge( source, newSpot, -1 ); + } + selectionModel.clearSpotSelection(); + selectionModel.addSpotToSelection( newSpot ); + } + imp.updateAndDraw(); + } + + @Override + public void drag( final int x, final int y ) + { + final RealLocalizable pos = toWorldCoords( x, y ); + newSpot.setPosition( pos.getDoublePosition( 0 ), 0 ); + newSpot.setPosition( pos.getDoublePosition( 1 ), 1 ); + imp.updateAndDraw(); + System.out.println( Util.printCoordinates( newSpot ) ); + + } + + @Override + public void end( final int x, final int y ) + { + model.endUpdate(); + newSpot = null; + imp.updateAndDraw(); + } + } + private static class MoveSpotBehaviour extends AbstractSpotEditBehaviour implements DragBehaviour { @@ -284,6 +363,8 @@ public void getCommandDescriptions( final CommandDescriptions descriptions ) descriptions.add( LINK_SPOTS_BACKWARD, LINK_SPOTS_BACKWARD_KEYS, "Link two spots backward in time by dragging from a source spot to a target spot in the previous time-point." ); descriptions.add( CLICK_SELECT_SPOT, CLICK_SELECT_SPOT_KEYS, "Select a spot at the mouse location." ); descriptions.add( CLICK_SELECT_ADD_SPOT, CLICK_SELECT_ADD_SPOT_KEYS, "Add or remove a spot from the selection at the mouse location." ); + descriptions.add( ADD_AND_LINK_SPOTS_FORWARD, ADD_AND_LINK_SPOTS_FORWARD_KEYS, "Add a new spot at the mouse location or link the source spot to a new target spot in the next time-point." ); + descriptions.add( ADD_AND_LINK_SPOTS_BACKWARD, ADD_AND_LINK_SPOTS_BACKWARD_KEYS, "Add a new spot at the mouse location or link the source spot to a new target spot in the previous time-point." ); } } } From f32c666e47bd84a2e211b89060cb4544e4d283b3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 17:45:59 +0200 Subject: [PATCH 315/371] Add and register selectAll() commands. --- .../visualization/ui/TrackMateActions.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java index 8c2df8836..6693729cd 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java @@ -3,6 +3,7 @@ import java.awt.Toolkit; import java.awt.event.InputEvent; import java.util.ArrayList; +import java.util.List; import org.jgrapht.graph.DefaultWeightedEdge; import org.scijava.plugin.Plugin; @@ -45,12 +46,22 @@ public class TrackMateActions private static final String DELETE_SELECTION = "delete selection"; private static final String[] DELETE_SELECTION_KEYS = new String[] { "BACK_SPACE", "DELETE" }; + private static final String SELECT_ALL = "select all"; + private static final String SELECT_ALL_SPOTS = "select all spots"; + private static final String SELECT_ALL_LINKS = "select all links"; + private static final String[] SELECT_ALL_KEYS; + private static final String[] SELECT_ALL_SPOTS_KEYS; + private static final String[] SELECT_ALL_LINKS_KEYS; + static { final int menuMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); final String modifier = ( menuMask == InputEvent.CTRL_DOWN_MASK ) ? "ctrl" : "meta"; UNDO_ACTION_KEYS = new String[] { modifier + " Z" }; REDO_ACTION_KEYS = new String[] { modifier + " Y", modifier + " shift Z" }; + SELECT_ALL_KEYS = new String[] { modifier + " A" }; + SELECT_ALL_SPOTS_KEYS = new String[] { modifier + " shift A" }; + SELECT_ALL_LINKS_KEYS = new String[] { modifier + " alt A" }; } public static final void install( final Actions actions, final Model model, final SelectionModel selectionModel ) @@ -73,6 +84,31 @@ public static final void install( final Actions actions, final Model model, fina // Delete selection actions.runnableAction( () -> deleteSelection( model, selectionModel ), DELETE_SELECTION, DELETE_SELECTION_KEYS ); + + // Select all + actions.runnableAction( () -> selectAll( model, selectionModel ), SELECT_ALL, SELECT_ALL_KEYS ); + actions.runnableAction( () -> selectAllSpots( model, selectionModel ), SELECT_ALL_SPOTS, SELECT_ALL_SPOTS_KEYS ); + actions.runnableAction( () -> selectAllLinks( model, selectionModel ), SELECT_ALL_LINKS, SELECT_ALL_LINKS_KEYS ); + } + + private static void selectAll( final Model model, final SelectionModel selectionModel ) + { + selectAllSpots( model, selectionModel ); + selectAllLinks( model, selectionModel ); + } + + private static void selectAllSpots( final Model model, final SelectionModel selectionModel ) + { + final List< Spot > spotsToAdd = new ArrayList<>(); + model.getSpots().iterable( true ).forEach( spotsToAdd::add ); + selectionModel.addSpotToSelection( spotsToAdd ); + } + + private static void selectAllLinks( final Model model, final SelectionModel selectionModel ) + { + final List< DefaultWeightedEdge > edgesToAdd = new ArrayList<>(); + model.getTrackModel().edgeSet().forEach( edgesToAdd::add ); + selectionModel.addEdgeToSelection( edgesToAdd ); } private static void deleteSelection( final Model model, final SelectionModel selectionModel ) @@ -118,6 +154,9 @@ public void getCommandDescriptions( final CommandDescriptions descriptions ) descriptions.add( NAVIGATE_TO_NEXT_TRACK, NAVIGATE_TO_NEXT_TRACK_KEYS, "Navigate to the next track." ); descriptions.add( DELETE_SELECTION, DELETE_SELECTION_KEYS, "Delete the selected spots and edges." ); + descriptions.add( SELECT_ALL, SELECT_ALL_KEYS, "Select all spots and edges." ); + descriptions.add( SELECT_ALL_SPOTS, SELECT_ALL_SPOTS_KEYS, "Select all spots." ); + descriptions.add( SELECT_ALL_LINKS, SELECT_ALL_LINKS_KEYS, "Select all edges." ); } } } From c1831e0dd0b1ea45d44ec3a4cfbe229f28cffe65 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 18:28:39 +0200 Subject: [PATCH 316/371] Semi auto tracking params is now a Configurator class. And - has a config page in the Preferences dialog. - is properly used in the upgraded stepwise time browsing, now taken from MaMuT. --- .../fiji/plugin/trackmate/gui/GuiModel.java | 10 ++- .../plugin/trackmate/gui/WindowManager.java | 2 + .../hyperstack/HyperStackDisplayer.java | 3 +- .../HyperStackDisplayerActions.java | 86 ++++++++++++++++--- .../behaviours/SpotEditToolParams.java | 35 -------- .../SemiAutoTracking.java | 31 +++---- .../SemiAutoTrackingParams.java | 84 ++++++++++++++++++ .../SpotEditToolSettingsPage.java | 78 +++++++++++++++++ 8 files changed, 263 insertions(+), 66 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditToolParams.java rename src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/{ => semiautotracking}/SemiAutoTracking.java (61%) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java index 158befb43..40d7f281a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java @@ -15,6 +15,7 @@ import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.editor.labkit.component.EditorKeymapManager; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SemiAutoTrackingParams; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import fiji.plugin.trackmate.visualization.ui.TrackMateActions; import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; @@ -26,7 +27,6 @@ */ public class GuiModel { - private final Model model; @@ -34,6 +34,8 @@ public class GuiModel private final DisplaySettings displaySettings; + private final SemiAutoTrackingParams semiAutoTrackingparams; + private final Actions globalActions; private final KeyPressedManager keyPressedManager; @@ -68,6 +70,7 @@ public GuiModel( final Model model, final Settings settings, final DisplaySettin this.selectionModel = new SelectionModel( model ); this.displaySettings = displaySettings; this.trackmate = createTrackMate( model, settings ); + this.semiAutoTrackingparams = new SemiAutoTrackingParams(); // Keymap and actions this.editorKeymapManager = new EditorKeymapManager(); @@ -229,4 +232,9 @@ public AppearanceManager getAppearanceManager() { return appearanceManager; } + + public SemiAutoTrackingParams getSemiAutoTrackingParams() + { + return semiAutoTrackingparams; + } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java index 2a64c5775..5b25959ed 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java @@ -35,6 +35,7 @@ import fiji.plugin.trackmate.visualization.TrackMateModelView; import fiji.plugin.trackmate.visualization.bvv.TrackMateBVV; import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SpotEditToolSettingsPage; import fiji.plugin.trackmate.visualization.table.AllSpotsTableView; import fiji.plugin.trackmate.visualization.table.TrackTableView; import fiji.plugin.trackmate.visualization.trackscheme.SpotImageUpdater; @@ -67,6 +68,7 @@ public WindowManager( final GuiModel guiModel ) preferencesDialog.setTitle( "TrackMate Preferences" ); preferencesDialog.setLocationRelativeTo( null ); BigDataViewerActions.toggleDialogAction( globalActions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); + preferencesDialog.addPage( new SpotEditToolSettingsPage( "Semi-auto tracking", guiModel.getSemiAutoTrackingParams() ) ); preferencesDialog.addPage( new KeymapSettingsPage( "Global keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); preferencesDialog.addPage( new KeymapSettingsPage( "Editor keymap", guiModel.getEditorKeymapManager(), guiModel.getEditorKeymapManager().getCommandDescriptions() ) ); preferencesDialog.addPage( new AppearanceSettingsPage( "Editor appearance", guiModel.getAppearanceManager() ) ); 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 3878045b4..0d53ca26e 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/HyperStackDisplayer.java @@ -159,14 +159,13 @@ public void windowClosing( final WindowEvent e ) final TrackMateKeymapManager keymapManager = guiModel.getKeymapManager(); final ImagePlusBehavioursAdapter adapter = new ImagePlusBehavioursAdapter( imp, keymapManager, new String[] { KeyConfigContexts.HYPERSTACK_DISPLAYER, KeyConfigContexts.TRACKMATE } ); SpotEditBehaviours.install( adapter.behaviours(), model, selectionModel, imp ); - HyperStackDisplayerActions.install( adapter.actions(), model, selectionModel, imp ); + HyperStackDisplayerActions.install( adapter.actions(), guiModel, imp ); // Select spots with freehand ROI. SelectSpotsWithRoiListener.install( model, selectionModel, imp ); // Global actions. final Actions globalActions = guiModel.getGlobalActions(); adapter.keybindings().addActionMap( "global", new WrappedActionMap( globalActions.getActionMap() ) ); adapter.keybindings().addInputMap( "global", new WrappedInputMap( globalActions.getInputMap() ) ); - } catch ( final Exception e ) { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java index da8806bf0..d61ac9e4f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/HyperStackDisplayerActions.java @@ -1,33 +1,45 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours; +import java.awt.event.ActionEvent; + import org.scijava.plugin.Plugin; import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; import org.scijava.ui.behaviour.io.gui.CommandDescriptions; +import org.scijava.ui.behaviour.util.AbstractNamedAction; import org.scijava.ui.behaviour.util.Actions; -import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.SelectionModel; +import fiji.plugin.trackmate.gui.GuiModel; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SemiAutoTracking; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SemiAutoTrackingParams; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import ij.ImagePlus; public class HyperStackDisplayerActions { private static final String TOGGLE_AUTO_LINKING = "toggle auto-linking"; + private static final String[] TOGGLE_AUTO_LINKING_KEYS = new String[] { "ctrl L" }; - + private static final String NEXT_TIMEPOINT = "next timepoint"; + private static final String PREVIOUS_TIMEPOINT = "previous timepoint"; + private static final String[] NEXT_TIMEPOINT_KEYS = new String[] { "G" }; + private static final String[] PREVIOUS_TIMEPOINT_KEYS = new String[] { "F" }; private static final String SEMI_AUTOMATIC_TRACKING = "semi-automatic tracking"; + private static final String[] SEMI_AUTOMATIC_TRACKING_KEYS = new String[] { "shift T" }; - + private static final String DO_NOTHING_ACTION = "do nothing"; + private static final String[] DO_NOTHING_ACTION_KEYS = new String[] { "W" }; - public static final void install( final Actions actions, final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + public static final void install( final Actions actions, final GuiModel guiModel, final ImagePlus imp ) { + final SemiAutoTrackingParams params = guiModel.getSemiAutoTrackingParams(); + // Toggle auto-linking actions.runnableAction( () -> SpotEditBehaviours.autoLinkingmode = !SpotEditBehaviours.autoLinkingmode, TOGGLE_AUTO_LINKING, TOGGLE_AUTO_LINKING_KEYS ); @@ -35,14 +47,12 @@ public static final void install( final Actions actions, final Model model, fina actions.runnableAction( () -> {}, DO_NOTHING_ACTION, DO_NOTHING_ACTION_KEYS ); // Change timepoint - actions.runnableAction( () -> imp.setT( imp.getT() + SemiAutoTracking.params.stepwiseTimeBrowsing ), NEXT_TIMEPOINT, NEXT_TIMEPOINT_KEYS ); - actions.runnableAction( () -> imp.setT( imp.getT() - SemiAutoTracking.params.stepwiseTimeBrowsing ), PREVIOUS_TIMEPOINT, PREVIOUS_TIMEPOINT_KEYS ); - + actions.namedAction( new StepWiseTimeBrowsingAction( imp, params, true ), NEXT_TIMEPOINT_KEYS ); + actions.namedAction( new StepWiseTimeBrowsingAction( imp, params, false ), PREVIOUS_TIMEPOINT_KEYS ); + // Semi-automatic tracking - final SemiAutoTracking semiAutoTracking = new SemiAutoTracking( model, selectionModel, imp ); + final SemiAutoTracking semiAutoTracking = new SemiAutoTracking( guiModel ); actions.runnableAction( () -> semiAutoTracking.run(), SEMI_AUTOMATIC_TRACKING, SEMI_AUTOMATIC_TRACKING_KEYS ); - - actions.runnableAction( () -> System.out.println( "[HyperStackDisplayer] TROLOLO" ), "refresh", new String[] { "R" } ); // DEBUG } @Plugin( type = CommandDescriptionProvider.class ) @@ -63,4 +73,58 @@ public void getCommandDescriptions( final CommandDescriptions descriptions ) descriptions.add( DO_NOTHING_ACTION, DO_NOTHING_ACTION_KEYS, "Do nothing. This is to avoid closing the window when pressing W." ); } } + + /** + * Ensure we browse a fixed divisions of the timepoints, rather than just + * incrementing or decrementing by one. This is useful when the timepoints + * are not consecutive, e.g. when the user has selected a subset of + * timepoints to display. Taken from what we did in MaMuT. + */ + private static class StepWiseTimeBrowsingAction extends AbstractNamedAction + { + + private static final long serialVersionUID = 1L; + + private final boolean forward; + + private final ImagePlus imp; + + private final SemiAutoTrackingParams params; + + public StepWiseTimeBrowsingAction( final ImagePlus imp, final SemiAutoTrackingParams params, final boolean forward ) + { + super( forward ? NEXT_TIMEPOINT : PREVIOUS_TIMEPOINT ); + this.imp = imp; + this.params = params; + this.forward = forward; + } + + @Override + public void actionPerformed( final ActionEvent e ) + { + final int currentT = imp.getT() - 1; + final int timeStep = params.stepwiseTimeBrowsing(); + final int prevStep = ( currentT / timeStep ) * timeStep; + int tp; // 0-based + if ( forward ) + { + tp = prevStep + timeStep; + } + else + { + if ( currentT == prevStep ) + tp = currentT - timeStep; + else + tp = prevStep; + } + + if ( tp < 0 ) + tp = 0; + + if ( tp > imp.getNFrames() - 1 ) + tp = imp.getNFrames() - 1; + + imp.setT( tp + 1 ); // ImageJ is 1-based + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditToolParams.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditToolParams.java deleted file mode 100644 index a11666b16..000000000 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SpotEditToolParams.java +++ /dev/null @@ -1,35 +0,0 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; - -public class SpotEditToolParams -{ - - /* - * Semi-auto tracking parameters - */ - /** - * The fraction of the initial quality above which we keep new spots. - * The highest, the more intolerant. - */ - double qualityThreshold = 0.5; - - /** - * How close must be the new spot found to be accepted, in radius units. - */ - double distanceTolerance = 2d; - - /** - * We process at most nFrames. Make it 0 or negative to have no bounds. - */ - int nFrames = 10; - - /** - * By how many frames to jump when we do step-wide time browsing. - */ - int stepwiseTimeBrowsing = 1; - - @Override - public String toString() - { - return super.toString() + ": " + "QualityThreshold = " + qualityThreshold + ", DistanceTolerance = " + distanceTolerance + ", nFrames = " + nFrames; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SemiAutoTracking.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTracking.java similarity index 61% rename from src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SemiAutoTracking.java rename to src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTracking.java index 918848894..1e1ca0a79 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/SemiAutoTracking.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTracking.java @@ -1,9 +1,10 @@ -package fiji.plugin.trackmate.visualization.hyperstack.behaviours; +package fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking; import fiji.plugin.trackmate.Logger; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.SelectionModel; import fiji.plugin.trackmate.detection.semiauto.SemiAutoTracker; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.Threads; import ij.ImagePlus; import ij.Prefs; @@ -11,29 +12,26 @@ public class SemiAutoTracking implements Runnable { - private final Model model; - - private final SelectionModel selectionModel; - - private final ImagePlus imp; - - public static final SpotEditToolParams params = new SpotEditToolParams(); - private final Logger logger = Logger.IJ_LOGGER; - public SemiAutoTracking( final Model model, final SelectionModel selectionModel, final ImagePlus imp ) + private final GuiModel guiModel; + + public SemiAutoTracking( final GuiModel guiModel ) { - this.model = model; - this.selectionModel = selectionModel; - this.imp = imp; + this.guiModel = guiModel; } @Override public void run() { - final double qualityThreshold = params.qualityThreshold; - final double distanceTolerance = params.distanceTolerance; - final int nFrames = params.nFrames; + final SemiAutoTrackingParams params = guiModel.getSemiAutoTrackingParams(); + final double qualityThreshold = params.qualityThreshold(); + final double distanceTolerance = params.distanceTolerance(); + final int nFrames = params.nFrames(); + + final Model model = guiModel.getModel(); + final SelectionModel selectionModel = guiModel.getSelectionModel(); + final ImagePlus imp = guiModel.getSettings().imp; final SemiAutoTracker< ? > autotracker = new SemiAutoTracker<>( model, selectionModel, imp, logger ); autotracker.setParameters( qualityThreshold, distanceTolerance, nFrames ); autotracker.setNumThreads( Prefs.getThreads() / 2 ); @@ -42,6 +40,5 @@ public void run() if ( !ok ) logger.error( autotracker.getErrorMessage() ); } ); - } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java new file mode 100644 index 000000000..93995352a --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java @@ -0,0 +1,84 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking; + +import org.scijava.ui.config.Configurator; +import org.scijava.ui.config.Parameters.DoubleParam; +import org.scijava.ui.config.Parameters.IntParam; + +public class SemiAutoTrackingParams extends Configurator +{ + + private final DoubleParam qualityThreshold; + + private final DoubleParam distanceTolerance; + + private final IntParam nFrames; + + private final IntParam stepwiseTimeBrowsing; + + public SemiAutoTrackingParams() + { + super( "Semi-automatic tracking parameters", "Parameters that configures the semi-automatic tracking tool." ); + + this.qualityThreshold = addDoubleParameter() + .key( "QUALITY_THRESHOLD" ) + .name( "Quality threshold" ) + .help( "The fraction of the quality of the initial spot above which we keep new spots. The highest, the more intolerant." ) + .defaultValue( 0.5d ) + .min( 0. ) + .max( 2. ) + .get(); + + this.distanceTolerance = addDoubleParameter() + .key( "DISTANCE_TOLERANCE" ) + .name( "Distance tolerance" ) + .help( "How close must be the new spot found to be accepted, in units of the radius of the initial spot." ) + .defaultValue( 2d ) + .min( 0. ) + .max( 10. ) + .get(); + + this.nFrames = addIntParameter() + .key( "N_FRAMES" ) + .name( "Max N frames" ) + .help( "The number of frames to process in one go. Set to 0 to have no bounds." ) + .defaultValue( 10 ) + .min( 0 ) + .get(); + + this.stepwiseTimeBrowsing = addIntParameter() + .key( "STEPWISE_TIME_BROWSING" ) + .name( "Stepwise time browsing" ) + .help( "By how many frames to jump when we do step-wise time browsing." ) + .defaultValue( 1 ) + .min( 1 ) + .get(); + } + + public double qualityThreshold() + { + return qualityThreshold.getValue(); + } + + public double distanceTolerance() + { + return distanceTolerance.getValue(); + } + + public int nFrames() + { + return nFrames.getValue(); + } + + public int stepwiseTimeBrowsing() + { + return stepwiseTimeBrowsing.getValue(); + } + + public void set( final SemiAutoTrackingParams other ) + { + this.qualityThreshold.set( other.qualityThreshold() ); + this.distanceTolerance.set( other.distanceTolerance() ); + this.nFrames.set( other.nFrames() ); + this.stepwiseTimeBrowsing.set( other.stepwiseTimeBrowsing() ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java new file mode 100644 index 000000000..d27edc9bd --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java @@ -0,0 +1,78 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking; + +import java.util.ArrayList; + +import javax.swing.JPanel; + +import org.scijava.listeners.Listeners; +import org.scijava.ui.config.visitors.gui.GuiBuilder; +import org.scijava.ui.config.visitors.gui.GuiBuilder.ConfigPanel; + +import bdv.ui.settings.ModificationListener; +import bdv.ui.settings.SettingsPage; + +public class SpotEditToolSettingsPage implements SettingsPage +{ + + private final String treePath; + + private final Listeners.List< ModificationListener > modificationListeners; + + private final SemiAutoTrackingParams tmpParams; + + private final ConfigPanel panel; + + public SpotEditToolSettingsPage( final String treePath, final SemiAutoTrackingParams params ) + { + this.treePath = treePath; + this.modificationListeners = new Listeners.SynchronizedList<>(); + this.tmpParams = new SemiAutoTrackingParams(); + this.panel = GuiBuilder.build( tmpParams ); + onApply( () -> params.set( tmpParams ) ); + onCancel( () -> tmpParams.set( params ) ); + } + + @Override + public String getTreePath() + { + return treePath; + } + + @Override + public JPanel getJPanel() + { + return panel; + } + + @Override + public Listeners< ModificationListener > modificationListeners() + { + return modificationListeners; + } + + protected final ArrayList< Runnable > runOnApply = new ArrayList<>(); + + public synchronized void onApply( final Runnable runnable ) + { + runOnApply.add( runnable ); + } + + protected final ArrayList< Runnable > runOnCancel = new ArrayList<>(); + + public synchronized void onCancel( final Runnable runnable ) + { + runOnCancel.add( runnable ); + } + + @Override + public void cancel() + { + runOnCancel.forEach( Runnable::run ); + } + + @Override + public void apply() + { + runOnApply.forEach( Runnable::run ); + } +} From 002c4ff2d2d1bb6459ac91edd8917e436c0c4a31 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 23:02:27 +0200 Subject: [PATCH 317/371] Properly implements cancel and apply in the SpotEditToolSettingsPage. Required making the SemiAutoTrackingParams config listenable. --- .../SemiAutoTrackingParams.java | 28 +++++++++++++++++++ .../SpotEditToolSettingsPage.java | 6 +++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java index 93995352a..cf5c18b56 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java @@ -1,8 +1,10 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking; +import org.scijava.listeners.Listeners; import org.scijava.ui.config.Configurator; import org.scijava.ui.config.Parameters.DoubleParam; import org.scijava.ui.config.Parameters.IntParam; +import org.scijava.ui.config.Parameters.UpdateListener; public class SemiAutoTrackingParams extends Configurator { @@ -15,9 +17,13 @@ public class SemiAutoTrackingParams extends Configurator private final IntParam stepwiseTimeBrowsing; + private final Listeners.SynchronizedList< UpdateListener > updateListeners; + public SemiAutoTrackingParams() { super( "Semi-automatic tracking parameters", "Parameters that configures the semi-automatic tracking tool." ); + this.updateListeners = new Listeners.SynchronizedList<>(); + final UpdateListener updateListener = () -> notifyUpdateListeners(); this.qualityThreshold = addDoubleParameter() .key( "QUALITY_THRESHOLD" ) @@ -26,6 +32,7 @@ public SemiAutoTrackingParams() .defaultValue( 0.5d ) .min( 0. ) .max( 2. ) + .updateListener( updateListener ) .get(); this.distanceTolerance = addDoubleParameter() @@ -35,6 +42,7 @@ public SemiAutoTrackingParams() .defaultValue( 2d ) .min( 0. ) .max( 10. ) + .updateListener( updateListener ) .get(); this.nFrames = addIntParameter() @@ -43,6 +51,7 @@ public SemiAutoTrackingParams() .help( "The number of frames to process in one go. Set to 0 to have no bounds." ) .defaultValue( 10 ) .min( 0 ) + .updateListener( updateListener ) .get(); this.stepwiseTimeBrowsing = addIntParameter() @@ -51,9 +60,20 @@ public SemiAutoTrackingParams() .help( "By how many frames to jump when we do step-wise time browsing." ) .defaultValue( 1 ) .min( 1 ) + .updateListener( updateListener ) .get(); } + private void notifyUpdateListeners() + { + updateListeners.list.forEach( UpdateListener::parameterUpdated ); + } + + public Listeners< UpdateListener > updateListeners() + { + return updateListeners; + } + public double qualityThreshold() { return qualityThreshold.getValue(); @@ -80,5 +100,13 @@ public void set( final SemiAutoTrackingParams other ) this.distanceTolerance.set( other.distanceTolerance() ); this.nFrames.set( other.nFrames() ); this.stepwiseTimeBrowsing.set( other.stepwiseTimeBrowsing() ); + notifyUpdateListeners(); + } + + @Override + public String toString() + { + return String.format( "SemiAutoTrackingParams [qualityThreshold=%.2f, distanceTolerance=%.2f, nFrames=%d, stepwiseTimeBrowsing=%d]", + qualityThreshold(), distanceTolerance(), nFrames(), stepwiseTimeBrowsing() ); } } diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java index d27edc9bd..f646cb43a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java @@ -28,8 +28,12 @@ public SpotEditToolSettingsPage( final String treePath, final SemiAutoTrackingPa this.modificationListeners = new Listeners.SynchronizedList<>(); this.tmpParams = new SemiAutoTrackingParams(); this.panel = GuiBuilder.build( tmpParams ); + tmpParams.updateListeners().add( () -> modificationListeners.list.forEach( ModificationListener::setModified ) ); onApply( () -> params.set( tmpParams ) ); - onCancel( () -> tmpParams.set( params ) ); + onCancel( () -> { + tmpParams.set( params ); + panel.refresh(); + } ); } @Override From becd0660c811ff8cd37daa8bb1235058100ed5ed Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 4 Aug 2026 23:46:01 +0200 Subject: [PATCH 318/371] Make the SemiAutoTrackingParams automatically de/serializes from/to JSon. --- .../fiji/plugin/trackmate/gui/GuiModel.java | 12 +- .../SemiAutoTrackingParams.java | 2 +- .../SemiAutoTrackingParamsIO.java | 179 ++++++++++++++++++ .../SpotEditToolSettingsPage.java | 29 ++- 4 files changed, 213 insertions(+), 9 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParamsIO.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java index 40d7f281a..c27f63eab 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java @@ -16,6 +16,7 @@ import fiji.plugin.trackmate.gui.editor.labkit.component.EditorKeymapManager; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SemiAutoTrackingParams; +import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SemiAutoTrackingParamsIO; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import fiji.plugin.trackmate.visualization.ui.TrackMateActions; import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; @@ -27,7 +28,7 @@ */ public class GuiModel { - + private final Model model; private final SelectionModel selectionModel; @@ -70,8 +71,8 @@ public GuiModel( final Model model, final Settings settings, final DisplaySettin this.selectionModel = new SelectionModel( model ); this.displaySettings = displaySettings; this.trackmate = createTrackMate( model, settings ); - this.semiAutoTrackingparams = new SemiAutoTrackingParams(); - + this.semiAutoTrackingparams = createSemiAutoTrackingParams(); + // Keymap and actions this.editorKeymapManager = new EditorKeymapManager(); this.appearanceManager = new AppearanceManager( EDITOR_KEYMAP_HOME ); @@ -170,6 +171,11 @@ protected TrackMate createTrackMate( final Model model, final Settings settings return trackmate; } + protected SemiAutoTrackingParams createSemiAutoTrackingParams() + { + return SemiAutoTrackingParamsIO.readPrefs(); + } + /** * Actions that operates on the whole TrackMate session. *

    diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java index cf5c18b56..3cce74765 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParams.java @@ -17,7 +17,7 @@ public class SemiAutoTrackingParams extends Configurator private final IntParam stepwiseTimeBrowsing; - private final Listeners.SynchronizedList< UpdateListener > updateListeners; + private final transient Listeners.SynchronizedList< UpdateListener > updateListeners; public SemiAutoTrackingParams() { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParamsIO.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParamsIO.java new file mode 100644 index 000000000..78b14368d --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParamsIO.java @@ -0,0 +1,179 @@ +package fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import org.scijava.ui.config.Configurator.SelectableParameters; +import org.scijava.ui.config.ParameterVisitor; +import org.scijava.ui.config.Parameters.BooleanParam; +import org.scijava.ui.config.Parameters.ChoiceParam; +import org.scijava.ui.config.Parameters.DoubleParam; +import org.scijava.ui.config.Parameters.EnumParam; +import org.scijava.ui.config.Parameters.IntParam; +import org.scijava.ui.config.Parameters.PathParam; +import org.scijava.ui.config.Parameters.StringParam; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +public class SemiAutoTrackingParamsIO +{ + + private static File userDefaultFile = new File( new File( System.getProperty( "user.home" ), ".trackmate" ), "semiautotrackerparams.json" ); + + public static SemiAutoTrackingParams readPrefs() + { + if ( !userDefaultFile.exists() ) + { + final SemiAutoTrackingParams params = new SemiAutoTrackingParams(); + savePrefs( params ); + return params; + } + + try (FileReader reader = new FileReader( userDefaultFile )) + { + final String str = Files.lines( Paths.get( userDefaultFile.getAbsolutePath() ) ) + .collect( Collectors.joining( System.lineSeparator() ) ); + + return fromJson( str ); + } + catch ( final FileNotFoundException e ) + {} + catch ( final IOException e ) + {} + return new SemiAutoTrackingParams(); + } + + public static void savePrefs( final SemiAutoTrackingParams params ) + { + final String str = toJson( params ); + + if ( !userDefaultFile.exists() ) + userDefaultFile.getParentFile().mkdirs(); + + try (FileWriter writer = new FileWriter( userDefaultFile )) + { + writer.append( str ); + } + catch ( final IOException e ) + { + System.err.println( "Could not write the " + params.getClass().getSimpleName() + " to " + userDefaultFile ); + e.printStackTrace(); + } + } + + private static SemiAutoTrackingParams fromJson( final String str ) + { + final SemiAutoTrackingParams params = new SemiAutoTrackingParams(); + final Type mapType = new TypeToken< Map< String, Object > >() + {}.getType(); + final Map< String, Object > valuesMap = getGson().fromJson( str, mapType ); + if ( valuesMap == null ) + { + System.err.println( "Could not read the " + SemiAutoTrackingParams.class.getSimpleName() + " from " + userDefaultFile ); + return params; + } + + final DeserializeVisitor visitor = new DeserializeVisitor( valuesMap ); + params.forEach( p -> p.accept( visitor ) ); + System.out.println( "Read: " + params ); + return params; + } + + public static String toJson( final SemiAutoTrackingParams params ) + { + final Map< String, Object > valuesMap = new HashMap<>(); + valuesMap.put( "QUALITY_THRESHOLD", params.qualityThreshold() ); + valuesMap.put( "DISTANCE_TOLERANCE", params.distanceTolerance() ); + valuesMap.put( "N_FRAMES", params.nFrames() ); + valuesMap.put( "STEPWISE_TIME_BROWSING", params.stepwiseTimeBrowsing() ); + return getGson().toJson( valuesMap ); + } + + private static Gson getGson() + { + final GsonBuilder builder = new GsonBuilder(); + return builder.setPrettyPrinting().create(); + } + + // TODO: Move back to config-ui? + private static class DeserializeVisitor implements ParameterVisitor + { + private final Map< String, Object > valuesMap; + + public DeserializeVisitor( final Map< String, Object > valuesMap ) + { + this.valuesMap = valuesMap; + } + + @Override + public void visit( final BooleanParam param ) + { + if ( valuesMap.containsKey( param.getKey() ) ) + param.set( ( Boolean ) valuesMap.get( param.getKey() ) ); + } + + @Override + public void visit( final ChoiceParam choiceParam ) + { + if ( valuesMap.containsKey( choiceParam.getKey() ) ) + choiceParam.set( ( String ) valuesMap.get( choiceParam.getKey() ) ); + } + + @Override + public void visit( final DoubleParam doubleParam ) + { + if ( valuesMap.containsKey( doubleParam.getKey() ) ) + doubleParam.set( ( Double ) valuesMap.get( doubleParam.getKey() ) ); + } + + @Override + public < E extends Enum< E > > void visit( final EnumParam< E > enumParam ) + { + if ( valuesMap.containsKey( enumParam.getKey() ) ) + { + final String value = ( String ) valuesMap.get( enumParam.getKey() ); + final E enumValue = Enum.valueOf( enumParam.getEnumClass(), value ); + enumParam.set( enumValue ); + } + } + + @Override + public void visit( final IntParam intParam ) + { + if ( valuesMap.containsKey( intParam.getKey() ) ) + intParam.set( ( ( Number ) valuesMap.get( intParam.getKey() ) ).intValue() ); + } + + @Override + public void visit( final PathParam pathParam ) + { + if ( valuesMap.containsKey( pathParam.getKey() ) ) + pathParam.set( ( String ) valuesMap.get( pathParam.getKey() ) ); + } + + @Override + public void visit( final SelectableParameters selectable ) + { + // TODO + ParameterVisitor.super.visit( selectable ); + } + + @Override + public void visit( final StringParam stringParam ) + { + if ( valuesMap.containsKey( stringParam.getKey() ) ) + stringParam.set( ( String ) valuesMap.get( stringParam.getKey() ) ); + } + }; +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java index f646cb43a..54a08749e 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SpotEditToolSettingsPage.java @@ -1,7 +1,10 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking; +import java.awt.BorderLayout; +import java.awt.FlowLayout; import java.util.ArrayList; +import javax.swing.JButton; import javax.swing.JPanel; import org.scijava.listeners.Listeners; @@ -20,20 +23,36 @@ public class SpotEditToolSettingsPage implements SettingsPage private final SemiAutoTrackingParams tmpParams; - private final ConfigPanel panel; + private final JPanel mainPanel; public SpotEditToolSettingsPage( final String treePath, final SemiAutoTrackingParams params ) { this.treePath = treePath; this.modificationListeners = new Listeners.SynchronizedList<>(); this.tmpParams = new SemiAutoTrackingParams(); - this.panel = GuiBuilder.build( tmpParams ); + tmpParams.set( params ); + final ConfigPanel configPanel = GuiBuilder.build( tmpParams ); tmpParams.updateListeners().add( () -> modificationListeners.list.forEach( ModificationListener::setModified ) ); - onApply( () -> params.set( tmpParams ) ); + onApply( () -> { + params.set( tmpParams ); + SemiAutoTrackingParamsIO.savePrefs( params ); + } ); onCancel( () -> { tmpParams.set( params ); - panel.refresh(); + configPanel.refresh(); + } ); + + this.mainPanel = new JPanel(); + mainPanel.setLayout( new BorderLayout() ); + mainPanel.add( configPanel, BorderLayout.CENTER ); + final JPanel buttonPanel = new JPanel( new FlowLayout( FlowLayout.RIGHT, 5, 5 ) ); + final JButton reset = new JButton( "Reset" ); + reset.addActionListener( e -> { + tmpParams.set( new SemiAutoTrackingParams() ); + configPanel.refresh(); } ); + buttonPanel.add( reset ); + mainPanel.add( buttonPanel, BorderLayout.SOUTH ); } @Override @@ -45,7 +64,7 @@ public String getTreePath() @Override public JPanel getJPanel() { - return panel; + return mainPanel; } @Override From 4e517f65ab03b1b431f6ff1eeced4dc8dd6eca5e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 5 Aug 2026 13:35:48 +0200 Subject: [PATCH 319/371] Use the config-ui JSon facility. --- .../SemiAutoTrackingParamsIO.java | 161 +----------------- 1 file changed, 5 insertions(+), 156 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParamsIO.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParamsIO.java index 78b14368d..4690f67e5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParamsIO.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/semiautotracking/SemiAutoTrackingParamsIO.java @@ -1,30 +1,8 @@ package fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking; import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.lang.reflect.Type; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; -import org.scijava.ui.config.Configurator.SelectableParameters; -import org.scijava.ui.config.ParameterVisitor; -import org.scijava.ui.config.Parameters.BooleanParam; -import org.scijava.ui.config.Parameters.ChoiceParam; -import org.scijava.ui.config.Parameters.DoubleParam; -import org.scijava.ui.config.Parameters.EnumParam; -import org.scijava.ui.config.Parameters.IntParam; -import org.scijava.ui.config.Parameters.PathParam; -import org.scijava.ui.config.Parameters.StringParam; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; +import org.scijava.ui.config.visitors.JSon; public class SemiAutoTrackingParamsIO { @@ -33,147 +11,18 @@ public class SemiAutoTrackingParamsIO public static SemiAutoTrackingParams readPrefs() { + final SemiAutoTrackingParams params = new SemiAutoTrackingParams(); if ( !userDefaultFile.exists() ) { - final SemiAutoTrackingParams params = new SemiAutoTrackingParams(); savePrefs( params ); return params; } - - try (FileReader reader = new FileReader( userDefaultFile )) - { - final String str = Files.lines( Paths.get( userDefaultFile.getAbsolutePath() ) ) - .collect( Collectors.joining( System.lineSeparator() ) ); - - return fromJson( str ); - } - catch ( final FileNotFoundException e ) - {} - catch ( final IOException e ) - {} - return new SemiAutoTrackingParams(); - } - - public static void savePrefs( final SemiAutoTrackingParams params ) - { - final String str = toJson( params ); - - if ( !userDefaultFile.exists() ) - userDefaultFile.getParentFile().mkdirs(); - - try (FileWriter writer = new FileWriter( userDefaultFile )) - { - writer.append( str ); - } - catch ( final IOException e ) - { - System.err.println( "Could not write the " + params.getClass().getSimpleName() + " to " + userDefaultFile ); - e.printStackTrace(); - } - } - - private static SemiAutoTrackingParams fromJson( final String str ) - { - final SemiAutoTrackingParams params = new SemiAutoTrackingParams(); - final Type mapType = new TypeToken< Map< String, Object > >() - {}.getType(); - final Map< String, Object > valuesMap = getGson().fromJson( str, mapType ); - if ( valuesMap == null ) - { - System.err.println( "Could not read the " + SemiAutoTrackingParams.class.getSimpleName() + " from " + userDefaultFile ); - return params; - } - - final DeserializeVisitor visitor = new DeserializeVisitor( valuesMap ); - params.forEach( p -> p.accept( visitor ) ); - System.out.println( "Read: " + params ); + JSon.deserialize( userDefaultFile.getAbsolutePath(), params ); return params; } - public static String toJson( final SemiAutoTrackingParams params ) - { - final Map< String, Object > valuesMap = new HashMap<>(); - valuesMap.put( "QUALITY_THRESHOLD", params.qualityThreshold() ); - valuesMap.put( "DISTANCE_TOLERANCE", params.distanceTolerance() ); - valuesMap.put( "N_FRAMES", params.nFrames() ); - valuesMap.put( "STEPWISE_TIME_BROWSING", params.stepwiseTimeBrowsing() ); - return getGson().toJson( valuesMap ); - } - - private static Gson getGson() + public static void savePrefs( final SemiAutoTrackingParams params ) { - final GsonBuilder builder = new GsonBuilder(); - return builder.setPrettyPrinting().create(); + JSon.serialize( userDefaultFile.getAbsolutePath(), params ); } - - // TODO: Move back to config-ui? - private static class DeserializeVisitor implements ParameterVisitor - { - private final Map< String, Object > valuesMap; - - public DeserializeVisitor( final Map< String, Object > valuesMap ) - { - this.valuesMap = valuesMap; - } - - @Override - public void visit( final BooleanParam param ) - { - if ( valuesMap.containsKey( param.getKey() ) ) - param.set( ( Boolean ) valuesMap.get( param.getKey() ) ); - } - - @Override - public void visit( final ChoiceParam choiceParam ) - { - if ( valuesMap.containsKey( choiceParam.getKey() ) ) - choiceParam.set( ( String ) valuesMap.get( choiceParam.getKey() ) ); - } - - @Override - public void visit( final DoubleParam doubleParam ) - { - if ( valuesMap.containsKey( doubleParam.getKey() ) ) - doubleParam.set( ( Double ) valuesMap.get( doubleParam.getKey() ) ); - } - - @Override - public < E extends Enum< E > > void visit( final EnumParam< E > enumParam ) - { - if ( valuesMap.containsKey( enumParam.getKey() ) ) - { - final String value = ( String ) valuesMap.get( enumParam.getKey() ); - final E enumValue = Enum.valueOf( enumParam.getEnumClass(), value ); - enumParam.set( enumValue ); - } - } - - @Override - public void visit( final IntParam intParam ) - { - if ( valuesMap.containsKey( intParam.getKey() ) ) - intParam.set( ( ( Number ) valuesMap.get( intParam.getKey() ) ).intValue() ); - } - - @Override - public void visit( final PathParam pathParam ) - { - if ( valuesMap.containsKey( pathParam.getKey() ) ) - pathParam.set( ( String ) valuesMap.get( pathParam.getKey() ) ); - } - - @Override - public void visit( final SelectableParameters selectable ) - { - // TODO - ParameterVisitor.super.visit( selectable ); - } - - @Override - public void visit( final StringParam stringParam ) - { - if ( valuesMap.containsKey( stringParam.getKey() ) ) - stringParam.set( ( String ) valuesMap.get( stringParam.getKey() ) ); - } - }; } From ffabce4428a563244f5a23cd6e028da78bf658e6 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 5 Aug 2026 15:38:58 +0200 Subject: [PATCH 320/371] Let DisplaySettings implements Style< DisplaySettings > --- .../trackmate/gui/displaysettings/DisplaySettings.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettings.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettings.java index e6172f42b..e52477b87 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettings.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettings.java @@ -29,9 +29,10 @@ import org.scijava.listeners.Listeners; +import bdv.ui.settings.style.Style; import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; -public class DisplaySettings +public class DisplaySettings implements Style< DisplaySettings > { /** The display settings name. */ @@ -148,6 +149,7 @@ private DisplaySettings( final String name ) * the name for the copied render settings. * @return a new {@link DisplaySettings} instance. */ + @Override public DisplaySettings copy( final String name ) { final DisplaySettings rs = new DisplaySettings(); @@ -157,6 +159,7 @@ public DisplaySettings copy( final String name ) return rs; } + @Override public DisplaySettings copy() { return copy( null ); @@ -208,11 +211,13 @@ synchronized void set( final DisplaySettings ds ) notifyListeners(); } + @Override public String getName() { return name; } + @Override public synchronized void setName( final String name ) { if ( !Objects.equals( this.name, name ) ) @@ -788,7 +793,7 @@ public enum TrackMateObject { DEFAULT( "Default" ), SPOTS( "spots" ), EDGES( "edges" ), TRACKS( "tracks" ); - private String name; + private final String name; private TrackMateObject( final String name ) { From cb3b4d585d5eeb08373ba93010e57890af74cab1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 5 Aug 2026 15:40:22 +0200 Subject: [PATCH 321/371] Refactor the DisplaySettingsPanel Make it implements ProfileEditPanel, so that we can later use it in a config page. Move the visitor to its own class, and wrap the panel itself in a scrollpane. --- .../ConfigTrackMateDisplaySettings.java | 7 +- .../displaysettings/DisplaySettingsPanel.java | 372 +++++++++++------- 2 files changed, 220 insertions(+), 159 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ConfigTrackMateDisplaySettings.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ConfigTrackMateDisplaySettings.java index 9e90ce88e..cacd05971 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ConfigTrackMateDisplaySettings.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ConfigTrackMateDisplaySettings.java @@ -27,7 +27,6 @@ import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON; import java.awt.BorderLayout; -import java.awt.Dimension; import javax.swing.BorderFactory; import javax.swing.Box; @@ -36,7 +35,6 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JScrollPane; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; @@ -107,10 +105,7 @@ public static JFrame editor( final DisplaySettings ds, final String titleStr, fi */ final DisplaySettingsPanel editor = new DisplaySettingsPanel( ds ); - final JScrollPane scrollPane = new JScrollPane( editor, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); - scrollPane.setPreferredSize( new Dimension( 350, 500 ) ); - scrollPane.getVerticalScrollBar().setUnitIncrement( 16 ); - configPanel.add( scrollPane, BorderLayout.CENTER ); + configPanel.add( editor, BorderLayout.CENTER ); /* * Listeners. diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsPanel.java index a8863f485..d01522753 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsPanel.java @@ -41,6 +41,8 @@ import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedSliderPanel; import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.separator; +import java.awt.BorderLayout; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -53,13 +55,20 @@ import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import org.scijava.listeners.Listeners; + import com.itextpdf.text.Font; +import bdv.ui.settings.ModificationListener; +import bdv.ui.settings.SelectAndEditProfileSettingsPage.ProfileEditPanel; +import bdv.ui.settings.style.StyleProfile; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackDisplayMode; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BooleanElement; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.ColorElement; @@ -74,173 +83,77 @@ import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StyleElement; import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StyleElementVisitor; -public class DisplaySettingsPanel extends JPanel +public class DisplaySettingsPanel extends JPanel implements ProfileEditPanel< StyleProfile< DisplaySettings > >, UpdateListener { private static final long serialVersionUID = 1L; private static final int tfCols = 4; - private final JColorChooser colorChooser; + private final Listeners.SynchronizedList< ModificationListener > modificationListeners; private final List< StyleElement > styleElements; + private final DisplaySettings editedStyle; + public DisplaySettingsPanel( final DisplaySettings ds ) { - super( new GridBagLayout() ); + super( new BorderLayout() ); + + this.editedStyle = ds.copy( "Edited" ); + this.styleElements = styleElements( editedStyle ); + this.modificationListeners = new Listeners.SynchronizedList<>(); + editedStyle.listeners().add( this ); + + final JPanel panel = new JPanel( new GridBagLayout() ); + final GuiVisitor visitor = new GuiVisitor( panel ); + styleElements.forEach( element -> element.accept( visitor ) ); + final JScrollPane scrollPane = new JScrollPane( panel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); + scrollPane.getVerticalScrollBar().setUnitIncrement( 8 ); + add( scrollPane, BorderLayout.CENTER ); + setPreferredSize( new Dimension( 350, 500 ) ); + } - colorChooser = new JColorChooser(); - styleElements = styleElements( ds ); + @Override + public Listeners< ModificationListener > modificationListeners() + { + return modificationListeners; + } - ds.listeners().add( () -> { - styleElements.forEach( StyleElement::update ); - repaint(); - } ); - - final GridBagConstraints c = new GridBagConstraints(); - c.fill = GridBagConstraints.HORIZONTAL; - c.weightx = 1.0; - c.gridwidth = 1; - c.gridx = 0; - c.gridy = 0; - - c.insets = new Insets( 2, 5, 2, 5 ); - - styleElements.forEach( element -> element.accept( - new StyleElementVisitor() - { - @Override - public void visit( final Separator element ) - { - add( Box.createVerticalStrut( 10 ), c ); - ++c.gridy; - addToLayout( new JSeparator( JSeparator.HORIZONTAL ) ); - } - - @Override - public void visit( final LabelElement element ) - { - final JLabel label = new JLabel( element.getLabel() ); - label.setFont( getFont().deriveFont( Font.BOLD ).deriveFont( getFont().getSize() + 2f ) ); - addToLayout( label ); - } - - @Override - public void visit( final BooleanElement element ) - { - final JCheckBox checkbox = linkedCheckBox( element, "" ); - checkbox.setHorizontalAlignment( SwingConstants.TRAILING ); - addToLayout( - checkbox, - new JLabel( element.getLabel() ) ); - } - - @Override - public void visit( final BoundedDoubleElement element ) - { - addToLayout( - linkedSliderPanel( element, tfCols, 0.1 ), - new JLabel( element.getLabel() ) ); - } - - @Override - public void visit( final DoubleElement element ) - { - addToLayout( - linkedFormattedTextField( element ), - new JLabel( element.getLabel() ) ); - } - - @Override - public void visit( final IntElement element ) - { - addToLayout( - linkedSliderPanel( element, tfCols ), - new JLabel( element.getLabel() ) ); - } - - @Override - public void visit( final ColorElement element ) - { - addToLayoutFlushRight( - linkedColorButton( element, colorChooser ), - new JLabel( element.getLabel() ) ); - } - - @Override - public void visit( final FeatureElement element ) - { - addToLayout( - linkedFeatureSelector( element ), - new JLabel( element.getLabel() ) ); - } - - @Override - public < E > void visit( final EnumElement< E > element ) - { - addToLayout( - linkedComboBoxEnumSelector( element ), - new JLabel( element.getLabel() ) ); - } - - @Override - public void visit( final ColormapElement element ) - { - addToLayout( - linkedColormapChooser( element ), - new JLabel( element.getLabel() ) ); - } - - @Override - public void visit( final FontElement element ) - { - addToLayout( - linkedFontButton( element, SwingUtilities.getWindowAncestor( DisplaySettingsPanel.this ) ), - new JLabel( element.getLabel() ) ); - } - - private void addToLayout( final JComponent comp1, final JComponent comp2 ) - { - c.gridwidth = 1; - c.anchor = GridBagConstraints.LINE_END; - add( comp1, c ); - c.gridx++; - c.weightx = 0.0; - c.anchor = GridBagConstraints.LINE_START; - add( comp2, c ); - c.gridx = 0; - c.weightx = 1.0; - c.gridy++; - } - - private void addToLayoutFlushRight( final JComponent comp1, final JComponent comp2 ) - { - c.fill = - c.gridwidth = 1; - c.fill = GridBagConstraints.NONE; - c.anchor = GridBagConstraints.EAST; - add( comp1, c ); - c.gridx++; - c.weightx = 0.0; - c.fill = GridBagConstraints.HORIZONTAL; - c.anchor = GridBagConstraints.LINE_START; - add( comp2, c ); - c.gridx = 0; - c.weightx = 1.0; - c.gridy++; - } - - private void addToLayout( final JComponent comp ) - { - c.gridwidth = 2; - c.anchor = GridBagConstraints.LINE_START; - c.gridx = 0; - c.weightx = 1.0; - add( comp, c ); - c.gridy++; - } - } ) ); + @Override + public JPanel getJPanel() + { + return this; } + private boolean trackModifications = true; + + @Override + public void loadProfile( final StyleProfile< DisplaySettings > profile ) + { + trackModifications = false; + editedStyle.set( profile.getStyle() ); + trackModifications = true; + } + + @Override + public void storeProfile( final StyleProfile< DisplaySettings > profile ) + { + trackModifications = false; + editedStyle.setName( profile.getStyle().getName() ); + trackModifications = true; + profile.getStyle().set( editedStyle ); + } + + @Override + public void displaySettingsChanged() + { + styleElements.forEach( StyleElement::update ); + if ( trackModifications ) + { + repaint(); + modificationListeners.list.forEach( ModificationListener::setModified ); + } + } private List< StyleElement > styleElements( final DisplaySettings ds ) { @@ -303,4 +216,157 @@ private List< StyleElement > styleElements( final DisplaySettings ds ) separator() ); } + + private static class GuiVisitor implements StyleElementVisitor + { + + private final JPanel panel; + + private final GridBagConstraints c; + + private final JColorChooser colorChooser; + + public GuiVisitor( final JPanel panel ) + { + this.panel = panel; + this.colorChooser = new JColorChooser(); + this.c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1.0; + c.gridwidth = 1; + c.gridx = 0; + c.gridy = 0; + c.insets = new Insets( 2, 5, 2, 5 ); + } + + @Override + public void visit( final Separator element ) + { + panel.add( Box.createVerticalStrut( 10 ), c ); + ++c.gridy; + addToLayout( new JSeparator( JSeparator.HORIZONTAL ) ); + } + + @Override + public void visit( final LabelElement element ) + { + final JLabel label = new JLabel( element.getLabel() ); + label.setFont( panel.getFont().deriveFont( Font.BOLD ).deriveFont( panel.getFont().getSize() + 2f ) ); + addToLayout( label ); + } + + @Override + public void visit( final BooleanElement element ) + { + final JCheckBox checkbox = linkedCheckBox( element, "" ); + checkbox.setHorizontalAlignment( SwingConstants.TRAILING ); + addToLayout( + checkbox, + new JLabel( element.getLabel() ) ); + } + + @Override + public void visit( final BoundedDoubleElement element ) + { + addToLayout( + linkedSliderPanel( element, tfCols, 0.1 ), + new JLabel( element.getLabel() ) ); + } + + @Override + public void visit( final DoubleElement element ) + { + addToLayout( + linkedFormattedTextField( element ), + new JLabel( element.getLabel() ) ); + } + + @Override + public void visit( final IntElement element ) + { + addToLayout( + linkedSliderPanel( element, tfCols ), + new JLabel( element.getLabel() ) ); + } + + @Override + public void visit( final ColorElement element ) + { + addToLayoutFlushRight( + linkedColorButton( element, colorChooser ), + new JLabel( element.getLabel() ) ); + } + + @Override + public void visit( final FeatureElement element ) + { + addToLayout( + linkedFeatureSelector( element ), + new JLabel( element.getLabel() ) ); + } + + @Override + public < E > void visit( final EnumElement< E > element ) + { + addToLayout( + linkedComboBoxEnumSelector( element ), + new JLabel( element.getLabel() ) ); + } + + @Override + public void visit( final ColormapElement element ) + { + addToLayout( + linkedColormapChooser( element ), + new JLabel( element.getLabel() ) ); + } + + @Override + public void visit( final FontElement element ) + { + addToLayout( + linkedFontButton( element, SwingUtilities.getWindowAncestor( panel ) ), + new JLabel( element.getLabel() ) ); + } + + private void addToLayout( final JComponent comp1, final JComponent comp2 ) + { + c.gridwidth = 1; + c.anchor = GridBagConstraints.LINE_END; + panel.add( comp1, c ); + c.gridx++; + c.weightx = 0.0; + c.anchor = GridBagConstraints.LINE_START; + panel.add( comp2, c ); + c.gridx = 0; + c.weightx = 1.0; + c.gridy++; + } + + private void addToLayoutFlushRight( final JComponent comp1, final JComponent comp2 ) + { + c.fill = c.gridwidth = 1; + c.fill = GridBagConstraints.NONE; + c.anchor = GridBagConstraints.EAST; + panel.add( comp1, c ); + c.gridx++; + c.weightx = 0.0; + c.fill = GridBagConstraints.HORIZONTAL; + c.anchor = GridBagConstraints.LINE_START; + panel.add( comp2, c ); + c.gridx = 0; + c.weightx = 1.0; + c.gridy++; + } + + private void addToLayout( final JComponent comp ) + { + c.gridwidth = 2; + c.anchor = GridBagConstraints.LINE_START; + c.gridx = 0; + c.weightx = 1.0; + panel.add( comp, c ); + c.gridy++; + } + } } From 2a0603d98ad1a328912d07e223d58b9ef605b88b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 5 Aug 2026 15:41:07 +0200 Subject: [PATCH 322/371] DisplaySettingsIO has methods to write / read specified files. --- .../displaysettings/DisplaySettingsIO.java | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java index 2e597c044..7f7cbcd02 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java @@ -24,7 +24,6 @@ import java.awt.Color; import java.awt.Font; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; @@ -88,6 +87,25 @@ private static Gson getGson() return builder.setPrettyPrinting().create(); } + public static void write( final DisplaySettings ds, final String path ) + { + final String str = toJson( ds ); + final File file = new File( path ); + + if ( !file.exists() ) + file.getParentFile().mkdirs(); + + try (FileWriter writer = new FileWriter( file )) + { + writer.append( str ); + } + catch ( final IOException e ) + { + System.err.println( "Could not write the settings to " + file ); + e.printStackTrace(); + } + } + public static void saveToUserDefault( final DisplaySettings ds ) { final String str = toJson( ds ); @@ -106,34 +124,34 @@ public static void saveToUserDefault( final DisplaySettings ds ) } } - public static DisplaySettings readUserDefault() + public static DisplaySettings read( final String path ) { - if ( !userDefaultFile.exists() ) + try (FileReader reader = new FileReader( path )) { - final DisplaySettings ds = DisplaySettings.defaultStyle().copy( "User-default" ); - saveToUserDefault( ds ); - return ds; - } - - try (FileReader reader = new FileReader( userDefaultFile )) - { - final String str = Files.lines( Paths.get( userDefaultFile.getAbsolutePath() ) ) + final String str = Files.lines( Paths.get( path ) ) .collect( Collectors.joining( System.lineSeparator() ) ); return fromJson( str ); } - catch ( final FileNotFoundException e ) + catch ( final IOException e ) { - System.err.println( "Could not find the user default settings file: " + userDefaultFile - + ". Using built-in default setting." ); + System.err.println( "Could not read the file: " + path ); e.printStackTrace(); } - catch ( final IOException e ) + return null; + } + + public static DisplaySettings readUserDefault() + { + if ( !userDefaultFile.exists() ) { - System.err.println( "Could not read the user default settings file: " + userDefaultFile - + ". Using built-in default setting." ); - e.printStackTrace(); + final DisplaySettings ds = DisplaySettings.defaultStyle().copy( "User-default" ); + saveToUserDefault( ds ); + return ds; } + final DisplaySettings ds = read( userDefaultFile.getAbsolutePath() ); + if ( ds != null ) + return ds; return DisplaySettings.defaultStyle().copy(); } From aa1d049aa0964a40b3fda80aaf11153f3f51457d Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 5 Aug 2026 15:41:36 +0200 Subject: [PATCH 323/371] A SelectAndEditProfileSettingsPage for DisplaySettings. With a manager that can deal with a collection of settings. --- .../DisplaySettingsConfigPage.java | 53 +++++++ .../DisplaySettingsManager.java | 149 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsConfigPage.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsConfigPage.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsConfigPage.java new file mode 100644 index 000000000..aa3099157 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsConfigPage.java @@ -0,0 +1,53 @@ +package fiji.plugin.trackmate.gui.displaysettings; + +import java.awt.BorderLayout; +import java.awt.Frame; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JDialog; +import javax.swing.WindowConstants; + +import bdv.ui.settings.SelectAndEditProfileSettingsPage; +import bdv.ui.settings.SettingsPanel; +import bdv.ui.settings.style.StyleProfile; +import bdv.ui.settings.style.StyleProfileManager; + +public class DisplaySettingsConfigPage extends SelectAndEditProfileSettingsPage< StyleProfile< DisplaySettings > > +{ + + public DisplaySettingsConfigPage( final String treePath, final DisplaySettingsManager displaySettingsManager ) + { + super( + treePath, + new StyleProfileManager<>( displaySettingsManager, new DisplaySettingsManager( false ) ), + new DisplaySettingsPanel( displaySettingsManager.getSelectedStyle() ) ); + } + + public static void main( final String[] args ) + { + final DisplaySettingsManager styleManager = new DisplaySettingsManager(); + + final SettingsPanel settings = new SettingsPanel(); + settings.addPage( new DisplaySettingsConfigPage( "Display settings", styleManager ) ); + + final JDialog dialog = new JDialog( ( Frame ) null, "Settings" ); + dialog.getContentPane().add( settings, BorderLayout.CENTER ); + dialog.pack(); + dialog.setLocationRelativeTo( null ); + + settings.onOk( () -> dialog.setVisible( false ) ); + settings.onCancel( () -> dialog.setVisible( false ) ); + + dialog.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); + dialog.addWindowListener( new WindowAdapter() + { + @Override + public void windowClosing( final WindowEvent e ) + { + settings.cancel(); + } + } ); + dialog.setVisible( true ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java new file mode 100644 index 000000000..0548f2309 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java @@ -0,0 +1,149 @@ +package fiji.plugin.trackmate.gui.displaysettings; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import bdv.ui.settings.style.AbstractStyleManager; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackDisplayMode; + +public class DisplaySettingsManager extends AbstractStyleManager< DisplaySettingsManager, DisplaySettings > +{ + + private static final String DISPLAY_SETTINGS_FOLDER = new File( new File( System.getProperty( "user.home" ), ".trackmate" ), "displaysettings" ).getAbsolutePath(); + + private static final String SELECTED_STYLE_FILENAME = "selected.txt"; + + private final DisplaySettings forwardDefaultStyle; + + private final DisplaySettings.UpdateListener updateForwardDefaultListeners; + + public DisplaySettingsManager() + { + this( true ); + } + + public DisplaySettingsManager( final boolean loadStyles ) + { + forwardDefaultStyle = DisplaySettings.defaultStyle().copy(); + updateForwardDefaultListeners = () -> forwardDefaultStyle.set( selectedStyle ); + selectedStyle.listeners().add( updateForwardDefaultListeners ); + if ( loadStyles ) + loadStyles(); + } + + public DisplaySettings getForwardDefaultStyle() + { + return forwardDefaultStyle; + } + + @Override + public synchronized void setSelectedStyle( final DisplaySettings ds ) + { + selectedStyle.listeners().remove( updateForwardDefaultListeners ); + selectedStyle = ds; + forwardDefaultStyle.set( selectedStyle ); + selectedStyle.listeners().add( updateForwardDefaultListeners ); + } + + @Override + protected List< DisplaySettings > loadBuiltinStyles() + { + final DisplaySettings ds1 = DisplaySettings.defaultStyle(); + final DisplaySettings ds2 = ds1.copy( "Dragon tail" ); + ds2.setLineThickness( 2. ); + ds2.setTrackDisplayMode( TrackDisplayMode.LOCAL_BACKWARD ); + return List.of( ds1, ds2 ); + } + + public void loadStyles() + { + loadStyles( DISPLAY_SETTINGS_FOLDER ); + } + + @Override + public void saveStyles() + { + saveStyles( DISPLAY_SETTINGS_FOLDER ); + } + + public void loadStyles( final String folder ) + { + // Load the selected style name from the text file + final File selectedFile = new File( folder, SELECTED_STYLE_FILENAME ); + String selectedName = null; + try + { + selectedName = Files.readString( selectedFile.toPath() ).trim(); + } + catch ( final IOException e ) + {} + + setSelectedStyle( builtinStyles.get( 0 ) ); + userStyles.clear(); + final Set< String > names = builtinStyles.stream().map( DisplaySettings::getName ).collect( Collectors.toSet() ); + + // Get all JSon files in the folder + final File[] files = new File( folder ).listFiles( ( dir, name ) -> name.toLowerCase().endsWith( ".json" ) ); + if ( files == null ) + return; + + // Read each file and add it if it is valid and has a unique name. + for ( final File file : files ) + { + final DisplaySettings ds = DisplaySettingsIO.read( file.getAbsolutePath() ); + if ( ds == null ) + continue; + if ( names.contains( ds.getName() ) ) + { + System.err.println( "Discarded settings with duplicate name \"" + ds.getName() + "\"." ); + continue; + } + userStyles.add( ds ); + if ( ds.getName().equals( selectedName ) ) + setSelectedStyle( ds ); + } + for ( final DisplaySettings ds : builtinStyles ) + { + if ( ds.getName().equals( selectedName ) ) + setSelectedStyle( ds ); + } + } + + public void saveStyles( final String folder ) + { + new File( folder ).mkdirs(); + + // Save what style is selected in a text file + final File selectedFile = new File( folder, SELECTED_STYLE_FILENAME ); + try + { + Files.writeString( selectedFile.toPath(), selectedStyle.getName() ); + } + catch ( final IOException e ) + { + e.printStackTrace(); + } + + // List all json files in the folder and delete those that do not + // correspond to a user style. + final File[] files = new File( folder ).listFiles( ( dir, name ) -> name.toLowerCase().endsWith( ".json" ) ); + if ( files != null ) + { + final Set< String > userStyleNames = userStyles.stream().map( DisplaySettings::getName ).collect( Collectors.toSet() ); + for ( final File file : files ) + { + final String filename = file.getName().substring( 0, file.getName().length() - 5 ); + if ( !userStyleNames.contains( filename ) ) + file.delete(); + } + } + + // Save all user styles to the folder + for ( final DisplaySettings ds : userStyles ) + DisplaySettingsIO.write( ds, new File( folder, ds.getName() + ".json" ).getAbsolutePath() ); + } +} From 01e14e46c797a80c1fd53b57d2f2ce2cba8ca6fd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 5 Aug 2026 16:23:38 +0200 Subject: [PATCH 324/371] The DisplaySettingsManager can be set to modify a specific instance. This is important, as we will load a DisplaySettings from a TrackMate file and want to use this instance. We also want it to appear in the config page and the modifications to be done on this one. --- .../DisplaySettingsConfigPage.java | 2 +- .../DisplaySettingsManager.java | 54 ++++++++++++++----- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsConfigPage.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsConfigPage.java index aa3099157..0918ba7fd 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsConfigPage.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsConfigPage.java @@ -20,7 +20,7 @@ public DisplaySettingsConfigPage( final String treePath, final DisplaySettingsMa { super( treePath, - new StyleProfileManager<>( displaySettingsManager, new DisplaySettingsManager( false ) ), + new StyleProfileManager<>( displaySettingsManager, new DisplaySettingsManager( null, false ) ), new DisplaySettingsPanel( displaySettingsManager.getSelectedStyle() ) ); } diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java index 0548f2309..7b119a4d6 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java @@ -23,16 +23,33 @@ public class DisplaySettingsManager extends AbstractStyleManager< DisplaySetting public DisplaySettingsManager() { - this( true ); + this( null, true ); } - public DisplaySettingsManager( final boolean loadStyles ) + /** + * Creates a new DisplaySettingsManager. + * + * @param styleToManage + * the style that will be managed by this manager. If + * null, the default style will be used. + * @param loadStyles + * if true, the styles will be loaded from the + * {@link #DISPLAY_SETTINGS_FOLDER} folder. If styleToManage is + * null, the main style will be set to the style + * that was loaded from the folder. + */ + public DisplaySettingsManager( final DisplaySettings styleToManage, final boolean loadStyles ) { - forwardDefaultStyle = DisplaySettings.defaultStyle().copy(); + final boolean loadSelectedStyle = ( null == styleToManage ); + if ( loadSelectedStyle ) + forwardDefaultStyle = DisplaySettings.defaultStyle().copy(); + else + forwardDefaultStyle = styleToManage; + updateForwardDefaultListeners = () -> forwardDefaultStyle.set( selectedStyle ); selectedStyle.listeners().add( updateForwardDefaultListeners ); if ( loadStyles ) - loadStyles(); + loadStyles( loadSelectedStyle ); } public DisplaySettings getForwardDefaultStyle() @@ -59,9 +76,9 @@ protected List< DisplaySettings > loadBuiltinStyles() return List.of( ds1, ds2 ); } - public void loadStyles() + public void loadStyles( final boolean loadSelectedStyle ) { - loadStyles( DISPLAY_SETTINGS_FOLDER ); + loadStyles( DISPLAY_SETTINGS_FOLDER, loadSelectedStyle ); } @Override @@ -70,7 +87,7 @@ public void saveStyles() saveStyles( DISPLAY_SETTINGS_FOLDER ); } - public void loadStyles( final String folder ) + public void loadStyles( final String folder, final boolean loadSelectedStyle ) { // Load the selected style name from the text file final File selectedFile = new File( folder, SELECTED_STYLE_FILENAME ); @@ -82,7 +99,9 @@ public void loadStyles( final String folder ) catch ( final IOException e ) {} - setSelectedStyle( builtinStyles.get( 0 ) ); + if ( loadSelectedStyle ) + setSelectedStyle( builtinStyles.get( 0 ) ); + userStyles.clear(); final Set< String > names = builtinStyles.stream().map( DisplaySettings::getName ).collect( Collectors.toSet() ); @@ -103,13 +122,24 @@ public void loadStyles( final String folder ) continue; } userStyles.add( ds ); - if ( ds.getName().equals( selectedName ) ) + if ( ds.getName().equals( selectedName ) && loadSelectedStyle ) setSelectedStyle( ds ); } - for ( final DisplaySettings ds : builtinStyles ) + + if ( loadSelectedStyle ) { - if ( ds.getName().equals( selectedName ) ) - setSelectedStyle( ds ); + for ( final DisplaySettings ds : builtinStyles ) + { + if ( ds.getName().equals( selectedName ) ) + setSelectedStyle( ds ); + } + } + else + { + final DisplaySettings copy = forwardDefaultStyle.copy(); + userStyles.removeIf( ds -> ds.getName().equals( copy.getName() ) ); + userStyles.add( copy ); + setSelectedStyle( copy ); } } From eb2d182c017421bc2fa1104012dea0105d88ecb3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 5 Aug 2026 16:24:20 +0200 Subject: [PATCH 325/371] The GuiModel has a DisplaySettingsManager, which is set to edit the DisplaySettings instance it was created with. --- src/main/java/fiji/plugin/trackmate/gui/GuiModel.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java index c27f63eab..d714c9e54 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java @@ -13,6 +13,7 @@ import fiji.plugin.trackmate.Settings; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsManager; import fiji.plugin.trackmate.gui.editor.labkit.component.EditorKeymapManager; import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SemiAutoTrackingParams; @@ -53,6 +54,8 @@ public class GuiModel private final WindowManager windowManager; + private final DisplaySettingsManager dsManager; + /** * Creates a new GuiModel. * @@ -83,6 +86,9 @@ public GuiModel( final Model model, final Settings settings, final DisplaySettin this.globalActions = new Actions( keymapManager.getForwardSelectedKeymap().getConfig(), KeyConfigContexts.TRACKMATE ); TrackMateActions.install( globalActions, model, selectionModel ); + // Other managers + this.dsManager = new DisplaySettingsManager( displaySettings, true ); + // Window manager this.windowManager = new WindowManager( this ); } @@ -243,4 +249,9 @@ public SemiAutoTrackingParams getSemiAutoTrackingParams() { return semiAutoTrackingparams; } + + public DisplaySettingsManager getDisplaySettingsManager() + { + return dsManager; + } } From 68b56cfe736831f91618c6478c831cddfc0945ac Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Wed, 5 Aug 2026 16:24:35 +0200 Subject: [PATCH 326/371] Add the display settings config page to the preferences. --- src/main/java/fiji/plugin/trackmate/gui/WindowManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java index 5b25959ed..0335be4f8 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java @@ -29,6 +29,7 @@ import bdv.ui.keymap.KeymapSettingsPage; import bdv.util.InvokeOnEDT; import bvv.vistools.BvvHandle; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsConfigPage; import fiji.plugin.trackmate.gui.editor.LabkitLauncher; import fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame; import fiji.plugin.trackmate.util.TMUtils; @@ -68,9 +69,10 @@ public WindowManager( final GuiModel guiModel ) preferencesDialog.setTitle( "TrackMate Preferences" ); preferencesDialog.setLocationRelativeTo( null ); BigDataViewerActions.toggleDialogAction( globalActions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); - preferencesDialog.addPage( new SpotEditToolSettingsPage( "Semi-auto tracking", guiModel.getSemiAutoTrackingParams() ) ); + preferencesDialog.addPage( new DisplaySettingsConfigPage( "Display settings", guiModel.getDisplaySettingsManager() ) ); preferencesDialog.addPage( new KeymapSettingsPage( "Global keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); preferencesDialog.addPage( new KeymapSettingsPage( "Editor keymap", guiModel.getEditorKeymapManager(), guiModel.getEditorKeymapManager().getCommandDescriptions() ) ); + preferencesDialog.addPage( new SpotEditToolSettingsPage( "Semi-auto tracking", guiModel.getSemiAutoTrackingParams() ) ); preferencesDialog.addPage( new AppearanceSettingsPage( "Editor appearance", guiModel.getAppearanceManager() ) ); } From 22f03c48ef8d9e883a98efda15c0b716640563ce Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 6 Aug 2026 11:25:10 +0200 Subject: [PATCH 327/371] Remove the old tool to configure display settings. Now everything is done in the Preferences dialog. The readUserDefault() method now returns the last settings used and configured by the user in the dialog. --- .../gui/components/ConfigureViewsPanel.java | 26 +--- .../ConfigTrackMateDisplaySettings.java | 145 ------------------ .../displaysettings/DisplaySettingsIO.java | 41 +---- .../DisplaySettingsManager.java | 74 +++++---- 4 files changed, 53 insertions(+), 233 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/ConfigTrackMateDisplaySettings.java 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 c4e789bf8..f60a66d2a 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -25,7 +25,6 @@ import static fiji.plugin.trackmate.gui.Fonts.FONT; import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT; import static fiji.plugin.trackmate.gui.Icons.BVV_ICON; -import static fiji.plugin.trackmate.gui.Icons.EDIT_SETTINGS_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; @@ -46,7 +45,6 @@ import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFormattedTextField; -import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRootPane; @@ -61,7 +59,6 @@ import fiji.plugin.trackmate.gui.GuiUtils; import fiji.plugin.trackmate.gui.Icons; import fiji.plugin.trackmate.gui.WindowManager; -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; @@ -97,6 +94,7 @@ public ConfigureViewsPanel( final GuiModel guiModel, final FeatureDisplaySelecto { this.guiModel = guiModel; this.windowManager = guiModel.getWindowManager(); + final DisplaySettings ds = guiModel.getDisplaySettings(); this.setPreferredSize( new Dimension( 300, 521 ) ); this.setSize( 300, 500 ); @@ -121,28 +119,6 @@ public ConfigureViewsPanel( final GuiModel guiModel, final FeatureDisplaySelecto gbcLabelDisplayOptions.gridy = 0; add( lblDisplayOptions, gbcLabelDisplayOptions ); - /* - * Settings editor. - */ - - final DisplaySettings ds = guiModel.getDisplaySettings(); - final JFrame editor = ConfigTrackMateDisplaySettings.editor( ds, - "Configure the display settings used in this current session.", - "TrackMate display settings" ); - editor.setLocationRelativeTo( this.getParent() ); - editor.setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE ); - - final JButton btnEditSettings = new JButton( "Edit settings", EDIT_SETTINGS_ICON ); - btnEditSettings.addActionListener( e -> editor.setVisible( !editor.isVisible() ) ); - - final GridBagConstraints gbcBtnEditSettings = new GridBagConstraints(); - gbcBtnEditSettings.fill = GridBagConstraints.NONE; - gbcBtnEditSettings.insets = new Insets( 5, 5, 5, 5 ); - gbcBtnEditSettings.anchor = GridBagConstraints.EAST; - gbcBtnEditSettings.gridx = 1; - gbcBtnEditSettings.gridy = 0; - add( btnEditSettings, gbcBtnEditSettings ); - /* * Display spot checkbox. */ diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ConfigTrackMateDisplaySettings.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ConfigTrackMateDisplaySettings.java deleted file mode 100644 index cacd05971..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ConfigTrackMateDisplaySettings.java +++ /dev/null @@ -1,145 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.displaysettings; - -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.Icons.TRACKMATE_ICON; - -import java.awt.BorderLayout; - -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.UIManager; -import javax.swing.UnsupportedLookAndFeelException; - -import org.scijava.command.Command; -import org.scijava.plugin.Plugin; - -import ij.ImageJ; - -@Plugin( type = Command.class, - label = "Configure TrackMate display settings...", - iconPath = "/icons/commands/information.png", - menuPath = "Edit > Options > Configure TrackMate display settings..." ) -public class ConfigTrackMateDisplaySettings implements Command -{ - - private static final String APPLY_TOOLTIP = "Save the current settings to the user default settings. " - + "They will be used in all the following TrackMate sessions."; - private static final String REVERT_TOOLTIP = "Revert the current settings to the ones saved in the " - + "user default settings file."; - private static final String RESET_TOOLTIP = "Reset the current settings to the built-in defaults."; - - @Override - public void run() - { - editor( DisplaySettingsIO.readUserDefault(), - "Configure the default settings to be used by TrackMate.", - "TrackMate user default settings" ).setVisible( true ); - } - - public static JFrame editor( final DisplaySettings ds, final String titleStr, final String frameName ) - { - final JPanel configPanel = new JPanel(); - configPanel.setLayout( new BorderLayout() ); - - /* - * Title. - */ - - final JLabel title = new JLabel( "" - + titleStr - + "" ); - title.setBorder( BorderFactory.createEmptyBorder( 10, 5, 10, 5 ) ); - configPanel.add( title, BorderLayout.NORTH ); - - /* - * Buttons. - */ - - final JPanel panelButton = new JPanel(); - 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 ) ); - configPanel.add( panelButton, BorderLayout.SOUTH ); - - /* - * Display settings editor. - */ - - final DisplaySettingsPanel editor = new DisplaySettingsPanel( ds ); - configPanel.add( editor, BorderLayout.CENTER ); - - /* - * Listeners. - */ - - btnReset.addActionListener( e -> { - ds.set( DisplaySettings.defaultStyle().copy( "User-default" ) ); - title.setText( "Reset the current settings to the built-in defaults." ); - } ); - btnRevert.addActionListener( e -> { - ds.set( DisplaySettingsIO.readUserDefault() ); - title.setText( "Reverted the current settings to the user defaults." ); - } ); - btnApply.addActionListener( e -> { - DisplaySettingsIO.saveToUserDefault( ds ); - title.setText( "Saved the current settings to the user defaults file." ); - } ); - - /* - * Create and show frame. - */ - - final JFrame frame = new JFrame( frameName ); - frame.setIconImage( TRACKMATE_ICON.getImage() ); - frame.getContentPane().add( configPanel ); - frame.pack(); - frame.setLocationRelativeTo( null ); - return frame; - } - - public static void main( final String[] args ) throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException - { - UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); - ImageJ.main( args ); - new ConfigTrackMateDisplaySettings().run(); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java index 7f7cbcd02..9c8d5052f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java @@ -50,8 +50,6 @@ public class DisplaySettingsIO { - private static File userDefaultFile = new File( new File( System.getProperty( "user.home" ), ".trackmate" ), "userdefaultsettings.json" ); - public static void toXML( final DisplaySettings ds, final Element dsel ) { dsel.setText( toJson( ds ) ); @@ -64,7 +62,9 @@ public static String toJson( final DisplaySettings ds ) public static DisplaySettings fromJson( final String str ) { - final DisplaySettings ds = ( str == null || str.isEmpty() ) ? readUserDefault() : getGson().fromJson( str, DisplaySettings.class ); + final DisplaySettings ds = ( str == null || str.isEmpty() ) + ? DisplaySettings.defaultStyle().copy() + : getGson().fromJson( str, DisplaySettings.class ); // Sanitize min and max. final double spotMin = ds.getSpotMin(); @@ -106,24 +106,6 @@ public static void write( final DisplaySettings ds, final String path ) } } - public static void saveToUserDefault( final DisplaySettings ds ) - { - final String str = toJson( ds ); - - if ( !userDefaultFile.exists() ) - userDefaultFile.getParentFile().mkdirs(); - - try (FileWriter writer = new FileWriter( userDefaultFile )) - { - writer.append( str ); - } - catch ( final IOException e ) - { - System.err.println( "Could not write the user default settings to " + userDefaultFile ); - e.printStackTrace(); - } - } - public static DisplaySettings read( final String path ) { try (FileReader reader = new FileReader( path )) @@ -143,16 +125,8 @@ public static DisplaySettings read( final String path ) public static DisplaySettings readUserDefault() { - if ( !userDefaultFile.exists() ) - { - final DisplaySettings ds = DisplaySettings.defaultStyle().copy( "User-default" ); - saveToUserDefault( ds ); - return ds; - } - final DisplaySettings ds = read( userDefaultFile.getAbsolutePath() ); - if ( ds != null ) - return ds; - return DisplaySettings.defaultStyle().copy(); + return new DisplaySettingsManager().getInstance().copy(); + } /** @@ -272,9 +246,4 @@ public Color deserialize( final JsonElement json, final Type typeOfT, final Json } } } - - public static void main( final String[] args ) - { - System.out.println( readUserDefault() ); - } } diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java index 7b119a4d6..c739185a1 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsManager.java @@ -8,7 +8,9 @@ import java.util.stream.Collectors; import bdv.ui.settings.style.AbstractStyleManager; +import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackDisplayMode; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; public class DisplaySettingsManager extends AbstractStyleManager< DisplaySettingsManager, DisplaySettings > { @@ -21,38 +23,48 @@ public class DisplaySettingsManager extends AbstractStyleManager< DisplaySetting private final DisplaySettings.UpdateListener updateForwardDefaultListeners; - public DisplaySettingsManager() - { - this( null, true ); - } - /** * Creates a new DisplaySettingsManager. * - * @param styleToManage + * @param instance * the style that will be managed by this manager. If - * null, the default style will be used. + * null, a new instance will be created with the + * default style. * @param loadStyles * if true, the styles will be loaded from the - * {@link #DISPLAY_SETTINGS_FOLDER} folder. If styleToManage is - * null, the main style will be set to the style - * that was loaded from the folder. + * {@link #DISPLAY_SETTINGS_FOLDER} folder. */ - public DisplaySettingsManager( final DisplaySettings styleToManage, final boolean loadStyles ) + public DisplaySettingsManager( final DisplaySettings instance, final boolean loadStyles ) { - final boolean loadSelectedStyle = ( null == styleToManage ); - if ( loadSelectedStyle ) - forwardDefaultStyle = DisplaySettings.defaultStyle().copy(); + final boolean instanceProvided = ( null != instance ); + if ( instanceProvided ) + forwardDefaultStyle = instance; else - forwardDefaultStyle = styleToManage; + forwardDefaultStyle = DisplaySettings.defaultStyle().copy(); updateForwardDefaultListeners = () -> forwardDefaultStyle.set( selectedStyle ); selectedStyle.listeners().add( updateForwardDefaultListeners ); if ( loadStyles ) - loadStyles( loadSelectedStyle ); + loadStyles( instanceProvided ); + } + + /** + * Creates a new DisplaySettingsManager and loads the styles from the + * {@link #DISPLAY_SETTINGS_FOLDER} folder. The {@link #getInstance()} will + * be set to the last configured style by the user. + */ + public DisplaySettingsManager() + { + this( null, true ); } - public DisplaySettings getForwardDefaultStyle() + /** + * Exposes the style that is managed by this manager. This instance will be + * modified by the settings editor using this manager. + * + * @return the style that is managed by this manager. + */ + public DisplaySettings getInstance() { return forwardDefaultStyle; } @@ -70,15 +82,23 @@ public synchronized void setSelectedStyle( final DisplaySettings ds ) protected List< DisplaySettings > loadBuiltinStyles() { final DisplaySettings ds1 = DisplaySettings.defaultStyle(); - final DisplaySettings ds2 = ds1.copy( "Dragon tail" ); - ds2.setLineThickness( 2. ); - ds2.setTrackDisplayMode( TrackDisplayMode.LOCAL_BACKWARD ); - return List.of( ds1, ds2 ); + + final DisplaySettings ds2 = ds1.copy( "Color by track" ); + ds2.setTrackColorBy( TrackMateObject.TRACKS, TrackIndexAnalyzer.TRACK_INDEX ); + ds2.setSpotColorBy( TrackMateObject.TRACKS, TrackIndexAnalyzer.TRACK_INDEX ); + + final DisplaySettings ds3 = ds2.copy( "Dragon tail" ); + ds3.setLineThickness( 2. ); + ds3.setTrackDisplayMode( TrackDisplayMode.LOCAL_BACKWARD ); + ds3.setSpotFilled( true ); + ds3.setSpotTransparencyAlpha( 0.8 ); + + return List.of( ds1, ds2, ds3 ); } - public void loadStyles( final boolean loadSelectedStyle ) + public void loadStyles( final boolean instanceProvided ) { - loadStyles( DISPLAY_SETTINGS_FOLDER, loadSelectedStyle ); + loadStyles( DISPLAY_SETTINGS_FOLDER, instanceProvided ); } @Override @@ -87,7 +107,7 @@ public void saveStyles() saveStyles( DISPLAY_SETTINGS_FOLDER ); } - public void loadStyles( final String folder, final boolean loadSelectedStyle ) + public void loadStyles( final String folder, final boolean instanceProvided ) { // Load the selected style name from the text file final File selectedFile = new File( folder, SELECTED_STYLE_FILENAME ); @@ -99,7 +119,7 @@ public void loadStyles( final String folder, final boolean loadSelectedStyle ) catch ( final IOException e ) {} - if ( loadSelectedStyle ) + if ( !instanceProvided ) setSelectedStyle( builtinStyles.get( 0 ) ); userStyles.clear(); @@ -122,11 +142,11 @@ public void loadStyles( final String folder, final boolean loadSelectedStyle ) continue; } userStyles.add( ds ); - if ( ds.getName().equals( selectedName ) && loadSelectedStyle ) + if ( ds.getName().equals( selectedName ) && !instanceProvided ) setSelectedStyle( ds ); } - if ( loadSelectedStyle ) + if ( !instanceProvided ) { for ( final DisplaySettings ds : builtinStyles ) { From 815a300d98b634b957410e80541285969d409e99 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 6 Aug 2026 11:25:31 +0200 Subject: [PATCH 328/371] In the wizard, make spots visible when filtering them if they are invisible. --- .../gui/wizard/descriptors/SpotFilterDescriptor.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 4037a22fc..2ea163d22 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 @@ -34,6 +34,7 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.TrackMate; import fiji.plugin.trackmate.features.FeatureFilter; +import fiji.plugin.trackmate.features.FeatureUtils; import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; import fiji.plugin.trackmate.gui.components.FilterGuiPanel; @@ -140,6 +141,11 @@ public void run() logger.log( String.format( "Calculating features done in %.1f s.\n", ( end - start ) / 1e3f ) ); panel.showProgressBar( false ); + // If spots are not very visible because of the display + // settings, make them visible. + guiModel.getDisplaySettings().setSpotVisible( true ); + guiModel.getDisplaySettings().setSpotColorBy( TrackMateObject.SPOTS, FeatureUtils.USE_UNIFORM_COLOR_KEY ); + // Refresh component. panel.refreshValues(); filterSpots(); From 320b724fb48b1a6b312565428a87553552ca9aa3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Thu, 6 Aug 2026 18:51:27 +0200 Subject: [PATCH 329/371] Remove the TrackMate style element classes and resources. We moved everything to the config-ui artifact. --- pom.xml | 6 - .../action/closegaps/CloseGapsPanel.java | 7 +- .../action/meshtools/MeshSmootherPanel.java | 17 +- .../components/FeatureDisplaySelector.java | 3 +- .../gui/components/PanelProbaThreshold.java | 6 +- .../gui/components/PanelSmoothContour.java | 6 +- .../gui/displaysettings/BoundedValue.java | 97 -- .../displaysettings/BoundedValueDouble.java | 97 -- .../gui/displaysettings/ColorIcon.java | 93 -- .../gui/displaysettings/Colormap.java | 313 ----- .../gui/displaysettings/ColormapIO.java | 169 --- .../gui/displaysettings/DisplaySettings.java | 1 + .../displaysettings/DisplaySettingsIO.java | 1 + .../displaysettings/DisplaySettingsPanel.java | 68 +- .../gui/displaysettings/SliderPanel.java | 205 --- .../displaysettings/SliderPanelDouble.java | 287 ---- .../gui/displaysettings/StyleElements.java | 1208 ----------------- .../TrackMateStyleElements.java | 118 ++ .../config/GenericConfigPanelPreview.java | 2 +- .../PerEdgeFeatureColorGenerator.java | 2 +- .../PerSpotFeatureColorGenerator.java | 2 +- .../PerTrackFeatureColorGenerator.java | 2 +- .../visualization/SpotColorGenerator.java | 3 +- .../SpotColorGeneratorPerEdgeFeature.java | 2 +- .../SpotColorGeneratorPerTrackFeature.java | 3 +- .../WholeTrackFeatureColorGenerator.java | 3 +- .../visualization/table/TablePanel.java | 3 +- .../gui/displaysettings/luts/Algae.lut | 257 ---- .../gui/displaysettings/luts/Amp.lut | 257 ---- .../gui/displaysettings/luts/Balance.lut | 257 ---- .../gui/displaysettings/luts/Curl.lut | 257 ---- .../gui/displaysettings/luts/Deep.lut | 257 ---- .../gui/displaysettings/luts/Delta.lut | 257 ---- .../gui/displaysettings/luts/Dense.lut | 257 ---- .../gui/displaysettings/luts/Gray.lut | 257 ---- .../gui/displaysettings/luts/Haline.lut | 257 ---- .../gui/displaysettings/luts/Ice.lut | 257 ---- .../gui/displaysettings/luts/Matter.lut | 257 ---- .../gui/displaysettings/luts/Oxy.lut | 257 ---- .../gui/displaysettings/luts/Phase.lut | 257 ---- .../gui/displaysettings/luts/Solar.lut | 257 ---- .../gui/displaysettings/luts/Speed.lut | 257 ---- .../gui/displaysettings/luts/Tempo.lut | 257 ---- .../gui/displaysettings/luts/Thermal.lut | 257 ---- .../gui/displaysettings/luts/Turbid.lut | 257 ---- 45 files changed, 188 insertions(+), 7162 deletions(-) delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/BoundedValue.java delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/BoundedValueDouble.java delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/ColorIcon.java delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/ColormapIO.java delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java delete mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java create mode 100644 src/main/java/fiji/plugin/trackmate/gui/displaysettings/TrackMateStyleElements.java delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Algae.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Amp.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Balance.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Curl.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Deep.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Delta.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Dense.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Gray.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Haline.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Ice.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Matter.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Oxy.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Phase.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Solar.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Speed.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Tempo.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Thermal.lut delete mode 100644 src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Turbid.lut diff --git a/pom.xml b/pom.xml index 838eff3ab..3e2b2eb82 100644 --- a/pom.xml +++ b/pom.xml @@ -179,7 +179,6 @@ (https://github.com/scijava/scijava-coding-style) --> imglib2 - 2.5.2 0.11.1 8.0.0 10.6.7 @@ -391,11 +390,6 @@ javaGeom ${javaGeom.version} - - org.drjekyll - fontchooser - ${fontchooser.version} - diff --git a/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsPanel.java b/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsPanel.java index 2012beb98..ae4d7d45a 100644 --- a/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/closegaps/CloseGapsPanel.java @@ -39,11 +39,12 @@ import javax.swing.SwingConstants; import javax.swing.border.EmptyBorder; +import org.scijava.ui.config.visitors.gui.elements.SliderPanelDouble; +import org.scijava.ui.config.visitors.gui.elements.StyleElements; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; + import fiji.plugin.trackmate.action.closegaps.GapClosingMethod.GapClosingParameter; import fiji.plugin.trackmate.gui.Fonts; -import fiji.plugin.trackmate.gui.displaysettings.SliderPanelDouble; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; /** * A basic UI to let a TrackMate user choose between several techniques for gap 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 4987a6f3b..c8c47d638 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherPanel.java @@ -39,14 +39,15 @@ import javax.swing.JRadioButton; import javax.swing.JTabbedPane; -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; -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 org.scijava.ui.config.visitors.gui.elements.SliderPanel; +import org.scijava.ui.config.visitors.gui.elements.SliderPanelDouble; +import org.scijava.ui.config.visitors.gui.elements.StyleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElementVisitor; +import org.scijava.ui.config.visitors.gui.elements.StyleElements; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.EnumElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.IntElement; + import net.imglib2.mesh.alg.TaubinSmoothing.TaubinWeightType; public class MeshSmootherPanel extends JPanel 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 019204688..512aff85d 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/FeatureDisplaySelector.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/FeatureDisplaySelector.java @@ -59,6 +59,8 @@ import javax.swing.JPopupMenu; import javax.swing.SwingConstants; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; + import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.ModelChangeEvent; import fiji.plugin.trackmate.Settings; @@ -67,7 +69,6 @@ import fiji.plugin.trackmate.features.manual.ManualSpotColorAnalyzerFactory; import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; import fiji.plugin.trackmate.gui.GuiUtils; -import fiji.plugin.trackmate.gui.displaysettings.Colormap; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/PanelProbaThreshold.java b/src/main/java/fiji/plugin/trackmate/gui/components/PanelProbaThreshold.java index bf0212b17..923828d37 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/PanelProbaThreshold.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/PanelProbaThreshold.java @@ -31,9 +31,9 @@ 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; +import org.scijava.ui.config.visitors.gui.elements.SliderPanelDouble; +import org.scijava.ui.config.visitors.gui.elements.StyleElements; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; /** * A utility widget that lets a user specify a threshold on a probability value, 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 466ae4c5f..741e66cf9 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/PanelSmoothContour.java @@ -32,9 +32,9 @@ 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; +import org.scijava.ui.config.visitors.gui.elements.SliderPanelDouble; +import org.scijava.ui.config.visitors.gui.elements.StyleElements; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; public class PanelSmoothContour extends JPanel { diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/BoundedValue.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/BoundedValue.java deleted file mode 100644 index 4c0efd3bc..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/BoundedValue.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.displaysettings; - -/** - * A int variable that can take any value in a given range. A - * {@link #setUpdateListener(UpdateListener) listener} is notified when the - * value or its allowed range is changed. - * - * @author Tobias Pietzsch - */ -public class BoundedValue -{ - private int rangeMin; - - private int rangeMax; - - private int currentValue; - - public interface UpdateListener - { - void update(); - } - - private UpdateListener updateListener; - - public BoundedValue( final int rangeMin, final int rangeMax, final int currentValue ) - { - this.rangeMin = rangeMin; - this.rangeMax = rangeMax; - this.currentValue = currentValue; - updateListener = null; - } - - public int getRangeMin() - { - return rangeMin; - } - - public int getRangeMax() - { - return rangeMax; - } - - public int getCurrentValue() - { - return currentValue; - } - - public void setRange( final int min, final int max ) - { - assert min <= max; - rangeMin = min; - rangeMax = max; - currentValue = Math.min( Math.max( currentValue, min ), max ); - - if ( updateListener != null ) - updateListener.update(); - } - - public void setCurrentValue( final int value ) - { - currentValue = value; - - if ( currentValue < rangeMin ) - currentValue = rangeMin; - else if ( currentValue > rangeMax ) - currentValue = rangeMax; - - if ( updateListener != null ) - updateListener.update(); - } - - public void setUpdateListener( final UpdateListener l ) - { - updateListener = l; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/BoundedValueDouble.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/BoundedValueDouble.java deleted file mode 100644 index 61f987d28..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/BoundedValueDouble.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.displaysettings; - -/** - * A {@code double} variable that can take any value in a given range. A - * {@link #setUpdateListener(UpdateListener) listener} is notified when the - * value or its allowed range is changed. - * - * @author Tobias Pietzsch - */ -public class BoundedValueDouble -{ - private double rangeMin; - - private double rangeMax; - - private double currentValue; - - public interface UpdateListener - { - void update(); - } - - private UpdateListener updateListener; - - public BoundedValueDouble( final double rangeMin, final double rangeMax, final double currentValue ) - { - this.rangeMin = rangeMin; - this.rangeMax = rangeMax; - this.currentValue = currentValue; - updateListener = null; - } - - public double getRangeMin() - { - return rangeMin; - } - - public double getRangeMax() - { - return rangeMax; - } - - public double getCurrentValue() - { - return currentValue; - } - - public void setRange( final double min, final double max ) - { - assert min <= max; - rangeMin = min; - rangeMax = max; - currentValue = Math.min( Math.max( currentValue, min ), max ); - - if ( updateListener != null ) - updateListener.update(); - } - - public void setCurrentValue( final double value ) - { - currentValue = value; - - if ( currentValue < rangeMin ) - currentValue = rangeMin; - else if ( currentValue > rangeMax ) - currentValue = rangeMax; - - if ( updateListener != null ) - updateListener.update(); - } - - public void setUpdateListener( final UpdateListener l ) - { - updateListener = l; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ColorIcon.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ColorIcon.java deleted file mode 100644 index 6356a4252..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ColorIcon.java +++ /dev/null @@ -1,93 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.displaysettings; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.geom.RoundRectangle2D; - -import javax.swing.Icon; - -/** - * Adapted from http://stackoverflow.com/a/3072979/230513 - */ -public class ColorIcon implements Icon -{ - private static final int DEFAULT_PAD = 0; - - private static final int DEFAUL_SIZE = 16; - - private final int size; - - private Color color; - - private final int pad; - - public ColorIcon( final Color color, final int size, final int pad ) - { - this.color = color; - this.size = size; - this.pad = pad; - } - - public ColorIcon( final Color color, final int size ) - { - this( color, size, DEFAULT_PAD ); - } - - public ColorIcon( final Color color ) - { - this( color, DEFAUL_SIZE ); - } - - @Override - public void paintIcon( final Component c, final Graphics g, final int x, final int y ) - { - final Graphics2D g2d = ( Graphics2D ) g; - g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); - g2d.setColor( color ); - final RoundRectangle2D.Float shape = new RoundRectangle2D.Float( x + pad, y + pad, size, size, 5, 5 ); - g2d.fill( shape ); - g2d.setColor( Color.BLACK ); - g2d.draw( shape ); - } - - public void setColor( final Color color ) - { - this.color = color; - } - - @Override - public int getIconWidth() - { - return size + 2 * pad; - } - - @Override - public int getIconHeight() - { - return size + 2 * pad; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java deleted file mode 100644 index 7fd52abd9..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/Colormap.java +++ /dev/null @@ -1,313 +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.gui.displaysettings; - -import java.awt.Color; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.TreeMap; - -import org.jfree.chart.renderer.PaintScale; - -/** - * This class implements a {@link PaintScale} that generate colors interpolated - * within a list of given color, using a linear scale. - * - * @author Jean-Yves Tinevez <jeanyves.tinevez@gmail.com> - Sept 2010 - */ -public class Colormap implements PaintScale, Serializable -{ - - private static final long serialVersionUID = 2977884191627862512L; - - private static final Color DEFAULT_COLOR = Color.BLACK; - - private final double lowerBound; - - private final double upperBound; - - private final TreeMap< Double, Color > colors = new TreeMap<>(); - - private final Color defaultColor; - - /* - * INNER CLASSES - */ - - - - /** - * An {@link Colormap} that maps a typical "Jet" colormap going - * from blue to red to the range [0, 1]. - */ - public static final Colormap Jet; - static - { - Jet = new Colormap( "Jet", 0., 1. ); - Jet.add( 0.00, new Color( 0.0f, 0.0f, 1.0f ) ); - Jet.add( 0.16, new Color( 0.0f, 0.5f, 1.0f ) ); - Jet.add( 0.33, new Color( 0.0f, 1.0f, 1.0f ) ); - Jet.add( 0.50, new Color( 0.5f, 1.0f, 0.5f ) ); - Jet.add( 0.66, new Color( 1.0f, 1.0f, 0.0f ) ); - Jet.add( 0.83, new Color( 1.0f, 0.5f, 0.0f ) ); - Jet.add( 1.00, new Color( 1.0f, 0.0f, 0.0f ) ); - } - - /** - * An {@link Colormap} that replicates the matplotlib "Viridis" - * colormap. - */ - public static final Colormap Viridis; - static - { - Viridis = new Colormap( "Viridis", 0., 1. ); - Viridis.add( 0.00, new Color( 68, 1, 84 ) ); - Viridis.add( 0.05, new Color( 71, 18, 101 ) ); - Viridis.add( 0.10, new Color( 72, 35, 116 ) ); - Viridis.add( 0.15, new Color( 69, 52, 127 ) ); - Viridis.add( 0.20, new Color( 64, 67, 135 ) ); - Viridis.add( 0.25, new Color( 58, 82, 139 ) ); - Viridis.add( 0.30, new Color( 52, 94, 141 ) ); - Viridis.add( 0.35, new Color( 46, 107, 142 ) ); - Viridis.add( 0.40, new Color( 41, 120, 142 ) ); - Viridis.add( 0.45, new Color( 36, 132, 141 ) ); - Viridis.add( 0.50, new Color( 32, 144, 140 ) ); - Viridis.add( 0.55, new Color( 30, 155, 137 ) ); - Viridis.add( 0.60, new Color( 34, 167, 132 ) ); - Viridis.add( 0.65, new Color( 47, 179, 123 ) ); - Viridis.add( 0.70, new Color( 68, 190, 112 ) ); - Viridis.add( 0.75, new Color( 94, 201, 97 ) ); - Viridis.add( 0.80, new Color( 121, 209, 81 ) ); - Viridis.add( 0.85, new Color( 154, 216, 60 ) ); - Viridis.add( 0.90, new Color( 189, 222, 38 ) ); - Viridis.add( 0.95, new Color( 223, 227, 24 ) ); - Viridis.add( 1.00, new Color( 253, 231, 36 ) ); - } - - /** - * The TURBO color-map, from Google LLC, Anton Mikhailov. - * https://gist.github.com/mikhailov-work/ee72ba4191942acecc03fe6da94fc73f - */ - public static final Colormap Turbo; - - private final String name; - static - { - final double[][] triplets = new double[][] { - { 0.18995, 0.07176, 0.23217 }, { 0.19483, 0.08339, 0.26149 }, { 0.19956, 0.09498, 0.29024 }, { 0.20415, 0.10652, 0.31844 }, { 0.20860, 0.11802, 0.34607 }, { 0.21291, 0.12947, 0.37314 }, { 0.21708, 0.14087, 0.39964 }, { 0.22111, 0.15223, 0.42558 }, { 0.22500, 0.16354, 0.45096 }, { 0.22875, 0.17481, 0.47578 }, { 0.23236, 0.18603, 0.50004 }, { 0.23582, 0.19720, 0.52373 }, { 0.23915, 0.20833, 0.54686 }, { 0.24234, 0.21941, 0.56942 }, { 0.24539, 0.23044, 0.59142 }, { 0.24830, 0.24143, 0.61286 }, { 0.25107, 0.25237, 0.63374 }, { 0.25369, 0.26327, 0.65406 }, { 0.25618, 0.27412, 0.67381 }, { 0.25853, 0.28492, 0.69300 }, { 0.26074, 0.29568, 0.71162 }, { 0.26280, 0.30639, 0.72968 }, { 0.26473, 0.31706, 0.74718 }, { 0.26652, 0.32768, 0.76412 }, - { 0.26816, 0.33825, 0.78050 }, { 0.26967, 0.34878, 0.79631 }, { 0.27103, 0.35926, 0.81156 }, { 0.27226, 0.36970, 0.82624 }, { 0.27334, 0.38008, 0.84037 }, { 0.27429, 0.39043, 0.85393 }, { 0.27509, 0.40072, 0.86692 }, { 0.27576, 0.41097, 0.87936 }, { 0.27628, 0.42118, 0.89123 }, { 0.27667, 0.43134, 0.90254 }, { 0.27691, 0.44145, 0.91328 }, { 0.27701, 0.45152, 0.92347 }, { 0.27698, 0.46153, 0.93309 }, { 0.27680, 0.47151, 0.94214 }, { 0.27648, 0.48144, 0.95064 }, { 0.27603, 0.49132, 0.95857 }, { 0.27543, 0.50115, 0.96594 }, { 0.27469, 0.51094, 0.97275 }, { 0.27381, 0.52069, 0.97899 }, { 0.27273, 0.53040, 0.98461 }, { 0.27106, 0.54015, 0.98930 }, { 0.26878, 0.54995, 0.99303 }, { 0.26592, 0.55979, 0.99583 }, { 0.26252, 0.56967, 0.99773 }, { 0.25862, 0.57958, 0.99876 }, - { 0.25425, 0.58950, 0.99896 }, { 0.24946, 0.59943, 0.99835 }, { 0.24427, 0.60937, 0.99697 }, { 0.23874, 0.61931, 0.99485 }, { 0.23288, 0.62923, 0.99202 }, { 0.22676, 0.63913, 0.98851 }, { 0.22039, 0.64901, 0.98436 }, { 0.21382, 0.65886, 0.97959 }, { 0.20708, 0.66866, 0.97423 }, { 0.20021, 0.67842, 0.96833 }, { 0.19326, 0.68812, 0.96190 }, { 0.18625, 0.69775, 0.95498 }, { 0.17923, 0.70732, 0.94761 }, { 0.17223, 0.71680, 0.93981 }, { 0.16529, 0.72620, 0.93161 }, { 0.15844, 0.73551, 0.92305 }, { 0.15173, 0.74472, 0.91416 }, { 0.14519, 0.75381, 0.90496 }, { 0.13886, 0.76279, 0.89550 }, { 0.13278, 0.77165, 0.88580 }, { 0.12698, 0.78037, 0.87590 }, { 0.12151, 0.78896, 0.86581 }, { 0.11639, 0.79740, 0.85559 }, { 0.11167, 0.80569, 0.84525 }, { 0.10738, 0.81381, 0.83484 }, - { 0.10357, 0.82177, 0.82437 }, { 0.10026, 0.82955, 0.81389 }, { 0.09750, 0.83714, 0.80342 }, { 0.09532, 0.84455, 0.79299 }, { 0.09377, 0.85175, 0.78264 }, { 0.09287, 0.85875, 0.77240 }, { 0.09267, 0.86554, 0.76230 }, { 0.09320, 0.87211, 0.75237 }, { 0.09451, 0.87844, 0.74265 }, { 0.09662, 0.88454, 0.73316 }, { 0.09958, 0.89040, 0.72393 }, { 0.10342, 0.89600, 0.71500 }, { 0.10815, 0.90142, 0.70599 }, { 0.11374, 0.90673, 0.69651 }, { 0.12014, 0.91193, 0.68660 }, { 0.12733, 0.91701, 0.67627 }, { 0.13526, 0.92197, 0.66556 }, { 0.14391, 0.92680, 0.65448 }, { 0.15323, 0.93151, 0.64308 }, { 0.16319, 0.93609, 0.63137 }, { 0.17377, 0.94053, 0.61938 }, { 0.18491, 0.94484, 0.60713 }, { 0.19659, 0.94901, 0.59466 }, { 0.20877, 0.95304, 0.58199 }, { 0.22142, 0.95692, 0.56914 }, - { 0.23449, 0.96065, 0.55614 }, { 0.24797, 0.96423, 0.54303 }, { 0.26180, 0.96765, 0.52981 }, { 0.27597, 0.97092, 0.51653 }, { 0.29042, 0.97403, 0.50321 }, { 0.30513, 0.97697, 0.48987 }, { 0.32006, 0.97974, 0.47654 }, { 0.33517, 0.98234, 0.46325 }, { 0.35043, 0.98477, 0.45002 }, { 0.36581, 0.98702, 0.43688 }, { 0.38127, 0.98909, 0.42386 }, { 0.39678, 0.99098, 0.41098 }, { 0.41229, 0.99268, 0.39826 }, { 0.42778, 0.99419, 0.38575 }, { 0.44321, 0.99551, 0.37345 }, { 0.45854, 0.99663, 0.36140 }, { 0.47375, 0.99755, 0.34963 }, { 0.48879, 0.99828, 0.33816 }, { 0.50362, 0.99879, 0.32701 }, { 0.51822, 0.99910, 0.31622 }, { 0.53255, 0.99919, 0.30581 }, { 0.54658, 0.99907, 0.29581 }, { 0.56026, 0.99873, 0.28623 }, { 0.57357, 0.99817, 0.27712 }, { 0.58646, 0.99739, 0.26849 }, - { 0.59891, 0.99638, 0.26038 }, { 0.61088, 0.99514, 0.25280 }, { 0.62233, 0.99366, 0.24579 }, { 0.63323, 0.99195, 0.23937 }, { 0.64362, 0.98999, 0.23356 }, { 0.65394, 0.98775, 0.22835 }, { 0.66428, 0.98524, 0.22370 }, { 0.67462, 0.98246, 0.21960 }, { 0.68494, 0.97941, 0.21602 }, { 0.69525, 0.97610, 0.21294 }, { 0.70553, 0.97255, 0.21032 }, { 0.71577, 0.96875, 0.20815 }, { 0.72596, 0.96470, 0.20640 }, { 0.73610, 0.96043, 0.20504 }, { 0.74617, 0.95593, 0.20406 }, { 0.75617, 0.95121, 0.20343 }, { 0.76608, 0.94627, 0.20311 }, { 0.77591, 0.94113, 0.20310 }, { 0.78563, 0.93579, 0.20336 }, { 0.79524, 0.93025, 0.20386 }, { 0.80473, 0.92452, 0.20459 }, { 0.81410, 0.91861, 0.20552 }, { 0.82333, 0.91253, 0.20663 }, { 0.83241, 0.90627, 0.20788 }, { 0.84133, 0.89986, 0.20926 }, - { 0.85010, 0.89328, 0.21074 }, { 0.85868, 0.88655, 0.21230 }, { 0.86709, 0.87968, 0.21391 }, { 0.87530, 0.87267, 0.21555 }, { 0.88331, 0.86553, 0.21719 }, { 0.89112, 0.85826, 0.21880 }, { 0.89870, 0.85087, 0.22038 }, { 0.90605, 0.84337, 0.22188 }, { 0.91317, 0.83576, 0.22328 }, { 0.92004, 0.82806, 0.22456 }, { 0.92666, 0.82025, 0.22570 }, { 0.93301, 0.81236, 0.22667 }, { 0.93909, 0.80439, 0.22744 }, { 0.94489, 0.79634, 0.22800 }, { 0.95039, 0.78823, 0.22831 }, { 0.95560, 0.78005, 0.22836 }, { 0.96049, 0.77181, 0.22811 }, { 0.96507, 0.76352, 0.22754 }, { 0.96931, 0.75519, 0.22663 }, { 0.97323, 0.74682, 0.22536 }, { 0.97679, 0.73842, 0.22369 }, { 0.98000, 0.73000, 0.22161 }, { 0.98289, 0.72140, 0.21918 }, { 0.98549, 0.71250, 0.21650 }, { 0.98781, 0.70330, 0.21358 }, - { 0.98986, 0.69382, 0.21043 }, { 0.99163, 0.68408, 0.20706 }, { 0.99314, 0.67408, 0.20348 }, { 0.99438, 0.66386, 0.19971 }, { 0.99535, 0.65341, 0.19577 }, { 0.99607, 0.64277, 0.19165 }, { 0.99654, 0.63193, 0.18738 }, { 0.99675, 0.62093, 0.18297 }, { 0.99672, 0.60977, 0.17842 }, { 0.99644, 0.59846, 0.17376 }, { 0.99593, 0.58703, 0.16899 }, { 0.99517, 0.57549, 0.16412 }, { 0.99419, 0.56386, 0.15918 }, { 0.99297, 0.55214, 0.15417 }, { 0.99153, 0.54036, 0.14910 }, { 0.98987, 0.52854, 0.14398 }, { 0.98799, 0.51667, 0.13883 }, { 0.98590, 0.50479, 0.13367 }, { 0.98360, 0.49291, 0.12849 }, { 0.98108, 0.48104, 0.12332 }, { 0.97837, 0.46920, 0.11817 }, { 0.97545, 0.45740, 0.11305 }, { 0.97234, 0.44565, 0.10797 }, { 0.96904, 0.43399, 0.10294 }, { 0.96555, 0.42241, 0.09798 }, - { 0.96187, 0.41093, 0.09310 }, { 0.95801, 0.39958, 0.08831 }, { 0.95398, 0.38836, 0.08362 }, { 0.94977, 0.37729, 0.07905 }, { 0.94538, 0.36638, 0.07461 }, { 0.94084, 0.35566, 0.07031 }, { 0.93612, 0.34513, 0.06616 }, { 0.93125, 0.33482, 0.06218 }, { 0.92623, 0.32473, 0.05837 }, { 0.92105, 0.31489, 0.05475 }, { 0.91572, 0.30530, 0.05134 }, { 0.91024, 0.29599, 0.04814 }, { 0.90463, 0.28696, 0.04516 }, { 0.89888, 0.27824, 0.04243 }, { 0.89298, 0.26981, 0.03993 }, { 0.88691, 0.26152, 0.03753 }, { 0.88066, 0.25334, 0.03521 }, { 0.87422, 0.24526, 0.03297 }, { 0.86760, 0.23730, 0.03082 }, { 0.86079, 0.22945, 0.02875 }, { 0.85380, 0.22170, 0.02677 }, { 0.84662, 0.21407, 0.02487 }, { 0.83926, 0.20654, 0.02305 }, { 0.83172, 0.19912, 0.02131 }, { 0.82399, 0.19182, 0.01966 }, - { 0.81608, 0.18462, 0.01809 }, { 0.80799, 0.17753, 0.01660 }, { 0.79971, 0.17055, 0.01520 }, { 0.79125, 0.16368, 0.01387 }, { 0.78260, 0.15693, 0.01264 }, { 0.77377, 0.15028, 0.01148 }, { 0.76476, 0.14374, 0.01041 }, { 0.75556, 0.13731, 0.00942 }, { 0.74617, 0.13098, 0.00851 }, { 0.73661, 0.12477, 0.00769 }, { 0.72686, 0.11867, 0.00695 }, { 0.71692, 0.11268, 0.00629 }, { 0.70680, 0.10680, 0.00571 }, { 0.69650, 0.10102, 0.00522 }, { 0.68602, 0.09536, 0.00481 }, { 0.67535, 0.08980, 0.00449 }, { 0.66449, 0.08436, 0.00424 }, { 0.65345, 0.07902, 0.00408 }, { 0.64223, 0.07380, 0.00401 }, { 0.63082, 0.06868, 0.00401 }, { 0.61923, 0.06367, 0.00410 }, { 0.60746, 0.05878, 0.00427 }, { 0.59550, 0.05399, 0.00453 }, { 0.58336, 0.04931, 0.00486 }, { 0.57103, 0.04474, 0.00529 }, - { 0.55852, 0.04028, 0.00579 }, { 0.54583, 0.03593, 0.00638 }, { 0.53295, 0.03169, 0.00705 }, { 0.51989, 0.02756, 0.00780 }, { 0.50664, 0.02354, 0.00863 }, { 0.49321, 0.01963, 0.00955 }, { 0.47960, 0.01583, 0.01055 } }; - Turbo = new Colormap( "Turbo", 0., 1. ); - final int nTriplets = triplets.length; - for ( int i = 0; i < nTriplets; i++ ) - { - final double alpha = ( i ) / ( nTriplets - 1. ); - final double[] triplet = triplets[ i ]; - Turbo.add( alpha, new Color( ( float ) triplet[ 0 ], ( float ) triplet[ 1 ], ( float ) triplet[ 2 ] ) ); - } - } - - private static final List< Colormap > LUTS; - static - { - final List< Colormap > tmpLUTS = new ArrayList<>(); - tmpLUTS.add( Jet ); - tmpLUTS.add( Turbo ); - tmpLUTS.add( Viridis ); - tmpLUTS.addAll( ColormapIO.getLUTs() ); - LUTS = Collections.unmodifiableList( tmpLUTS ); - } - - public static List< Colormap > getAvailableLUTs() - { - return LUTS; - } - - /* - * CONSTRUCTORS - */ - - /** - * 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 ) - { - this.name = name; - this.lowerBound = lowerBound; - this.upperBound = upperBound; - this.defaultColor = defaultColor; - } - - /** - * 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 ) - { - this( name, lowerBound, upperBound, DEFAULT_COLOR ); - } - - /** - * 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 ) - { - this( name, 0, 1 ); - } - - /* - * PUBLIC METHODS - */ - - public String getName() - { - return name; - } - - /** - * Add a color to the color list of this paint scale, at the position given - * 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 ) - { - if ( value > upperBound ) - return; - if ( value < lowerBound ) - return; - colors.put( value, color ); - } - - @Override - public double getLowerBound() - { - return lowerBound; - } - - /** - * Return a color interpolated within the color list of this paint scale. - * The interpolation is a linear one between the two colors in the list - * whose associated values frame the one given. - */ - @Override - public Color getPaint( double value ) - { - if ( colors.isEmpty() ) - return defaultColor; - if ( colors.size() == 1 ) - return colors.get( colors.firstKey() ); - - if ( value > upperBound ) - value = upperBound; - if ( value < lowerBound ) - value = lowerBound; - final Set< Double > keys = colors.keySet(); - double bottom = colors.firstKey(); - double top = colors.lastKey(); - for ( final double key : keys ) - { - top = key; - if ( value < key ) - break; - - bottom = top; - } - - double alpha; - if ( top == bottom ) - alpha = 0; // we reached the end of the list - else - alpha = ( value - bottom ) / ( top - bottom ); - - final Color colorBottom = colors.get( bottom ); - final Color colorTop = colors.get( top ); - final int red = ( int ) ( ( 1 - alpha ) * colorBottom.getRed() + alpha * colorTop.getRed() ); - final int green = ( int ) ( ( 1 - alpha ) * colorBottom.getGreen() + alpha * colorTop.getGreen() ); - final int blue = ( int ) ( ( 1 - alpha ) * colorBottom.getBlue() + alpha * colorTop.getBlue() ); - return new Color( red, green, blue ); - } - - @Override - public double getUpperBound() - { - return upperBound; - } - - @Override - public Colormap clone() - { - final Colormap ips = new Colormap( name, lowerBound, upperBound ); - for ( final double key : colors.keySet() ) - ips.add( key, colors.get( key ) ); - return ips; - } - - - public static void main( final String[] args ) - { - final StringBuilder str = new StringBuilder(); - str.append( "{ " ); - getAvailableLUTs().forEach( ( cm ) -> str.append( '"' + cm.getName() + "\", " ) ); - str.deleteCharAt( str.length() - 1 ); - str.deleteCharAt( str.length() - 1 ); - str.append( " }" ); - System.out.println( str ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ColormapIO.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ColormapIO.java deleted file mode 100644 index 69864fc1b..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/ColormapIO.java +++ /dev/null @@ -1,169 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.displaysettings; - -import java.awt.Color; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Scanner; -import java.util.concurrent.atomic.AtomicInteger; - -import org.scijava.util.IntArray; - -/** - * Loat LUTS for {@link Colormap}. Code adapted from what we did in Mastodon. - * - * @author Jean-Yves Tinevez 2019 - */ -public class ColormapIO -{ - - private static final List< URI > LUT_FOLDERS = new ArrayList<>(); - static - { - try - { - final URI BUILTIN_LUT_FOLDER = ColormapIO.class.getResource( "luts/" ).toURI(); - LUT_FOLDERS.add( BUILTIN_LUT_FOLDER ); - } - catch ( final URISyntaxException e ) - { - e.printStackTrace(); - } - } - - static List< Colormap > getLUTs() - { - return loadLUTs(); - } - - private static List< Colormap > loadLUTs() - { - final List< Colormap > luts = new ArrayList<>(); - for ( final URI lutFolder : LUT_FOLDERS ) - { - try - { - luts.addAll( loadLUTs( lutFolder ) ); - } - catch ( final IOException e ) - { - e.printStackTrace(); - } - } - return luts; - } - - private static List< Colormap > loadLUTs( final URI folder ) throws IOException - { - if ( folder.getScheme().equals( "jar" ) ) - { - // Try to read from within the jar file. - final String[] array = folder.toString().split( "!" ); - try (FileSystem fileSystem = FileSystems.newFileSystem( URI.create( array[ 0 ] ), Collections.emptyMap() )) - { - final Path folderPath = fileSystem.getPath( array[ 1 ] ); - return loadLUTs( folderPath ); - } - } - else - { - // Read from a standard folder. - final Path folderPath = Paths.get( folder ); - return loadLUTs( folderPath ); - } - } - - private static List< Colormap > loadLUTs( final Path folderPath ) throws IOException - { - final List< Colormap > luts = new ArrayList<>(); - if ( Files.exists( folderPath ) ) - { - final String glob = "*.lut"; - try (final DirectoryStream< Path > folderStream = Files.newDirectoryStream( folderPath, glob )) - { - for ( final Path path : folderStream ) - { - - final Colormap lut = importLUT( path ); - if ( null == lut ) - System.err.println( "Could not read LUT file: " + path + ". Skipping." ); - - luts.add( lut ); - } - } - } - return luts; - } - - private static final Colormap importLUT( final Path path ) throws IOException - { - final String fileName = path.getFileName().toString(); - final String lutName = fileName.substring( 0, fileName.indexOf( '.' ) ); - - try (final Scanner scanner = new Scanner( path )) - { - - final List< Color > colors = new ArrayList<>(); - final IntArray intAlphas = new IntArray(); - final AtomicInteger nLines = new AtomicInteger( 0 ); - - final Colormap ips = new Colormap( lutName, 0., 1. ); - while ( scanner.hasNext() ) - { - if ( !scanner.hasNextInt() ) - { - scanner.next(); - continue; - } - intAlphas.addValue( scanner.nextInt() ); - final Color color = new Color( scanner.nextInt(), scanner.nextInt(), scanner.nextInt() ); - colors.add( color ); - nLines.incrementAndGet(); - } - - if ( nLines.get() < 2 ) - return null; - - final double[] alphas = new double[ intAlphas.size() ]; - for ( int i = 0; i < alphas.length; i++ ) - { - final double alpha = ( double ) intAlphas.get( i ) / ( nLines.get() - 1 ); - final Color color = colors.get( i ); - ips.add( alpha, color ); - } - - return ips; - } - } - -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettings.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettings.java index e52477b87..e616b583c 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettings.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettings.java @@ -28,6 +28,7 @@ import java.util.Objects; import org.scijava.listeners.Listeners; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; import bdv.ui.settings.style.Style; import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java index 9c8d5052f..a2cbf1b37 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsIO.java @@ -33,6 +33,7 @@ import java.util.stream.Collectors; import org.jdom2.Element; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsPanel.java index d01522753..7c96ee44f 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/DisplaySettingsPanel.java @@ -21,25 +21,25 @@ */ package fiji.plugin.trackmate.gui.displaysettings; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.booleanElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.boundedDoubleElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.colorElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.colormapElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.doubleElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.enumElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.featureElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.fontElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.intElement; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.label; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedCheckBox; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedColorButton; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedColormapChooser; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedComboBoxEnumSelector; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedFeatureSelector; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedFontButton; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedFormattedTextField; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.linkedSliderPanel; -import static fiji.plugin.trackmate.gui.displaysettings.StyleElements.separator; +import static fiji.plugin.trackmate.gui.displaysettings.TrackMateStyleElements.featureElement; +import static fiji.plugin.trackmate.gui.displaysettings.TrackMateStyleElements.linkedFeatureSelector; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.booleanElement; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.boundedDoubleElement; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.colorElement; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.colormapElement; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.doubleElement; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.enumElement; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.fontElement; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.intElement; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.label; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.linkedCheckBox; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.linkedColorButton; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.linkedColormapChooser; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.linkedComboBoxEnumSelector; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.linkedFontButton; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.linkedFormattedTextField; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.linkedSliderPanel; +import static org.scijava.ui.config.visitors.gui.elements.StyleElements.separator; import java.awt.BorderLayout; import java.awt.Dimension; @@ -61,6 +61,17 @@ import javax.swing.SwingUtilities; import org.scijava.listeners.Listeners; +import org.scijava.ui.config.visitors.gui.elements.StyleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.BooleanElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.ColorElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.ColormapElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.DoubleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.EnumElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.FontElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.IntElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.LabelElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElements.Separator; import com.itextpdf.text.Font; @@ -69,19 +80,8 @@ import bdv.ui.settings.style.StyleProfile; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackDisplayMode; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BooleanElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.BoundedDoubleElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.ColorElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.ColormapElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.DoubleElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.EnumElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.FeatureElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.FontElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.IntElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.LabelElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.Separator; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StyleElement; -import fiji.plugin.trackmate.gui.displaysettings.StyleElements.StyleElementVisitor; +import fiji.plugin.trackmate.gui.displaysettings.TrackMateStyleElements.FeatureElement; +import fiji.plugin.trackmate.gui.displaysettings.TrackMateStyleElements.TrackMateStyleElementVisitor; public class DisplaySettingsPanel extends JPanel implements ProfileEditPanel< StyleProfile< DisplaySettings > >, UpdateListener { @@ -217,7 +217,7 @@ private List< StyleElement > styleElements( final DisplaySettings ds ) separator() ); } - private static class GuiVisitor implements StyleElementVisitor + private static class GuiVisitor implements TrackMateStyleElementVisitor { private final JPanel panel; @@ -277,7 +277,7 @@ public void visit( final BoundedDoubleElement element ) public void visit( final DoubleElement element ) { addToLayout( - linkedFormattedTextField( element ), + linkedFormattedTextField( element, null, null ), new JLabel( element.getLabel() ) ); } diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java deleted file mode 100644 index a9da3e5f6..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanel.java +++ /dev/null @@ -1,205 +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.gui.displaysettings; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSlider; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; -import javax.swing.SwingConstants; -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}. - */ -public class SliderPanel extends JPanel implements BoundedValue.UpdateListener -{ - private static final long serialVersionUID = 6444334522127424416L; - - public static final Dimension PANEL_SIZE = new Dimension( 150, 20 ); - - private final JSlider slider; - - private final JSpinner spinner; - - private final BoundedValue model; - - /** - * Create a {@link SliderPanel} to modify a given {@link BoundedValue - * value}. - * - * @param name - * 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 ) - { - super(); - setLayout( new BorderLayout( 10, 10 ) ); - setPreferredSize( PANEL_SIZE ); - - 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 ); - - 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 = new JSpinner(); - spinner.setModel( new SpinnerNumberModel( value, min, max, spinnerStepSize ) ); - - slider.addChangeListener( new ChangeListener() - { - @Override - public void stateChanged( final ChangeEvent e ) - { - final int value = slider.getValue(); - model.setCurrentValue( value ); - } - } ); - - final MouseWheelListener mwl = new MouseWheelListener() - { - - @Override - public void mouseWheelMoved( final MouseWheelEvent e ) - { - final int notches = e.getWheelRotation(); - int value = slider.getValue(); - value -= notches * spinnerStepSize; - if ( value < slider.getMinimum() ) - value = slider.getMinimum(); - else if ( value > slider.getMaximum() ) - value = slider.getMaximum(); - slider.setValue( value ); - } - }; - slider.addMouseWheelListener( mwl ); - spinner.addMouseWheelListener( mwl ); - - spinner.addChangeListener( new ChangeListener() - { - @Override - public void stateChanged( final ChangeEvent e ) - { - final int value = ( ( Integer ) spinner.getValue() ).intValue(); - model.setCurrentValue( value ); - } - } ); - - if ( name != null ) - { - final JLabel label = new JLabel( name, SwingConstants.CENTER ); - label.setAlignmentX( Component.CENTER_ALIGNMENT ); - add( label, BorderLayout.WEST ); - } - - 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 ); - } - - public void setNumColummns( final int cols ) - { - ( ( JSpinner.NumberEditor ) spinner.getEditor() ).getTextField().setColumns( cols ); - } - - @Override - public void setToolTipText( final String text ) - { - super.setToolTipText( text ); - if ( spinner != null ) - spinner.setToolTipText( text ); - if ( slider != null ) - slider.setToolTipText( text ); - } - - @Override - public void setEnabled( final boolean enabled ) - { - spinner.setEnabled( enabled ); - slider.setEnabled( enabled ); - super.setEnabled( enabled ); - } - - @Override - public void setFont( final Font font ) - { - GuiUtils.setFont( this, font ); - } - - @Override - public void update() - { - final int value = model.getCurrentValue(); - final int min = model.getRangeMin(); - final int max = model.getRangeMax(); - if ( slider.getMaximum() != max || slider.getMinimum() != min ) - { - slider.setMinimum( min ); - slider.setMaximum( max ); - final SpinnerNumberModel spinnerModel = ( SpinnerNumberModel ) spinner.getModel(); - spinnerModel.setMinimum( min ); - spinnerModel.setMaximum( max ); - } - slider.setValue( value ); - spinner.setValue( value ); - } -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java deleted file mode 100644 index f707d9d96..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/SliderPanelDouble.java +++ /dev/null @@ -1,287 +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.gui.displaysettings; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Font; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; - -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSlider; -import javax.swing.JSpinner; -import javax.swing.JSpinner.NumberEditor; -import javax.swing.SpinnerNumberModel; -import javax.swing.SwingConstants; -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}. - */ -public class SliderPanelDouble extends JPanel implements BoundedValueDouble.UpdateListener -{ - private static final long serialVersionUID = 6444334522127424416L; - - private static final int sliderLength = 50; - - private final JSlider slider; - - private final JSpinner spinner; - - private final BoundedValueDouble model; - - private double dmin; - - private double dmax; - - private boolean userDefinedNumberFormat = false; - - private RangeListener rangeListener; - - public interface RangeListener - { - void rangeChanged(); - } - - /** - * 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, - final BoundedValueDouble model, - final double spinnerStepSize ) - { - super(); - setLayout( new BorderLayout( 10, 10 ) ); - setPreferredSize( SliderPanel.PANEL_SIZE ); - - 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 ); - - spinner = new JSpinner(); - 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() - { - @Override - public void stateChanged( final ChangeEvent e ) - { - final int value = slider.getValue(); - model.setCurrentValue( fromSlider( value ) ); - } - } ); - - slider.addComponentListener( new ComponentAdapter() - { - @Override - public void componentResized( final ComponentEvent e ) - { - updateNumberFormat(); - } - } ); - - final MouseWheelListener mwl = new MouseWheelListener() - { - - @Override - public void mouseWheelMoved( final MouseWheelEvent e ) - { - final int notches = e.getWheelRotation(); - double value = ( ( Number ) spinner.getValue() ).doubleValue(); - value -= notches * spinnerStepSize; - if ( value < dmin ) - value = dmin; - else if ( value > dmax ) - value = dmax; - spinner.setValue( value ); - } - }; - slider.addMouseWheelListener( mwl ); - spinner.addMouseWheelListener( mwl ); - - spinner.addChangeListener( new ChangeListener() - { - @Override - public void stateChanged( final ChangeEvent e ) - { - final double value = ( ( Double ) spinner.getValue() ).doubleValue(); - model.setCurrentValue( value ); - } - } ); - - if ( name != null ) - { - final JLabel label = new JLabel( name, SwingConstants.CENTER ); - label.setAlignmentX( Component.CENTER_ALIGNMENT ); - add( label, BorderLayout.WEST ); - } - - 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 ); - } - - public void setDecimalFormat( final String pattern ) - { - if ( pattern == null ) - { - userDefinedNumberFormat = false; - updateNumberFormat(); - } - else - { - userDefinedNumberFormat = true; - ( ( JSpinner.NumberEditor ) spinner.getEditor() ).getFormat().applyPattern( pattern ); - } - } - - public void setNumColummns( final int cols ) - { - ( ( JSpinner.NumberEditor ) spinner.getEditor() ).getTextField().setColumns( cols ); - } - - @Override - public void setToolTipText( final String text ) - { - super.setToolTipText( text ); - if ( spinner != null ) - spinner.setToolTipText( text ); - if ( slider != null ) - slider.setToolTipText( text ); - } - - @Override - public void setEnabled( final boolean enabled ) - { - spinner.setEnabled( enabled ); - slider.setEnabled( enabled ); - super.setEnabled( enabled ); - } - - @Override - public void setFont( final Font font ) - { - GuiUtils.setFont( this, font ); - } - - @Override - public void update() - { - final double value = model.getCurrentValue(); - final double min = model.getRangeMin(); - final double max = model.getRangeMax(); - - final boolean rangeChanged = ( dmax != max || dmin != min ); - if ( rangeChanged ) - { - dmin = min; - dmax = max; - final SpinnerNumberModel spinnerModel = ( SpinnerNumberModel ) spinner.getModel(); - spinnerModel.setMinimum( min ); - spinnerModel.setMaximum( max ); - } - slider.setValue( toSlider( value ) ); - spinner.setValue( value ); - - if ( rangeChanged ) - updateNumberFormat(); - - if ( rangeChanged && rangeListener != null ) - rangeListener.rangeChanged(); - - } - - public void setRangeListener( final RangeListener listener ) - { - this.rangeListener = listener; - } - - private void updateNumberFormat() - { - if ( userDefinedNumberFormat ) - return; - - final int sw = slider.getWidth(); - if ( sw > 0 ) - { - final double range = dmax - dmin; - final int digits = ( int ) Math.ceil( Math.log10( sw / range ) ); - final NumberEditor numberEditor = ( ( JSpinner.NumberEditor ) spinner.getEditor() ); - numberEditor.getFormat().setMaximumFractionDigits( digits ); - numberEditor.stateChanged( new ChangeEvent( spinner ) ); - } - } - - private int toSlider( final double value ) - { - return ( int ) Math.round( ( value - dmin ) * sliderLength / ( dmax - dmin ) ); - } - - private double fromSlider( final int value ) - { - return ( value * ( dmax - dmin ) / sliderLength ) + dmin; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java deleted file mode 100644 index d4a1b7a6e..000000000 --- a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/StyleElements.java +++ /dev/null @@ -1,1208 +0,0 @@ -/*- - * #%L - * TrackMate: your buddy for everyday tracking. - * %% - * Copyright (C) 2010 - 2026 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.displaysettings; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Dialog.ModalityType; -import java.awt.Dimension; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Insets; -import java.awt.Window; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.FocusAdapter; -import java.text.DecimalFormat; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; -import java.util.Vector; -import java.util.function.BiConsumer; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; -import java.util.function.DoubleSupplier; -import java.util.function.IntSupplier; -import java.util.function.Supplier; - -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.DefaultComboBoxModel; -import javax.swing.DefaultListCellRenderer; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JColorChooser; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFormattedTextField; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.JSpinner.DefaultEditor; -import javax.swing.JTextField; -import javax.swing.ListCellRenderer; -import javax.swing.SpinnerListModel; -import javax.swing.SpinnerNumberModel; -import javax.swing.SwingConstants; -import javax.swing.WindowConstants; -import javax.swing.border.EmptyBorder; - -import org.drjekyll.fontchooser.FontDialog; - -import fiji.plugin.trackmate.Settings; -import fiji.plugin.trackmate.gui.Fonts; -import fiji.plugin.trackmate.gui.GuiUtils; -import fiji.plugin.trackmate.gui.Icons; -import fiji.plugin.trackmate.gui.components.CategoryJComboBox; -import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; -import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; - -public class StyleElements -{ - private static final DecimalFormat format = new DecimalFormat( "#.###" ); - - public static Separator separator() - { - return new Separator(); - } - - public static LabelElement label( final String label ) - { - return new LabelElement( label ); - } - - public static StringElement stringElement( final String label, final Supplier< String > get, final Consumer< String > set ) - { - return new StringElement( label ) - { - - @Override - public String get() - { - return get.get(); - } - - @Override - public void set( final String s ) - { - set.accept( s ); - } - }; - } - - public static BooleanElement booleanElement( final String label, final BooleanSupplier get, final Consumer< Boolean > set ) - { - return new BooleanElement( label ) - { - @Override - public boolean get() - { - return get.getAsBoolean(); - } - - @Override - public void set( final boolean b ) - { - set.accept( b ); - } - }; - } - - public static ColorElement colorElement( final String label, final Supplier< Color > get, final Consumer< Color > set ) - { - return new ColorElement( label ) - { - @Override - public Color getColor() - { - return get.get(); - } - - @Override - public void setColor( final Color c ) - { - set.accept( c ); - } - }; - } - - public static ColormapElement colormapElement( final String label, final Supplier< Colormap > get, final Consumer< Colormap > set ) - { - return new ColormapElement( label ) - { - - @Override - public Colormap get() - { - return get.get(); - } - - @Override - public void set( final Colormap v ) - { - set.accept( v ); - } - }; - } - - public static BoundedDoubleElement boundedDoubleElement( final String label, final double rangeMin, final double rangeMax, final DoubleSupplier get, final Consumer< Double > set ) - { - return new BoundedDoubleElement( label, rangeMin, rangeMax ) - { - @Override - public double get() - { - return get.getAsDouble(); - } - - @Override - public void set( final double v ) - { - set.accept( v ); - } - }; - } - - public static DoubleElement doubleElement( final String label, final DoubleSupplier get, final Consumer< Double > set ) - { - return new DoubleElement( label ) - { - @Override - public double get() - { - return get.getAsDouble(); - } - - @Override - public void set( final double v ) - { - set.accept( v ); - } - }; - } - - public static IntElement intElement( final String label, final int rangeMin, final int rangeMax, final IntSupplier get, final Consumer< Integer > set ) - { - return new IntElement( label, rangeMin, rangeMax ) - { - @Override - public int get() - { - return get.getAsInt(); - } - - @Override - public void set( final int v ) - { - set.accept( v ); - } - }; - } - - public static < E > EnumElement< E > enumElement( final String label, final E[] values, final Supplier< E > get, final Consumer< E > set ) - { - return new EnumElement< E >( label, values ) - { - - @Override - public E getValue() - { - return get.get(); - } - - @Override - public void setValue( final E e ) - { - set.accept( e ); - } - }; - } - - public static < E > ListElement< E > listElement( final String label, final List< E > values, final Supplier< E > get, final Consumer< E > set ) - { - return new ListElement< E >( label, values ) - { - - @Override - public E getValue() - { - return get.get(); - } - - @Override - public void setValue( final E e ) - { - set.accept( e ); - } - }; - } - - public static FeatureElement featureElement( final String label, final Supplier< TrackMateObject > typeGet, final Supplier< String > featureGet, final BiConsumer< TrackMateObject, String > set ) - { - return new FeatureElement( label ) - { - - @Override - public void setValue( final TrackMateObject type, final String feature ) - { - set.accept( type, feature ); - } - - @Override - public TrackMateObject getType() - { - return typeGet.get(); - } - - @Override - public String getFeature() - { - return featureGet.get(); - } - }; - } - - public static FontElement fontElement( final String label, final Supplier< Font > get, final Consumer< Font > set ) - { - return new FontElement( label ) - { - - @Override - public void set( final Font font ) - { - set.accept( font ); - } - - @Override - public Font get() - { - return get.get(); - } - }; - } - - /* - * Visitor interface. - */ - - public interface StyleElementVisitor - { - public default void visit( final Separator element ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final LabelElement label ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final ColorElement colorElement ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final BooleanElement booleanElement ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final BoundedDoubleElement doubleElement ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final DoubleElement doubleElement ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final IntElement intElement ) - { - throw new UnsupportedOperationException(); - } - - public default < E > void visit( final EnumElement< E > enumElement ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final FeatureElement featureElement ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final ColormapElement element ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final FontElement fontElement ) - { - throw new UnsupportedOperationException(); - } - - public default void visit( final StringElement stringElement ) - { - throw new UnsupportedOperationException(); - } - - public default < E > void visit( final ListElement< E > listElement ) - { - throw new UnsupportedOperationException(); - } - } - - /* - * - * =============================================================== - * - */ - - public interface StyleElement - { - public default void update() - {} - - public void accept( StyleElementVisitor visitor ); - } - - public static class Separator implements StyleElement - { - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - } - - public static class LabelElement implements StyleElement - { - private final String label; - - public LabelElement( final String label ) - { - this.label = label; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - } - - public static abstract class StringElement implements StyleElement - { - - private final ArrayList< Consumer< String > > onSet = new ArrayList<>(); - - private final String label; - - private String value; - - public StringElement( final String label ) - { - this.label = label; - this.value = ""; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public abstract String get(); - - public abstract void set( String s ); - - public void onSet( final Consumer< String > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - if ( get() != value ) - value = get(); - onSet.forEach( c -> c.accept( get() ) ); - } - } - - public static abstract class EnumElement< E > implements StyleElement - { - private final ArrayList< Consumer< E > > onSet = new ArrayList<>(); - - private final String label; - - private final E[] values; - - public EnumElement( final String label, final E[] values ) - { - this.label = label; - this.values = values; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public void onSet( final Consumer< E > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - onSet.forEach( c -> c.accept( getValue() ) ); - } - - public abstract E getValue(); - - public abstract void setValue( E e ); - - public E[] getValues() - { - return values; - } - } - - public static abstract class ListElement< E > implements StyleElement - { - private final ArrayList< Consumer< E > > onSet = new ArrayList<>(); - - private final String label; - - private final List< E > values; - - public ListElement( final String label, final List< E > values ) - { - this.label = label; - this.values = values; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public void onSet( final Consumer< E > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - onSet.forEach( c -> c.accept( getValue() ) ); - } - - public abstract E getValue(); - - public abstract void setValue( E e ); - - public List< E > getValues() - { - return values; - } - } - - public static abstract class FeatureElement implements StyleElement - { - private final ArrayList< BiConsumer< TrackMateObject, String > > onSet = new ArrayList<>(); - - private final String label; - - public FeatureElement( final String label ) - { - this.label = label; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public void onSet( final BiConsumer< TrackMateObject, String > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - onSet.forEach( c -> c.accept( getType(), getFeature() ) ); - } - - public abstract TrackMateObject getType(); - - public abstract String getFeature(); - - public abstract void setValue( TrackMateObject type, String feature ); - } - - public static abstract class ColorElement implements StyleElement - { - private final ArrayList< Consumer< Color > > onSet = new ArrayList<>(); - - private final String label; - - public ColorElement( final String label ) - { - this.label = label; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public void onSet( final Consumer< Color > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - onSet.forEach( c -> c.accept( getColor() ) ); - } - - public abstract Color getColor(); - - public abstract void setColor( Color c ); - } - - public static abstract class BooleanElement implements StyleElement - { - private final String label; - - private final ArrayList< Consumer< Boolean > > onSet = new ArrayList<>(); - - public BooleanElement( final String label ) - { - this.label = label; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public void onSet( final Consumer< Boolean > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - onSet.forEach( c -> c.accept( get() ) ); - } - - public abstract boolean get(); - - public abstract void set( boolean b ); - } - - public static abstract class BoundedDoubleElement implements StyleElement - { - private final BoundedValueDouble value; - - private final String label; - - public BoundedDoubleElement( final String label, final double rangeMin, final double rangeMax ) - { - final double currentValue = Math.max( rangeMin, Math.min( rangeMax, get() ) ); - value = new BoundedValueDouble( rangeMin, rangeMax, currentValue ) - { - @Override - public void setCurrentValue( final double value ) - { - super.setCurrentValue( value ); - if ( get() != getCurrentValue() ) - set( getCurrentValue() ); - } - }; - this.label = label; - } - - public BoundedValueDouble getValue() - { - return value; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public abstract double get(); - - public abstract void set( double v ); - - @Override - public void update() - { - if ( get() != value.getCurrentValue() ) - value.setCurrentValue( get() ); - } - } - - public static abstract class DoubleElement implements StyleElement - { - - private final ArrayList< Consumer< Double > > onSet = new ArrayList<>(); - - private double value; - - private final String label; - - public DoubleElement( final String label ) - { - value = 0.; - this.label = label; - } - - public double getValue() - { - return value; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public abstract double get(); - - public abstract void set( double v ); - - public void onSet( final Consumer< Double > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - if ( get() != value ) - value = get(); - onSet.forEach( c -> c.accept( get() ) ); - } - } - - public static abstract class IntElement implements StyleElement - { - private final BoundedValue value; - - private final String label; - - public IntElement( final String label, final int rangeMin, final int rangeMax ) - { - final int currentValue = Math.max( rangeMin, Math.min( rangeMax, get() ) ); - value = new BoundedValue( rangeMin, rangeMax, currentValue ) - { - @Override - public void setCurrentValue( final int value ) - { - super.setCurrentValue( value ); - if ( get() != getCurrentValue() ) - set( getCurrentValue() ); - } - }; - this.label = label; - } - - public BoundedValue getValue() - { - return value; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public abstract int get(); - - public abstract void set( int v ); - - @Override - public void update() - { - if ( get() != value.getCurrentValue() ) - value.setCurrentValue( get() ); - } - } - - public static abstract class ColormapElement implements StyleElement - { - private final ArrayList< Consumer< Colormap > > onSet = new ArrayList<>(); - - private final String label; - - public ColormapElement( final String label ) - { - this.label = label; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public abstract Colormap get(); - - public abstract void set( Colormap v ); - - public void onSet( final Consumer< Colormap > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - onSet.forEach( c -> c.accept( get() ) ); - } - } - - public static abstract class FontElement implements StyleElement - { - - private final ArrayList< Consumer< Font > > onSet = new ArrayList<>(); - - private Font value; - - private final String label; - - public FontElement( final String label ) - { - this.label = label; - } - - public Font getValue() - { - return value; - } - - public String getLabel() - { - return label; - } - - @Override - public void accept( final StyleElementVisitor visitor ) - { - visitor.visit( this ); - } - - public abstract Font get(); - - public abstract void set( Font font ); - - public void onSet( final Consumer< Font > set ) - { - onSet.add( set ); - } - - @Override - public void update() - { - if ( get() != value ) - value = get(); - } - } - - /* - * - * =============================================================== - * - */ - - public static JLabel linkedLabel( final LabelElement element ) - { - return new JLabel( element.getLabel() ); - } - - public static CategoryJComboBox< TrackMateObject, String > linkedFeatureSelector( final FeatureElement element ) - { - final Settings settings = new Settings(); - settings.addAllAnalyzers(); - final CategoryJComboBox< TrackMateObject, String > selector = FeatureDisplaySelector.createComboBoxSelector( null, settings ); - selector.setSelectedItem( element.getFeature() ); - selector.addActionListener( e -> element.setValue( selector.getSelectedCategory(), selector.getSelectedItem() ) ); - element.onSet( ( type, feature ) -> { - if ( !feature.equals( selector.getSelectedItem() ) ) - selector.setSelectedItem( feature ); - } ); - return selector; - } - - public static JComboBox< Colormap > linkedColormapChooser( final ColormapElement element ) - { - final JComboBox< Colormap > cb = new JComboBox< Colormap >( - Colormap.getAvailableLUTs().toArray( new Colormap[] {} ) ); - cb.setRenderer( new ColormapRenderer() ); - cb.setSelectedItem( element.get() ); - cb.addActionListener( e -> element.set( ( Colormap ) cb.getSelectedItem() ) ); - element.onSet( cm -> { - if ( cm != cb.getSelectedItem() ) - cb.setSelectedItem( cm ); - } ); - return cb; - } - - private static final class ColormapRenderer extends JPanel implements ListCellRenderer< Colormap > - { - - private static final long serialVersionUID = 1L; - - private Colormap lut = Colormap.Jet; - - private final DefaultListCellRenderer lbl; - - public ColormapRenderer() - { - setPreferredSize( new Dimension( 150, 20 ) ); - final BoxLayout itemlayout = new BoxLayout( this, BoxLayout.LINE_AXIS ); - this.lbl = new DefaultListCellRenderer(); - setLayout( itemlayout ); - add( lbl ); - add( Box.createHorizontalGlue() ); - add( new JComponent() - { - - private static final long serialVersionUID = 1L; - - @Override - public void paint( final Graphics g ) - { - - final int width = getWidth(); - final int height = getHeight(); - for ( int i = 0; i < width; i++ ) - { - final double beta = ( double ) i / ( width - 1 ); - g.setColor( lut.getPaint( beta ) ); - g.drawLine( i, 0, i, height ); - } - g.setColor( this.getParent().getBackground() ); - g.drawRect( 0, 0, width, height ); - } - - @Override - public Dimension getMaximumSize() - { - return new Dimension( 100, 20 ); - } - - @Override - public Dimension getPreferredSize() - { - return getMaximumSize(); - } - } ); - } - - @Override - public Component getListCellRendererComponent( - final JList< ? extends Colormap > list, - final Colormap value, - final int index, - final boolean isSelected, - final boolean cellHasFocus ) - { - this.lut = value; - lbl.getListCellRendererComponent( list, value.getName(), index, isSelected, cellHasFocus ); - setBackground( lbl.getBackground() ); - return this; - } - } - - public static JCheckBox linkedCheckBox( final BooleanElement element, final String label ) - { - final JCheckBox checkbox = new JCheckBox( label, element.get() ); - checkbox.addActionListener( ( e ) -> element.set( checkbox.isSelected() ) ); - element.onSet( b -> { - if ( b != checkbox.isSelected() ) - checkbox.setSelected( b ); - } ); - return checkbox; - } - - public static JButton linkedColorButton( final ColorElement element, final JColorChooser colorChooser ) - { - final ColorIcon icon = new ColorIcon( element.getColor(), 16, 0 ); - final JButton button = new JButton( icon ); - button.setOpaque( false ); - button.setContentAreaFilled( false ); - button.setBorderPainted( false ); - button.setFont( new JButton().getFont() ); - button.setMargin( new Insets( 0, 0, 0, 0 ) ); - button.setBorder( new EmptyBorder( 2, 5, 2, 2 ) ); - button.setHorizontalAlignment( SwingConstants.LEFT ); - button.addActionListener( e -> { - colorChooser.setColor( element.getColor() ); - final JDialog d = JColorChooser.createDialog( button, "Choose a color", true, colorChooser, new ActionListener() - { - @Override - public void actionPerformed( final ActionEvent arg0 ) - { - final Color c = colorChooser.getColor(); - if ( c != null ) - { - icon.setColor( c ); - button.repaint(); - element.setColor( c ); - } - } - }, null ); - d.setVisible( true ); - } ); - element.onSet( icon::setColor ); - return button; - } - - public static SliderPanel linkedSliderPanel( final IntElement element, final int tfCols ) - { - final SliderPanel slider = new SliderPanel( null, element.getValue(), 1 ); - slider.setNumColummns( tfCols ); - slider.setBorder( new EmptyBorder( 0, 0, 0, 0 ) ); - return slider; - } - - public static JSpinner linkedSpinner( final IntElement element ) - { - final BoundedValue value = element.getValue(); - final SpinnerNumberModel model = new SpinnerNumberModel( element.get(), value.getRangeMin(), value.getRangeMax(), 1 ); - final JSpinner spinner = new JSpinner( model ); - spinner.setMaximumSize( new Dimension( 80, spinner.getMaximumSize().height ) ); - model.addChangeListener( e -> element.set( ( ( Number ) model.getValue() ).intValue() ) ); - return spinner; - } - - public static SliderPanelDouble linkedSliderPanel( final BoundedDoubleElement element, final int tfCols ) - { - return linkedSliderPanel( element, tfCols, 1. ); - } - - public static SliderPanelDouble linkedSliderPanel( final BoundedDoubleElement element, final int tfCols, final double stepSize ) - { - final SliderPanelDouble slider = new SliderPanelDouble( null, element.getValue(), stepSize ); - slider.setDecimalFormat( "0.####" ); - slider.setNumColummns( tfCols ); - slider.setBorder( new EmptyBorder( 0, 0, 0, 0 ) ); - return slider; - } - - @SuppressWarnings( "unchecked" ) - public static < E > JSpinner linkedSpinnerEnumSelector( final EnumElement< E > element ) - { - final SpinnerListModel model = new SpinnerListModel( element.getValues() ); - final JSpinner spinner = new JSpinner( model ); - spinner.setFont( Fonts.SMALL_FONT ); - ( ( DefaultEditor ) spinner.getEditor() ).getTextField().setEditable( false ); - model.setValue( element.getValue() ); - model.addChangeListener( e -> element.setValue( ( E ) model.getValue() ) ); - element.onSet( e -> { - if ( e != model.getValue() ) - model.setValue( e ); - } ); - return spinner; - } - - @SuppressWarnings( "unchecked" ) - public static < E > JComboBox< E > linkedComboBoxEnumSelector( final EnumElement< E > element ) - { - final DefaultComboBoxModel< E > model = new DefaultComboBoxModel<>( element.values ); - final JComboBox< E > cb = new JComboBox<>( model ); - cb.setFont( Fonts.SMALL_FONT ); - cb.addActionListener( e -> element.setValue( ( E ) model.getSelectedItem() ) ); - element.onSet( e -> { - if ( e != model.getSelectedItem() ) - model.setSelectedItem( e ); - } ); - cb.setSelectedItem( element.getValue() ); - return cb; - } - - @SuppressWarnings( "unchecked" ) - public static < E > JComboBox< E > linkedComboBoxSelector( final ListElement< E > element ) - { - final DefaultComboBoxModel< E > model = new DefaultComboBoxModel<>( new Vector<>( element.values ) ); - final JComboBox< E > cb = new JComboBox<>( model ); - cb.setFont( Fonts.SMALL_FONT ); - cb.addActionListener( e -> element.setValue( ( E ) model.getSelectedItem() ) ); - element.onSet( e -> { - if ( e != model.getSelectedItem() ) - model.setSelectedItem( e ); - } ); - return cb; - } - - public static JFormattedTextField linkedFormattedTextField( final DoubleElement element ) - { - final JFormattedTextField ftf = new JFormattedTextField( format ); - ftf.setHorizontalAlignment( JFormattedTextField.RIGHT ); - ftf.setValue( Double.valueOf( element.get() ) ); - - ftf.addActionListener( e -> element.set( ( ( Number ) ftf.getValue() ).doubleValue() ) ); - ftf.addFocusListener( new FocusAdapter() - { - @Override - public void focusLost( final java.awt.event.FocusEvent e ) - { - try - { - ftf.commitEdit(); - element.set( ( ( Number ) ftf.getValue() ).doubleValue() ); - } - catch ( final ParseException e1 ) - {} - } - } ); - GuiUtils.selectAllOnFocus( ftf ); - element.onSet( d -> { - if ( d != ( ( Number ) ftf.getValue() ).doubleValue() ) - ftf.setValue( Double.valueOf( element.value ) ); - } ); - - return ftf; - } - - public static JTextField linkedTextField( final StringElement element ) - { - final JTextField tf = new JTextField( element.get() ); - tf.setHorizontalAlignment( JFormattedTextField.LEFT ); - - tf.addActionListener( e -> element.set( tf.getText() ) ); - GuiUtils.selectAllOnFocus( tf ); - tf.addFocusListener( new FocusAdapter() - { - @Override - public void focusLost( final java.awt.event.FocusEvent e ) - { - element.set( tf.getText() ); - } - } ); - element.onSet( d -> { - if ( d != ( tf.getText() ) ) - tf.setText( element.value ); - } ); - - return tf; - } - - public static JButton linkedFontButton( final FontElement element, final Window parent ) - { - final JButton btn = new JButton( "Select font" ); - btn.setFont( element.get() ); - btn.addPropertyChangeListener( "font", e -> element.set( btn.getFont() ) ); - element.onSet( font -> { - if ( !font.equals( btn.getFont() ) ) - btn.setFont( font ); - } ); - btn.addActionListener( e -> { - final FontDialog dialog = new FontDialog( parent, "Select font for TrackMate display", ModalityType.APPLICATION_MODAL ); - dialog.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE ); - dialog.setSelectedFont( btn.getFont() ); - GuiUtils.positionWindow( dialog, parent ); - dialog.setIconImage( Icons.TRACKMATE_ICON.getImage() ); - dialog.setVisible( true ); - if ( !dialog.isCancelSelected() ) - btn.setFont( dialog.getSelectedFont() ); - } ); - return btn; - } -} diff --git a/src/main/java/fiji/plugin/trackmate/gui/displaysettings/TrackMateStyleElements.java b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/TrackMateStyleElements.java new file mode 100644 index 000000000..97c3265e4 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/gui/displaysettings/TrackMateStyleElements.java @@ -0,0 +1,118 @@ +package fiji.plugin.trackmate.gui.displaysettings; + +import java.util.ArrayList; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +import org.scijava.ui.config.visitors.gui.elements.StyleElement; +import org.scijava.ui.config.visitors.gui.elements.StyleElementVisitor; + +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.gui.components.CategoryJComboBox; +import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.TrackMateObject; + +public class TrackMateStyleElements +{ + + public static CategoryJComboBox< TrackMateObject, String > linkedFeatureSelector( final FeatureElement element ) + { + final Settings settings = new Settings(); + settings.addAllAnalyzers(); + final CategoryJComboBox< TrackMateObject, String > selector = FeatureDisplaySelector.createComboBoxSelector( null, settings ); + selector.setSelectedItem( element.getFeature() ); + selector.addActionListener( e -> element.setValue( selector.getSelectedCategory(), selector.getSelectedItem() ) ); + element.onSet( ( type, feature ) -> { + if ( !feature.equals( selector.getSelectedItem() ) ) + selector.setSelectedItem( feature ); + } ); + return selector; + } + + public static FeatureElement featureElement( final String label, final Supplier< TrackMateObject > typeGet, final Supplier< String > featureGet, final BiConsumer< TrackMateObject, String > set ) + { + return new FeatureElement( label ) + { + + @Override + public void setValue( final TrackMateObject type, final String feature ) + { + set.accept( type, feature ); + } + + @Override + public TrackMateObject getType() + { + return typeGet.get(); + } + + @Override + public String getFeature() + { + return featureGet.get(); + } + + @Override + public void accept( final StyleElementVisitor visitor ) + { + if ( visitor instanceof final TrackMateStyleElementVisitor extendedVisitor ) + extendedVisitor.visit( this ); + else + throw new UnsupportedOperationException( "Visitor " + visitor.getClass().getName() + " does not support " + this.getClass().getName() ); + } + }; + } + + public static interface TrackMateStyleElement extends StyleElement + { + void accept( TrackMateStyleElementVisitor visitor ); + } + + public static interface TrackMateStyleElementVisitor extends StyleElementVisitor + { + public default void visit( final FeatureElement element ) + { + throw new UnsupportedOperationException(); + } + } + + public static abstract class FeatureElement implements TrackMateStyleElement + { + private final ArrayList< BiConsumer< TrackMateObject, String > > onSet = new ArrayList<>(); + + private final String label; + + public FeatureElement( final String label ) + { + this.label = label; + } + + public String getLabel() + { + return label; + } + + @Override + public void accept( final TrackMateStyleElementVisitor visitor ) + { + visitor.visit( this ); + } + + public void onSet( final BiConsumer< TrackMateObject, String > set ) + { + onSet.add( set ); + } + + @Override + public void update() + { + onSet.forEach( c -> c.accept( getType(), getFeature() ) ); + } + + public abstract TrackMateObject getType(); + + public abstract String getFeature(); + + public abstract void setValue( TrackMateObject type, String feature ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java index 22975e21f..b6e269f16 100644 --- a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java +++ b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java @@ -5,10 +5,10 @@ import java.util.function.Supplier; import org.scijava.ui.config.Configurator; +import org.scijava.ui.config.visitors.gui.elements.StyleElement; import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; import org.scijava.ui.config.visitors.gui.elements.StyleElements.DoubleElement; import org.scijava.ui.config.visitors.gui.elements.StyleElements.IntElement; -import org.scijava.ui.config.visitors.gui.elements.StyleElements.StyleElement; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/PerEdgeFeatureColorGenerator.java b/src/main/java/fiji/plugin/trackmate/visualization/PerEdgeFeatureColorGenerator.java index 7eebd01ac..555b4fa78 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/PerEdgeFeatureColorGenerator.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/PerEdgeFeatureColorGenerator.java @@ -24,9 +24,9 @@ import java.awt.Color; import org.jgrapht.graph.DefaultWeightedEdge; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.gui.displaysettings.Colormap; public class PerEdgeFeatureColorGenerator implements TrackColorGenerator { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/PerSpotFeatureColorGenerator.java b/src/main/java/fiji/plugin/trackmate/visualization/PerSpotFeatureColorGenerator.java index e19f219f0..e8c4f8539 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/PerSpotFeatureColorGenerator.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/PerSpotFeatureColorGenerator.java @@ -24,10 +24,10 @@ import java.awt.Color; import org.jgrapht.graph.DefaultWeightedEdge; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.gui.displaysettings.Colormap; public class PerSpotFeatureColorGenerator implements TrackColorGenerator { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/PerTrackFeatureColorGenerator.java b/src/main/java/fiji/plugin/trackmate/visualization/PerTrackFeatureColorGenerator.java index 224158a22..cdf05954c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/PerTrackFeatureColorGenerator.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/PerTrackFeatureColorGenerator.java @@ -30,12 +30,12 @@ import java.util.Set; import org.jgrapht.graph.DefaultWeightedEdge; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; import fiji.plugin.trackmate.FeatureModel; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.TrackModel; import fiji.plugin.trackmate.features.track.TrackIndexAnalyzer; -import fiji.plugin.trackmate.gui.displaysettings.Colormap; /** * A {@link TrackColorGenerator} that generate colors based on the whole track diff --git a/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGenerator.java b/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGenerator.java index 41d32ce75..56216010d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGenerator.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGenerator.java @@ -23,8 +23,9 @@ import java.awt.Color; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; + import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.gui.displaysettings.Colormap; public class SpotColorGenerator implements FeatureColorGenerator< Spot > { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGeneratorPerEdgeFeature.java b/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGeneratorPerEdgeFeature.java index da70b6101..f2e743e86 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGeneratorPerEdgeFeature.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGeneratorPerEdgeFeature.java @@ -25,10 +25,10 @@ import java.util.Set; import org.jgrapht.graph.DefaultWeightedEdge; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.gui.displaysettings.Colormap; public class SpotColorGeneratorPerEdgeFeature implements FeatureColorGenerator< Spot > { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGeneratorPerTrackFeature.java b/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGeneratorPerTrackFeature.java index 03538ef8d..42e4ec85c 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGeneratorPerTrackFeature.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/SpotColorGeneratorPerTrackFeature.java @@ -23,9 +23,10 @@ import java.awt.Color; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; + import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; -import fiji.plugin.trackmate.gui.displaysettings.Colormap; public class SpotColorGeneratorPerTrackFeature implements FeatureColorGenerator< Spot > { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/WholeTrackFeatureColorGenerator.java b/src/main/java/fiji/plugin/trackmate/visualization/WholeTrackFeatureColorGenerator.java index 9a529eda3..f5087f9cb 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/WholeTrackFeatureColorGenerator.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/WholeTrackFeatureColorGenerator.java @@ -23,8 +23,9 @@ import java.awt.Color; +import org.scijava.ui.config.visitors.gui.elements.colormap.Colormap; + import fiji.plugin.trackmate.Model; -import fiji.plugin.trackmate.gui.displaysettings.Colormap; public class WholeTrackFeatureColorGenerator implements FeatureColorGenerator< Integer > { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/TablePanel.java b/src/main/java/fiji/plugin/trackmate/visualization/table/TablePanel.java index 83d0ba021..e0a793af2 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/TablePanel.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/TablePanel.java @@ -68,10 +68,11 @@ import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; +import org.scijava.ui.config.visitors.gui.elements.ColorIcon; + import com.opencsv.CSVWriter; import fiji.plugin.trackmate.gui.GuiUtils; -import fiji.plugin.trackmate.gui.displaysettings.ColorIcon; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; import gnu.trove.map.hash.TObjectIntHashMap; diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Algae.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Algae.lut deleted file mode 100644 index 90f95a659..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Algae.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 17 36 20 -1 17 37 20 -2 18 37 21 -3 18 38 22 -4 18 39 22 -5 19 40 23 -6 19 41 24 -7 19 42 25 -8 20 43 25 -9 20 43 26 -10 20 44 27 -11 21 45 27 -12 21 46 28 -13 21 47 29 -14 21 48 29 -15 22 49 30 -16 22 49 31 -17 22 50 31 -18 22 51 32 -19 23 52 33 -20 23 53 33 -21 23 54 34 -22 23 55 35 -23 23 55 35 -24 23 56 36 -25 24 57 36 -26 24 58 37 -27 24 59 38 -28 24 60 38 -29 24 61 39 -30 24 61 40 -31 25 62 40 -32 25 63 41 -33 25 64 41 -34 25 65 42 -35 25 66 43 -36 25 67 43 -37 25 67 44 -38 25 68 44 -39 25 69 45 -40 25 70 46 -41 25 71 46 -42 25 72 47 -43 25 73 47 -44 25 73 48 -45 25 74 49 -46 25 75 49 -47 25 76 50 -48 25 77 50 -49 25 78 51 -50 25 79 51 -51 25 80 52 -52 25 80 52 -53 25 81 53 -54 25 82 54 -55 25 83 54 -56 25 84 55 -57 25 85 55 -58 25 86 56 -59 25 87 56 -60 24 87 57 -61 24 88 57 -62 24 89 58 -63 24 90 58 -64 24 91 59 -65 23 92 59 -66 23 93 60 -67 23 94 60 -68 23 94 61 -69 23 95 61 -70 22 96 62 -71 22 97 62 -72 22 98 63 -73 22 99 63 -74 21 100 63 -75 21 101 64 -76 21 102 64 -77 20 102 65 -78 20 103 65 -79 19 104 66 -80 19 105 66 -81 19 106 67 -82 18 107 67 -83 18 108 67 -84 18 109 68 -85 17 110 68 -86 17 110 69 -87 16 111 69 -88 16 112 69 -89 15 113 70 -90 15 114 70 -91 14 115 70 -92 14 116 71 -93 13 117 71 -94 13 118 72 -95 12 119 72 -96 12 119 72 -97 11 120 73 -98 11 121 73 -99 10 122 73 -100 10 123 74 -101 9 124 74 -102 9 125 74 -103 8 126 74 -104 8 127 75 -105 7 128 75 -106 7 129 75 -107 7 129 76 -108 7 130 76 -109 6 131 76 -110 6 132 76 -111 6 133 77 -112 6 134 77 -113 6 135 77 -114 7 136 77 -115 7 137 77 -116 7 138 78 -117 8 138 78 -118 8 139 78 -119 9 140 78 -120 9 141 78 -121 10 142 79 -122 11 143 79 -123 12 144 79 -124 13 145 79 -125 15 146 79 -126 16 146 79 -127 17 147 79 -128 19 148 80 -129 20 149 80 -130 21 150 80 -131 23 151 80 -132 25 152 80 -133 26 153 80 -134 28 153 80 -135 30 154 80 -136 31 155 80 -137 33 156 81 -138 35 157 81 -139 37 157 81 -140 39 158 81 -141 41 159 81 -142 43 160 81 -143 45 161 81 -144 47 161 82 -145 50 162 82 -146 52 163 82 -147 54 164 82 -148 56 164 83 -149 58 165 83 -150 61 166 84 -151 63 167 84 -152 65 167 84 -153 67 168 85 -154 69 169 86 -155 71 169 86 -156 74 170 87 -157 76 171 87 -158 78 171 88 -159 80 172 89 -160 82 173 90 -161 83 174 90 -162 85 174 91 -163 87 175 92 -164 89 176 93 -165 91 176 94 -166 93 177 95 -167 94 178 96 -168 96 178 96 -169 98 179 97 -170 100 180 98 -171 101 180 99 -172 103 181 100 -173 104 182 101 -174 106 183 102 -175 108 183 103 -176 109 184 104 -177 111 185 105 -178 112 185 107 -179 114 186 108 -180 115 187 109 -181 117 188 110 -182 118 188 111 -183 120 189 112 -184 121 190 113 -185 123 191 114 -186 124 191 115 -187 126 192 117 -188 127 193 118 -189 129 194 119 -190 130 194 120 -191 132 195 121 -192 133 196 122 -193 134 197 124 -194 136 197 125 -195 137 198 126 -196 139 199 127 -197 140 200 128 -198 141 200 130 -199 143 201 131 -200 144 202 132 -201 145 203 133 -202 147 203 135 -203 148 204 136 -204 149 205 137 -205 151 206 138 -206 152 207 140 -207 153 207 141 -208 155 208 142 -209 156 209 143 -210 157 210 145 -211 159 211 146 -212 160 211 147 -213 161 212 149 -214 163 213 150 -215 164 214 151 -216 165 215 153 -217 166 215 154 -218 168 216 155 -219 169 217 156 -220 170 218 158 -221 172 219 159 -222 173 219 160 -223 174 220 162 -224 175 221 163 -225 177 222 165 -226 178 223 166 -227 179 224 167 -228 180 225 169 -229 182 225 170 -230 183 226 171 -231 184 227 173 -232 186 228 174 -233 187 229 176 -234 188 230 177 -235 189 231 178 -236 191 231 180 -237 192 232 181 -238 193 233 183 -239 194 234 184 -240 196 235 185 -241 197 236 187 -242 198 237 188 -243 199 238 190 -244 201 239 191 -245 202 239 193 -246 203 240 194 -247 204 241 195 -248 206 242 197 -249 207 243 198 -250 208 244 200 -251 209 245 201 -252 211 246 203 -253 212 247 204 -254 213 248 206 -255 214 249 207 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Amp.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Amp.lut deleted file mode 100644 index 401695c0e..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Amp.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 60 9 17 -1 61 9 18 -2 62 9 19 -3 63 9 19 -4 65 10 20 -5 66 10 20 -6 67 10 21 -7 69 10 22 -8 70 11 22 -9 71 11 23 -10 72 11 24 -11 74 11 24 -12 75 12 25 -13 76 12 25 -14 77 12 26 -15 79 12 27 -16 80 12 27 -17 81 13 28 -18 83 13 28 -19 84 13 29 -20 85 13 29 -21 86 13 30 -22 88 13 30 -23 89 13 31 -24 90 14 32 -25 92 14 32 -26 93 14 32 -27 94 14 33 -28 96 14 33 -29 97 14 34 -30 98 14 34 -31 100 14 35 -32 101 14 35 -33 102 14 36 -34 104 14 36 -35 105 14 37 -36 106 14 37 -37 108 14 37 -38 109 14 38 -39 110 14 38 -40 112 14 38 -41 113 14 39 -42 114 14 39 -43 116 14 39 -44 117 14 39 -45 118 14 40 -46 120 14 40 -47 121 14 40 -48 122 14 40 -49 124 14 40 -50 125 13 41 -51 126 13 41 -52 128 13 41 -53 129 14 41 -54 130 14 41 -55 132 14 41 -56 133 14 41 -57 134 14 41 -58 135 14 41 -59 137 14 41 -60 138 14 41 -61 139 15 41 -62 141 15 40 -63 142 16 40 -64 143 16 40 -65 144 16 40 -66 145 17 40 -67 147 18 40 -68 148 18 40 -69 149 19 39 -70 150 19 39 -71 151 20 39 -72 152 21 39 -73 154 22 38 -74 155 23 38 -75 156 24 38 -76 157 24 38 -77 158 25 38 -78 159 26 37 -79 160 27 37 -80 161 28 37 -81 162 30 37 -82 163 31 37 -83 164 32 36 -84 165 33 36 -85 166 34 36 -86 166 35 36 -87 167 36 36 -88 168 37 36 -89 169 39 36 -90 170 40 36 -91 171 41 36 -92 172 42 36 -93 172 44 36 -94 173 45 36 -95 174 46 36 -96 175 47 36 -97 175 49 36 -98 176 50 36 -99 177 51 37 -100 177 53 37 -101 178 54 37 -102 179 55 38 -103 179 57 38 -104 180 58 38 -105 180 59 39 -106 181 61 40 -107 181 62 40 -108 182 63 41 -109 183 65 41 -110 183 66 42 -111 184 67 43 -112 184 69 44 -113 185 70 44 -114 185 71 45 -115 186 73 46 -116 186 74 47 -117 187 75 48 -118 187 76 49 -119 188 78 50 -120 188 79 51 -121 188 80 52 -122 189 81 53 -123 189 83 54 -124 190 84 55 -125 190 85 56 -126 191 86 57 -127 191 88 58 -128 192 89 60 -129 192 90 61 -130 192 91 62 -131 193 93 63 -132 193 94 64 -133 194 95 65 -134 194 96 67 -135 194 97 68 -136 195 99 69 -137 195 100 70 -138 196 101 71 -139 196 102 73 -140 196 103 74 -141 197 105 75 -142 197 106 77 -143 198 107 78 -144 198 108 79 -145 198 109 80 -146 199 110 82 -147 199 112 83 -148 199 113 84 -149 200 114 86 -150 200 115 87 -151 201 116 88 -152 201 117 90 -153 201 119 91 -154 202 120 92 -155 202 121 94 -156 202 122 95 -157 203 123 96 -158 203 124 98 -159 204 125 99 -160 204 127 100 -161 204 128 102 -162 205 129 103 -163 205 130 105 -164 205 131 106 -165 206 132 107 -166 206 133 109 -167 206 135 110 -168 207 136 111 -169 207 137 113 -170 207 138 114 -171 208 139 116 -172 208 140 117 -173 208 141 119 -174 209 143 120 -175 209 144 121 -176 209 145 123 -177 210 146 124 -178 210 147 126 -179 211 148 127 -180 211 149 128 -181 211 151 130 -182 212 152 131 -183 212 153 133 -184 212 154 134 -185 213 155 136 -186 213 156 137 -187 213 157 138 -188 214 158 140 -189 214 160 141 -190 214 161 143 -191 215 162 144 -192 215 163 146 -193 215 164 147 -194 216 165 148 -195 216 166 150 -196 216 168 151 -197 217 169 153 -198 217 170 154 -199 217 171 156 -200 218 172 157 -201 218 173 159 -202 218 174 160 -203 219 176 161 -204 219 177 163 -205 219 178 164 -206 220 179 166 -207 220 180 167 -208 220 181 169 -209 221 182 170 -210 221 184 172 -211 222 185 173 -212 222 186 174 -213 222 187 176 -214 223 188 177 -215 223 189 179 -216 223 191 180 -217 224 192 182 -218 224 193 183 -219 224 194 185 -220 225 195 186 -221 225 196 188 -222 226 198 189 -223 226 199 190 -224 226 200 192 -225 227 201 193 -226 227 202 195 -227 228 203 196 -228 228 204 198 -229 228 206 199 -230 229 207 200 -231 229 208 202 -232 230 209 203 -233 230 210 205 -234 230 212 206 -235 231 213 208 -236 231 214 209 -237 232 215 211 -238 232 216 212 -239 233 217 213 -240 233 219 215 -241 233 220 216 -242 234 221 218 -243 234 222 219 -244 235 223 220 -245 235 225 222 -246 236 226 223 -247 236 227 225 -248 237 228 226 -249 237 229 227 -250 238 231 229 -251 238 232 230 -252 239 233 232 -253 240 234 233 -254 240 235 234 -255 241 236 236 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Balance.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Balance.lut deleted file mode 100644 index 3e39ff1da..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Balance.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 23 28 66 -1 24 29 69 -2 25 31 72 -3 26 32 75 -4 27 33 78 -5 28 35 81 -6 29 36 85 -7 30 37 88 -8 31 39 91 -9 32 40 94 -10 33 41 97 -11 34 43 101 -12 35 44 104 -13 35 45 107 -14 36 47 111 -15 37 48 114 -16 37 49 118 -17 38 51 121 -18 39 52 125 -19 39 53 128 -20 40 55 132 -21 40 56 136 -22 40 57 139 -23 41 58 143 -24 41 60 147 -25 41 61 150 -26 41 63 154 -27 40 64 158 -28 40 65 161 -29 39 67 165 -30 39 68 168 -31 38 70 172 -32 36 72 175 -33 35 74 178 -34 33 75 181 -35 30 77 184 -36 27 80 186 -37 24 82 187 -38 21 84 188 -39 18 86 189 -40 15 89 189 -41 13 91 190 -42 11 93 190 -43 10 95 189 -44 9 98 189 -45 10 100 189 -46 11 102 189 -47 12 104 188 -48 14 106 188 -49 16 108 188 -50 19 110 187 -51 21 112 187 -52 24 114 187 -53 27 116 187 -54 29 117 186 -55 32 119 186 -56 35 121 186 -57 37 123 186 -58 40 125 186 -59 43 127 186 -60 45 128 185 -61 48 130 185 -62 51 132 185 -63 53 134 185 -64 56 135 185 -65 59 137 185 -66 61 139 185 -67 64 140 185 -68 66 142 185 -69 69 144 185 -70 72 145 185 -71 74 147 186 -72 77 149 186 -73 80 150 186 -74 83 152 186 -75 86 154 186 -76 89 155 186 -77 91 157 186 -78 94 158 187 -79 97 160 187 -80 100 162 187 -81 104 163 188 -82 107 165 188 -83 110 166 188 -84 113 168 189 -85 116 169 189 -86 119 171 190 -87 123 172 190 -88 126 173 191 -89 129 175 191 -90 132 176 192 -91 135 178 193 -92 139 179 193 -93 142 181 194 -94 145 182 195 -95 148 184 196 -96 151 185 197 -97 154 186 197 -98 157 188 198 -99 160 189 199 -100 163 191 200 -101 166 192 201 -102 169 194 202 -103 172 195 203 -104 175 197 204 -105 178 198 206 -106 181 200 207 -107 184 202 208 -108 187 203 209 -109 190 205 210 -110 193 206 212 -111 196 208 213 -112 199 210 214 -113 202 211 215 -114 204 213 217 -115 207 214 218 -116 210 216 219 -117 213 218 221 -118 216 220 222 -119 219 221 223 -120 221 223 225 -121 224 225 226 -122 227 227 228 -123 230 228 229 -124 232 230 231 -125 235 232 232 -126 238 234 234 -127 240 236 235 -128 240 236 235 -129 239 233 232 -130 238 231 230 -131 237 229 227 -132 236 226 224 -133 235 224 221 -134 234 222 218 -135 233 219 215 -136 232 217 213 -137 231 214 210 -138 231 212 207 -139 230 210 204 -140 229 207 201 -141 228 205 198 -142 227 203 195 -143 227 200 192 -144 226 198 190 -145 225 196 187 -146 224 193 184 -147 223 191 181 -148 223 189 178 -149 222 186 175 -150 221 184 172 -151 221 182 169 -152 220 179 166 -153 219 177 163 -154 218 175 161 -155 218 173 158 -156 217 170 155 -157 216 168 152 -158 216 166 149 -159 215 163 146 -160 214 161 143 -161 214 159 140 -162 213 157 138 -163 212 154 135 -164 212 152 132 -165 211 150 129 -166 210 148 126 -167 210 145 123 -168 209 143 120 -169 208 141 118 -170 208 138 115 -171 207 136 112 -172 206 134 109 -173 205 132 106 -174 205 129 104 -175 204 127 101 -176 203 125 98 -177 203 122 95 -178 202 120 93 -179 201 118 90 -180 200 115 87 -181 200 113 85 -182 199 111 82 -183 198 108 79 -184 197 106 77 -185 197 104 74 -186 196 101 72 -187 195 99 69 -188 194 96 67 -189 193 94 64 -190 192 92 62 -191 192 89 60 -192 191 87 58 -193 190 84 55 -194 189 82 53 -195 188 79 51 -196 187 77 49 -197 186 74 47 -198 185 72 45 -199 184 69 44 -200 183 66 42 -201 182 64 41 -202 181 61 40 -203 180 58 39 -204 179 56 38 -205 177 53 37 -206 176 50 36 -207 175 48 36 -208 173 45 36 -209 172 43 36 -210 170 40 36 -211 168 38 36 -212 167 35 36 -213 165 33 36 -214 163 31 37 -215 161 29 37 -216 159 27 37 -217 157 25 38 -218 155 23 38 -219 153 21 39 -220 150 20 39 -221 148 18 40 -222 146 17 40 -223 143 16 40 -224 141 15 40 -225 138 15 41 -226 136 14 41 -227 133 14 41 -228 130 14 41 -229 128 13 41 -230 125 13 41 -231 122 14 40 -232 120 14 40 -233 117 14 39 -234 114 14 39 -235 112 14 38 -236 109 14 38 -237 106 14 37 -238 104 14 36 -239 101 14 35 -240 98 14 34 -241 96 14 33 -242 93 14 33 -243 90 14 32 -244 88 13 30 -245 85 13 29 -246 83 13 28 -247 80 12 27 -248 77 12 26 -249 75 12 25 -250 72 11 24 -251 70 11 22 -252 67 10 21 -253 65 10 20 -254 62 9 19 -255 60 9 17 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Curl.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Curl.lut deleted file mode 100644 index 820059123..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Curl.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 20 29 67 -1 21 31 68 -2 21 33 69 -3 22 35 70 -4 22 37 71 -5 23 39 72 -6 23 41 74 -7 24 43 75 -8 24 45 76 -9 24 47 77 -10 25 48 78 -11 25 50 79 -12 25 52 80 -13 26 54 82 -14 26 56 83 -15 26 58 84 -16 27 60 85 -17 27 61 86 -18 27 63 87 -19 27 65 89 -20 27 67 90 -21 27 69 91 -22 28 71 92 -23 28 72 93 -24 28 74 94 -25 28 76 96 -26 28 78 97 -27 27 80 98 -28 27 82 99 -29 27 83 100 -30 27 85 101 -31 27 87 102 -32 26 89 103 -33 26 91 105 -34 26 93 106 -35 25 95 107 -36 25 96 108 -37 24 98 109 -38 23 100 110 -39 23 102 111 -40 22 104 112 -41 21 106 113 -42 21 108 114 -43 20 110 115 -44 19 112 115 -45 18 113 116 -46 18 115 117 -47 17 117 118 -48 17 119 119 -49 16 121 119 -50 16 123 120 -51 16 125 121 -52 17 127 122 -53 17 128 122 -54 18 130 123 -55 20 132 123 -56 21 134 124 -57 23 136 124 -58 26 138 125 -59 28 139 125 -60 31 141 126 -61 34 143 126 -62 37 145 126 -63 41 146 127 -64 44 148 127 -65 48 150 127 -66 52 151 128 -67 56 153 128 -68 60 154 129 -69 64 156 129 -70 68 157 130 -71 72 159 130 -72 76 160 131 -73 80 162 131 -74 84 163 132 -75 88 165 133 -76 92 166 133 -77 95 168 134 -78 99 169 135 -79 103 170 136 -80 107 172 137 -81 111 173 138 -82 115 174 140 -83 118 176 141 -84 122 177 142 -85 125 178 143 -86 129 180 145 -87 133 181 146 -88 136 183 148 -89 140 184 150 -90 143 185 151 -91 146 187 153 -92 150 188 155 -93 153 189 156 -94 156 191 158 -95 160 192 160 -96 163 194 162 -97 166 195 164 -98 169 196 166 -99 173 198 169 -100 176 199 171 -101 179 201 173 -102 182 202 175 -103 185 204 177 -104 188 205 180 -105 191 207 182 -106 194 208 185 -107 197 210 187 -108 200 212 190 -109 203 213 192 -110 206 215 195 -111 209 216 197 -112 212 218 200 -113 214 220 203 -114 217 221 205 -115 220 223 208 -116 223 225 211 -117 226 226 214 -118 229 228 216 -119 231 230 219 -120 234 232 222 -121 237 233 225 -122 240 235 228 -123 243 237 231 -124 245 239 234 -125 248 241 237 -126 251 243 240 -127 253 245 243 -128 253 245 243 -129 251 242 240 -130 250 240 237 -131 249 237 233 -132 248 235 230 -133 247 233 227 -134 246 230 223 -135 245 228 220 -136 244 225 216 -137 243 223 213 -138 242 220 210 -139 241 218 206 -140 240 215 203 -141 239 213 200 -142 238 210 196 -143 237 208 193 -144 236 205 190 -145 236 203 187 -146 235 200 183 -147 234 198 180 -148 233 195 177 -149 233 193 174 -150 232 190 171 -151 231 188 168 -152 231 186 165 -153 230 183 162 -154 229 181 159 -155 229 178 156 -156 228 176 153 -157 227 173 150 -158 227 171 147 -159 226 168 145 -160 225 166 142 -161 225 163 139 -162 224 160 137 -163 223 158 134 -164 223 155 132 -165 222 153 129 -166 221 150 127 -167 221 148 125 -168 220 145 123 -169 219 143 121 -170 219 140 119 -171 218 138 117 -172 217 135 115 -173 216 133 113 -174 215 130 112 -175 215 128 110 -176 214 125 109 -177 213 123 107 -178 212 120 106 -179 211 118 105 -180 210 115 104 -181 209 113 103 -182 208 111 102 -183 206 108 101 -184 205 106 100 -185 204 104 100 -186 203 101 99 -187 201 99 98 -188 200 97 98 -189 199 94 97 -190 197 92 97 -191 196 90 97 -192 194 88 96 -193 193 86 96 -194 191 83 96 -195 189 81 96 -196 188 79 96 -197 186 77 96 -198 184 75 96 -199 183 73 95 -200 181 71 95 -201 179 69 95 -202 177 67 95 -203 176 65 95 -204 174 63 95 -205 172 61 96 -206 170 60 96 -207 168 58 96 -208 166 56 96 -209 164 54 96 -210 162 52 96 -211 160 51 96 -212 158 49 96 -213 156 47 96 -214 154 45 96 -215 151 44 96 -216 149 42 96 -217 147 41 96 -218 145 39 96 -219 143 38 96 -220 140 36 96 -221 138 35 96 -222 136 33 96 -223 133 32 95 -224 131 31 95 -225 129 30 95 -226 126 28 95 -227 124 27 94 -228 121 26 94 -229 119 25 93 -230 116 25 93 -231 114 24 92 -232 111 23 91 -233 109 22 90 -234 106 22 89 -235 103 21 88 -236 101 21 87 -237 98 20 86 -238 95 20 85 -239 93 20 83 -240 90 19 82 -241 87 19 80 -242 85 19 79 -243 82 18 77 -244 79 18 75 -245 77 18 74 -246 74 17 72 -247 72 17 70 -248 69 16 68 -249 66 16 66 -250 64 16 64 -251 61 15 61 -252 59 14 59 -253 56 14 57 -254 54 13 55 -255 51 13 53 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Deep.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Deep.lut deleted file mode 100644 index 8da16d49f..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Deep.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 39 26 44 -1 40 26 45 -2 41 27 46 -3 41 28 48 -4 42 29 49 -5 43 29 50 -6 43 30 52 -7 44 31 53 -8 45 32 55 -9 45 32 56 -10 46 33 57 -11 47 34 59 -12 47 35 60 -13 48 35 62 -14 49 36 63 -15 49 37 65 -16 50 38 66 -17 50 38 68 -18 51 39 69 -19 52 40 71 -20 52 41 72 -21 53 41 74 -22 53 42 75 -23 54 43 77 -24 55 43 78 -25 55 44 80 -26 56 45 82 -27 56 46 83 -28 57 46 85 -29 57 47 86 -30 58 48 88 -31 58 48 90 -32 59 49 91 -33 59 50 93 -34 60 51 95 -35 60 51 96 -36 61 52 98 -37 61 53 99 -38 61 54 101 -39 62 54 103 -40 62 55 105 -41 62 56 106 -42 63 57 108 -43 63 57 110 -44 63 58 111 -45 64 59 113 -46 64 60 115 -47 64 61 116 -48 64 61 118 -49 65 62 120 -50 65 63 121 -51 65 64 123 -52 65 65 124 -53 65 66 126 -54 65 67 127 -55 65 68 129 -56 65 69 130 -57 65 69 132 -58 65 70 133 -59 65 71 134 -60 64 72 135 -61 64 74 136 -62 64 75 137 -63 64 76 138 -64 64 77 139 -65 63 78 140 -66 63 79 141 -67 63 80 142 -68 63 81 142 -69 62 82 143 -70 62 83 143 -71 62 84 144 -72 62 85 144 -73 62 87 145 -74 62 88 145 -75 61 89 145 -76 61 90 146 -77 61 91 146 -78 61 92 146 -79 61 93 147 -80 61 94 147 -81 61 95 147 -82 61 96 147 -83 61 97 148 -84 61 98 148 -85 61 99 148 -86 61 100 148 -87 61 101 149 -88 61 102 149 -89 62 103 149 -90 62 105 149 -91 62 106 149 -92 62 107 150 -93 62 108 150 -94 62 109 150 -95 62 110 150 -96 63 111 150 -97 63 112 151 -98 63 113 151 -99 63 114 151 -100 64 115 151 -101 64 116 151 -102 64 117 152 -103 64 118 152 -104 64 119 152 -105 65 120 152 -106 65 121 153 -107 65 122 153 -108 66 123 153 -109 66 124 153 -110 66 125 153 -111 66 126 154 -112 67 127 154 -113 67 128 154 -114 67 129 154 -115 68 129 154 -116 68 130 155 -117 68 131 155 -118 69 132 155 -119 69 133 155 -120 69 134 156 -121 70 135 156 -122 70 136 156 -123 70 137 156 -124 71 138 157 -125 71 139 157 -126 71 140 157 -127 72 141 157 -128 72 142 157 -129 72 143 158 -130 73 144 158 -131 73 145 158 -132 73 146 158 -133 74 147 159 -134 74 148 159 -135 74 149 159 -136 75 150 159 -137 75 151 159 -138 75 152 160 -139 76 153 160 -140 76 154 160 -141 76 155 160 -142 77 156 160 -143 77 157 161 -144 78 159 161 -145 78 160 161 -146 78 161 161 -147 79 162 161 -148 79 163 162 -149 80 164 162 -150 80 165 162 -151 81 166 162 -152 81 167 162 -153 81 168 162 -154 82 169 162 -155 82 170 163 -156 83 171 163 -157 83 172 163 -158 84 173 163 -159 85 174 163 -160 85 175 163 -161 86 176 163 -162 86 177 163 -163 87 178 163 -164 88 179 163 -165 88 180 163 -166 89 181 163 -167 90 182 163 -168 91 183 163 -169 92 184 163 -170 92 185 163 -171 93 186 163 -172 94 187 163 -173 95 188 163 -174 96 189 163 -175 97 190 163 -176 98 191 163 -177 99 192 163 -178 100 193 163 -179 102 194 163 -180 103 195 163 -181 104 196 163 -182 105 197 163 -183 107 198 163 -184 108 199 163 -185 110 200 163 -186 111 201 163 -187 113 202 163 -188 114 202 163 -189 116 203 163 -190 118 204 163 -191 119 205 162 -192 121 206 162 -193 123 207 162 -194 125 208 162 -195 127 209 162 -196 129 209 162 -197 131 210 162 -198 133 211 162 -199 135 212 163 -200 137 213 163 -201 139 213 163 -202 141 214 163 -203 143 215 163 -204 145 216 163 -205 147 216 163 -206 149 217 164 -207 152 218 164 -208 154 219 164 -209 156 219 165 -210 158 220 165 -211 160 221 165 -212 163 222 166 -213 165 222 166 -214 167 223 167 -215 169 224 167 -216 171 225 168 -217 174 225 168 -218 176 226 169 -219 178 227 169 -220 180 227 170 -221 182 228 171 -222 184 229 171 -223 187 230 172 -224 189 230 173 -225 191 231 173 -226 193 232 174 -227 195 232 175 -228 197 233 176 -229 199 234 176 -230 201 235 177 -231 204 235 178 -232 206 236 179 -233 208 237 180 -234 210 237 181 -235 212 238 182 -236 214 239 183 -237 216 240 184 -238 218 240 185 -239 220 241 186 -240 222 242 187 -241 224 242 188 -242 226 243 189 -243 228 244 190 -244 230 245 191 -245 232 245 192 -246 235 246 193 -247 237 247 194 -248 239 248 195 -249 241 248 196 -250 243 249 198 -251 245 250 199 -252 247 251 200 -253 249 252 201 -254 251 252 202 -255 253 253 204 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Delta.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Delta.lut deleted file mode 100644 index 63d48561c..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Delta.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 16 31 63 -1 18 32 66 -2 19 34 69 -3 20 35 73 -4 22 37 76 -5 23 38 79 -6 24 39 82 -7 25 41 86 -8 27 42 89 -9 28 43 92 -10 29 45 96 -11 30 46 99 -12 31 47 103 -13 32 48 107 -14 33 50 110 -15 34 51 114 -16 35 52 118 -17 36 53 122 -18 37 55 125 -19 37 56 129 -20 38 57 133 -21 38 59 137 -22 38 60 141 -23 38 62 144 -24 37 64 148 -25 35 66 150 -26 34 68 152 -27 33 70 153 -28 31 72 154 -29 30 75 155 -30 29 77 155 -31 29 79 156 -32 28 81 156 -33 27 83 157 -34 27 86 157 -35 27 88 157 -36 26 90 157 -37 26 92 158 -38 26 94 158 -39 26 96 158 -40 27 98 159 -41 27 100 159 -42 27 102 159 -43 28 104 160 -44 28 106 160 -45 29 108 160 -46 30 110 161 -47 31 112 161 -48 31 114 161 -49 32 116 162 -50 33 118 162 -51 34 120 163 -52 35 121 163 -53 36 123 164 -54 38 125 164 -55 39 127 165 -56 40 129 165 -57 41 131 165 -58 42 133 166 -59 44 135 166 -60 45 137 167 -61 46 139 167 -62 48 141 168 -63 49 142 168 -64 51 144 169 -65 53 146 169 -66 54 148 170 -67 56 150 170 -68 58 152 171 -69 60 154 171 -70 62 156 172 -71 64 157 172 -72 66 159 173 -73 68 161 173 -74 71 163 174 -75 73 165 174 -76 76 167 174 -77 79 168 175 -78 82 170 175 -79 86 172 176 -80 89 173 176 -81 93 175 176 -82 96 177 177 -83 100 178 177 -84 104 180 178 -85 108 181 179 -86 112 183 179 -87 116 184 180 -88 120 185 181 -89 124 187 182 -90 128 188 183 -91 132 190 184 -92 136 191 185 -93 140 193 186 -94 143 194 187 -95 147 196 188 -96 151 197 190 -97 154 198 191 -98 158 200 192 -99 162 201 193 -100 165 203 195 -101 169 204 196 -102 172 206 197 -103 175 208 199 -104 179 209 200 -105 182 211 202 -106 185 212 203 -107 189 214 204 -108 192 215 206 -109 195 217 207 -110 199 219 209 -111 202 220 210 -112 205 222 212 -113 208 224 213 -114 211 226 215 -115 214 227 216 -116 217 229 218 -117 221 231 219 -118 224 233 220 -119 227 235 222 -120 230 237 223 -121 233 238 224 -122 236 240 225 -123 239 242 226 -124 243 244 227 -125 246 246 228 -126 249 248 229 -127 253 250 229 -128 254 252 203 -129 253 249 199 -130 251 247 195 -131 250 244 191 -132 249 242 187 -133 247 240 182 -134 246 237 178 -135 245 235 174 -136 243 233 169 -137 242 230 165 -138 241 228 161 -139 239 226 156 -140 238 223 152 -141 236 221 148 -142 235 219 143 -143 233 217 139 -144 232 215 135 -145 230 213 130 -146 229 210 126 -147 227 208 122 -148 226 206 117 -149 224 204 113 -150 222 202 109 -151 220 201 104 -152 218 199 100 -153 216 197 96 -154 214 195 91 -155 212 193 87 -156 210 192 83 -157 207 190 79 -158 205 188 75 -159 203 187 71 -160 200 185 67 -161 197 184 63 -162 195 182 59 -163 192 181 56 -164 189 179 52 -165 186 178 48 -166 183 177 45 -167 180 175 42 -168 177 174 39 -169 174 173 35 -170 170 172 32 -171 167 170 29 -172 164 169 27 -173 160 168 24 -174 157 167 21 -175 154 166 19 -176 150 164 16 -177 147 163 14 -178 143 162 12 -179 140 161 10 -180 136 160 8 -181 133 158 7 -182 129 157 6 -183 126 156 5 -184 122 155 5 -185 119 153 5 -186 115 152 5 -187 112 151 6 -188 108 150 7 -189 104 149 8 -190 101 147 9 -191 97 146 11 -192 93 145 12 -193 90 143 14 -194 86 142 15 -195 82 141 17 -196 79 139 19 -197 75 138 20 -198 71 137 22 -199 68 135 23 -200 64 134 25 -201 61 132 26 -202 57 131 28 -203 54 129 29 -204 50 128 30 -205 47 126 32 -206 43 125 33 -207 40 123 34 -208 37 122 35 -209 34 120 36 -210 31 118 37 -211 28 117 38 -212 25 115 39 -213 23 113 40 -214 20 112 40 -215 18 110 41 -216 16 108 42 -217 14 106 42 -218 13 105 43 -219 12 103 43 -220 11 101 44 -221 10 99 44 -222 10 97 44 -223 11 95 44 -224 11 94 44 -225 12 92 44 -226 12 90 44 -227 13 88 44 -228 14 86 44 -229 15 84 44 -230 16 82 43 -231 17 80 43 -232 18 78 43 -233 19 76 42 -234 20 75 42 -235 20 73 41 -236 21 71 40 -237 22 69 40 -238 22 67 39 -239 23 65 38 -240 23 63 37 -241 24 61 36 -242 24 59 35 -243 24 57 34 -244 25 55 33 -245 25 53 32 -246 25 52 31 -247 25 50 30 -248 25 48 28 -249 25 46 27 -250 24 44 26 -251 24 42 24 -252 24 40 23 -253 23 38 21 -254 23 36 20 -255 23 35 18 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Dense.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Dense.lut deleted file mode 100644 index 8dc548f29..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Dense.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 54 14 36 -1 55 14 37 -2 56 14 38 -3 57 15 39 -4 59 15 40 -5 60 15 42 -6 61 15 43 -7 62 16 44 -8 64 16 45 -9 65 16 47 -10 66 16 48 -11 67 17 49 -12 68 17 50 -13 69 17 52 -14 71 17 53 -15 72 18 55 -16 73 18 56 -17 74 18 57 -18 75 18 59 -19 76 19 60 -20 77 19 62 -21 78 19 63 -22 79 19 65 -23 80 20 66 -24 81 20 68 -25 82 20 69 -26 83 21 71 -27 84 21 73 -28 85 22 74 -29 86 22 76 -30 87 22 77 -31 88 23 79 -32 89 23 81 -33 90 24 82 -34 91 24 84 -35 92 25 86 -36 92 25 87 -37 93 26 89 -38 94 26 91 -39 95 27 92 -40 96 28 94 -41 96 28 96 -42 97 29 97 -43 98 29 99 -44 99 30 101 -45 99 31 102 -46 100 31 104 -47 101 32 106 -48 101 33 107 -49 102 34 109 -50 103 34 110 -51 103 35 112 -52 104 36 114 -53 105 37 115 -54 105 37 117 -55 106 38 119 -56 106 39 120 -57 107 40 122 -58 107 41 123 -59 108 42 125 -60 108 42 127 -61 109 43 128 -62 109 44 130 -63 110 45 131 -64 110 46 133 -65 111 47 134 -66 111 48 136 -67 112 49 138 -68 112 49 139 -69 113 50 141 -70 113 51 142 -71 113 52 144 -72 114 53 145 -73 114 54 147 -74 115 55 148 -75 115 56 150 -76 115 57 151 -77 116 58 153 -78 116 59 154 -79 116 60 155 -80 116 61 157 -81 117 62 158 -82 117 63 160 -83 117 64 161 -84 118 65 163 -85 118 66 164 -86 118 67 165 -87 118 68 167 -88 119 69 168 -89 119 70 170 -90 119 71 171 -91 119 72 172 -92 119 73 174 -93 119 74 175 -94 120 75 176 -95 120 76 178 -96 120 77 179 -97 120 78 180 -98 120 80 181 -99 120 81 183 -100 120 82 184 -101 120 83 185 -102 120 84 186 -103 121 85 188 -104 121 86 189 -105 121 87 190 -106 121 88 191 -107 121 89 192 -108 121 91 194 -109 121 92 195 -110 121 93 196 -111 121 94 197 -112 121 95 198 -113 121 96 199 -114 121 97 200 -115 120 99 201 -116 120 100 202 -117 120 101 203 -118 120 102 204 -119 120 103 205 -120 120 104 206 -121 120 105 207 -122 120 107 208 -123 120 108 209 -124 119 109 210 -125 119 110 211 -126 119 111 212 -127 119 113 213 -128 119 114 213 -129 119 115 214 -130 118 116 215 -131 118 117 216 -132 118 119 216 -133 118 120 217 -134 118 121 218 -135 118 122 218 -136 117 123 219 -137 117 125 220 -138 117 126 220 -139 117 127 221 -140 117 128 221 -141 116 129 222 -142 116 131 222 -143 116 132 223 -144 116 133 223 -145 116 134 224 -146 116 135 224 -147 115 137 225 -148 115 138 225 -149 115 139 225 -150 115 140 226 -151 115 141 226 -152 115 143 226 -153 115 144 227 -154 115 145 227 -155 115 146 227 -156 115 147 227 -157 115 148 227 -158 115 150 227 -159 115 151 228 -160 115 152 228 -161 115 153 228 -162 115 154 228 -163 116 155 228 -164 116 156 228 -165 116 158 228 -166 116 159 228 -167 117 160 228 -168 117 161 228 -169 117 162 228 -170 118 163 228 -171 118 164 228 -172 119 165 228 -173 119 166 228 -174 120 167 228 -175 120 169 228 -176 121 170 228 -177 122 171 228 -178 122 172 228 -179 123 173 227 -180 124 174 227 -181 124 175 227 -182 125 176 227 -183 126 177 227 -184 127 178 227 -185 128 179 227 -186 129 180 227 -187 130 181 227 -188 131 182 227 -189 132 183 227 -190 133 184 226 -191 134 185 226 -192 135 186 226 -193 136 187 226 -194 137 188 226 -195 138 189 226 -196 139 190 226 -197 140 190 226 -198 141 191 226 -199 143 192 226 -200 144 193 226 -201 145 194 226 -202 146 195 226 -203 148 196 226 -204 149 197 226 -205 150 198 226 -206 152 199 226 -207 153 200 226 -208 154 201 226 -209 156 201 226 -210 157 202 226 -211 159 203 226 -212 160 204 226 -213 161 205 226 -214 163 206 226 -215 164 207 226 -216 166 208 226 -217 167 209 226 -218 169 209 226 -219 170 210 226 -220 172 211 227 -221 173 212 227 -222 175 213 227 -223 176 214 227 -224 178 215 227 -225 179 215 227 -226 181 216 228 -227 183 217 228 -228 184 218 228 -229 186 219 228 -230 187 220 228 -231 189 220 229 -232 191 221 229 -233 192 222 229 -234 194 223 230 -235 196 224 230 -236 197 225 230 -237 199 225 231 -238 201 226 231 -239 202 227 231 -240 204 228 232 -241 206 229 232 -242 207 230 233 -243 209 230 233 -244 211 231 234 -245 212 232 234 -246 214 233 235 -247 216 234 235 -248 217 235 236 -249 219 235 236 -250 221 236 237 -251 223 237 237 -252 224 238 238 -253 226 239 239 -254 228 239 239 -255 230 240 240 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Gray.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Gray.lut deleted file mode 100644 index 685dcfc3a..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Gray.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 0 0 0 -1 0 0 0 -2 0 0 0 -3 0 0 0 -4 0 0 0 -5 1 1 1 -6 1 1 1 -7 2 1 2 -8 2 2 2 -9 3 3 3 -10 3 3 3 -11 4 4 4 -12 5 5 5 -13 6 6 6 -14 7 7 7 -15 8 7 7 -16 9 8 8 -17 10 10 9 -18 11 11 11 -19 12 12 12 -20 13 13 13 -21 14 14 14 -22 15 15 15 -23 16 16 16 -24 17 17 17 -25 18 18 18 -26 19 19 19 -27 20 20 20 -28 21 21 21 -29 22 22 22 -30 23 23 23 -31 24 24 24 -32 25 25 25 -33 26 26 26 -34 27 27 27 -35 28 28 28 -36 29 29 28 -37 30 30 29 -38 31 30 30 -39 32 31 31 -40 33 32 32 -41 34 33 33 -42 35 34 34 -43 36 35 35 -44 37 36 36 -45 37 37 37 -46 38 38 38 -47 39 39 39 -48 40 40 40 -49 41 41 41 -50 42 42 41 -51 43 43 42 -52 44 43 43 -53 45 44 44 -54 46 45 45 -55 47 46 46 -56 48 47 47 -57 48 48 48 -58 49 49 49 -59 50 50 50 -60 51 51 51 -61 52 52 51 -62 53 52 52 -63 54 53 53 -64 55 54 54 -65 56 55 55 -66 57 56 56 -67 57 57 57 -68 58 58 58 -69 59 59 59 -70 60 60 59 -71 61 61 60 -72 62 61 61 -73 63 62 62 -74 64 63 63 -75 65 64 64 -76 66 65 65 -77 66 66 66 -78 67 67 67 -79 68 68 67 -80 69 69 68 -81 70 70 69 -82 71 70 70 -83 72 71 71 -84 73 72 72 -85 74 73 73 -86 75 74 74 -87 75 75 75 -88 76 76 76 -89 77 77 76 -90 78 78 77 -91 79 79 78 -92 80 79 79 -93 81 80 80 -94 82 81 81 -95 83 82 82 -96 84 83 83 -97 84 84 84 -98 85 85 84 -99 86 86 85 -100 87 87 86 -101 88 88 87 -102 89 89 88 -103 90 89 89 -104 91 90 90 -105 92 91 91 -106 93 92 92 -107 94 93 93 -108 94 94 94 -109 95 95 94 -110 96 96 95 -111 97 97 96 -112 98 98 97 -113 99 99 98 -114 100 100 99 -115 101 100 100 -116 102 101 101 -117 103 102 102 -118 104 103 103 -119 105 104 104 -120 106 105 105 -121 106 106 106 -122 107 107 106 -123 108 108 107 -124 109 109 108 -125 110 110 109 -126 111 111 110 -127 112 112 111 -128 113 113 112 -129 114 114 113 -130 115 114 114 -131 116 115 115 -132 117 116 116 -133 118 117 117 -134 119 118 118 -135 120 119 119 -136 121 120 120 -137 122 121 121 -138 123 122 122 -139 124 123 122 -140 125 124 123 -141 125 125 124 -142 126 126 125 -143 127 127 126 -144 128 128 127 -145 129 129 128 -146 130 130 129 -147 131 131 130 -148 132 132 131 -149 133 133 132 -150 134 134 133 -151 135 135 134 -152 136 136 135 -153 137 137 136 -154 138 138 137 -155 139 139 138 -156 140 140 139 -157 141 141 140 -158 142 142 141 -159 143 143 142 -160 144 144 143 -161 145 145 144 -162 146 146 145 -163 147 147 146 -164 148 148 147 -165 149 149 148 -166 150 150 149 -167 151 151 150 -168 152 152 151 -169 153 153 152 -170 154 154 153 -171 155 155 154 -172 156 156 155 -173 157 157 156 -174 158 158 157 -175 160 159 158 -176 161 160 159 -177 162 161 160 -178 163 162 161 -179 164 163 163 -180 165 164 164 -181 166 165 165 -182 167 167 166 -183 168 168 167 -184 169 169 168 -185 170 170 169 -186 171 171 170 -187 172 172 171 -188 173 173 172 -189 174 174 173 -190 176 175 174 -191 177 176 175 -192 178 177 176 -193 179 178 178 -194 180 180 179 -195 181 181 180 -196 182 182 181 -197 183 183 182 -198 184 184 183 -199 185 185 184 -200 187 186 185 -201 188 187 186 -202 189 189 188 -203 190 190 189 -204 191 191 190 -205 192 192 191 -206 193 193 192 -207 194 194 193 -208 196 195 194 -209 197 197 195 -210 198 198 197 -211 199 199 198 -212 200 200 199 -213 201 201 200 -214 203 202 201 -215 204 204 202 -216 205 205 204 -217 206 206 205 -218 207 207 206 -219 209 208 207 -220 210 209 208 -221 211 211 210 -222 212 212 211 -223 213 213 212 -224 215 214 213 -225 216 216 214 -226 217 217 216 -227 218 218 217 -228 219 219 218 -229 221 220 219 -230 222 222 220 -231 223 223 222 -232 224 224 223 -233 226 225 224 -234 227 227 225 -235 228 228 227 -236 229 229 228 -237 231 230 229 -238 232 232 230 -239 233 233 232 -240 235 234 233 -241 236 236 234 -242 237 237 236 -243 238 238 237 -244 240 239 238 -245 241 241 240 -246 242 242 241 -247 244 243 242 -248 245 245 243 -249 246 246 245 -250 248 247 246 -251 249 249 247 -252 250 250 249 -253 252 251 250 -254 253 253 251 -255 254 254 253 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Haline.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Haline.lut deleted file mode 100644 index 1d97c65df..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Haline.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 41 24 107 -1 42 24 110 -2 42 24 112 -3 42 25 114 -4 43 25 117 -5 43 25 119 -6 44 25 122 -7 44 26 124 -8 44 26 127 -9 45 26 129 -10 45 26 132 -11 45 27 134 -12 45 27 137 -13 46 27 139 -14 46 28 142 -15 46 28 144 -16 46 29 147 -17 46 29 149 -18 45 30 151 -19 45 31 153 -20 45 32 155 -21 44 33 157 -22 43 34 159 -23 42 35 160 -24 41 37 161 -25 40 39 162 -26 39 40 162 -27 38 42 162 -28 37 44 162 -29 35 46 162 -30 34 48 162 -31 33 49 162 -32 31 51 161 -33 30 53 161 -34 29 55 160 -35 27 56 160 -36 26 58 159 -37 25 59 159 -38 24 61 158 -39 22 62 157 -40 21 64 157 -41 20 65 156 -42 19 66 155 -43 18 68 155 -44 17 69 154 -45 16 70 153 -46 15 71 153 -47 15 73 152 -48 14 74 152 -49 13 75 151 -50 13 76 150 -51 12 77 150 -52 12 78 149 -53 12 79 149 -54 12 81 148 -55 12 82 148 -56 12 83 147 -57 12 84 147 -58 12 85 146 -59 12 86 146 -60 13 87 145 -61 13 88 145 -62 14 89 144 -63 14 90 144 -64 15 91 144 -65 15 92 143 -66 16 93 143 -67 17 93 143 -68 18 94 142 -69 18 95 142 -70 19 96 142 -71 20 97 141 -72 21 98 141 -73 21 99 141 -74 22 100 140 -75 23 101 140 -76 24 102 140 -77 25 102 140 -78 26 103 139 -79 26 104 139 -80 27 105 139 -81 28 106 139 -82 29 107 139 -83 30 108 139 -84 31 109 138 -85 31 109 138 -86 32 110 138 -87 33 111 138 -88 34 112 138 -89 35 113 138 -90 36 114 137 -91 36 115 137 -92 37 115 137 -93 38 116 137 -94 39 117 137 -95 39 118 137 -96 40 119 137 -97 41 120 137 -98 42 121 137 -99 42 121 137 -100 43 122 137 -101 44 123 136 -102 44 124 136 -103 45 125 136 -104 46 126 136 -105 46 127 136 -106 47 127 136 -107 48 128 136 -108 48 129 136 -109 49 130 136 -110 49 131 136 -111 50 132 136 -112 51 133 136 -113 51 133 136 -114 52 134 136 -115 52 135 136 -116 53 136 136 -117 54 137 136 -118 54 138 135 -119 55 139 135 -120 55 140 135 -121 56 140 135 -122 56 141 135 -123 57 142 135 -124 57 143 135 -125 58 144 135 -126 58 145 135 -127 59 146 135 -128 59 147 135 -129 60 148 134 -130 60 148 134 -131 61 149 134 -132 61 150 134 -133 62 151 134 -134 62 152 134 -135 63 153 134 -136 64 154 133 -137 64 155 133 -138 65 156 133 -139 65 157 133 -140 66 158 132 -141 66 158 132 -142 67 159 132 -143 67 160 132 -144 68 161 131 -145 69 162 131 -146 69 163 131 -147 70 164 131 -148 70 165 130 -149 71 166 130 -150 72 167 130 -151 72 168 129 -152 73 169 129 -153 74 169 128 -154 74 170 128 -155 75 171 128 -156 76 172 127 -157 77 173 127 -158 78 174 126 -159 78 175 126 -160 79 176 125 -161 80 177 125 -162 81 178 124 -163 82 179 124 -164 83 179 123 -165 84 180 123 -166 85 181 122 -167 86 182 121 -168 87 183 121 -169 88 184 120 -170 89 185 120 -171 90 186 119 -172 91 187 118 -173 92 187 118 -174 94 188 117 -175 95 189 116 -176 96 190 115 -177 98 191 115 -178 99 192 114 -179 100 193 113 -180 102 193 112 -181 103 194 112 -182 105 195 111 -183 106 196 110 -184 108 197 109 -185 109 198 108 -186 111 198 107 -187 113 199 107 -188 114 200 106 -189 116 201 105 -190 118 201 104 -191 120 202 103 -192 122 203 102 -193 124 204 101 -194 126 204 100 -195 128 205 100 -196 130 206 99 -197 132 206 98 -198 134 207 97 -199 136 208 96 -200 138 208 96 -201 141 209 95 -202 143 210 94 -203 145 210 93 -204 148 211 93 -205 150 211 92 -206 152 212 92 -207 155 213 92 -208 157 213 91 -209 160 214 91 -210 162 214 91 -211 165 215 91 -212 167 215 91 -213 170 216 92 -214 172 216 92 -215 175 216 92 -216 177 217 93 -217 179 217 94 -218 182 218 94 -219 184 218 95 -220 186 219 96 -221 189 219 97 -222 191 220 98 -223 193 220 99 -224 195 221 100 -225 197 221 102 -226 199 222 103 -227 202 222 104 -228 204 223 106 -229 206 223 107 -230 208 224 109 -231 210 224 110 -232 212 225 112 -233 214 225 113 -234 216 226 115 -235 217 226 117 -236 219 227 118 -237 221 228 120 -238 223 228 122 -239 225 229 123 -240 227 229 125 -241 229 230 127 -242 230 230 129 -243 232 231 131 -244 234 232 132 -245 236 232 134 -246 238 233 136 -247 239 233 138 -248 241 234 140 -249 243 235 142 -250 245 235 144 -251 246 236 145 -252 248 236 147 -253 250 237 149 -254 251 238 151 -255 253 238 153 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Ice.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Ice.lut deleted file mode 100644 index a59bf0cc9..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Ice.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 3 5 18 -1 4 6 19 -2 5 7 21 -3 6 8 22 -4 6 8 24 -5 7 9 25 -6 8 10 27 -7 9 11 28 -8 10 12 30 -9 11 13 31 -10 12 14 32 -11 13 15 34 -12 14 16 35 -13 15 16 37 -14 16 17 38 -15 17 18 40 -16 18 19 41 -17 19 20 42 -18 20 21 44 -19 21 21 45 -20 22 22 47 -21 23 23 48 -22 24 24 50 -23 25 25 51 -24 26 25 53 -25 27 26 54 -26 27 27 56 -27 28 28 57 -28 29 29 59 -29 30 29 60 -30 31 30 62 -31 32 31 63 -32 33 32 65 -33 34 32 66 -34 34 33 68 -35 35 34 69 -36 36 35 71 -37 37 35 72 -38 38 36 74 -39 39 37 75 -40 39 38 77 -41 40 38 79 -42 41 39 80 -43 42 40 82 -44 42 41 83 -45 43 41 85 -46 44 42 87 -47 45 43 88 -48 45 44 90 -49 46 45 91 -50 47 45 93 -51 48 46 95 -52 48 47 96 -53 49 48 98 -54 50 48 100 -55 50 49 101 -56 51 50 103 -57 52 51 105 -58 52 51 106 -59 53 52 108 -60 53 53 110 -61 54 54 111 -62 55 55 113 -63 55 55 115 -64 56 56 116 -65 56 57 118 -66 57 58 120 -67 57 59 121 -68 58 59 123 -69 58 60 125 -70 58 61 126 -71 59 62 128 -72 59 63 130 -73 60 64 131 -74 60 65 133 -75 60 65 134 -76 60 66 136 -77 61 67 138 -78 61 68 139 -79 61 69 141 -80 61 70 142 -81 62 71 144 -82 62 72 145 -83 62 73 147 -84 62 74 148 -85 62 75 150 -86 62 76 151 -87 62 77 152 -88 62 78 154 -89 62 79 155 -90 62 80 156 -91 62 81 157 -92 62 82 159 -93 62 83 160 -94 62 84 161 -95 62 85 162 -96 62 87 163 -97 62 88 164 -98 62 89 165 -99 62 90 166 -100 62 91 167 -101 62 92 168 -102 62 93 168 -103 62 94 169 -104 62 96 170 -105 62 97 171 -106 62 98 172 -107 62 99 172 -108 62 100 173 -109 62 101 174 -110 62 102 174 -111 62 103 175 -112 62 105 175 -113 62 106 176 -114 62 107 177 -115 62 108 177 -116 62 109 178 -117 63 110 178 -118 63 111 179 -119 63 112 179 -120 63 114 180 -121 63 115 180 -122 64 116 180 -123 64 117 181 -124 64 118 181 -125 65 119 182 -126 65 120 182 -127 65 121 183 -128 66 122 183 -129 66 123 183 -130 67 125 184 -131 67 126 184 -132 68 127 185 -133 68 128 185 -134 69 129 185 -135 69 130 186 -136 70 131 186 -137 70 132 186 -138 71 133 187 -139 72 134 187 -140 72 135 188 -141 73 136 188 -142 74 137 188 -143 74 139 189 -144 75 140 189 -145 76 141 189 -146 76 142 190 -147 77 143 190 -148 78 144 190 -149 79 145 191 -150 79 146 191 -151 80 147 192 -152 81 148 192 -153 82 149 192 -154 82 150 193 -155 83 151 193 -156 84 152 193 -157 85 153 194 -158 86 154 194 -159 87 156 195 -160 88 157 195 -161 88 158 195 -162 89 159 196 -163 90 160 196 -164 91 161 197 -165 92 162 197 -166 93 163 197 -167 94 164 198 -168 95 165 198 -169 96 166 199 -170 97 167 199 -171 98 168 199 -172 99 169 200 -173 100 170 200 -174 101 171 201 -175 102 172 201 -176 103 174 201 -177 104 175 202 -178 105 176 202 -179 106 177 203 -180 107 178 203 -181 109 179 203 -182 110 180 204 -183 111 181 204 -184 112 182 205 -185 113 183 205 -186 114 184 205 -187 116 185 206 -188 117 186 206 -189 118 187 207 -190 119 188 207 -191 121 189 208 -192 122 190 208 -193 123 191 208 -194 125 192 209 -195 126 193 209 -196 128 194 210 -197 129 195 210 -198 131 196 211 -199 132 198 211 -200 134 199 211 -201 135 200 212 -202 137 201 212 -203 138 202 213 -204 140 203 213 -205 142 203 214 -206 143 204 214 -207 145 205 215 -208 147 206 215 -209 149 207 216 -210 150 208 216 -211 152 209 217 -212 154 210 217 -213 156 211 218 -214 158 212 219 -215 160 213 219 -216 161 214 220 -217 163 215 220 -218 165 216 221 -219 167 217 222 -220 169 218 222 -221 171 219 223 -222 173 220 224 -223 175 221 225 -224 176 221 225 -225 178 222 226 -226 180 223 227 -227 182 224 228 -228 184 225 228 -229 186 226 229 -230 188 227 230 -231 190 228 231 -232 192 229 232 -233 193 230 233 -234 195 231 234 -235 197 232 234 -236 199 233 235 -237 201 234 236 -238 203 235 237 -239 205 236 238 -240 207 237 239 -241 208 238 240 -242 210 239 241 -243 212 240 242 -244 214 241 243 -245 216 242 244 -246 218 243 245 -247 219 244 245 -248 221 245 246 -249 223 246 247 -250 225 247 248 -251 227 248 249 -252 228 249 250 -253 230 250 251 -254 232 251 252 -255 234 252 253 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Matter.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Matter.lut deleted file mode 100644 index 826cb16c9..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Matter.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 47 15 61 -1 48 15 62 -2 49 15 63 -3 50 16 64 -4 52 16 65 -5 53 16 66 -6 54 17 67 -7 56 17 68 -8 57 17 68 -9 58 18 69 -10 59 18 70 -11 61 18 71 -12 62 18 72 -13 63 19 73 -14 64 19 73 -15 66 19 74 -16 67 19 75 -17 68 20 76 -18 70 20 76 -19 71 20 77 -20 72 20 78 -21 74 21 79 -22 75 21 79 -23 76 21 80 -24 78 21 81 -25 79 21 82 -26 80 22 82 -27 82 22 83 -28 83 22 84 -29 84 22 84 -30 85 22 85 -31 87 22 86 -32 88 23 86 -33 89 23 87 -34 91 23 87 -35 92 23 88 -36 94 23 89 -37 95 23 89 -38 96 23 90 -39 98 23 90 -40 99 24 91 -41 100 24 91 -42 102 24 92 -43 103 24 92 -44 104 24 93 -45 106 24 93 -46 107 24 93 -47 108 25 94 -48 110 25 94 -49 111 25 95 -50 112 25 95 -51 114 25 95 -52 115 25 96 -53 116 25 96 -54 118 26 96 -55 119 26 97 -56 120 26 97 -57 122 26 97 -58 123 26 97 -59 125 26 98 -60 126 27 98 -61 127 27 98 -62 129 27 98 -63 130 27 98 -64 131 28 98 -65 133 28 99 -66 134 28 99 -67 135 28 99 -68 136 29 99 -69 138 29 99 -70 139 29 99 -71 140 30 99 -72 142 30 99 -73 143 30 99 -74 144 31 99 -75 146 31 99 -76 147 31 99 -77 148 32 99 -78 149 32 99 -79 151 33 99 -80 152 33 99 -81 153 33 98 -82 155 34 98 -83 156 34 98 -84 157 35 98 -85 158 35 98 -86 160 36 98 -87 161 36 98 -88 162 37 97 -89 163 37 97 -90 165 38 97 -91 166 39 97 -92 167 39 97 -93 168 40 96 -94 169 40 96 -95 171 41 96 -96 172 42 96 -97 173 42 95 -98 174 43 95 -99 175 44 95 -100 177 44 95 -101 178 45 94 -102 179 46 94 -103 180 46 94 -104 181 47 93 -105 182 48 93 -106 184 48 93 -107 185 49 93 -108 186 50 92 -109 187 51 92 -110 188 51 92 -111 189 52 91 -112 190 53 91 -113 191 54 91 -114 192 55 90 -115 193 56 90 -116 195 56 90 -117 196 57 89 -118 197 58 89 -119 198 59 89 -120 199 60 88 -121 200 61 88 -122 201 62 88 -123 202 63 87 -124 203 64 87 -125 204 65 87 -126 205 66 86 -127 206 67 86 -128 207 68 86 -129 208 69 85 -130 208 70 85 -131 209 71 85 -132 210 72 84 -133 211 73 84 -134 212 74 84 -135 213 75 84 -136 214 76 83 -137 215 78 83 -138 215 79 83 -139 216 80 83 -140 217 81 83 -141 218 82 83 -142 218 83 82 -143 219 85 82 -144 220 86 82 -145 221 87 82 -146 221 88 82 -147 222 89 82 -148 223 91 82 -149 223 92 82 -150 224 93 82 -151 225 95 82 -152 225 96 82 -153 226 97 82 -154 226 98 82 -155 227 100 83 -156 227 101 83 -157 228 102 83 -158 229 104 83 -159 229 105 83 -160 230 106 84 -161 230 108 84 -162 231 109 84 -163 231 110 85 -164 232 112 85 -165 232 113 85 -166 232 114 86 -167 233 116 86 -168 233 117 87 -169 234 118 87 -170 234 120 88 -171 235 121 88 -172 235 123 89 -173 235 124 89 -174 236 125 90 -175 236 127 91 -176 237 128 91 -177 237 129 92 -178 237 131 92 -179 238 132 93 -180 238 133 94 -181 238 135 94 -182 239 136 95 -183 239 138 96 -184 239 139 97 -185 240 140 97 -186 240 142 98 -187 240 143 99 -188 241 144 100 -189 241 146 101 -190 241 147 101 -191 241 148 102 -192 242 150 103 -193 242 151 104 -194 242 153 105 -195 243 154 106 -196 243 155 107 -197 243 157 108 -198 243 158 108 -199 244 159 109 -200 244 161 110 -201 244 162 111 -202 244 163 112 -203 245 165 113 -204 245 166 114 -205 245 168 115 -206 245 169 116 -207 245 170 117 -208 246 172 118 -209 246 173 119 -210 246 174 120 -211 246 176 121 -212 247 177 122 -213 247 178 124 -214 247 180 125 -215 247 181 126 -216 247 183 127 -217 247 184 128 -218 248 185 129 -219 248 187 130 -220 248 188 131 -221 248 189 132 -222 248 191 134 -223 249 192 135 -224 249 194 136 -225 249 195 137 -226 249 196 138 -227 249 198 139 -228 249 199 141 -229 250 200 142 -230 250 202 143 -231 250 203 144 -232 250 205 145 -233 250 206 147 -234 250 207 148 -235 250 209 149 -236 251 210 150 -237 251 212 152 -238 251 213 153 -239 251 214 154 -240 251 216 156 -241 251 217 157 -242 251 219 158 -243 252 220 159 -244 252 221 161 -245 252 223 162 -246 252 224 163 -247 252 226 165 -248 252 227 166 -249 252 228 167 -250 252 230 169 -251 253 231 170 -252 253 233 172 -253 253 234 173 -254 253 235 174 -255 253 237 176 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Oxy.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Oxy.lut deleted file mode 100644 index cabc53237..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Oxy.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 63 5 5 -1 65 5 5 -2 67 5 5 -3 68 5 6 -4 70 6 6 -5 72 6 7 -6 73 6 7 -7 75 6 7 -8 77 6 8 -9 78 6 8 -10 80 6 9 -11 82 6 9 -12 83 7 10 -13 85 7 10 -14 87 7 11 -15 89 7 11 -16 90 7 11 -17 92 7 12 -18 94 7 12 -19 95 7 12 -20 97 7 13 -21 99 6 13 -22 101 6 13 -23 102 6 14 -24 104 6 14 -25 106 6 14 -26 107 6 14 -27 109 6 14 -28 111 5 15 -29 113 5 15 -30 114 5 15 -31 116 5 15 -32 118 5 15 -33 119 4 14 -34 121 4 14 -35 123 4 14 -36 124 5 14 -37 126 5 13 -38 128 6 13 -39 129 6 12 -40 131 7 12 -41 132 9 11 -42 133 10 10 -43 135 12 10 -44 136 14 9 -45 137 16 9 -46 138 17 8 -47 139 19 8 -48 141 21 8 -49 142 23 7 -50 143 24 7 -51 92 68 64 -52 80 79 79 -53 81 80 80 -54 82 81 81 -55 83 82 82 -56 84 83 83 -57 85 84 84 -58 86 85 85 -59 86 86 86 -60 87 87 87 -61 88 88 87 -62 89 89 88 -63 90 90 89 -64 91 91 90 -65 92 92 91 -66 93 92 92 -67 94 93 93 -68 95 94 94 -69 96 95 95 -70 97 96 96 -71 98 97 97 -72 99 98 98 -73 100 99 99 -74 101 100 100 -75 102 101 101 -76 102 102 102 -77 103 103 102 -78 104 104 103 -79 105 105 104 -80 106 106 105 -81 107 107 106 -82 108 108 107 -83 109 109 108 -84 110 110 109 -85 111 111 110 -86 112 112 111 -87 113 113 112 -88 114 114 113 -89 115 114 114 -90 116 115 115 -91 117 116 116 -92 118 117 117 -93 119 118 118 -94 120 119 119 -95 121 120 120 -96 122 121 121 -97 123 122 122 -98 124 123 123 -99 125 124 124 -100 126 125 125 -101 127 126 126 -102 128 127 127 -103 129 128 128 -104 130 129 129 -105 131 130 130 -106 132 131 131 -107 133 132 132 -108 134 133 133 -109 135 134 134 -110 136 135 135 -111 137 136 136 -112 138 137 137 -113 139 138 138 -114 140 139 139 -115 141 140 140 -116 142 141 141 -117 143 143 142 -118 144 144 143 -119 145 145 144 -120 146 146 145 -121 147 147 146 -122 148 148 147 -123 149 149 148 -124 150 150 149 -125 151 151 150 -126 152 152 151 -127 153 153 152 -128 154 154 153 -129 156 155 154 -130 157 156 155 -131 158 157 156 -132 159 158 157 -133 160 159 159 -134 161 160 160 -135 162 162 161 -136 163 163 162 -137 164 164 163 -138 165 165 164 -139 166 166 165 -140 167 167 166 -141 168 168 167 -142 170 169 168 -143 171 170 169 -144 172 171 171 -145 173 173 172 -146 174 174 173 -147 175 175 174 -148 176 176 175 -149 177 177 176 -150 178 178 177 -151 180 179 178 -152 181 180 179 -153 182 182 181 -154 183 183 182 -155 184 184 183 -156 185 185 184 -157 186 186 185 -158 188 187 186 -159 189 188 187 -160 190 190 189 -161 191 191 190 -162 192 192 191 -163 193 193 192 -164 195 194 193 -165 196 195 194 -166 197 197 196 -167 198 198 197 -168 199 199 198 -169 201 200 199 -170 202 201 200 -171 203 203 202 -172 204 204 203 -173 205 205 204 -174 207 206 205 -175 208 208 206 -176 209 209 208 -177 210 210 209 -178 211 211 210 -179 213 212 211 -180 214 214 213 -181 215 215 214 -182 216 216 215 -183 218 217 216 -184 219 219 218 -185 220 220 219 -186 222 221 220 -187 223 223 221 -188 224 224 223 -189 225 225 224 -190 227 226 225 -191 228 228 226 -192 229 229 228 -193 231 230 229 -194 232 232 230 -195 233 233 232 -196 234 234 233 -197 236 236 234 -198 237 237 236 -199 238 238 237 -200 240 240 238 -201 241 241 240 -202 242 242 241 -203 244 244 242 -204 245 247 216 -205 247 253 103 -206 245 252 100 -207 243 251 97 -208 242 250 94 -209 240 249 91 -210 238 247 88 -211 237 246 84 -212 236 245 80 -213 235 243 76 -214 234 242 73 -215 234 240 69 -216 234 239 66 -217 233 237 63 -218 233 235 61 -219 233 233 59 -220 233 232 57 -221 233 230 55 -222 233 228 53 -223 233 226 52 -224 232 225 51 -225 232 223 49 -226 232 221 48 -227 232 220 47 -228 231 218 46 -229 231 216 45 -230 231 214 44 -231 230 213 43 -232 230 211 42 -233 230 209 41 -234 229 208 40 -235 229 206 39 -236 229 205 38 -237 228 203 38 -238 228 201 37 -239 227 200 36 -240 227 198 35 -241 227 196 34 -242 226 195 34 -243 226 193 33 -244 225 192 32 -245 225 190 32 -246 224 188 31 -247 224 187 30 -248 223 185 30 -249 223 184 29 -250 223 182 28 -251 222 181 28 -252 222 179 27 -253 221 178 26 -254 221 176 26 -255 220 174 25 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Phase.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Phase.lut deleted file mode 100644 index 39ad52cd3..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Phase.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 167 119 12 -1 169 118 14 -2 170 117 16 -3 172 116 18 -4 173 115 20 -5 175 115 22 -6 176 114 23 -7 178 113 25 -8 179 112 27 -9 180 111 28 -10 182 110 30 -11 183 109 31 -12 184 107 33 -13 186 106 35 -14 187 105 36 -15 188 104 38 -16 189 103 39 -17 190 102 41 -18 192 101 43 -19 193 100 44 -20 194 99 46 -21 195 98 47 -22 196 97 49 -23 197 96 51 -24 198 95 52 -25 199 93 54 -26 200 92 56 -27 201 91 58 -28 202 90 59 -29 203 89 61 -30 204 88 63 -31 205 86 65 -32 206 85 67 -33 207 84 68 -34 208 83 70 -35 209 81 72 -36 210 80 74 -37 211 79 76 -38 211 78 78 -39 212 76 80 -40 213 75 83 -41 214 74 85 -42 214 72 87 -43 215 71 89 -44 216 69 91 -45 216 68 94 -46 217 67 96 -47 218 65 99 -48 218 64 101 -49 219 62 104 -50 219 61 106 -51 220 59 109 -52 220 58 112 -53 220 57 114 -54 221 55 117 -55 221 54 120 -56 221 52 123 -57 222 51 126 -58 222 49 129 -59 222 48 132 -60 222 47 135 -61 222 45 138 -62 222 44 141 -63 222 43 144 -64 222 42 147 -65 222 41 150 -66 222 40 153 -67 221 39 156 -68 221 38 160 -69 221 38 163 -70 220 37 166 -71 220 37 169 -72 219 37 172 -73 219 37 175 -74 218 37 178 -75 217 37 181 -76 216 38 184 -77 216 38 187 -78 215 39 190 -79 214 40 193 -80 213 41 195 -81 212 42 198 -82 211 43 201 -83 210 44 203 -84 209 45 206 -85 207 47 208 -86 206 48 210 -87 205 50 212 -88 204 51 215 -89 202 53 217 -90 201 54 219 -91 199 56 220 -92 198 58 222 -93 196 60 224 -94 195 61 226 -95 193 63 227 -96 192 65 229 -97 190 66 230 -98 188 68 232 -99 187 70 233 -100 185 71 234 -101 183 73 235 -102 181 75 236 -103 180 77 237 -104 178 78 238 -105 176 80 239 -106 174 82 240 -107 172 83 240 -108 170 85 241 -109 168 86 242 -110 166 88 242 -111 164 90 242 -112 162 91 243 -113 160 93 243 -114 158 94 243 -115 156 96 243 -116 153 97 244 -117 151 99 244 -118 149 100 244 -119 147 102 243 -120 144 103 243 -121 142 105 243 -122 140 106 243 -123 137 107 242 -124 135 109 242 -125 132 110 242 -126 130 112 241 -127 127 113 240 -128 125 114 240 -129 122 115 239 -130 120 117 238 -131 117 118 237 -132 115 119 236 -133 112 120 235 -134 109 122 234 -135 106 123 233 -136 104 124 232 -137 101 125 230 -138 98 126 229 -139 95 127 228 -140 93 128 226 -141 90 129 225 -142 87 130 223 -143 84 131 221 -144 82 132 220 -145 79 133 218 -146 76 134 216 -147 73 135 214 -148 71 136 212 -149 68 137 210 -150 66 137 208 -151 63 138 206 -152 61 139 204 -153 58 139 202 -154 56 140 200 -155 53 141 198 -156 51 141 196 -157 49 142 194 -158 47 142 192 -159 45 143 190 -160 43 143 188 -161 42 144 185 -162 40 144 183 -163 38 144 181 -164 37 145 179 -165 35 145 177 -166 34 146 175 -167 33 146 173 -168 32 146 171 -169 31 147 169 -170 30 147 167 -171 29 147 165 -172 28 147 163 -173 27 148 161 -174 26 148 159 -175 25 148 157 -176 24 148 156 -177 23 149 154 -178 22 149 152 -179 22 149 150 -180 21 149 148 -181 20 150 146 -182 19 150 144 -183 18 150 142 -184 17 150 140 -185 16 151 138 -186 15 151 136 -187 15 151 134 -188 14 151 132 -189 13 151 130 -190 12 152 128 -191 12 152 125 -192 11 152 123 -193 11 152 121 -194 11 152 119 -195 11 153 116 -196 11 153 114 -197 12 153 112 -198 13 153 109 -199 15 153 107 -200 16 153 104 -201 18 153 102 -202 20 153 99 -203 22 153 96 -204 25 154 93 -205 27 154 91 -206 30 154 88 -207 33 154 85 -208 36 153 82 -209 39 153 79 -210 42 153 76 -211 45 153 73 -212 49 153 69 -213 52 153 66 -214 56 152 63 -215 59 152 60 -216 63 152 56 -217 67 151 53 -218 71 151 50 -219 74 150 46 -220 78 150 43 -221 82 149 40 -222 86 149 37 -223 90 148 34 -224 93 147 32 -225 97 146 29 -226 100 146 27 -227 104 145 25 -228 107 144 23 -229 110 143 21 -230 113 142 19 -231 116 141 18 -232 119 141 17 -233 121 140 16 -234 124 139 15 -235 127 138 15 -236 129 137 14 -237 131 136 14 -238 134 135 13 -239 136 135 13 -240 138 134 13 -241 140 133 13 -242 142 132 13 -243 145 131 13 -244 147 130 13 -245 149 129 13 -246 151 128 13 -247 153 127 13 -248 154 126 13 -249 156 125 13 -250 158 124 13 -251 160 123 13 -252 162 122 13 -253 164 121 13 -254 166 120 12 -255 167 119 12 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Solar.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Solar.lut deleted file mode 100644 index 1a4b19b3d..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Solar.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 51 19 23 -1 52 20 24 -2 53 20 24 -3 55 21 25 -4 56 21 25 -5 57 21 26 -6 58 22 26 -7 59 22 27 -8 61 23 27 -9 62 23 28 -10 63 23 28 -11 64 24 28 -12 66 24 29 -13 67 25 29 -14 68 25 30 -15 69 25 30 -16 71 26 30 -17 72 26 31 -18 73 27 31 -19 74 27 31 -20 76 27 32 -21 77 28 32 -22 78 28 32 -23 79 28 33 -24 81 29 33 -25 82 29 33 -26 83 29 34 -27 84 30 34 -28 86 30 34 -29 87 30 34 -30 88 31 35 -31 89 31 35 -32 91 31 35 -33 92 32 35 -34 93 32 35 -35 94 32 35 -36 96 33 36 -37 97 33 36 -38 98 33 36 -39 99 34 36 -40 101 34 36 -41 102 34 36 -42 103 35 36 -43 104 35 36 -44 106 36 36 -45 107 36 36 -46 108 36 36 -47 109 37 36 -48 111 37 36 -49 112 37 36 -50 113 38 36 -51 114 38 36 -52 116 38 36 -53 117 39 36 -54 118 39 36 -55 119 40 36 -56 120 40 35 -57 122 41 35 -58 123 41 35 -59 124 42 35 -60 125 42 35 -61 126 43 34 -62 127 43 34 -63 129 44 34 -64 130 44 34 -65 131 45 33 -66 132 45 33 -67 133 46 33 -68 134 46 33 -69 135 47 32 -70 136 48 32 -71 137 48 32 -72 138 49 31 -73 139 50 31 -74 140 50 31 -75 141 51 31 -76 142 52 30 -77 143 52 30 -78 144 53 30 -79 145 54 29 -80 146 55 29 -81 147 56 29 -82 148 56 28 -83 149 57 28 -84 150 58 28 -85 151 59 27 -86 151 60 27 -87 152 60 27 -88 153 61 27 -89 154 62 26 -90 155 63 26 -91 156 64 26 -92 156 65 25 -93 157 66 25 -94 158 67 25 -95 159 67 25 -96 160 68 24 -97 160 69 24 -98 161 70 24 -99 162 71 23 -100 163 72 23 -101 164 73 23 -102 164 74 23 -103 165 75 23 -104 166 76 22 -105 166 77 22 -106 167 78 22 -107 168 79 22 -108 169 80 21 -109 169 81 21 -110 170 82 21 -111 171 83 21 -112 171 84 21 -113 172 85 20 -114 173 86 20 -115 173 87 20 -116 174 88 20 -117 175 89 20 -118 175 90 20 -119 176 91 20 -120 177 92 19 -121 177 93 19 -122 178 94 19 -123 179 95 19 -124 179 96 19 -125 180 97 19 -126 180 98 19 -127 181 99 19 -128 182 100 19 -129 182 101 19 -130 183 102 19 -131 183 103 18 -132 184 104 18 -133 185 105 18 -134 185 106 18 -135 186 107 18 -136 186 108 18 -137 187 109 18 -138 187 110 18 -139 188 111 19 -140 188 113 19 -141 189 114 19 -142 190 115 19 -143 190 116 19 -144 191 117 19 -145 191 118 19 -146 192 119 19 -147 192 120 19 -148 193 121 19 -149 193 122 20 -150 194 123 20 -151 194 124 20 -152 195 126 20 -153 195 127 20 -154 196 128 20 -155 196 129 21 -156 197 130 21 -157 197 131 21 -158 198 132 21 -159 198 133 22 -160 199 134 22 -161 199 135 22 -162 199 137 22 -163 200 138 23 -164 200 139 23 -165 201 140 23 -166 201 141 24 -167 202 142 24 -168 202 143 24 -169 203 144 25 -170 203 146 25 -171 203 147 25 -172 204 148 26 -173 204 149 26 -174 205 150 27 -175 205 151 27 -176 205 153 27 -177 206 154 28 -178 206 155 28 -179 207 156 29 -180 207 157 29 -181 207 158 30 -182 208 159 30 -183 208 161 31 -184 209 162 31 -185 209 163 32 -186 209 164 32 -187 210 165 32 -188 210 167 33 -189 210 168 33 -190 211 169 34 -191 211 170 34 -192 211 171 35 -193 212 173 36 -194 212 174 36 -195 212 175 37 -196 213 176 37 -197 213 177 38 -198 213 179 38 -199 214 180 39 -200 214 181 39 -201 214 182 40 -202 215 183 40 -203 215 185 41 -204 215 186 42 -205 216 187 42 -206 216 188 43 -207 216 190 43 -208 216 191 44 -209 217 192 44 -210 217 193 45 -211 217 195 46 -212 217 196 46 -213 218 197 47 -214 218 198 47 -215 218 200 48 -216 218 201 49 -217 219 202 49 -218 219 203 50 -219 219 205 51 -220 219 206 51 -221 220 207 52 -222 220 208 52 -223 220 210 53 -224 220 211 54 -225 220 212 54 -226 221 214 55 -227 221 215 56 -228 221 216 56 -229 221 218 57 -230 221 219 58 -231 222 220 58 -232 222 222 59 -233 222 223 60 -234 222 224 60 -235 222 226 61 -236 222 227 61 -237 223 228 62 -238 223 230 63 -239 223 231 63 -240 223 232 64 -241 223 234 65 -242 223 235 65 -243 223 236 66 -244 223 238 67 -245 223 239 68 -246 224 240 68 -247 224 242 69 -248 224 243 70 -249 224 245 70 -250 224 246 71 -251 224 247 72 -252 224 249 72 -253 224 250 73 -254 224 252 74 -255 224 253 74 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Speed.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Speed.lut deleted file mode 100644 index e450ce9eb..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Speed.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 23 35 18 -1 23 35 19 -2 23 36 20 -3 23 37 21 -4 23 38 21 -5 24 39 22 -6 24 40 23 -7 24 41 24 -8 24 42 24 -9 24 43 25 -10 24 44 26 -11 25 45 26 -12 25 46 27 -13 25 47 28 -14 25 48 28 -15 25 49 29 -16 25 50 30 -17 25 51 30 -18 25 52 31 -19 25 52 32 -20 25 53 32 -21 25 54 33 -22 25 55 33 -23 25 56 34 -24 24 57 34 -25 24 58 35 -26 24 59 35 -27 24 60 36 -28 24 61 36 -29 24 62 37 -30 23 63 37 -31 23 64 38 -32 23 65 38 -33 23 66 39 -34 22 67 39 -35 22 68 39 -36 22 69 40 -37 21 70 40 -38 21 71 40 -39 21 72 41 -40 20 73 41 -41 20 74 41 -42 20 74 42 -43 19 75 42 -44 19 76 42 -45 18 77 42 -46 18 78 43 -47 17 79 43 -48 17 80 43 -49 16 81 43 -50 16 82 43 -51 15 83 44 -52 15 84 44 -53 15 85 44 -54 14 86 44 -55 14 87 44 -56 13 88 44 -57 13 89 44 -58 12 90 44 -59 12 91 44 -60 12 92 44 -61 11 92 44 -62 11 93 44 -63 11 94 44 -64 11 95 44 -65 10 96 44 -66 10 97 44 -67 10 98 44 -68 10 99 44 -69 11 100 44 -70 11 101 44 -71 11 102 43 -72 12 103 43 -73 12 103 43 -74 13 104 43 -75 13 105 43 -76 14 106 42 -77 15 107 42 -78 16 108 42 -79 17 109 41 -80 18 110 41 -81 19 111 41 -82 20 111 41 -83 21 112 40 -84 22 113 40 -85 24 114 39 -86 25 115 39 -87 26 116 39 -88 28 117 38 -89 29 117 38 -90 31 118 37 -91 32 119 37 -92 34 120 36 -93 35 121 36 -94 37 121 35 -95 38 122 35 -96 40 123 34 -97 42 124 33 -98 43 125 33 -99 45 125 32 -100 46 126 32 -101 48 127 31 -102 50 128 30 -103 52 129 30 -104 53 129 29 -105 55 130 28 -106 57 131 28 -107 59 131 27 -108 60 132 26 -109 62 133 26 -110 64 134 25 -111 66 134 24 -112 67 135 23 -113 69 136 23 -114 71 137 22 -115 73 137 21 -116 75 138 20 -117 77 139 20 -118 78 139 19 -119 80 140 18 -120 82 141 17 -121 84 141 16 -122 86 142 16 -123 87 143 15 -124 89 143 14 -125 91 144 13 -126 93 145 12 -127 95 145 12 -128 97 146 11 -129 98 146 10 -130 100 147 9 -131 102 148 9 -132 104 148 8 -133 106 149 8 -134 107 150 7 -135 109 150 7 -136 111 151 6 -137 113 151 6 -138 115 152 5 -139 116 153 5 -140 118 153 5 -141 120 154 5 -142 122 155 5 -143 124 155 5 -144 125 156 5 -145 127 156 6 -146 129 157 6 -147 131 158 6 -148 132 158 7 -149 134 159 7 -150 136 159 8 -151 138 160 9 -152 139 161 10 -153 141 161 11 -154 143 162 12 -155 145 162 13 -156 146 163 14 -157 148 164 15 -158 150 164 16 -159 151 165 17 -160 153 165 18 -161 155 166 20 -162 157 167 21 -163 158 167 22 -164 160 168 23 -165 162 168 25 -166 163 169 26 -167 165 170 28 -168 167 170 29 -169 168 171 30 -170 170 171 32 -171 171 172 33 -172 173 173 35 -173 175 173 36 -174 176 174 38 -175 178 175 40 -176 179 175 41 -177 181 176 43 -178 182 177 44 -179 184 177 46 -180 185 178 48 -181 187 179 50 -182 188 179 51 -183 190 180 53 -184 191 181 55 -185 193 181 57 -186 194 182 59 -187 195 183 60 -188 197 183 62 -189 198 184 64 -190 199 185 66 -191 201 186 68 -192 202 186 70 -193 203 187 72 -194 205 188 74 -195 206 189 76 -196 207 190 78 -197 208 190 80 -198 209 191 82 -199 211 192 84 -200 212 193 86 -201 213 194 88 -202 214 195 91 -203 215 196 93 -204 216 196 95 -205 217 197 97 -206 218 198 99 -207 219 199 101 -208 220 200 103 -209 221 201 106 -210 222 202 108 -211 223 203 110 -212 223 204 112 -213 224 205 114 -214 225 206 116 -215 226 207 119 -216 227 208 121 -217 228 209 123 -218 229 210 125 -219 229 211 127 -220 230 212 129 -221 231 213 132 -222 232 214 134 -223 232 215 136 -224 233 216 138 -225 234 217 140 -226 235 219 143 -227 235 220 145 -228 236 221 147 -229 237 222 149 -230 238 223 151 -231 238 224 153 -232 239 225 156 -233 240 226 158 -234 240 227 160 -235 241 229 162 -236 242 230 164 -237 242 231 166 -238 243 232 168 -239 244 233 171 -240 244 234 173 -241 245 236 175 -242 246 237 177 -243 246 238 179 -244 247 239 181 -245 248 240 183 -246 248 241 185 -247 249 243 188 -248 250 244 190 -249 250 245 192 -250 251 246 194 -251 252 247 196 -252 252 249 198 -253 253 250 200 -254 254 251 202 -255 254 252 205 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Tempo.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Tempo.lut deleted file mode 100644 index 8a6985a5e..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Tempo.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 20 29 67 -1 21 30 68 -2 21 31 68 -3 21 32 69 -4 21 33 69 -5 22 34 70 -6 22 35 70 -7 22 36 71 -8 22 37 71 -9 22 38 72 -10 23 39 72 -11 23 40 73 -12 23 41 74 -13 23 42 74 -14 24 43 75 -15 24 44 75 -16 24 45 76 -17 24 46 76 -18 24 47 77 -19 24 47 78 -20 25 48 78 -21 25 49 79 -22 25 50 79 -23 25 51 80 -24 25 52 80 -25 26 53 81 -26 26 54 82 -27 26 55 82 -28 26 56 83 -29 26 57 83 -30 26 58 84 -31 26 59 84 -32 27 60 85 -33 27 60 86 -34 27 61 86 -35 27 62 87 -36 27 63 87 -37 27 64 88 -38 27 65 89 -39 27 66 89 -40 27 67 90 -41 27 68 90 -42 27 69 91 -43 28 70 91 -44 28 70 92 -45 28 71 93 -46 28 72 93 -47 28 73 94 -48 28 74 94 -49 28 75 95 -50 28 76 96 -51 28 77 96 -52 28 78 97 -53 28 79 97 -54 27 80 98 -55 27 81 98 -56 27 81 99 -57 27 82 100 -58 27 83 100 -59 27 84 101 -60 27 85 101 -61 27 86 102 -62 27 87 102 -63 27 88 103 -64 26 89 103 -65 26 90 104 -66 26 91 105 -67 26 92 105 -68 26 93 106 -69 25 94 106 -70 25 94 107 -71 25 95 107 -72 25 96 108 -73 24 97 108 -74 24 98 109 -75 24 99 109 -76 23 100 110 -77 23 101 110 -78 23 102 111 -79 22 103 111 -80 22 104 112 -81 22 105 112 -82 21 106 113 -83 21 107 113 -84 21 108 114 -85 20 109 114 -86 20 109 115 -87 20 110 115 -88 19 111 115 -89 19 112 116 -90 19 113 116 -91 18 114 117 -92 18 115 117 -93 18 116 117 -94 17 117 118 -95 17 118 118 -96 17 119 119 -97 17 120 119 -98 16 121 119 -99 16 122 120 -100 16 123 120 -101 16 124 120 -102 16 125 121 -103 16 125 121 -104 17 126 121 -105 17 127 122 -106 17 128 122 -107 18 129 122 -108 18 130 123 -109 19 131 123 -110 19 132 123 -111 20 133 123 -112 21 134 124 -113 22 135 124 -114 23 136 124 -115 24 137 124 -116 25 137 125 -117 26 138 125 -118 28 139 125 -119 29 140 125 -120 31 141 125 -121 32 142 126 -122 34 143 126 -123 35 144 126 -124 37 144 126 -125 38 145 126 -126 40 146 127 -127 42 147 127 -128 44 148 127 -129 46 149 127 -130 47 149 127 -131 49 150 128 -132 51 151 128 -133 53 152 128 -134 55 153 128 -135 57 153 128 -136 59 154 129 -137 61 155 129 -138 63 156 129 -139 65 157 129 -140 67 157 129 -141 69 158 130 -142 71 159 130 -143 73 160 130 -144 75 160 131 -145 77 161 131 -146 79 162 131 -147 81 162 131 -148 83 163 132 -149 85 164 132 -150 87 165 133 -151 89 165 133 -152 91 166 133 -153 93 167 134 -154 95 167 134 -155 97 168 135 -156 99 169 135 -157 101 169 136 -158 103 170 136 -159 105 171 137 -160 106 171 137 -161 108 172 138 -162 110 173 138 -163 112 174 139 -164 114 174 139 -165 116 175 140 -166 118 176 141 -167 119 176 141 -168 121 177 142 -169 123 178 143 -170 125 178 143 -171 127 179 144 -172 128 180 145 -173 130 180 145 -174 132 181 146 -175 134 182 147 -176 136 182 148 -177 137 183 148 -178 139 184 149 -179 141 184 150 -180 142 185 151 -181 144 186 152 -182 146 186 153 -183 148 187 153 -184 149 188 154 -185 151 188 155 -186 153 189 156 -187 154 190 157 -188 156 191 158 -189 157 191 159 -190 159 192 160 -191 161 193 161 -192 162 193 162 -193 164 194 163 -194 166 195 164 -195 167 195 165 -196 169 196 166 -197 170 197 167 -198 172 198 168 -199 174 198 169 -200 175 199 170 -201 177 200 171 -202 178 201 172 -203 180 201 174 -204 181 202 175 -205 183 203 176 -206 184 204 177 -207 186 204 178 -208 187 205 179 -209 189 206 181 -210 190 207 182 -211 192 207 183 -212 193 208 184 -213 195 209 185 -214 196 210 187 -215 198 210 188 -216 199 211 189 -217 201 212 190 -218 202 213 192 -219 204 214 193 -220 205 214 194 -221 207 215 195 -222 208 216 197 -223 210 217 198 -224 211 218 199 -225 212 218 201 -226 214 219 202 -227 215 220 203 -228 217 221 205 -229 218 222 206 -230 220 223 207 -231 221 223 209 -232 222 224 210 -233 224 225 212 -234 225 226 213 -235 227 227 214 -236 228 228 216 -237 229 229 217 -238 231 229 219 -239 232 230 220 -240 234 231 221 -241 235 232 223 -242 236 233 224 -243 238 234 226 -244 239 235 227 -245 241 236 229 -246 242 237 230 -247 243 238 232 -248 245 238 233 -249 246 239 235 -250 247 240 236 -251 249 241 238 -252 250 242 239 -253 252 243 241 -254 253 244 242 -255 254 245 244 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Thermal.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Thermal.lut deleted file mode 100644 index 63408e96b..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Thermal.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 3 35 51 -1 4 35 53 -2 4 36 55 -3 4 37 57 -4 4 38 58 -5 4 38 60 -6 5 39 62 -7 5 40 64 -8 5 40 66 -9 5 41 68 -10 5 42 70 -11 6 42 73 -12 6 43 75 -13 6 43 77 -14 7 44 79 -15 7 44 81 -16 8 45 83 -17 8 46 86 -18 9 46 88 -19 10 47 90 -20 10 47 93 -21 11 48 95 -22 12 48 98 -23 13 48 100 -24 14 49 103 -25 15 49 105 -26 16 50 108 -27 17 50 110 -28 18 50 113 -29 20 50 116 -30 21 51 118 -31 23 51 121 -32 24 51 124 -33 26 51 126 -34 27 51 129 -35 29 51 132 -36 31 51 134 -37 33 51 137 -38 35 51 140 -39 37 51 142 -40 39 51 144 -41 41 51 147 -42 43 51 149 -43 46 51 151 -44 48 51 153 -45 50 51 154 -46 53 50 156 -47 55 51 157 -48 57 51 157 -49 59 51 158 -50 61 51 159 -51 63 51 159 -52 65 52 159 -53 67 52 159 -54 69 53 159 -55 71 53 159 -56 73 54 159 -57 74 54 158 -58 76 55 158 -59 78 55 158 -60 79 56 157 -61 81 57 157 -62 83 57 156 -63 84 58 156 -64 86 59 156 -65 87 59 155 -66 89 60 155 -67 90 61 154 -68 92 61 154 -69 93 62 153 -70 95 63 153 -71 96 64 152 -72 97 64 152 -73 99 65 151 -74 100 66 151 -75 102 66 150 -76 103 67 150 -77 104 67 149 -78 106 68 149 -79 107 69 148 -80 108 69 148 -81 110 70 148 -82 111 71 147 -83 112 71 147 -84 114 72 146 -85 115 73 146 -86 116 73 146 -87 118 74 145 -88 119 74 145 -89 121 75 144 -90 122 76 144 -91 123 76 144 -92 125 77 143 -93 126 77 143 -94 127 78 143 -95 129 78 142 -96 130 79 142 -97 131 80 142 -98 133 80 142 -99 134 81 141 -100 135 81 141 -101 137 82 141 -102 138 82 140 -103 140 83 140 -104 141 83 140 -105 142 84 139 -106 144 84 139 -107 145 85 139 -108 147 85 138 -109 148 86 138 -110 150 86 138 -111 151 87 137 -112 152 87 137 -113 154 88 136 -114 155 88 136 -115 157 89 136 -116 158 89 135 -117 160 90 135 -118 161 90 134 -119 163 91 134 -120 164 91 133 -121 166 92 133 -122 167 92 133 -123 169 93 132 -124 170 93 131 -125 172 93 131 -126 173 94 130 -127 175 94 130 -128 176 95 129 -129 178 95 129 -130 179 96 128 -131 181 96 127 -132 182 97 127 -133 184 97 126 -134 185 97 125 -135 187 98 124 -136 188 98 124 -137 190 99 123 -138 191 99 122 -139 193 100 121 -140 194 100 120 -141 196 101 120 -142 197 101 119 -143 199 102 118 -144 200 102 117 -145 202 103 116 -146 203 103 115 -147 205 104 114 -148 206 104 113 -149 207 105 112 -150 209 105 111 -151 210 106 110 -152 212 107 109 -153 213 107 108 -154 214 108 107 -155 216 108 105 -156 217 109 104 -157 219 110 103 -158 220 110 102 -159 221 111 101 -160 222 112 100 -161 224 112 98 -162 225 113 97 -163 226 114 96 -164 227 115 95 -165 229 116 93 -166 230 116 92 -167 231 117 91 -168 232 118 90 -169 233 119 88 -170 234 120 87 -171 235 121 86 -172 236 122 85 -173 237 123 83 -174 238 124 82 -175 239 125 81 -176 240 126 80 -177 241 127 79 -178 241 128 78 -179 242 130 76 -180 243 131 75 -181 243 132 74 -182 244 133 73 -183 245 135 72 -184 245 136 71 -185 246 137 70 -186 246 139 69 -187 247 140 69 -188 247 141 68 -189 248 143 67 -190 248 144 66 -191 249 146 65 -192 249 147 65 -193 249 148 64 -194 250 150 63 -195 250 151 63 -196 250 153 62 -197 250 154 62 -198 250 156 62 -199 251 157 61 -200 251 159 61 -201 251 161 61 -202 251 162 60 -203 251 164 60 -204 251 165 60 -205 251 167 60 -206 251 168 60 -207 251 170 60 -208 251 172 60 -209 251 173 60 -210 251 175 60 -211 251 176 60 -212 251 178 60 -213 251 180 60 -214 251 181 61 -215 251 183 61 -216 250 184 61 -217 250 186 62 -218 250 188 62 -219 250 189 62 -220 250 191 63 -221 249 193 63 -222 249 194 64 -223 249 196 64 -224 249 198 65 -225 248 199 65 -226 248 201 66 -227 248 203 67 -228 247 204 67 -229 247 206 68 -230 246 208 69 -231 246 209 69 -232 246 211 70 -233 245 213 71 -234 245 214 72 -235 244 216 72 -236 244 218 73 -237 243 219 74 -238 243 221 75 -239 242 223 76 -240 242 224 77 -241 241 226 77 -242 241 228 78 -243 240 229 79 -244 239 231 80 -245 239 233 81 -246 238 234 82 -247 237 236 83 -248 237 238 84 -249 236 240 85 -250 235 241 86 -251 235 243 86 -252 234 245 87 -253 233 246 88 -254 232 248 89 -255 231 250 90 diff --git a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Turbid.lut b/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Turbid.lut deleted file mode 100644 index d4454fd08..000000000 --- a/src/main/resources/fiji/plugin/trackmate/gui/displaysettings/luts/Turbid.lut +++ /dev/null @@ -1,257 +0,0 @@ -Index Red Green Blue -0 34 30 27 -1 35 31 27 -2 36 31 28 -3 37 32 28 -4 38 33 29 -5 39 33 29 -6 40 34 30 -7 41 35 30 -8 42 35 31 -9 43 36 31 -10 44 37 31 -11 45 37 32 -12 46 38 32 -13 47 39 33 -14 48 39 33 -15 49 40 34 -16 50 40 34 -17 51 41 35 -18 52 42 35 -19 53 42 36 -20 54 43 36 -21 55 44 36 -22 56 44 37 -23 57 45 37 -24 58 45 38 -25 59 46 38 -26 60 47 39 -27 61 47 39 -28 62 48 39 -29 63 49 40 -30 64 49 40 -31 65 50 41 -32 66 50 41 -33 67 51 41 -34 68 52 42 -35 69 52 42 -36 70 53 42 -37 71 53 43 -38 72 54 43 -39 73 55 44 -40 74 55 44 -41 75 56 44 -42 76 56 45 -43 77 57 45 -44 78 58 45 -45 79 58 46 -46 80 59 46 -47 81 59 46 -48 82 60 47 -49 83 60 47 -50 84 61 47 -51 85 62 48 -52 86 62 48 -53 87 63 48 -54 88 63 49 -55 89 64 49 -56 90 65 49 -57 92 65 49 -58 93 66 50 -59 94 66 50 -60 95 67 50 -61 96 68 51 -62 97 68 51 -63 98 69 51 -64 99 69 51 -65 100 70 52 -66 101 71 52 -67 102 71 52 -68 103 72 52 -69 104 72 53 -70 105 73 53 -71 106 74 53 -72 107 74 53 -73 108 75 53 -74 109 75 54 -75 110 76 54 -76 111 76 54 -77 112 77 54 -78 113 78 54 -79 114 78 55 -80 115 79 55 -81 116 80 55 -82 117 80 55 -83 119 81 55 -84 120 81 55 -85 121 82 55 -86 122 83 56 -87 123 83 56 -88 124 84 56 -89 125 85 56 -90 126 85 56 -91 127 86 56 -92 128 86 56 -93 129 87 56 -94 130 88 57 -95 131 88 57 -96 132 89 57 -97 133 90 57 -98 134 90 57 -99 135 91 57 -100 136 92 57 -101 137 92 57 -102 138 93 57 -103 139 94 57 -104 140 94 57 -105 141 95 57 -106 142 96 57 -107 142 97 58 -108 143 97 58 -109 144 98 58 -110 145 99 58 -111 146 99 58 -112 147 100 58 -113 148 101 58 -114 149 102 58 -115 150 102 58 -116 151 103 58 -117 152 104 58 -118 153 105 58 -119 154 105 58 -120 154 106 58 -121 155 107 58 -122 156 108 58 -123 157 108 58 -124 158 109 58 -125 159 110 59 -126 160 111 59 -127 161 111 59 -128 161 112 59 -129 162 113 59 -130 163 114 59 -131 164 115 59 -132 165 116 59 -133 166 116 59 -134 166 117 59 -135 167 118 59 -136 168 119 60 -137 169 120 60 -138 169 121 60 -139 170 121 60 -140 171 122 60 -141 172 123 60 -142 173 124 60 -143 173 125 61 -144 174 126 61 -145 175 127 61 -146 175 128 61 -147 176 129 61 -148 177 129 62 -149 178 130 62 -150 178 131 62 -151 179 132 62 -152 180 133 63 -153 180 134 63 -154 181 135 63 -155 182 136 64 -156 182 137 64 -157 183 138 64 -158 184 139 65 -159 184 140 65 -160 185 141 66 -161 185 142 66 -162 186 143 66 -163 187 144 67 -164 187 145 67 -165 188 146 68 -166 188 147 68 -167 189 148 69 -168 190 149 69 -169 190 150 70 -170 191 151 71 -171 191 152 71 -172 192 153 72 -173 192 154 73 -174 193 155 73 -175 193 156 74 -176 194 157 75 -177 194 158 75 -178 195 159 76 -179 195 160 77 -180 196 161 78 -181 196 162 78 -182 197 163 79 -183 197 164 80 -184 198 165 81 -185 198 167 82 -186 199 168 83 -187 199 169 84 -188 200 170 84 -189 200 171 85 -190 201 172 86 -191 201 173 87 -192 202 174 88 -193 202 175 89 -194 202 176 90 -195 203 177 91 -196 203 178 92 -197 204 180 94 -198 204 181 95 -199 205 182 96 -200 205 183 97 -201 206 184 98 -202 206 185 99 -203 206 186 100 -204 207 187 101 -205 207 188 102 -206 208 189 104 -207 208 191 105 -208 209 192 106 -209 209 193 107 -210 210 194 108 -211 210 195 110 -212 210 196 111 -213 211 197 112 -214 211 198 113 -215 212 199 115 -216 212 201 116 -217 213 202 117 -218 213 203 119 -219 214 204 120 -220 214 205 121 -221 215 206 122 -222 215 207 124 -223 216 208 125 -224 216 210 126 -225 217 211 128 -226 217 212 129 -227 217 213 130 -228 218 214 132 -229 218 215 133 -230 219 216 135 -231 219 218 136 -232 220 219 137 -233 220 220 139 -234 221 221 140 -235 221 222 142 -236 222 223 143 -237 222 224 144 -238 223 226 146 -239 223 227 147 -240 224 228 149 -241 225 229 150 -242 225 230 152 -243 226 231 153 -244 226 232 155 -245 227 234 156 -246 227 235 157 -247 228 236 159 -248 228 237 160 -249 229 238 162 -250 229 239 163 -251 230 241 165 -252 231 242 166 -253 231 243 168 -254 232 244 169 -255 232 245 171 From e7c4186657c557e232b4ab90d94abff34e1c8460 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Fri, 7 Aug 2026 15:08:18 +0200 Subject: [PATCH 330/371] Depend on config-ui 0.0.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3e2b2eb82..716d93577 100644 --- a/pom.xml +++ b/pom.xml @@ -200,7 +200,7 @@ org.scijava config-ui - 0.0.1-SNAPSHOT + 0.0.1 From 2cafe3a15aae27d79b45418fd8adbb5492eaeba1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 08:35:15 +0200 Subject: [PATCH 331/371] Fix bug in AddAndLinkSpotBehaviour. We cannot create a KDTree if the spot collection to search is empty. This was silently failing the spot linking, because the exception is caught somewhere else. --- .../behaviours/AddAndLinkSpotBehaviour.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AddAndLinkSpotBehaviour.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AddAndLinkSpotBehaviour.java index d1bce509b..3ae5a342d 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AddAndLinkSpotBehaviour.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/behaviours/AddAndLinkSpotBehaviour.java @@ -23,7 +23,7 @@ public AddAndLinkSpotBehaviour( final Model model, final ImagePlus imp, final bo @Override public void init( final int x, final int y ) { - if ( source != null && target != null ) + if ( source != null || target != null ) return; final RealLocalizable pos = toWorldCoords( x, y ); @@ -40,7 +40,7 @@ public void init( final int x, final int y ) } else { - // We a source, link from it. + // We have a source, link from it. // Cannot link if at first frame and backward, etc. final int frame = spot.getFeature( Spot.FRAME ).intValue(); if ( backward && frame == 0 ) @@ -61,8 +61,15 @@ public void init( final int x, final int y ) // Build KD-tree of target frame spots. final Iterable< Spot > targetSpots = model.getSpots().iterable( targetFrame, true ); final int nTargetSpots = model.getSpots().getNSpots( targetFrame, true ); - final KDTree< Spot > tree = new KDTree< Spot >( nTargetSpots, targetSpots, targetSpots ); - this.search = new NearestNeighborSearchOnKDTree<>( tree ); + if ( nTargetSpots > 0 ) + { + final KDTree< Spot > tree = new KDTree< Spot >( nTargetSpots, targetSpots, targetSpots ); + this.search = new NearestNeighborSearchOnKDTree<>( tree ); + } + else + { + this.search = null; + } } // Add a new spot at the mouse location in the target frame. From bb9e931923391b17659fd3ad69c8b41da0ae7b38 Mon Sep 17 00:00:00 2001 From: Jean-Yves TINEVEZ Date: Thu, 23 Jul 2026 18:02:14 +0200 Subject: [PATCH 332/371] Add Assertj dep to test scope. --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 716d93577..7896ea32a 100644 --- a/pom.xml +++ b/pom.xml @@ -397,6 +397,12 @@ junit test + + org.assertj + assertj-core + 3.27.7 + test + From 2967c0522dc507e77922d7b83fbab0eda0da43f7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 15:40:08 +0200 Subject: [PATCH 333/371] Make TrackModel.setVisibility() public. --- src/main/java/fiji/plugin/trackmate/TrackModel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/TrackModel.java b/src/main/java/fiji/plugin/trackmate/TrackModel.java index 0173ece42..8b104b854 100644 --- a/src/main/java/fiji/plugin/trackmate/TrackModel.java +++ b/src/main/java/fiji/plugin/trackmate/TrackModel.java @@ -312,7 +312,7 @@ void setEdgeWeight( final DefaultWeightedEdge edge, final double weight ) edgesModified.add( edge ); } - Boolean setVisibility( final Integer trackID, final boolean visible ) + public boolean setVisibility( final Integer trackID, final boolean visible ) { return visibility.put( trackID, Boolean.valueOf( visible ) ); } @@ -1312,7 +1312,7 @@ public void edgeRemoved( final GraphEdgeChangeEvent< Spot, DefaultWeightedEdge > for ( final Spot v : targetVCS ) vertexToID.put( v, newid ); - final Boolean targetVisibility = visibility.get( id ); + final boolean targetVisibility = visibility.get( id ); visibility.put( newid, targetVisibility ); names.put( newid, nameGenerator.next() ); // Transaction: both children tracks are marked for From 9f5b9335130afd01c0458e464fe9d316d2036e84 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 16:13:31 +0200 Subject: [PATCH 334/371] Make setting track name undoable. Trickier that it looks like, since tracks can be appaearing or disappearing when spots or edges are added and removed. Got the help of Claude for this one. --- .../java/fiji/plugin/trackmate/Model.java | 61 ++++- .../trackmate/action/MergeFileAction.java | 2 +- .../plugin/trackmate/undo/UndoRedoStack.java | 217 +++++++++++++++++- 3 files changed, 276 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java index 9abdd3eee..dde697bef 100644 --- a/src/main/java/fiji/plugin/trackmate/Model.java +++ b/src/main/java/fiji/plugin/trackmate/Model.java @@ -90,6 +90,11 @@ public class Model private final HashSet< Spot > spotsUpdated = new HashSet<>(); + /** + * Track IDs whose names were modified during the current transaction. + */ + private final HashSet< Integer > tracksNamedModified = new HashSet<>(); + /** * The event cache. During a transaction, some modifications might trigger * the need to fire a model change event. We want to fire these events only @@ -598,6 +603,8 @@ public synchronized Spot removeSpot( final Spot spotToRemove ) if ( DEBUG ) System.out.println( "[TrackMateModel] Removing spot " + spotToRemove + " from frame " + fromFrame ); + // Flag all tracks for undo before removing spot (may split tracks) + undoRedoStack.flagAllTracksForUndo(); trackModel.removeSpot( spotToRemove ); // changes to edges will be caught automatically by the // TrackGraphModel @@ -634,6 +641,15 @@ public synchronized Spot removeSpot( final Spot spotToRemove ) */ public synchronized DefaultWeightedEdge addEdge( final Spot source, final Spot target, final double weight ) { + // Check if this edge will merge two tracks + final Integer sourceTrackId = trackModel.trackIDOf( source ); + final Integer targetTrackId = trackModel.trackIDOf( target ); + if ( sourceTrackId != null && targetTrackId != null && !sourceTrackId.equals( targetTrackId ) ) + { + // Flag both tracks for undo before merging + undoRedoStack.flagTrackForUndo( sourceTrackId ); + undoRedoStack.flagTrackForUndo( targetTrackId ); + } return trackModel.addEdge( source, target, weight ); } @@ -649,6 +665,8 @@ public synchronized DefaultWeightedEdge addEdge( final Spot source, final Spot t */ public synchronized DefaultWeightedEdge removeEdge( final Spot source, final Spot target ) { + // Flag all tracks for undo before removing edge (may split tracks) + undoRedoStack.flagAllTracksForUndo(); return trackModel.removeEdge( source, target ); } @@ -674,6 +692,8 @@ public synchronized DefaultWeightedEdge removeEdge( final Spot source, final Spo */ public synchronized boolean removeEdge( final DefaultWeightedEdge edge ) { + // Flag all tracks for undo before removing edge (may split tracks) + undoRedoStack.flagAllTracksForUndo(); return trackModel.removeEdge( edge ); } @@ -735,6 +755,37 @@ public synchronized boolean setTrackVisibility( final Integer trackID, final boo return oldvis; } + /** + * Sets the name of the track with the specified ID. + *

    + * This method captures the current track state for undo support before + * changing the name. For the model update to happen correctly and listeners + * to be notified properly, a call to this method must happen within a + * transaction, as in: + * + *

    +	 * model.beginUpdate();
    +	 * try {
    +	 * 	model.setTrackName( trackId, "MyTrackName" );
    +	 * } finally {
    +	 * 	model.endUpdate();
    +	 * }
    +	 * 
    + * + * @param trackID + * the track ID. + * @param name + * the name for the track. + */ + public synchronized void setTrackName( final Integer trackID, final String name ) + { + // Capture current track state for undo + undoRedoStack.flagTrackForUndo( trackID ); + trackModel.setName( trackID, name ); + // Mark track as having its name modified for the event system + tracksNamedModified.add( trackID ); + } + /** * Returns a copy of this model. *

    @@ -799,7 +850,6 @@ public Model copy() */ private void flushUpdate() { - if ( DEBUG ) { System.out.println( "[TrackMateModel] #flushUpdate()." ); @@ -824,6 +874,9 @@ private void flushUpdate() tracksToUpdate.add( trackModel.trackIDOf( modifiedEdge ) ); } + // Add tracks whose names were modified + tracksToUpdate.addAll( tracksNamedModified ); + // Deal with new or moved spots: we need to update their features. final int nSpotsToUpdate = spotsAdded.size() + spotsMoved.size() + spotsUpdated.size(); if ( nSpotsToUpdate > 0 ) @@ -888,9 +941,12 @@ private void flushUpdate() // Configure it with the tracks we found need updating event.setTracksUpdated( tracksToUpdate ); + // Fire the event if there are any changes to signal + final boolean hasChanges = nEdgesToSignal + nSpotsToSignal > 0 || !tracksNamedModified.isEmpty(); + try { - if ( nEdgesToSignal + nSpotsToSignal > 0 ) + if ( hasChanges ) { if ( DEBUG ) { @@ -925,6 +981,7 @@ private void flushUpdate() spotsRemoved.clear(); spotsMoved.clear(); spotsUpdated.clear(); + tracksNamedModified.clear(); trackModel.edgesAdded.clear(); trackModel.edgesRemoved.clear(); trackModel.edgesModified.clear(); diff --git a/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java b/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java index 5b690883d..fd3e299c7 100644 --- a/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java +++ b/src/main/java/fiji/plugin/trackmate/action/MergeFileAction.java @@ -147,7 +147,7 @@ public void execute( final GuiModel guiModel, final Frame parent ) final String trackName = modelToMerge.getTrackModel().name( id ); final int newId = model.getTrackModel().trackIDOf( newSpot ); - model.getTrackModel().setName( newId, trackName ); + model.setTrackName( newId, trackName ); progress++; logger.setProgress( ( double ) progress / nNewTracks ); diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index 9b94052de..0c3e3f85a 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.Deque; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -40,6 +41,12 @@ public class UndoRedoStack implements ModelChangeListener private final Map< Spot, String > spotNameBefore = new HashMap<>(); + /** + * Track states before the operation, keyed by track ID. Only holds "before" + * values. + */ + private final Map< Integer, TrackState > trackStatesBefore = new HashMap<>(); + private final int maxSize; public UndoRedoStack( final Model model ) @@ -91,12 +98,14 @@ public void modelChanged( final ModelChangeEvent event ) { // Only deal with model modified events. Other events are not // undoable and results in clearing the undo/redo stack. + undoStack.clear(); redoStack.clear(); spotFeatureValuesBefore.clear(); edgeFeatureValuesBefore.clear(); spotPolygonValuesBefore.clear(); spotNameBefore.clear(); + trackStatesBefore.clear(); return; } @@ -114,6 +123,49 @@ public void modelChanged( final ModelChangeEvent event ) private ModelUndoableCommand toCommand( final ModelChangeEvent event ) { final ModelUndoableCommand command = new ModelUndoableCommand(); + + // First, process track states that were flagged via flagTrackForUndo() + // (e.g., for track renames or before merge operations) + // We need to fill in the "after" state + for ( final Map.Entry< Integer, TrackState > entry : trackStatesBefore.entrySet() ) + { + final Integer trackId = entry.getKey(); + final TrackState beforeState = entry.getValue(); + final TrackModel trackModel = model.getTrackModel(); + + // Capture "after" state (current state after the modification) + // Track may no longer exist if it was removed (all spots deleted) + final String nameAfter = trackModel.name( trackId ); + // Check if track still exists before getting visibility (isVisible + // throws NPE for non-existent tracks) + final Boolean visibilityAfter = nameAfter != null ? trackModel.isVisible( trackId ) : null; + final Set< Spot > spotsAfter = trackModel.trackSpots( trackId ); + + final TrackState fullState = new TrackState( + beforeState.nameBefore, + beforeState.visibilityBefore, + nameAfter != null ? nameAfter : beforeState.nameBefore, + visibilityAfter != null ? visibilityAfter : beforeState.visibilityBefore, + spotsAfter != null ? new HashSet<>( spotsAfter ) : new HashSet<>( beforeState.spots ) ); + command.trackStatesBefore.put( trackId, fullState ); + } + + // Also capture track states for tracks affected by edge/spot operations + // (these only have "before" state captured, "after" will be same as + // before for now) + for ( final Integer trackId : event.getTrackUpdated() ) + { + if ( !command.trackStatesBefore.containsKey( trackId ) ) + { + final TrackModel trackModel = model.getTrackModel(); + final String name = trackModel.name( trackId ); + final boolean visibility = trackModel.isVisible( trackId ); + final Set< Spot > spots = new HashSet<>( trackModel.trackSpots( trackId ) ); + final TrackState state = new TrackState( name, visibility, name, visibility, spots ); + command.trackStatesBefore.put( trackId, state ); + } + } + for ( final Spot spot : event.getSpots() ) { if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_ADDED ) @@ -166,9 +218,38 @@ else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) edgeFeatureValuesBefore.clear(); spotPolygonValuesBefore.clear(); spotNameBefore.clear(); + trackStatesBefore.clear(); return command; } + /** + * Holds the state of a track before and after an operation. Used to restore + * track names and visibility after undo/redo. + */ + private static class TrackState + { + final String nameBefore; + + final String nameAfter; + + final boolean visibilityBefore; + + final boolean visibilityAfter; + + final Set< Spot > spots; + + TrackState( final String nameBefore, final boolean visibilityBefore, + final String nameAfter, final boolean visibilityAfter, + final Set< Spot > spots ) + { + this.nameBefore = nameBefore; + this.visibilityBefore = visibilityBefore; + this.nameAfter = nameAfter; + this.visibilityAfter = visibilityAfter; + this.spots = spots; + } + } + private static class ModelUndoableCommand { @@ -199,6 +280,9 @@ private static record EdgeRep( Spot source, Spot target, double weight ) private final Map< Spot, String > spotNameBefore = new HashMap<>(); + /** Track states before the operation, keyed by track ID. */ + private final Map< Integer, TrackState > trackStatesBefore = new HashMap<>(); + public void restoreBefore( final Model model ) { model.pauseUndo(); @@ -236,6 +320,10 @@ public void restoreBefore( final Model model ) else model.getFeatureModel().putEdgeFeature( edge, key, value ); } ); + + // Restore track names and visibility after topology is rebuilt + // (undo = restore before state) + restoreTrackStatesFromCommand( model, true ); } finally { @@ -281,6 +369,10 @@ public void restoreAfter( final Model model ) else model.getFeatureModel().putEdgeFeature( edge, key, value ); } ); + + // Restore track names and visibility after topology is rebuilt + // (redo = restore after state) + restoreTrackStatesFromCommand( model, false ); } finally { @@ -288,6 +380,88 @@ public void restoreAfter( final Model model ) } model.resumeUndo(); } + + /** + * Restores track names and visibility from the captured states in the + * command. After an undo or redo operation, the track topology is + * rebuilt by the {@link TrackModel.MyGraphListener}, but track names + * and visibility are not restored. This method finds the tracks that + * contain the spots from the captured states and restores their names + * and visibility. + * + * @param model + * the model + * @param restoreBefore + * if true, restore the "before" state (undo); if false, + * restore the "after" state (redo) + */ + private void restoreTrackStatesFromCommand( final Model model, final boolean restoreBefore ) + { + final TrackModel trackModel = model.getTrackModel(); + + // Build a map from spot ID to current track ID + final Map< Integer, Integer > spotToCurrentTrackId = new HashMap<>(); + for ( final Integer trackId : trackModel.trackIDs( false ) ) + { + for ( final Spot spot : trackModel.trackSpots( trackId ) ) + spotToCurrentTrackId.put( spot.ID(), trackId ); + } + + // Group captured track states by the current track they map to + // This handles the case where multiple tracks (e.g., from a split) + // merge back into one + final Map< Integer, Map.Entry< Integer, TrackState > > currentTrackToBestState = new HashMap<>(); + + for ( final Map.Entry< Integer, TrackState > entry : trackStatesBefore.entrySet() ) + { + final Integer oldTrackId = entry.getKey(); + final TrackState state = entry.getValue(); + + // Find a spot from the old track that still exists + Spot referenceSpot = null; + for ( final Spot spot : state.spots ) + { + if ( trackModel.vertexSet().contains( spot ) ) + { + referenceSpot = spot; + break; + } + } + + if ( referenceSpot != null ) + { + final Integer currentTrackId = spotToCurrentTrackId.get( referenceSpot.ID() ); + if ( currentTrackId != null ) + { + // If this current track already has a state, keep the + // one with the lowest oldTrackId + // (the original track before split) + if ( !currentTrackToBestState.containsKey( currentTrackId ) || oldTrackId < currentTrackToBestState.get( currentTrackId ).getKey() ) + currentTrackToBestState.put( currentTrackId, entry ); + } + } + } + + // Now restore names - each current track gets at most one name + // restoration + for ( final Map.Entry< Integer, Map.Entry< Integer, TrackState > > e : currentTrackToBestState.entrySet() ) + { + final Integer currentTrackId = e.getKey(); + final Map.Entry< Integer, TrackState > stateEntry = e.getValue(); + final TrackState state = stateEntry.getValue(); + + // Choose which state to restore based on restoreBefore flag + final String nameToRestore = restoreBefore ? state.nameBefore : state.nameAfter; + final boolean visibilityToRestore = restoreBefore ? state.visibilityBefore : state.visibilityAfter; + + // Restore name and visibility + trackModel.setName( currentTrackId, nameToRestore ); + trackModel.setVisibility( currentTrackId, visibilityToRestore ); + } + + // Don't clear trackStatesBefore - it's part of the command and may + // be needed for redo + } } private class UndoStorer implements SpotVisitor @@ -337,6 +511,48 @@ public void flagForUndo( final Spot spot ) spot.accept( undoStorer ); } + /** + * Flags a track for undo by capturing its current name and visibility. This + * should be called before modifying a track's name or structure. + * + * @param trackId + * the track ID to flag for undo + */ + public void flagTrackForUndo( final Integer trackId ) + { + if ( paused ) + { + + return; + } + final TrackModel trackModel = model.getTrackModel(); + final String currentName = trackModel.name( trackId ); + final boolean currentVisibility = trackModel.isVisible( trackId ); + final Set< Spot > currentSpots = new HashSet<>( trackModel.trackSpots( trackId ) ); + // Capture "before" state; "after" state will be filled in toCommand() + trackStatesBefore.put( trackId, new TrackState( currentName, currentVisibility, null, false, currentSpots ) ); + } + + /** + * Flags all tracks for undo by capturing their current names and + * visibility. This should be called before operations that may restructure + * tracks (e.g., adding/removing edges that may merge or split tracks). + */ + public void flagAllTracksForUndo() + { + if ( paused ) + return; + final TrackModel trackModel = model.getTrackModel(); + for ( final Integer trackId : trackModel.trackIDs( false ) ) + { + final String currentName = trackModel.name( trackId ); + final boolean currentVisibility = trackModel.isVisible( trackId ); + final Set< Spot > currentSpots = new HashSet<>( trackModel.trackSpots( trackId ) ); + // Capture "before" state; "after" will be filled in toCommand() + trackStatesBefore.put( trackId, new TrackState( currentName, currentVisibility, null, false, currentSpots ) ); + } + } + private static final double[][] toPolygon( final SpotRoi spot ) { final int nPoints = spot.nPoints(); @@ -359,5 +575,4 @@ private static final void updatePolygon( final SpotRoi spot, final double[][] po spot.setYr( i, polygon[ 1 ][ i ] ); } } - } From 4709c161fa30e8c59db2d36487753265ad41b484 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 16:14:33 +0200 Subject: [PATCH 335/371] Put calls to set track names between beingUpdate() / endUpdate() so that they can be undone. --- .../trackmate/features/TrackCollectionDataset.java | 10 +++++++++- .../visualization/table/TrackTableView.java | 12 +++++++++++- .../trackscheme/TrackSchemeGraphComponent.java | 12 +++++++++++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/features/TrackCollectionDataset.java b/src/main/java/fiji/plugin/trackmate/features/TrackCollectionDataset.java index b3786de53..8567b586e 100644 --- a/src/main/java/fiji/plugin/trackmate/features/TrackCollectionDataset.java +++ b/src/main/java/fiji/plugin/trackmate/features/TrackCollectionDataset.java @@ -69,7 +69,15 @@ public String getItemLabel( final int item ) @Override public void setItemLabel( final int item, final String label ) { - model.getTrackModel().setName( trackIDs.get( item ), label ); + model.beginUpdate(); + try + { + model.setTrackName( trackIDs.get( item ), label ); + } + finally + { + model.endUpdate(); + } } @Override diff --git a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java index 9432c44f8..8093b4a31 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/table/TrackTableView.java @@ -233,7 +233,17 @@ public static final TablePanel< Integer > createTrackTable( final Model model, f final Map< String, Boolean > isInts = model.getFeatureModel().getTrackFeatureIsInt(); final Map< String, String > infoTexts = new HashMap<>(); final Function< Integer, String > labelGenerator = id -> model.getTrackModel().name( id ); - final BiConsumer< Integer, String > labelSetter = ( id, label ) -> model.getTrackModel().setName( id, label ); + final BiConsumer< Integer, String > labelSetter = ( id, label ) -> { + model.beginUpdate(); + try + { + model.setTrackName( id, label ); + } + finally + { + model.endUpdate(); + } + }; final Supplier< FeatureColorGenerator< Integer > > coloring = () -> FeatureUtils.createWholeTrackColorGenerator( model, ds ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeGraphComponent.java b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeGraphComponent.java index b23dff1ec..0309a7b8a 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeGraphComponent.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/trackscheme/TrackSchemeGraphComponent.java @@ -59,6 +59,7 @@ import com.mxgraph.view.mxGraph; import com.mxgraph.view.mxGraphView; +import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; @@ -557,7 +558,16 @@ public void actionPerformed( final ActionEvent arg0 ) if ( guiModel.getModel().getTrackModel().unsortedTrackIDs( false ).contains( trackID ) ) { final String newname = textArea.getText(); - guiModel.getModel().getTrackModel().setName( trackID, newname ); + final Model model = guiModel.getModel(); + model.beginUpdate(); + try + { + model.setTrackName( trackID, newname ); + } + finally + { + model.endUpdate(); + } } scrollPane.remove( textArea ); ColumnHeader.this.remove( scrollPane ); From fe1e34bc45ec1d534d4665561ad2d22cb6f3d350 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 16:15:10 +0200 Subject: [PATCH 336/371] JUnit test for track name undo / redo. --- .../undo/TrackNameUndoRedoGuiTest.java | 86 ++++++++++ .../trackmate/undo/TrackNameUndoRedoTest.java | 151 ++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/undo/TrackNameUndoRedoGuiTest.java create mode 100644 src/test/java/fiji/plugin/trackmate/undo/TrackNameUndoRedoTest.java diff --git a/src/test/java/fiji/plugin/trackmate/undo/TrackNameUndoRedoGuiTest.java b/src/test/java/fiji/plugin/trackmate/undo/TrackNameUndoRedoGuiTest.java new file mode 100644 index 000000000..d38220b8e --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/undo/TrackNameUndoRedoGuiTest.java @@ -0,0 +1,86 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2026 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.undo; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; + +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.interactivetests.GraphTest; + +/** + * Tests for undo/redo of track name changes as they would occur from GUI actions. + * This verifies that track name changes wrapped in transactions are properly undoable. + */ +public class TrackNameUndoRedoGuiTest +{ + + private Model model; + + @Before + public void setUp() + { + this.model = GraphTest.getExampleModel(); + } + + @Test + public void testUndoTrackRenameWithTransaction() + { + // Simulate GUI-style track rename (wrapped in transaction) + final Integer trackId = model.getTrackModel().trackIDs( false ).iterator().next(); + final String originalName = model.getTrackModel().name( trackId ); + + // GUI components wrap the rename in a transaction + model.beginUpdate(); + try + { + model.setTrackName( trackId, "RenamedFromGUI" ); + } + finally + { + model.endUpdate(); + } + + // Verify name changed + assertThat( model.getTrackModel().name( trackId ) ) + .as( "Track name after rename" ) + .isEqualTo( "RenamedFromGUI" ); + + // Undo + model.undo(); + + // Verify name is restored + assertThat( model.getTrackModel().name( trackId ) ) + .as( "Track name after undo" ) + .isEqualTo( originalName ); + + // Redo + model.redo(); + + // Verify name is changed again + assertThat( model.getTrackModel().name( trackId ) ) + .as( "Track name after redo" ) + .isEqualTo( "RenamedFromGUI" ); + } +} diff --git a/src/test/java/fiji/plugin/trackmate/undo/TrackNameUndoRedoTest.java b/src/test/java/fiji/plugin/trackmate/undo/TrackNameUndoRedoTest.java new file mode 100644 index 000000000..1cfab2003 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/undo/TrackNameUndoRedoTest.java @@ -0,0 +1,151 @@ +package fiji.plugin.trackmate.undo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.Before; +import org.junit.Test; + +import fiji.plugin.trackmate.AssertJTrackMate; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.interactivetests.GraphTest; + +/** + * Tests for undo/redo of track name changes. + *

    + * Note: Track topology changes (split/merge) are already handled by the existing + * undo mechanism which restores edges. This test focuses on track name preservation + * through undo/redo operations. + */ +public class TrackNameUndoRedoTest +{ + + private Model model; + + private Model original; + + @Before + public void setUp() + { + this.model = GraphTest.getExampleModel(); + this.original = model.copy(); + } + + @Test + public void testUndoTrackRename() + { + // Get the first track ID + final Integer trackId = model.getTrackModel().trackIDs( false ).iterator().next(); + final String originalName = model.getTrackModel().name( trackId ); + + // Rename the track + model.beginUpdate(); + try + { + model.setTrackName( trackId, "MyCustomTrackName" ); + } + finally + { + model.endUpdate(); + } + + // Verify name changed + assertThat( model.getTrackModel().name( trackId ) ) + .as( "Track name after rename" ) + .isEqualTo( "MyCustomTrackName" ); + + // Model should differ from original + assertThatThrownBy( () -> AssertJTrackMate.testModelEquality( model, original ) ) + .isInstanceOf( AssertionError.class ); + + // Undo + model.undo(); + + // Verify name is restored + assertThat( model.getTrackModel().name( trackId ) ) + .as( "Track name after undo" ) + .isEqualTo( originalName ); + + // Model should be back to original + AssertJTrackMate.testModelEquality( model, original ); + + // Redo + model.redo(); + + // Verify name is changed again + assertThat( model.getTrackModel().name( trackId ) ) + .as( "Track name after redo" ) + .isEqualTo( "MyCustomTrackName" ); + } + + @Test + public void testUndoTrackRenameMultipleTracks() + { + // Rename multiple tracks + final Integer trackId1 = model.getTrackModel().trackIDs( false ).iterator().next(); + final String originalName1 = model.getTrackModel().name( trackId1 ); + + model.beginUpdate(); + try + { + model.setTrackName( trackId1, "FirstTrack" ); + } + finally + { + model.endUpdate(); + } + + // Verify + assertThat( model.getTrackModel().name( trackId1 ) ).isEqualTo( "FirstTrack" ); + + // Undo + model.undo(); + assertThat( model.getTrackModel().name( trackId1 ) ).isEqualTo( originalName1 ); + + // Redo + model.redo(); + assertThat( model.getTrackModel().name( trackId1 ) ).isEqualTo( "FirstTrack" ); + } + + @Test + public void testUndoTrackRenameAfterRedo() + { + // Test multiple undo/redo cycles + final Integer trackId = model.getTrackModel().trackIDs( false ).iterator().next(); + final String originalName = model.getTrackModel().name( trackId ); + + // First rename + model.beginUpdate(); + try + { + model.setTrackName( trackId, "FirstRename" ); + } + finally + { + model.endUpdate(); + } + + // Undo back to original + model.undo(); + assertThat( model.getTrackModel().name( trackId ) ).isEqualTo( originalName ); + + // Redo to first rename + model.redo(); + assertThat( model.getTrackModel().name( trackId ) ).isEqualTo( "FirstRename" ); + + // Rename again + model.beginUpdate(); + try + { + model.setTrackName( trackId, "SecondRename" ); + } + finally + { + model.endUpdate(); + } + + // Should undo to first rename, not original + model.undo(); + assertThat( model.getTrackModel().name( trackId ) ).isEqualTo( "FirstRename" ); + } +} From cedbccacff2ad74720cf21d22b3557ef18afcf43 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 17:09:29 +0200 Subject: [PATCH 337/371] Edge case when listening to spot added and removed. If a spot was added then removed in one transaction, it messed with the event content. This fixes it. --- .../java/fiji/plugin/trackmate/Model.java | 5 +++- .../plugin/trackmate/undo/UndoRedoStack.java | 28 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/Model.java b/src/main/java/fiji/plugin/trackmate/Model.java index dde697bef..b4e963ef8 100644 --- a/src/main/java/fiji/plugin/trackmate/Model.java +++ b/src/main/java/fiji/plugin/trackmate/Model.java @@ -905,7 +905,10 @@ private void flushUpdate() } for ( final Spot spot : spotsRemoved ) { - event.putSpotFlag( spot, ModelChangeEvent.FLAG_SPOT_REMOVED ); + // Skip spots that were both added and removed in the same transaction + // (they are transient and should not be flagged as removed) + if ( !spotsAdded.contains( spot ) ) + event.putSpotFlag( spot, ModelChangeEvent.FLAG_SPOT_REMOVED ); } for ( final Spot spot : spotsMoved ) { diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index 0c3e3f85a..0a93da0cf 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -166,17 +166,22 @@ private ModelUndoableCommand toCommand( final ModelChangeEvent event ) } } + // First pass: collect spots by their flag for ( final Spot spot : event.getSpots() ) { - if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_ADDED ) + final Integer flag = event.getSpotFlag( spot ); + if ( flag == null ) + continue; + + if ( flag == ModelChangeEvent.FLAG_SPOT_ADDED ) { command.spotsAdded.add( spot ); } - else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_REMOVED ) + else if ( flag == ModelChangeEvent.FLAG_SPOT_REMOVED ) { command.spotsRemoved.add( spot ); } - else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_MODIFIED ) + else if ( flag == ModelChangeEvent.FLAG_SPOT_MODIFIED ) { final Map< String, Double > previousFeatureValues = spotFeatureValuesBefore.get( spot ); command.spotFeatureValuesBefore.put( spot, previousFeatureValues ); @@ -192,6 +197,23 @@ else if ( event.getSpotFlag( spot ) == ModelChangeEvent.FLAG_SPOT_MODIFIED ) } } } + + // Second pass: handle spots that were both added and removed in the same transaction + // (they are transient and should be removed from both lists) + final Set< Spot > transientSpots = new HashSet<>(); + for ( final Spot removedSpot : command.spotsRemoved ) + { + for ( final Spot addedSpot : command.spotsAdded ) + { + if ( removedSpot.ID() == addedSpot.ID() ) + { + transientSpots.add( removedSpot ); + break; + } + } + } + command.spotsAdded.removeAll( transientSpots ); + command.spotsRemoved.removeAll( transientSpots ); for ( final DefaultWeightedEdge edge : event.getEdges() ) { if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_ADDED ) From 5383f9be712db6123ecdc4426038493d35e8a2c3 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 17:10:12 +0200 Subject: [PATCH 338/371] Fixes the old GraphTest utility. So that we can use it for undo / redo tests. --- .../fiji/plugin/trackmate/interactivetests/GraphTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java b/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java index 767cc29a2..b3886ee55 100644 --- a/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java +++ b/src/test/java/fiji/plugin/trackmate/interactivetests/GraphTest.java @@ -123,6 +123,7 @@ public static final Model getExampleModel() // Add them to the graph + model.pauseUndo(); model.beginUpdate(); try { @@ -167,6 +168,7 @@ public static final Model getExampleModel() finally { model.endUpdate(); + model.resumeUndo(); } // Done! @@ -191,6 +193,7 @@ public static final Model getComplicatedExample() } // Update model + model.pauseUndo(); model.beginUpdate(); try { @@ -206,6 +209,7 @@ public static final Model getComplicatedExample() finally { model.endUpdate(); + model.resumeUndo(); } return model; From a44668111df99dc3c844a59dd2f0d8e7282d113b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 17:10:28 +0200 Subject: [PATCH 339/371] A utility to test for TrackMate objects equality. --- .../plugin/trackmate/AssertJTrackMate.java | 392 ++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/AssertJTrackMate.java diff --git a/src/test/java/fiji/plugin/trackmate/AssertJTrackMate.java b/src/test/java/fiji/plugin/trackmate/AssertJTrackMate.java new file mode 100644 index 000000000..2096c266b --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/AssertJTrackMate.java @@ -0,0 +1,392 @@ +package fiji.plugin.trackmate; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.assertj.core.presentation.StandardRepresentation; +import org.jgrapht.graph.DefaultWeightedEdge; + +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; + +/** + * Utility class to provide AssertJ assertions for TrackMate objects. + */ +public class AssertJTrackMate +{ + + public static void testDisplaySettingsEquality( final DisplaySettings actual, final DisplaySettings expected ) + { + assertThat( actual ) + .withRepresentation( new StandardRepresentation() + { + @Override + public String toStringOf( final Object obj ) + { + if ( obj instanceof final DisplaySettings ds ) + return String.format( "DisplaySettings[%d]", ds.hashCode() ); + return super.toStringOf( obj ); + } + } ) + .usingRecursiveComparison() + .isEqualTo( expected ); + } + + public static void testSettingsEquality( final Settings actual, final Settings expected ) + { + assertThat( actual ) + .withRepresentation( new StandardRepresentation() + { + @Override + public String toStringOf( final Object obj ) + { + if ( obj instanceof final Settings s ) + return String.format( "Settings[%d]", s.hashCode() ); + return super.toStringOf( obj ); + } + } ) + .usingRecursiveComparison() + .isEqualTo( expected ); + } + + public static void testModelEquality( final Model actual, final Model expected ) + { + testModelEquality( actual, expected, true ); + } + + /** + * Compares two models for equality. + *

    + * If {@code compareTrackIds} is true, tracks are matched by their track IDs. + * If false, tracks are matched by their spot content and names, ignoring + * track ID mismatches (useful after undo/redo operations that may change track IDs). + * + * @param actual the actual Model + * @param expected the expected Model + * @param compareTrackIds if true, compare tracks by ID; if false, compare by spot content and name + */ + public static void testModelEquality( final Model actual, final Model expected, final boolean compareTrackIds ) + { + assertThat( actual ) + .withRepresentation( new StandardRepresentation() + { + @Override + public String toStringOf( final Object obj ) + { + if ( obj instanceof final Model m ) + return String.format( "Model[%d]", m.hashCode() ); + return super.toStringOf( obj ); + } + } ) + .usingRecursiveComparison() + .withEqualsForType( + // Used to pair spots in sets. + ( a, b ) -> a.ID() == b.ID(), Spot.class ) + .ignoringFields( + // Treated separately + "spots", + "featureModel", + "trackModel", + // Listeners + "modelChangeListeners", + // Utils + "logger", + // Transaction state fields + "updateLevel", + "spotsAdded", + "spotsRemoved", + "spotsMoved", + "spotsUpdated", + "eventCache", + // Undo / redo + "undoRedoStack" ) + .isEqualTo( expected ); + + /* + * SpotCollection + */ + + compareSpotCollections( actual.getSpots(), expected.getSpots() ); + + /* + * Feature model + */ + + assertThat( actual.getFeatureModel() ) + .withRepresentation( new StandardRepresentation() + { + @Override + public String toStringOf( final Object obj ) + { + if ( obj instanceof final FeatureModel fm ) + return String.format( "FeatureModel[%d]", fm.hashCode() ); + return super.toStringOf( obj ); + } + } ) + .usingRecursiveComparison() + .ignoringFields( + "model", // Transient + "edgeFeatureValues" ) // Treated separately ) + .isEqualTo( expected.getFeatureModel() ); + + // Compare edge feature values separately, because the keys are + // DefaultWeightedEdge objects + compareEdgeFeatureValues( actual, expected ); + + /* + * Track model + */ + + compareTrackModels( actual.getTrackModel(), expected.getTrackModel(), compareTrackIds ); + } + + private static void compareSpotCollections( final SpotCollection actual, final SpotCollection expected ) + { + // Same frames + assertThat( actual.keySet() ) + .as( "SpotCollection frames" ) + .containsExactlyInAnyOrderElementsOf( expected.keySet() ); + + actual.keySet().forEach( frame -> { + + final Iterable< Spot > actualSpots = actual.iterable( frame, false ); + final Iterable< Spot > expectedSpots = expected.iterable( frame, false ); + + // Same number of spots per frame + assertThat( actualSpots ) + .as( "number of spots in frame %d", frame ) + .hasSameSizeAs( expectedSpots ); + + // Build ID -> Spot lookup for expected + final Map< Integer, Spot > expectedById = new HashMap<>(); + expectedSpots.forEach( s -> expectedById.put( s.ID(), s ) ); + + actualSpots.forEach( actualSpot -> { + + // Spot exists in expected + final Spot expectedSpot = expectedById.get( actualSpot.ID() ); + assertThat( expectedSpot ) + .as( "Spot ID=%d missing in frame %d", actualSpot.ID(), frame ) + .isNotNull(); + + // Delegate to single-spot comparison + assertSpotEquals( actualSpot, expectedSpot ); + } ); + } ); + } + + private static void assertSpotEquals( final Spot actual, final Spot expected ) + { + // 1. Check same implementation type + assertThat( actual.getClass() ) + .as( "Spot ID=%d implementation type", actual.ID() ) + .isEqualTo( expected.getClass() ); + + // 2. Basic identity + assertThat( actual.ID() ) + .as( "Spot ID" ) + .isEqualTo( expected.ID() ); + + assertThat( actual.getName() ) + .as( "Spot ID=%d name", actual.ID() ) + .isEqualTo( expected.getName() ); + + // 3. Feature keys + assertThat( actual.getFeatures().keySet() ) + .as( "Spot ID=%d feature keys", actual.ID() ) + .containsExactlyInAnyOrderElementsOf( + expected.getFeatures().keySet() ); + + // 4. Feature values + assertThat( actual.getFeatures() ) + .as( "Spot ID=%d feature values", actual.ID() ) + .allSatisfy( ( key, value ) -> assertThat( value ) + .as( "Spot ID=%d feature '%s'", actual.ID(), key ) + .isEqualTo( + expected.getFeatures().getOrDefault( key, Double.NaN ) ) ); + + // 5. SpotRoi-specific: polygon coordinates + if ( actual instanceof SpotRoi ) + assertSpotRoiEquals( ( SpotRoi ) actual, ( SpotRoi ) expected ); + } + + private static void assertSpotRoiEquals( final SpotRoi actual, final SpotRoi expected ) + { + // Number of polygon vertices + assertThat( actual.nPoints() ) + .as( "SpotRoi ID=%d number of polygon points", actual.ID() ) + .isEqualTo( expected.nPoints() ); + + // X coordinates of polygon vertices (relative to center) + for ( int i = 0; i < actual.nPoints(); i++ ) + { + final int idx = i; // for lambda capture + assertThat( actual.xr( i ) ) + .as( "SpotRoi ID=%d polygon x[%d]", actual.ID(), idx ) + .isEqualTo( expected.xr( i ) ); + + assertThat( actual.yr( i ) ) + .as( "SpotRoi ID=%d polygon y[%d]", actual.ID(), idx ) + .isEqualTo( expected.yr( i ) ); + } + } + + private static void compareTrackModels( final TrackModel actual, final TrackModel expected, final boolean compareById ) + { + // 1. All spots in the graph (including isolated ones) + assertThat( actual.vertexSet().stream() + .map( s -> s.ID() ) + .collect( Collectors.toSet() ) ) + .as( "TrackModel: spot IDs" ) + .containsExactlyInAnyOrderElementsOf( + expected.vertexSet().stream() + .map( s -> s.ID() ) + .collect( Collectors.toSet() ) ); + + // 2. Edge topology and weights + final Map< String, Double > actualEdges = buildEdgeWeightMap( actual ); + final Map< String, Double > expectedEdges = buildEdgeWeightMap( expected ); + + assertThat( actualEdges.keySet() ) + .as( "TrackModel: edges" ) + .containsExactlyInAnyOrderElementsOf( expectedEdges.keySet() ); + + assertThat( actualEdges ) + .as( "TrackModel: edge weights" ) + .allSatisfy( ( edgeKey, weight ) -> assertThat( weight ) + .as( "weight of edge [%s]", edgeKey ) + .isEqualTo( expectedEdges.get( edgeKey ) ) ); + + // 3. Track structure, visibility and names + compareTracks( actual, expected, compareById ); + } + + /** + * Build "srcID->tgtID" -> weight map, independent of edge object identity. + */ + private static Map< String, Double > buildEdgeWeightMap( final TrackModel model ) + { + final Map< String, Double > map = new LinkedHashMap<>(); + model.edgeSet().forEach( e -> { + final String key = model.getEdgeSource( e ).ID() + "->" + model.getEdgeTarget( e ).ID(); + map.put( key, model.getEdgeWeight( e ) ); + } ); + return map; + } + + /** + * Compares tracks between two TrackModels. + *

    + * If {@code compareById} is true, tracks are matched by their track IDs + * (the default behavior). If false, tracks are matched by their spot content + * and names, ignoring track ID mismatches (useful after undo/redo operations + * that may change track IDs). + * + * @param actual the actual TrackModel + * @param expected the expected TrackModel + * @param compareById if true, compare by track ID; if false, compare by spot content and name + */ + private static void compareTracks( final TrackModel actual, final TrackModel expected, final boolean compareById ) + { + final Set< Integer > actualTrackIDs = actual.trackIDs( false ); + final Set< Integer > expectedTrackIDs = expected.trackIDs( false ); + + if ( compareById ) + { + // Strict comparison: track IDs must match exactly + assertThat( actualTrackIDs ) + .as( "TrackModel: identical tracks IDs" ) + .isEqualTo( expectedTrackIDs ); + + actualTrackIDs.forEach( actualTID -> { + + // Visibility + assertThat( actual.isVisible( actualTID ) ) + .as( "visibility of track ID: [%d]", actualTID ) + .isEqualTo( expected.isVisible( actualTID ) ); + + // Name + assertThat( actual.name( actualTID ) ) + .as( "name of track ID: [%d]", actualTID ) + .isEqualTo( expected.name( actualTID ) ); + } ); + } + else + { + // Flexible comparison: match tracks by spot content and name + assertThat( actualTrackIDs.size() ) + .as( "TrackModel: number of tracks" ) + .isEqualTo( expectedTrackIDs.size() ); + + // Build a map of track spots for matching + final Map< Set< Integer >, Integer > expectedTrackSpotsToId = new HashMap<>(); + for ( final Integer expectedTID : expectedTrackIDs ) + { + final Set< Integer > spotIds = expected.trackSpots( expectedTID ).stream() + .map( Spot::ID ) + .collect( Collectors.toSet() ); + expectedTrackSpotsToId.put( spotIds, expectedTID ); + } + + // For each actual track, find matching expected track by spot content + actualTrackIDs.forEach( actualTID -> { + final Set< Integer > actualSpotIds = actual.trackSpots( actualTID ).stream() + .map( Spot::ID ) + .collect( Collectors.toSet() ); + + final Integer expectedTID = expectedTrackSpotsToId.get( actualSpotIds ); + assertThat( expectedTID ) + .as( "Track with spot IDs %s not found in expected model", actualSpotIds ) + .isNotNull(); + + // Visibility + assertThat( actual.isVisible( actualTID ) ) + .as( "visibility of track with spots %s", actualSpotIds ) + .isEqualTo( expected.isVisible( expectedTID ) ); + + // Name + assertThat( actual.name( actualTID ) ) + .as( "name of track with spots %s", actualSpotIds ) + .isEqualTo( expected.name( expectedTID ) ); + } ); + } + } + + private static void compareEdgeFeatureValues( final Model m1, final Model m2 ) + { + final FeatureModel fm1 = m1.getFeatureModel(); + final FeatureModel fm2 = m2.getFeatureModel(); + + // If so, iterate edges from the track model instead: + m1.getTrackModel().edgeSet().forEach( edge -> { + final int sId = m1.getTrackModel().getEdgeSource( edge ).ID(); + final int tId = m1.getTrackModel().getEdgeTarget( edge ).ID(); + + // Find matching edge in readback model + final DefaultWeightedEdge matchedEdge = m2.getTrackModel().edgeSet().stream() + .filter( e -> m2.getTrackModel().getEdgeSource( e ).ID() == sId + && m2.getTrackModel().getEdgeTarget( e ).ID() == tId ) + .findFirst() + .orElseThrow( () -> new AssertionError( "No matching edge for ID" + sId + " -> ID" + tId ) ); + + // Compare feature by feature + fm1.getEdgeFeatures() + .forEach( featureKey -> { + final Double actual = fm1.getEdgeFeature( edge, featureKey ); + final Double expected = fm2.getEdgeFeature( matchedEdge, featureKey ); + + // Both null is ok. + if ( actual == null && expected == null ) + return; + + assertThat( actual ) + .as( "feature '%s' on edge (ID%d -> ID%d)", featureKey, sId, tId ) + .isEqualTo( expected ); + } ); + } ); + } +} From 6546103e29672fa04228d062843528dbb572c513 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 17:10:36 +0200 Subject: [PATCH 340/371] Undo / redo tests. --- .../plugin/trackmate/undo/UndoRedoTest.java | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java diff --git a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java new file mode 100644 index 000000000..40946ad3b --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java @@ -0,0 +1,171 @@ +package fiji.plugin.trackmate.undo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Set; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.junit.Before; +import org.junit.Test; + +import fiji.plugin.trackmate.AssertJTrackMate; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Spot; +import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.interactivetests.GraphTest; + +public class UndoRedoTest +{ + + private Model model; + + private Model original; + + @Before + public void setUp() + { + this.model = GraphTest.getExampleModel(); + this.original = model.copy(); + } + + @Test + public void testUndoAddSpot() + { + testCore( () -> addSpot() ); + } + + @Test + public void testUndoRemoveSpot() + { + testCore( () -> removeSpot() ); + } + + @Test + public void testUndoAddEdge() + { + testCore( () -> addEdge() ); + } + + @Test + public void testUndoRemoveEdge() + { + testCore( () -> removeEdge() ); + } + + @Test + public void testSeveralModifs() + { + final Runnable doModifs = () -> { + addSpot(); + removeSpot(); + addEdge(); + removeEdge(); + }; + testCore( doModifs ); + } + + @Test + public void testUndoChangePosition() + { + final Spot spot = model.getSpots().iterator( 0, true ).next(); + final double originalX = spot.getDoublePosition( 0 ); + + model.beginUpdate(); + try + { + model.beforeEdit( spot ); + spot.setPosition( originalX + 1,0 ); + } + finally + { + model.endUpdate(); + } + assertThat( originalX ).isNotEqualTo( spot.getDoublePosition( 0 ) ); + + // Undo command. + model.undo(); + + // Must succeed: model is back to original state. + assertThat( originalX ).isEqualTo( spot.getDoublePosition( 0 ) ); + } + + @Test + public void testUndoChangeName() + { + final Spot spot = model.getSpots().iterator( 0, true ).next(); + final String originalName = spot.getName(); + + model.beginUpdate(); + try + { + model.beforeEdit( spot ); + spot.setName( originalName + "_New name" ); + } + finally + { + model.endUpdate(); + } + assertThat( originalName ).isNotEqualTo( spot.getName() ); + + // Undo command. + model.undo(); + + // Must succeed: model is back to original state. + assertThat( originalName ).isEqualTo( spot.getName() ); + } + + private void testCore( final Runnable doModifs ) + { + // Must succeed: model is not modified yet. + AssertJTrackMate.testModelEquality( model, original, false ); + + // Do modifications. + model.beginUpdate(); + try + { + doModifs.run(); + } + finally + { + model.endUpdate(); + } + + // Must fail: model is modified. + assertThatThrownBy( () -> AssertJTrackMate.testModelEquality( model, original, false ) ) + .isInstanceOf( AssertionError.class ); + + // Undo commands. + model.undo(); + + // Must succeed: model is back to original state. + // Use flexible comparison (ignore track IDs) since track topology changes + // may result in different track IDs even when content is restored. + AssertJTrackMate.testModelEquality( model, original, false ); + } + + private void addSpot() + { + final Spot spot = new SpotBase( 0d, 0d, 0d, 1d, -1d ); + model.addSpotTo( spot, 0 ); + } + + private void removeSpot() + { + final Spot spot = model.getSpots().iterator( 0, true ).next(); + model.removeSpot( spot ); + } + + private void addEdge() + { + final Spot source = model.getSpots().iterator( 0, true ).next(); + final Spot target = model.getSpots().iterator( 3, true ).next(); + model.addEdge( source, target, 0 ); + } + + private void removeEdge() + { + final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgeSet(); + model.removeEdge( edges.iterator().next() ); + } +} From fe2fe86765fc81183043b510f67ba07a38df825b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 18:06:24 +0200 Subject: [PATCH 341/371] Optimize storage of undo states. We only store the feature and properties of spots and edges that have changed, not all of them. --- .../plugin/trackmate/undo/UndoRedoStack.java | 146 ++++++++++++++++-- 1 file changed, 130 insertions(+), 16 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index 0a93da0cf..6f26d4b1e 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -184,16 +184,51 @@ else if ( flag == ModelChangeEvent.FLAG_SPOT_REMOVED ) else if ( flag == ModelChangeEvent.FLAG_SPOT_MODIFIED ) { final Map< String, Double > previousFeatureValues = spotFeatureValuesBefore.get( spot ); - command.spotFeatureValuesBefore.put( spot, previousFeatureValues ); - command.spotFeatureValuesAfter.put( spot, new HashMap<>( spot.getFeatures() ) ); + final Map< String, Double > currentFeatureValues = spot.getFeatures(); final String previousName = spotNameBefore.get( spot ); - command.spotNameBefore.put( spot, previousName ); - command.spotNameAfter.put( spot, spot.getName() ); + final String currentName = spot.getName(); + + // Only store features that actually changed + final Map< String, Double > changedFeatures = new HashMap<>(); + for ( final Map.Entry< String, Double > entry : previousFeatureValues.entrySet() ) + { + final String key = entry.getKey(); + final Double beforeValue = entry.getValue(); + final Double afterValue = currentFeatureValues.get( key ); + if ( beforeValue == null && afterValue != null || + beforeValue != null && !beforeValue.equals( afterValue ) ) + { + changedFeatures.put( key, beforeValue ); + } + } + // Also check for features that were added (not in before but in after) + // These don't need to be stored for undo, but we need to know to remove them + // Actually, for undo we only need to restore what was there before + + // Only store if there are actual changes + if ( !changedFeatures.isEmpty() ) + { + command.spotFeatureValuesBefore.put( spot, changedFeatures ); + } + + // Store name only if it changed + if ( previousName != null && !previousName.equals( currentName ) ) + { + command.spotNameBefore.put( spot, previousName ); + command.spotNameAfter.put( spot, currentName ); + } + if ( spot instanceof SpotRoi ) { final SpotRoi spotRoi = ( SpotRoi ) spot; - command.spotPolygonValuesBefore.put( spotRoi, spotPolygonValuesBefore.get( spotRoi ) ); - command.spotPolygonValuesAfter.put( spotRoi, toPolygon( spotRoi ) ); + final double[][] polygonBefore = spotPolygonValuesBefore.get( spotRoi ); + final double[][] polygonAfter = toPolygon( spotRoi ); + // Only store if polygon changed + if ( !polygonsEqual( polygonBefore, polygonAfter ) ) + { + command.spotPolygonValuesBefore.put( spotRoi, polygonBefore ); + command.spotPolygonValuesAfter.put( spotRoi, polygonAfter ); + } } } } @@ -232,8 +267,37 @@ else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_REMOVED ) } else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) { - command.edgeFeatureValuesBefore.put( edge, edgeFeatureValuesBefore.get( edge ) ); - command.edgeFeatureValuesAfter.put( edge, copyEdgeFeatures( edge ) ); + // Only store edge features that actually changed + // Note: beforeFeatures can be null if the edge wasn't flagged with beforeEdit() + final Map< String, Double > beforeFeatures = edgeFeatureValuesBefore.get( edge ); + final Map< String, Double > afterFeatures = copyEdgeFeatures( edge ); + + if ( beforeFeatures != null ) + { + final Map< String, Double > changedFeatures = new HashMap<>(); + + for ( final Map.Entry< String, Double > entry : beforeFeatures.entrySet() ) + { + final String key = entry.getKey(); + final Double beforeValue = entry.getValue(); + final Double afterValue = afterFeatures.get( key ); + if ( beforeValue == null && afterValue != null || + beforeValue != null && !beforeValue.equals( afterValue ) ) + { + changedFeatures.put( key, beforeValue ); + } + } + + if ( !changedFeatures.isEmpty() ) + { + command.edgeFeatureValuesBefore.put( edge, changedFeatures ); + } + } + else + { + // No before state captured, store all features for safety + command.edgeFeatureValuesBefore.put( edge, afterFeatures ); + } } } spotFeatureValuesBefore.clear(); @@ -323,16 +387,29 @@ public void restoreBefore( final Model model ) for ( final EdgeRep edge : edgesRemoved ) model.addEdge( edge.source, edge.target, edge.weight ); - for ( final Spot spot : spotFeatureValuesBefore.keySet() ) + // Collect all spots that need restoration (features, name, or polygon changed) + final Set< Spot > spotsToRestore = new HashSet<>(); + spotsToRestore.addAll( spotFeatureValuesBefore.keySet() ); + spotsToRestore.addAll( spotNameBefore.keySet() ); + spotsToRestore.addAll( spotPolygonValuesBefore.keySet() ); + + for ( final Spot spot : spotsToRestore ) { model.beforeEdit( spot ); // to notify about update - spot.setName( spotNameBefore.get( spot ) ); - spotFeatureValuesBefore.get( spot ).forEach( ( key, value ) -> spot.putFeature( key, value ) ); + final String nameBefore = spotNameBefore.get( spot ); + if ( nameBefore != null ) + spot.setName( nameBefore ); + + final Map< String, Double > featuresBefore = spotFeatureValuesBefore.get( spot ); + if ( featuresBefore != null ) + featuresBefore.forEach( ( key, value ) -> spot.putFeature( key, value ) ); + if ( spot instanceof SpotRoi ) { final SpotRoi spotRoi = ( SpotRoi ) spot; final double[][] polygonBefore = spotPolygonValuesBefore.get( spotRoi ); - updatePolygon( spotRoi, polygonBefore ); + if ( polygonBefore != null ) + updatePolygon( spotRoi, polygonBefore ); } } for ( final DefaultWeightedEdge edge : edgeFeatureValuesBefore.keySet() ) @@ -372,16 +449,29 @@ public void restoreAfter( final Model model ) for ( final EdgeRep edge : edgesAdded ) model.addEdge( edge.source, edge.target, edge.weight ); - for ( final Spot spot : spotFeatureValuesAfter.keySet() ) + // Collect all spots that need restoration (features, name, or polygon changed) + final Set< Spot > spotsToRestore = new HashSet<>(); + spotsToRestore.addAll( spotFeatureValuesAfter.keySet() ); + spotsToRestore.addAll( spotNameAfter.keySet() ); + spotsToRestore.addAll( spotPolygonValuesAfter.keySet() ); + + for ( final Spot spot : spotsToRestore ) { model.beforeEdit( spot ); // to notify about update - spot.setName( spotNameAfter.get( spot ) ); - spotFeatureValuesAfter.get( spot ).forEach( ( key, value ) -> spot.putFeature( key, value ) ); + final String nameAfter = spotNameAfter.get( spot ); + if ( nameAfter != null ) + spot.setName( nameAfter ); + + final Map< String, Double > featuresAfter = spotFeatureValuesAfter.get( spot ); + if ( featuresAfter != null ) + featuresAfter.forEach( ( key, value ) -> spot.putFeature( key, value ) ); + if ( spot instanceof SpotRoi ) { final SpotRoi spotRoi = ( SpotRoi ) spot; final double[][] polygonAfter = spotPolygonValuesAfter.get( spotRoi ); - updatePolygon( spotRoi, polygonAfter ); + if ( polygonAfter != null ) + updatePolygon( spotRoi, polygonAfter ); } } for ( final DefaultWeightedEdge edge : edgeFeatureValuesAfter.keySet() ) @@ -597,4 +687,28 @@ private static final void updatePolygon( final SpotRoi spot, final double[][] po spot.setYr( i, polygon[ 1 ][ i ] ); } } + + private static final boolean polygonsEqual( final double[][] a, final double[][] b ) + { + if ( a == null && b == null ) + return true; + if ( a == null || b == null ) + return false; + if ( a.length != b.length ) + return false; + if ( a.length == 0 ) + return true; + if ( a[ 0 ].length != b[ 0 ].length ) + return false; + + for ( int i = 0; i < a[ 0 ].length; i++ ) + { + if ( Double.compare( a[ 0 ][ i ], b[ 0 ][ i ] ) != 0 || + Double.compare( a[ 1 ][ i ], b[ 1 ][ i ] ) != 0 ) + { + return false; + } + } + return true; + } } From f91432b788f2bb199f2749ef7881165c1a499eee Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 18:06:33 +0200 Subject: [PATCH 342/371] More JUnit tests for undo / redo. --- .../plugin/trackmate/undo/UndoRedoTest.java | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java index 40946ad3b..beb8c40b0 100644 --- a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java +++ b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java @@ -10,9 +10,11 @@ import org.junit.Test; import fiji.plugin.trackmate.AssertJTrackMate; +import fiji.plugin.trackmate.FeatureModel; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.interactivetests.GraphTest; public class UndoRedoTest @@ -115,6 +117,131 @@ public void testUndoChangeName() assertThat( originalName ).isEqualTo( spot.getName() ); } + @Test + public void testUndoChangeSpotFeature() + { + final Spot spot = model.getSpots().iterator( 0, true ).next(); + final String featureKey = "QUALITY"; + final double originalQuality = spot.getFeature( featureKey ); + final double newQuality = originalQuality + 100.0; + + model.beginUpdate(); + try + { + model.beforeEdit( spot ); + spot.putFeature( featureKey, newQuality ); + } + finally + { + model.endUpdate(); + } + + // Verify the feature was changed + assertThat( originalQuality ).isNotEqualTo( spot.getFeature( featureKey ) ); + assertThat( spot.getFeature( featureKey ) ).isEqualTo( newQuality ); + + // Undo command. + model.undo(); + + // Must succeed: model is back to original state. + assertThat( spot.getFeature( featureKey ) ) + .as( "Spot feature %s should be restored after undo", featureKey ) + .isEqualTo( originalQuality ); + } + + @Test + public void testUndoChangeEdgeFeature() + { + // Get an edge from the model + final DefaultWeightedEdge edge = model.getTrackModel().edgeSet().iterator().next(); + final Spot source = model.getTrackModel().getEdgeSource( edge ); + final FeatureModel featureModel = model.getFeatureModel(); + + // Get the first available edge feature key + final String featureKey = featureModel.getEdgeFeatures().iterator().next(); + final Double originalValue = featureModel.getEdgeFeature( edge, featureKey ); + final Double newValue = ( originalValue == null ? 42.0 : originalValue + 100.0 ); + + model.beginUpdate(); + try + { + // Flag the edge for undo by flagging the source spot + // (edge features are captured when touching spots are flagged) + model.beforeEdit( source ); + featureModel.putEdgeFeature( edge, featureKey, newValue ); + } + finally + { + model.endUpdate(); + } + + // Verify the feature was changed + assertThat( featureModel.getEdgeFeature( edge, featureKey ) ).isEqualTo( newValue ); + assertThat( featureModel.getEdgeFeature( edge, featureKey ) ).isNotEqualTo( originalValue ); + + // Undo command. + model.undo(); + + // Must succeed: model is back to original state. + assertThat( featureModel.getEdgeFeature( edge, featureKey ) ) + .as( "Edge feature %s should be restored after undo", featureKey ) + .isEqualTo( originalValue ); + } + + @Test + public void testUndoChangeSpotPolygon() + { + // Create a spot with ROI (a simple triangle) + final double[] xCoords = { -0.5, 0.5, 0.0 }; + final double[] yCoords = { -0.5, -0.5, 0.5 }; + final SpotRoi spotWithRoi = new SpotRoi( 0d, 0d, 0d, 1d, -1d, "TestSpot", xCoords, yCoords ); + + // Capture original polygon coordinates + final int nPoints = spotWithRoi.nPoints(); + final double[] originalXr = new double[ nPoints ]; + final double[] originalYr = new double[ nPoints ]; + for ( int i = 0; i < nPoints; i++ ) + { + originalXr[ i ] = spotWithRoi.xr( i ); + originalYr[ i ] = spotWithRoi.yr( i ); + } + + model.beginUpdate(); + try + { + model.beforeEdit( spotWithRoi ); + // Modify the polygon (change first point) + spotWithRoi.setXr( 0, originalXr[ 0 ] + 1.0 ); + spotWithRoi.setYr( 0, originalYr[ 0 ] + 1.0 ); + } + finally + { + model.endUpdate(); + } + + // Verify the polygon was changed + assertThat( spotWithRoi.xr( 0 ) ) + .as( "Polygon X[0] should be modified" ) + .isEqualTo( originalXr[ 0 ] + 1.0 ); + assertThat( spotWithRoi.yr( 0 ) ) + .as( "Polygon Y[0] should be modified" ) + .isEqualTo( originalYr[ 0 ] + 1.0 ); + + // Undo command. + model.undo(); + + // Must succeed: model is back to original state. + for ( int i = 0; i < nPoints; i++ ) + { + assertThat( spotWithRoi.xr( i ) ) + .as( "Polygon X[%d] should be restored after undo", i ) + .isEqualTo( originalXr[ i ] ); + assertThat( spotWithRoi.yr( i ) ) + .as( "Polygon Y[%d] should be restored after undo", i ) + .isEqualTo( originalYr[ i ] ); + } + } + private void testCore( final Runnable doModifs ) { // Must succeed: model is not modified yet. From d2f74415143f700b8d876d0293b4fe019034afcc Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 18:41:51 +0200 Subject: [PATCH 343/371] Fix regression in undo / redo Redo was not doing anything when spots were moved. Also added handling the hypothetical case where a user could move a spot from one frame to another. Though the GUI does not let us do that. --- .../plugin/trackmate/undo/UndoRedoStack.java | 43 ++++++++++++++--- .../plugin/trackmate/undo/UndoRedoTest.java | 46 +++++++++++++++++++ 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index 6f26d4b1e..c71b144a7 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -189,7 +189,8 @@ else if ( flag == ModelChangeEvent.FLAG_SPOT_MODIFIED ) final String currentName = spot.getName(); // Only store features that actually changed - final Map< String, Double > changedFeatures = new HashMap<>(); + final Map< String, Double > changedFeaturesBefore = new HashMap<>(); + final Map< String, Double > changedFeaturesAfter = new HashMap<>(); for ( final Map.Entry< String, Double > entry : previousFeatureValues.entrySet() ) { final String key = entry.getKey(); @@ -198,17 +199,16 @@ else if ( flag == ModelChangeEvent.FLAG_SPOT_MODIFIED ) if ( beforeValue == null && afterValue != null || beforeValue != null && !beforeValue.equals( afterValue ) ) { - changedFeatures.put( key, beforeValue ); + changedFeaturesBefore.put( key, beforeValue ); + changedFeaturesAfter.put( key, afterValue ); } } - // Also check for features that were added (not in before but in after) - // These don't need to be stored for undo, but we need to know to remove them - // Actually, for undo we only need to restore what was there before // Only store if there are actual changes - if ( !changedFeatures.isEmpty() ) + if ( !changedFeaturesBefore.isEmpty() ) { - command.spotFeatureValuesBefore.put( spot, changedFeatures ); + command.spotFeatureValuesBefore.put( spot, changedFeaturesBefore ); + command.spotFeatureValuesAfter.put( spot, changedFeaturesAfter ); } // Store name only if it changed @@ -231,6 +231,35 @@ else if ( flag == ModelChangeEvent.FLAG_SPOT_MODIFIED ) } } } + else if ( flag == ModelChangeEvent.FLAG_SPOT_FRAME_CHANGED ) + { + // Spot moved - capture position features that changed + final Map< String, Double > previousFeatureValues = spotFeatureValuesBefore.get( spot ); + final Map< String, Double > currentFeatureValues = spot.getFeatures(); + + if ( previousFeatureValues != null ) + { + final Map< String, Double > changedFeaturesBefore = new HashMap<>(); + final Map< String, Double > changedFeaturesAfter = new HashMap<>(); + + for ( final String posKey : new String[] { Spot.POSITION_X, Spot.POSITION_Y, Spot.POSITION_Z } ) + { + final Double beforeValue = previousFeatureValues.get( posKey ); + final Double afterValue = currentFeatureValues.get( posKey ); + if ( beforeValue != null && ( afterValue == null || !beforeValue.equals( afterValue ) ) ) + { + changedFeaturesBefore.put( posKey, beforeValue ); + changedFeaturesAfter.put( posKey, afterValue ); + } + } + + if ( !changedFeaturesBefore.isEmpty() ) + { + command.spotFeatureValuesBefore.put( spot, changedFeaturesBefore ); + command.spotFeatureValuesAfter.put( spot, changedFeaturesAfter ); + } + } + } } // Second pass: handle spots that were both added and removed in the same transaction diff --git a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java index beb8c40b0..3ecf679c1 100644 --- a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java +++ b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java @@ -242,6 +242,52 @@ public void testUndoChangeSpotPolygon() } } + @Test + public void testUndoRedoSpotMove() + { + final Spot spot = model.getSpots().iterator( 0, true ).next(); + final double originalX = spot.getDoublePosition( 0 ); + final double originalY = spot.getDoublePosition( 1 ); + final double originalZ = spot.getDoublePosition( 2 ); + final double newX = originalX + 10.0; + final double newY = originalY + 10.0; + final double newZ = originalZ + 10.0; + + model.beginUpdate(); + try + { + model.beforeEdit( spot ); + spot.setPosition( newX, 0 ); + spot.setPosition( newY, 1 ); + spot.setPosition( newZ, 2 ); + } + finally + { + model.endUpdate(); + } + + // Verify the spot was moved + assertThat( spot.getDoublePosition( 0 ) ).isEqualTo( newX ); + assertThat( spot.getDoublePosition( 1 ) ).isEqualTo( newY ); + assertThat( spot.getDoublePosition( 2 ) ).isEqualTo( newZ ); + + // Undo command. + model.undo(); + + // Verify position is restored after undo + assertThat( spot.getDoublePosition( 0 ) ).isEqualTo( originalX ); + assertThat( spot.getDoublePosition( 1 ) ).isEqualTo( originalY ); + assertThat( spot.getDoublePosition( 2 ) ).isEqualTo( originalZ ); + + // Redo command. + model.redo(); + + // Verify position is restored after redo + assertThat( spot.getDoublePosition( 0 ) ).isEqualTo( newX ); + assertThat( spot.getDoublePosition( 1 ) ).isEqualTo( newY ); + assertThat( spot.getDoublePosition( 2 ) ).isEqualTo( newZ ); + } + private void testCore( final Runnable doModifs ) { // Must succeed: model is not modified yet. From ca70b680a121ded0d50aae27ac3cede1c510c557 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 18:51:35 +0200 Subject: [PATCH 344/371] Depend on config-ui v0.0.6 --- pom.xml | 2 +- .../plugin/trackmate/util/config/GenericConfigPanelPreview.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 838eff3ab..d95998e0d 100644 --- a/pom.xml +++ b/pom.xml @@ -201,7 +201,7 @@ org.scijava config-ui - 0.0.1-SNAPSHOT + 0.0.6 diff --git a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java index 22975e21f..b6e269f16 100644 --- a/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java +++ b/src/main/java/fiji/plugin/trackmate/util/config/GenericConfigPanelPreview.java @@ -5,10 +5,10 @@ import java.util.function.Supplier; import org.scijava.ui.config.Configurator; +import org.scijava.ui.config.visitors.gui.elements.StyleElement; import org.scijava.ui.config.visitors.gui.elements.StyleElements.BoundedDoubleElement; import org.scijava.ui.config.visitors.gui.elements.StyleElements.DoubleElement; import org.scijava.ui.config.visitors.gui.elements.StyleElements.IntElement; -import org.scijava.ui.config.visitors.gui.elements.StyleElements.StyleElement; import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Settings; From a894d1c29b1f97636774f1c13976647c81cc37d9 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 18:56:29 +0200 Subject: [PATCH 345/371] Fix CI headless test failure Add _JAVA_OPTIONS to enable headless mode for GUI tests on Ubuntu runners. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02c9c4e6c..155a6eaa4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,7 @@ jobs: - name: Execute the build run: .github/build.sh env: + _JAVA_OPTIONS: -Djava.awt.headless=true GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} MAVEN_USER: ${{ secrets.MAVEN_USER }} From 4ea440b79d41e951f8f7cc894882ee908b653f5f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 18:59:08 +0200 Subject: [PATCH 346/371] Fix CI headless test failure Another attempt... Set MAVEN_OPTS environment variable in GitHub Actions workflow to enable headless mode for GUI tests on Ubuntu runners. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 155a6eaa4..851b4521a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: - name: Execute the build run: .github/build.sh env: - _JAVA_OPTIONS: -Djava.awt.headless=true + MAVEN_OPTS: -Djava.awt.headless=true GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} MAVEN_USER: ${{ secrets.MAVEN_USER }} From 94a2034936567cbf6a39ff912c746304e3b9321b Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 19:06:44 +0200 Subject: [PATCH 347/371] Default to control when we cannot get the toolkit getMenuShortcutKeyMaskEx --- .../trackmate/visualization/ui/TrackMateActions.java | 12 +++++++++++- .../fiji/plugin/trackmate/TrackMatePluginTest.java | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java index 6693729cd..317d7de0e 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateActions.java @@ -1,5 +1,6 @@ package fiji.plugin.trackmate.visualization.ui; +import java.awt.HeadlessException; import java.awt.Toolkit; import java.awt.event.InputEvent; import java.util.ArrayList; @@ -55,7 +56,16 @@ public class TrackMateActions static { - final int menuMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); + int menuMask; + try + { + menuMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); + } + catch ( final HeadlessException e ) + { + // Default to Ctrl in headless environments (CI, tests) + menuMask = InputEvent.CTRL_DOWN_MASK; + } final String modifier = ( menuMask == InputEvent.CTRL_DOWN_MASK ) ? "ctrl" : "meta"; UNDO_ACTION_KEYS = new String[] { modifier + " Z" }; REDO_ACTION_KEYS = new String[] { modifier + " Y", modifier + " shift Z" }; diff --git a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java index a9ee0de23..bc66ccf4c 100644 --- a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java +++ b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java @@ -35,7 +35,7 @@ public void testTrackMateRegistration() { final TestTrackMatePlugin testPlugin = new TestTrackMatePlugin(); testPlugin.setUp(); final ObjectService objectService = testPlugin.getLocalContext().service(ObjectService.class); - + final List trackMateInstances = objectService.getObjects(TrackMate.class); assertTrue(trackMateInstances.size() == 1); assertTrue(trackMateInstances.get(0) instanceof TrackMate); From c7f518868758243cc77d373ede13f8d27c087c25 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 19:11:41 +0200 Subject: [PATCH 348/371] Fix CI: skip GUI test in headless mode TrackMatePluginTest requires GUI initialization which fails on headless CI runners. Skip the test when java.awt.headless=true. The test will still run locally with a display available. --- pom.xml | 12 ++++++++++++ .../fiji/plugin/trackmate/TrackMatePluginTest.java | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/pom.xml b/pom.xml index 8a5aa8aed..f44be5a1e 100644 --- a/pom.xml +++ b/pom.xml @@ -194,6 +194,18 @@ false + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Djava.awt.headless=true + + + + + diff --git a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java index bc66ccf4c..47995510d 100644 --- a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java +++ b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java @@ -22,6 +22,7 @@ package fiji.plugin.trackmate; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; import java.util.List; @@ -32,6 +33,9 @@ public class TrackMatePluginTest { @Test public void testTrackMateRegistration() { + // Skip this test in headless mode - it requires GUI initialization + assumeFalse("Skipping GUI test in headless mode", Boolean.getBoolean("java.awt.headless")); + final TestTrackMatePlugin testPlugin = new TestTrackMatePlugin(); testPlugin.setUp(); final ObjectService objectService = testPlugin.getLocalContext().service(ObjectService.class); From a73b0dc6153d3aa8a34200c3ec6e9417d29355ac Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sat, 8 Aug 2026 19:13:38 +0200 Subject: [PATCH 349/371] Fix CI: skip GUI test in headless environment Use GraphicsEnvironment.isHeadless() to detect if running in a headless environment (like GitHub Actions CI) and skip the test that requires GUI initialization. The test runs normally on local machines with a display. --- pom.xml | 12 ------------ .../fiji/plugin/trackmate/TrackMatePluginTest.java | 5 +++-- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index f44be5a1e..8a5aa8aed 100644 --- a/pom.xml +++ b/pom.xml @@ -194,18 +194,6 @@ false - - - - org.apache.maven.plugins - maven-surefire-plugin - - -Djava.awt.headless=true - - - - - diff --git a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java index 47995510d..f0a1125fd 100644 --- a/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java +++ b/src/test/java/fiji/plugin/trackmate/TrackMatePluginTest.java @@ -22,8 +22,9 @@ package fiji.plugin.trackmate; import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; +import java.awt.GraphicsEnvironment; import java.util.List; import org.junit.Test; @@ -34,7 +35,7 @@ public class TrackMatePluginTest { @Test public void testTrackMateRegistration() { // Skip this test in headless mode - it requires GUI initialization - assumeFalse("Skipping GUI test in headless mode", Boolean.getBoolean("java.awt.headless")); + assumeTrue("Skipping GUI test in headless mode", !GraphicsEnvironment.isHeadless()); final TestTrackMatePlugin testPlugin = new TestTrackMatePlugin(); testPlugin.setUp(); From 280947dd4cbd40d7bcc9b45ac198e6f12ab9d251 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 9 Aug 2026 21:39:25 +0200 Subject: [PATCH 350/371] Simplify and fix the SpotMesh.scale() method. --- .../java/fiji/plugin/trackmate/SpotMesh.java | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index 690b7a125..b4f8797d4 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -265,32 +265,17 @@ public void scale( final double alpha ) super.scale( alpha ); final net.imglib2.mesh.Vertices vertices = mesh.vertices(); final long nVertices = vertices.size(); + final float fAlpha = ( float ) alpha; 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 ) ); + // Exploit the fact that the mesh is centered on (0,0,0). + final float xa = fAlpha * vertices.xf( v ); + final float ya = fAlpha * vertices.yf( v ); + final float za = fAlpha * vertices.zf( v ); vertices.setPositionf( v, xa, ya, za ); } this.boundingBox = Meshes.boundingBox( mesh ); + resetZSliceCache(); } @Override From 4f2491577ec9fb3821515d2f2c4a8df4f44ce82e Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 9 Aug 2026 21:52:55 +0200 Subject: [PATCH 351/371] Undo / redo for SpotMesh modifications. --- .../plugin/trackmate/undo/UndoRedoStack.java | 125 +++++++++++++++++- 1 file changed, 118 insertions(+), 7 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index c71b144a7..c1bd25ad0 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -19,8 +19,13 @@ import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.Spot.SpotVisitor; import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.TrackModel; +import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.impl.nio.BufferMesh; +import net.imglib2.mesh.impl.nio.BufferMesh.Triangles; +import net.imglib2.mesh.impl.nio.BufferMesh.Vertices; public class UndoRedoStack implements ModelChangeListener { @@ -37,6 +42,8 @@ public class UndoRedoStack implements ModelChangeListener private final Map< SpotRoi, double[][] > spotPolygonValuesBefore = new HashMap<>(); + private final Map< SpotMesh, BufferMesh > spotMeshValuesBefore = new HashMap<>(); + private final Map< DefaultWeightedEdge, Map< String, Double > > edgeFeatureValuesBefore = new HashMap<>(); private final Map< Spot, String > spotNameBefore = new HashMap<>(); @@ -222,7 +229,7 @@ else if ( flag == ModelChangeEvent.FLAG_SPOT_MODIFIED ) { final SpotRoi spotRoi = ( SpotRoi ) spot; final double[][] polygonBefore = spotPolygonValuesBefore.get( spotRoi ); - final double[][] polygonAfter = toPolygon( spotRoi ); + final double[][] polygonAfter = copyPolygon( spotRoi ); // Only store if polygon changed if ( !polygonsEqual( polygonBefore, polygonAfter ) ) { @@ -230,6 +237,19 @@ else if ( flag == ModelChangeEvent.FLAG_SPOT_MODIFIED ) command.spotPolygonValuesAfter.put( spotRoi, polygonAfter ); } } + + if ( spot instanceof SpotMesh ) + { + final SpotMesh spotMesh = ( SpotMesh ) spot; + final BufferMesh meshBefore = spotMeshValuesBefore.get( spotMesh ); + final BufferMesh meshAfter = copyMesh( spotMesh ); + // Only store if mesh changed + if ( !meshesEqual( meshBefore, meshAfter ) ) + { + command.spotMeshValuesBefore.put( spotMesh, meshBefore ); + command.spotMeshValuesAfter.put( spotMesh, meshAfter ); + } + } } else if ( flag == ModelChangeEvent.FLAG_SPOT_FRAME_CHANGED ) { @@ -332,6 +352,7 @@ else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) spotFeatureValuesBefore.clear(); edgeFeatureValuesBefore.clear(); spotPolygonValuesBefore.clear(); + spotMeshValuesBefore.clear(); spotNameBefore.clear(); trackStatesBefore.clear(); return command; @@ -387,6 +408,10 @@ private static record EdgeRep( Spot source, Spot target, double weight ) private final Map< SpotRoi, double[][] > spotPolygonValuesAfter = new HashMap<>(); + private final Map< SpotMesh, BufferMesh > spotMeshValuesBefore = new HashMap<>(); + + private final Map< SpotMesh, BufferMesh > spotMeshValuesAfter = new HashMap<>(); + private final Map< DefaultWeightedEdge, Map< String, Double > > edgeFeatureValuesBefore = new HashMap<>(); private final Map< DefaultWeightedEdge, Map< String, Double > > edgeFeatureValuesAfter = new HashMap<>(); @@ -416,11 +441,12 @@ public void restoreBefore( final Model model ) for ( final EdgeRep edge : edgesRemoved ) model.addEdge( edge.source, edge.target, edge.weight ); - // Collect all spots that need restoration (features, name, or polygon changed) + // Collect all spots that need restoration (features, name, polygon, or mesh changed) final Set< Spot > spotsToRestore = new HashSet<>(); spotsToRestore.addAll( spotFeatureValuesBefore.keySet() ); spotsToRestore.addAll( spotNameBefore.keySet() ); spotsToRestore.addAll( spotPolygonValuesBefore.keySet() ); + spotsToRestore.addAll( spotMeshValuesBefore.keySet() ); for ( final Spot spot : spotsToRestore ) { @@ -440,6 +466,14 @@ public void restoreBefore( final Model model ) if ( polygonBefore != null ) updatePolygon( spotRoi, polygonBefore ); } + + if ( spot instanceof SpotMesh ) + { + final SpotMesh spotMesh = ( SpotMesh ) spot; + final BufferMesh meshBefore = spotMeshValuesBefore.get( spotMesh ); + if ( meshBefore != null ) + updateMesh( spotMesh, meshBefore ); + } } for ( final DefaultWeightedEdge edge : edgeFeatureValuesBefore.keySet() ) edgeFeatureValuesBefore.get( edge ).forEach( ( key, value ) -> { @@ -478,11 +512,12 @@ public void restoreAfter( final Model model ) for ( final EdgeRep edge : edgesAdded ) model.addEdge( edge.source, edge.target, edge.weight ); - // Collect all spots that need restoration (features, name, or polygon changed) + // Collect all spots that need restoration (features, name, polygon, or mesh changed) final Set< Spot > spotsToRestore = new HashSet<>(); spotsToRestore.addAll( spotFeatureValuesAfter.keySet() ); spotsToRestore.addAll( spotNameAfter.keySet() ); spotsToRestore.addAll( spotPolygonValuesAfter.keySet() ); + spotsToRestore.addAll( spotMeshValuesAfter.keySet() ); for ( final Spot spot : spotsToRestore ) { @@ -502,6 +537,14 @@ public void restoreAfter( final Model model ) if ( polygonAfter != null ) updatePolygon( spotRoi, polygonAfter ); } + + if ( spot instanceof SpotMesh ) + { + final SpotMesh spotMesh = ( SpotMesh ) spot; + final BufferMesh meshAfter = spotMeshValuesAfter.get( spotMesh ); + if ( meshAfter != null ) + updateMesh( spotMesh, meshAfter ); + } } for ( final DefaultWeightedEdge edge : edgeFeatureValuesAfter.keySet() ) edgeFeatureValuesAfter.get( edge ).forEach( ( key, value ) -> { @@ -626,7 +669,14 @@ public void visit( final SpotBase spot ) public void visit( final SpotRoi spot ) { visit( ( SpotBase ) spot ); - spotPolygonValuesBefore.put( spot, toPolygon( spot ) ); + spotPolygonValuesBefore.put( spot, copyPolygon( spot ) ); + } + + @Override + public void visit( final SpotMesh spot ) + { + visit( ( SpotBase ) spot ); + spotMeshValuesBefore.put( spot, copyMesh( spot ) ); } } @@ -694,7 +744,7 @@ public void flagAllTracksForUndo() } } - private static final double[][] toPolygon( final SpotRoi spot ) + private static final double[][] copyPolygon( final SpotRoi spot ) { final int nPoints = spot.nPoints(); final double[] x = new double[ nPoints ]; @@ -732,12 +782,73 @@ private static final boolean polygonsEqual( final double[][] a, final double[][] for ( int i = 0; i < a[ 0 ].length; i++ ) { - if ( Double.compare( a[ 0 ][ i ], b[ 0 ][ i ] ) != 0 || - Double.compare( a[ 1 ][ i ], b[ 1 ][ i ] ) != 0 ) + if ( Double.compare( a[ 0 ][ i ], b[ 0 ][ i ] ) != 0 || Double.compare( a[ 1 ][ i ], b[ 1 ][ i ] ) != 0 ) + return false; + } + return true; + } + + private static final BufferMesh copyMesh( final SpotMesh spot ) + { + final BufferMesh source = spot.getMesh(); + final BufferMesh copy = new BufferMesh( source.vertices().size(), source.triangles().size() ); + Meshes.copy( source, copy ); + // This copy is centered on (0,0,0) so we need to translate it to the + // spot's position + Meshes.translate( copy, spot.positionAsDoubleArray() ); + return copy; + } + + private static final void updateMesh( final SpotMesh spot, final BufferMesh mesh ) + { + spot.setMesh( mesh ); + } + + private static final boolean meshesEqual( final BufferMesh a, final BufferMesh b ) + { + if ( a == null && b == null ) + return true; + if ( a == null || b == null ) + return false; + + final Vertices verticesA = a.vertices(); + final Vertices verticesB = b.vertices(); + final long nVerticesA = verticesA.size(); + final long nVerticesB = verticesB.size(); + + if ( nVerticesA != nVerticesB ) + return false; + + // Compare vertex positions + for ( long i = 0; i < nVerticesA; i++ ) + { + if ( Float.compare( verticesA.xf( i ), verticesB.xf( i ) ) != 0 || + Float.compare( verticesA.yf( i ), verticesB.yf( i ) ) != 0 || + Float.compare( verticesA.zf( i ), verticesB.zf( i ) ) != 0 ) + { + return false; + } + } + + // Compare triangles + final Triangles trianglesA = a.triangles(); + final Triangles trianglesB = b.triangles(); + final long nTrianglesA = trianglesA.size(); + final long nTrianglesB = trianglesB.size(); + + if ( nTrianglesA != nTrianglesB ) + return false; + + for ( long i = 0; i < nTrianglesA; i++ ) + { + if ( trianglesA.vertex0( i ) != trianglesB.vertex0( i ) || + trianglesA.vertex1( i ) != trianglesB.vertex1( i ) || + trianglesA.vertex2( i ) != trianglesB.vertex2( i ) ) { return false; } } + return true; } } From 407024f80e9978ce6a17920e4e427a77ef856686 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 9 Aug 2026 21:53:45 +0200 Subject: [PATCH 352/371] JUnit test for the method that tests for mesh equality. --- .../trackmate/undo/MeshesEqualTest.java | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/undo/MeshesEqualTest.java diff --git a/src/test/java/fiji/plugin/trackmate/undo/MeshesEqualTest.java b/src/test/java/fiji/plugin/trackmate/undo/MeshesEqualTest.java new file mode 100644 index 000000000..299d6f6e4 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/undo/MeshesEqualTest.java @@ -0,0 +1,194 @@ +/*- + * #%L + * TrackMate: your buddy for everyday tracking. + * %% + * Copyright (C) 2010 - 2026 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.undo; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +import net.imglib2.mesh.impl.nio.BufferMesh; + +/** + * Tests for the meshesEqual helper method in UndoRedoStack. + */ +public class MeshesEqualTest +{ + + @Test + public void testMeshesEqualIdenticalMeshes() + { + final BufferMesh mesh1 = createTetrahedronMesh(); + final BufferMesh mesh2 = createTetrahedronMesh(); + assertThat( meshesEqual( mesh1, mesh2 ) ).isTrue(); + } + + @Test + public void testMeshesEqualSameReference() + { + final BufferMesh mesh = createTetrahedronMesh(); + assertThat( meshesEqual( mesh, mesh ) ).isTrue(); + } + + @Test + public void testMeshesEqualBothNull() + { + assertThat( meshesEqual( null, null ) ).isTrue(); + } + + @Test + public void testMeshesEqualOneNull() + { + final BufferMesh mesh = createTetrahedronMesh(); + assertThat( meshesEqual( mesh, null ) ).isFalse(); + assertThat( meshesEqual( null, mesh ) ).isFalse(); + } + + @Test + public void testMeshesEqualDifferentVertexPosition() + { + final BufferMesh mesh1 = createTetrahedronMesh(); + final BufferMesh mesh2 = createTetrahedronMesh(); + mesh2.vertices().setPositionf( 0, 1.0f, 0.0f, 1.0f ); + assertThat( meshesEqual( mesh1, mesh2 ) ).isFalse(); + } + + @Test + public void testMeshesEqualDifferentTriangle() + { + final BufferMesh mesh1 = createTetrahedronMesh(); + // Create mesh with different triangle connectivity + final BufferMesh mesh2 = new BufferMesh( 4, 4 ); + mesh2.vertices().add( 0.0f, 0.0f, 1.0f ); + mesh2.vertices().add( 0.0f, 0.942809f, -0.333333f ); + mesh2.vertices().add( -0.816497f, -0.471405f, -0.333333f ); + mesh2.vertices().add( 0.816497f, -0.471405f, -0.333333f ); + // Different triangle connectivity (swapped vertex order) + mesh2.triangles().add( 0, 2, 1 ); // Was: 0, 1, 2 + mesh2.triangles().add( 0, 3, 2 ); // Was: 0, 2, 3 + mesh2.triangles().add( 0, 1, 3 ); // Was: 0, 3, 1 + mesh2.triangles().add( 1, 2, 3 ); // Was: 1, 3, 2 + assertThat( meshesEqual( mesh1, mesh2 ) ).isFalse(); + } + + @Test + public void testMeshesEqualDifferentVertexCount() + { + final BufferMesh mesh1 = createTetrahedronMesh(); + final BufferMesh mesh2 = new BufferMesh( 5, 4 ); + mesh2.vertices().add( 0.0f, 0.0f, 1.0f ); + mesh2.vertices().add( 0.0f, 0.942809f, -0.333333f ); + mesh2.vertices().add( -0.816497f, -0.471405f, -0.333333f ); + mesh2.vertices().add( 0.816497f, -0.471405f, -0.333333f ); + mesh2.vertices().add( 1.0f, 1.0f, 1.0f ); // Extra vertex + mesh2.triangles().add( 0, 1, 2 ); + mesh2.triangles().add( 0, 2, 3 ); + mesh2.triangles().add( 0, 3, 1 ); + mesh2.triangles().add( 1, 3, 2 ); + assertThat( meshesEqual( mesh1, mesh2 ) ).isFalse(); + } + + @Test + public void testMeshesEqualDifferentTriangleCount() + { + final BufferMesh mesh1 = createTetrahedronMesh(); + final BufferMesh mesh2 = new BufferMesh( 4, 3 ); + mesh2.vertices().add( 0.0f, 0.0f, 1.0f ); + mesh2.vertices().add( 0.0f, 0.942809f, -0.333333f ); + mesh2.vertices().add( -0.816497f, -0.471405f, -0.333333f ); + mesh2.vertices().add( 0.816497f, -0.471405f, -0.333333f ); + mesh2.triangles().add( 0, 1, 2 ); + mesh2.triangles().add( 0, 2, 3 ); + mesh2.triangles().add( 0, 3, 1 ); + // One less triangle + assertThat( meshesEqual( mesh1, mesh2 ) ).isFalse(); + } + + /** + * Creates a simple tetrahedron mesh for testing. + */ + private static BufferMesh createTetrahedronMesh() + { + final BufferMesh mesh = new BufferMesh( 4, 4 ); + // 4 vertices of a tetrahedron + mesh.vertices().add( 0.0f, 0.0f, 1.0f ); + mesh.vertices().add( 0.0f, 0.942809f, -0.333333f ); + mesh.vertices().add( -0.816497f, -0.471405f, -0.333333f ); + mesh.vertices().add( 0.816497f, -0.471405f, -0.333333f ); + // 4 triangles + mesh.triangles().add( 0, 1, 2 ); + mesh.triangles().add( 0, 2, 3 ); + mesh.triangles().add( 0, 3, 1 ); + mesh.triangles().add( 1, 3, 2 ); + return mesh; + } + + /** + * Copies the meshesEqual logic from UndoRedoStack for testing. + */ + private static boolean meshesEqual( final BufferMesh a, final BufferMesh b ) + { + if ( a == null && b == null ) + return true; + if ( a == null || b == null ) + return false; + + final net.imglib2.mesh.Vertices verticesA = a.vertices(); + final net.imglib2.mesh.Vertices verticesB = b.vertices(); + final long nVerticesA = verticesA.size(); + final long nVerticesB = verticesB.size(); + + if ( nVerticesA != nVerticesB ) + return false; + + // Compare vertex positions + for ( long i = 0; i < nVerticesA; i++ ) + { + if ( Float.compare( verticesA.xf( i ), verticesB.xf( i ) ) != 0 || + Float.compare( verticesA.yf( i ), verticesB.yf( i ) ) != 0 || + Float.compare( verticesA.zf( i ), verticesB.zf( i ) ) != 0 ) + { + return false; + } + } + + // Compare triangles + final net.imglib2.mesh.Triangles trianglesA = a.triangles(); + final net.imglib2.mesh.Triangles trianglesB = b.triangles(); + final long nTrianglesA = trianglesA.size(); + final long nTrianglesB = trianglesB.size(); + + if ( nTrianglesA != nTrianglesB ) + return false; + + for ( long i = 0; i < nTrianglesA; i++ ) + { + if ( trianglesA.vertex0( i ) != trianglesB.vertex0( i ) || + trianglesA.vertex1( i ) != trianglesB.vertex1( i ) || + trianglesA.vertex2( i ) != trianglesB.vertex2( i ) ) + { + return false; + } + } + + return true; + } +} From 4527982955a53bf831b6aea5af97af3d8bccc46a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 9 Aug 2026 21:54:24 +0200 Subject: [PATCH 353/371] Undo / redo test for SpotMesh --- .../plugin/trackmate/undo/UndoRedoTest.java | 172 +++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java index 3ecf679c1..7eeaf91ce 100644 --- a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java +++ b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.within; import java.util.Set; @@ -14,8 +15,10 @@ import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotBase; +import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.interactivetests.GraphTest; +import net.imglib2.mesh.impl.nio.BufferMesh; public class UndoRedoTest { @@ -288,6 +291,173 @@ public void testUndoRedoSpotMove() assertThat( spot.getDoublePosition( 2 ) ).isEqualTo( newZ ); } + @Test + public void testUndoChangeSpotMesh() + { + // Create a simple tetrahedron mesh + final BufferMesh originalMesh = createTetrahedronMesh(); + final SpotMesh spotWithMesh = new SpotMesh( originalMesh, -1., "TestSpotMesh" ); + + // Add spot to model + model.beginUpdate(); + try + { + model.addSpotTo( spotWithMesh, 0 ); + } + finally + { + model.endUpdate(); + } + + // Capture original vertex positions + final int nVertices = originalMesh.vertices().size(); + final float[] originalX = new float[ nVertices ]; + final float[] originalY = new float[ nVertices ]; + final float[] originalZ = new float[ nVertices ]; + for ( int i = 0; i < nVertices; i++ ) + { + originalX[ i ] = originalMesh.vertices().xf( i ); + originalY[ i ] = originalMesh.vertices().yf( i ); + originalZ[ i ] = originalMesh.vertices().zf( i ); + } + + // Modify the mesh (move first vertex) + model.beginUpdate(); + try + { + model.beforeEdit( spotWithMesh ); + spotWithMesh.getMesh().vertices().setPositionf( 0, + originalX[ 0 ] + 1.0f, + originalY[ 0 ] + 1.0f, + originalZ[ 0 ] + 1.0f ); + } + finally + { + model.endUpdate(); + } + + // Verify the mesh was changed + assertThat( spotWithMesh.getMesh().vertices().xf( 0 ) ) + .as( "Mesh vertex X[0] should be modified" ) + .isEqualTo( originalX[ 0 ] + 1.0f ); + + // Undo command. + model.undo(); + + // Verify mesh is restored (use offset comparison for floating point tolerance) + for ( int i = 0; i < nVertices; i++ ) + { + assertThat( spotWithMesh.getMesh().vertices().xf( i ) ) + .as( "Mesh vertex X[%d] should be restored after undo", i ) + .isEqualTo( originalX[ i ], within( 1e-5f ) ); + assertThat( spotWithMesh.getMesh().vertices().yf( i ) ) + .as( "Mesh vertex Y[%d] should be restored after undo", i ) + .isEqualTo( originalY[ i ], within( 1e-5f ) ); + assertThat( spotWithMesh.getMesh().vertices().zf( i ) ) + .as( "Mesh vertex Z[%d] should be restored after undo", i ) + .isEqualTo( originalZ[ i ], within( 1e-5f ) ); + } + } + + @Test + public void testUndoRedoSpotMeshScale() + { + // Create a simple tetrahedron mesh + final BufferMesh originalMesh = createTetrahedronMesh(); + final SpotMesh spotWithMesh = new SpotMesh( originalMesh, -1., "TestSpotMesh" ); + + // Add spot to model + model.beginUpdate(); + try + { + model.addSpotTo( spotWithMesh, 0 ); + } + finally + { + model.endUpdate(); + } + + // Capture original vertex positions and radius + final int nVertices = originalMesh.vertices().size(); + final float[] originalX = new float[ nVertices ]; + final float[] originalY = new float[ nVertices ]; + final float[] originalZ = new float[ nVertices ]; + final double originalRadius = spotWithMesh.getFeature( Spot.RADIUS ); + for ( int i = 0; i < nVertices; i++ ) + { + originalX[ i ] = originalMesh.vertices().xf( i ); + originalY[ i ] = originalMesh.vertices().yf( i ); + originalZ[ i ] = originalMesh.vertices().zf( i ); + } + + // Scale the mesh by factor of 2 + final double scaleFactor = 2.0; + model.beginUpdate(); + try + { + model.beforeEdit( spotWithMesh ); + spotWithMesh.scale( scaleFactor ); + } + finally + { + model.endUpdate(); + } + + // Verify the mesh was scaled + assertThat( spotWithMesh.getFeature( Spot.RADIUS ) ) + .as( "Radius should be scaled" ) + .isCloseTo( originalRadius * scaleFactor, within( 1e-10 ) ); + + // Undo command + model.undo(); + + // Verify radius is restored + assertThat( spotWithMesh.getFeature( Spot.RADIUS ) ) + .as( "Radius should be restored after undo" ) + .isCloseTo( originalRadius, within( 1e-10 ) ); + + // Verify mesh vertices are restored + for ( int i = 0; i < nVertices; i++ ) + { + assertThat( spotWithMesh.getMesh().vertices().xf( i ) ) + .as( "Mesh vertex X[%d] should be restored after undo", i ) + .isCloseTo( originalX[ i ], within( 1e-5f ) ); + assertThat( spotWithMesh.getMesh().vertices().yf( i ) ) + .as( "Mesh vertex Y[%d] should be restored after undo", i ) + .isCloseTo( originalY[ i ], within( 1e-5f ) ); + assertThat( spotWithMesh.getMesh().vertices().zf( i ) ) + .as( "Mesh vertex Z[%d] should be restored after undo", i ) + .isCloseTo( originalZ[ i ], within( 1e-5f ) ); + } + + // Redo command + model.redo(); + + // Verify radius is restored after redo + assertThat( spotWithMesh.getFeature( Spot.RADIUS ) ) + .as( "Radius should be restored after redo" ) + .isCloseTo( originalRadius * scaleFactor, within( 1e-10 ) ); + } + + /** + * Creates a simple tetrahedron mesh for testing. + */ + private static BufferMesh createTetrahedronMesh() + { + final BufferMesh mesh = new BufferMesh( 4, 4 ); + // 4 vertices of a tetrahedron + mesh.vertices().add( 0.0f, 0.0f, 1.0f ); + mesh.vertices().add( 0.0f, 0.942809f, -0.333333f ); + mesh.vertices().add( -0.816497f, -0.471405f, -0.333333f ); + mesh.vertices().add( 0.816497f, -0.471405f, -0.333333f ); + // 4 triangles + mesh.triangles().add( 0, 1, 2 ); + mesh.triangles().add( 0, 2, 3 ); + mesh.triangles().add( 0, 3, 1 ); + mesh.triangles().add( 1, 3, 2 ); + return mesh; + } + private void testCore( final Runnable doModifs ) { // Must succeed: model is not modified yet. @@ -341,4 +511,4 @@ private void removeEdge() final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgeSet(); model.removeEdge( edges.iterator().next() ); } -} +} \ No newline at end of file From cd84e3c1b750e8a6108b9c334d945e2613d2efe7 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Sun, 9 Aug 2026 21:57:20 +0200 Subject: [PATCH 354/371] Actually don't test for direct mesh modification. The spot is recentered after mesh recalculation. We should probably forbid direct mesh modication, by making it read only. --- .../plugin/trackmate/undo/UndoRedoTest.java | 68 ------------------- 1 file changed, 68 deletions(-) diff --git a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java index 7eeaf91ce..09014bcc8 100644 --- a/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java +++ b/src/test/java/fiji/plugin/trackmate/undo/UndoRedoTest.java @@ -291,74 +291,6 @@ public void testUndoRedoSpotMove() assertThat( spot.getDoublePosition( 2 ) ).isEqualTo( newZ ); } - @Test - public void testUndoChangeSpotMesh() - { - // Create a simple tetrahedron mesh - final BufferMesh originalMesh = createTetrahedronMesh(); - final SpotMesh spotWithMesh = new SpotMesh( originalMesh, -1., "TestSpotMesh" ); - - // Add spot to model - model.beginUpdate(); - try - { - model.addSpotTo( spotWithMesh, 0 ); - } - finally - { - model.endUpdate(); - } - - // Capture original vertex positions - final int nVertices = originalMesh.vertices().size(); - final float[] originalX = new float[ nVertices ]; - final float[] originalY = new float[ nVertices ]; - final float[] originalZ = new float[ nVertices ]; - for ( int i = 0; i < nVertices; i++ ) - { - originalX[ i ] = originalMesh.vertices().xf( i ); - originalY[ i ] = originalMesh.vertices().yf( i ); - originalZ[ i ] = originalMesh.vertices().zf( i ); - } - - // Modify the mesh (move first vertex) - model.beginUpdate(); - try - { - model.beforeEdit( spotWithMesh ); - spotWithMesh.getMesh().vertices().setPositionf( 0, - originalX[ 0 ] + 1.0f, - originalY[ 0 ] + 1.0f, - originalZ[ 0 ] + 1.0f ); - } - finally - { - model.endUpdate(); - } - - // Verify the mesh was changed - assertThat( spotWithMesh.getMesh().vertices().xf( 0 ) ) - .as( "Mesh vertex X[0] should be modified" ) - .isEqualTo( originalX[ 0 ] + 1.0f ); - - // Undo command. - model.undo(); - - // Verify mesh is restored (use offset comparison for floating point tolerance) - for ( int i = 0; i < nVertices; i++ ) - { - assertThat( spotWithMesh.getMesh().vertices().xf( i ) ) - .as( "Mesh vertex X[%d] should be restored after undo", i ) - .isEqualTo( originalX[ i ], within( 1e-5f ) ); - assertThat( spotWithMesh.getMesh().vertices().yf( i ) ) - .as( "Mesh vertex Y[%d] should be restored after undo", i ) - .isEqualTo( originalY[ i ], within( 1e-5f ) ); - assertThat( spotWithMesh.getMesh().vertices().zf( i ) ) - .as( "Mesh vertex Z[%d] should be restored after undo", i ) - .isEqualTo( originalZ[ i ], within( 1e-5f ) ); - } - } - @Test public void testUndoRedoSpotMeshScale() { From 70efb688351c4795117b99c41ab0dae808b5459f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 12:21:16 +0200 Subject: [PATCH 355/371] Depend on imglib2-mesh 1.2.0 --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 8a5aa8aed..96fd9e354 100644 --- a/pom.xml +++ b/pom.xml @@ -182,6 +182,7 @@ 0.11.1 8.0.0 10.6.7 + 1.2.0 21 From c99c6b568f08fb8798573aceaadc7e01c6f19d32 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 12:23:23 +0200 Subject: [PATCH 356/371] Depend on imglib2 and bdv versions defined upstream. --- pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pom.xml b/pom.xml index 96fd9e354..19d045462 100644 --- a/pom.xml +++ b/pom.xml @@ -180,8 +180,6 @@ imglib2 0.11.1 - 8.0.0 - 10.6.7 1.2.0 From 7eec17e17d400e6194805fd201d405dca8e30c99 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 13:51:39 +0200 Subject: [PATCH 357/371] SpotMesh returns a read-ony view of the mesh it wraps. --- src/main/java/fiji/plugin/trackmate/SpotMesh.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/SpotMesh.java b/src/main/java/fiji/plugin/trackmate/SpotMesh.java index b4f8797d4..b2f2de0fd 100644 --- a/src/main/java/fiji/plugin/trackmate/SpotMesh.java +++ b/src/main/java/fiji/plugin/trackmate/SpotMesh.java @@ -40,6 +40,7 @@ import net.imglib2.mesh.alg.zslicer.Slice; import net.imglib2.mesh.alg.zslicer.ZSlicer; import net.imglib2.mesh.impl.nio.BufferMesh; +import net.imglib2.mesh.view.ReadOnlyMesh; import net.imglib2.type.numeric.RealType; public class SpotMesh extends SpotBase @@ -166,15 +167,16 @@ public SpotMesh( final int ID, final BufferMesh 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). + * Returns a read-only view of the mesh stored 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 BufferMesh getMesh() + public Mesh getMesh() { - return mesh; + return ReadOnlyMesh.readOnly( mesh ); } @Override From c68100ab027cf5ba94470229d963132f141402f1 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 13:52:33 +0200 Subject: [PATCH 358/371] Use methods of imglib2-mesh v1.2.0 in the undo/redo stack. --- .../plugin/trackmate/undo/UndoRedoStack.java | 67 +++---------------- 1 file changed, 11 insertions(+), 56 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java index c1bd25ad0..f66c1be19 100644 --- a/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java +++ b/src/main/java/fiji/plugin/trackmate/undo/UndoRedoStack.java @@ -22,10 +22,9 @@ import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.SpotRoi; import fiji.plugin.trackmate.TrackModel; +import net.imglib2.mesh.Mesh; import net.imglib2.mesh.Meshes; import net.imglib2.mesh.impl.nio.BufferMesh; -import net.imglib2.mesh.impl.nio.BufferMesh.Triangles; -import net.imglib2.mesh.impl.nio.BufferMesh.Vertices; public class UndoRedoStack implements ModelChangeListener { @@ -244,7 +243,7 @@ else if ( flag == ModelChangeEvent.FLAG_SPOT_MODIFIED ) final BufferMesh meshBefore = spotMeshValuesBefore.get( spotMesh ); final BufferMesh meshAfter = copyMesh( spotMesh ); // Only store if mesh changed - if ( !meshesEqual( meshBefore, meshAfter ) ) + if ( !Meshes.equals( meshBefore, meshAfter ) ) { command.spotMeshValuesBefore.put( spotMesh, meshBefore ); command.spotMeshValuesAfter.put( spotMesh, meshAfter ); @@ -282,7 +281,8 @@ else if ( flag == ModelChangeEvent.FLAG_SPOT_FRAME_CHANGED ) } } - // Second pass: handle spots that were both added and removed in the same transaction + // Second pass: handle spots that were both added and removed in the + // same transaction // (they are transient and should be removed from both lists) final Set< Spot > transientSpots = new HashSet<>(); for ( final Spot removedSpot : command.spotsRemoved ) @@ -317,7 +317,8 @@ else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_REMOVED ) else if ( event.getEdgeFlag( edge ) == ModelChangeEvent.FLAG_EDGE_MODIFIED ) { // Only store edge features that actually changed - // Note: beforeFeatures can be null if the edge wasn't flagged with beforeEdit() + // Note: beforeFeatures can be null if the edge wasn't flagged + // with beforeEdit() final Map< String, Double > beforeFeatures = edgeFeatureValuesBefore.get( edge ); final Map< String, Double > afterFeatures = copyEdgeFeatures( edge ); @@ -441,7 +442,8 @@ public void restoreBefore( final Model model ) for ( final EdgeRep edge : edgesRemoved ) model.addEdge( edge.source, edge.target, edge.weight ); - // Collect all spots that need restoration (features, name, polygon, or mesh changed) + // Collect all spots that need restoration (features, name, + // polygon, or mesh changed) final Set< Spot > spotsToRestore = new HashSet<>(); spotsToRestore.addAll( spotFeatureValuesBefore.keySet() ); spotsToRestore.addAll( spotNameBefore.keySet() ); @@ -512,7 +514,8 @@ public void restoreAfter( final Model model ) for ( final EdgeRep edge : edgesAdded ) model.addEdge( edge.source, edge.target, edge.weight ); - // Collect all spots that need restoration (features, name, polygon, or mesh changed) + // Collect all spots that need restoration (features, name, + // polygon, or mesh changed) final Set< Spot > spotsToRestore = new HashSet<>(); spotsToRestore.addAll( spotFeatureValuesAfter.keySet() ); spotsToRestore.addAll( spotNameAfter.keySet() ); @@ -790,7 +793,7 @@ private static final boolean polygonsEqual( final double[][] a, final double[][] private static final BufferMesh copyMesh( final SpotMesh spot ) { - final BufferMesh source = spot.getMesh(); + final Mesh source = spot.getMesh(); final BufferMesh copy = new BufferMesh( source.vertices().size(), source.triangles().size() ); Meshes.copy( source, copy ); // This copy is centered on (0,0,0) so we need to translate it to the @@ -803,52 +806,4 @@ private static final void updateMesh( final SpotMesh spot, final BufferMesh mesh { spot.setMesh( mesh ); } - - private static final boolean meshesEqual( final BufferMesh a, final BufferMesh b ) - { - if ( a == null && b == null ) - return true; - if ( a == null || b == null ) - return false; - - final Vertices verticesA = a.vertices(); - final Vertices verticesB = b.vertices(); - final long nVerticesA = verticesA.size(); - final long nVerticesB = verticesB.size(); - - if ( nVerticesA != nVerticesB ) - return false; - - // Compare vertex positions - for ( long i = 0; i < nVerticesA; i++ ) - { - if ( Float.compare( verticesA.xf( i ), verticesB.xf( i ) ) != 0 || - Float.compare( verticesA.yf( i ), verticesB.yf( i ) ) != 0 || - Float.compare( verticesA.zf( i ), verticesB.zf( i ) ) != 0 ) - { - return false; - } - } - - // Compare triangles - final Triangles trianglesA = a.triangles(); - final Triangles trianglesB = b.triangles(); - final long nTrianglesA = trianglesA.size(); - final long nTrianglesB = trianglesB.size(); - - if ( nTrianglesA != nTrianglesB ) - return false; - - for ( long i = 0; i < nTrianglesA; i++ ) - { - if ( trianglesA.vertex0( i ) != trianglesB.vertex0( i ) || - trianglesA.vertex1( i ) != trianglesB.vertex1( i ) || - trianglesA.vertex2( i ) != trianglesB.vertex2( i ) ) - { - return false; - } - } - - return true; - } } From f70a0a08eaab560361e5ca13b3d3c8621eab3fca Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 13:53:00 +0200 Subject: [PATCH 359/371] Simplify the mesh smoother tool. We don't have to use our own undo, since it is now supported in the core model. --- .../action/meshtools/MeshSmoother.java | 84 ++++++------------- .../meshtools/MeshSmootherController.java | 39 +-------- 2 files changed, 28 insertions(+), 95 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 dcfe73f39..bba7e8908 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmoother.java @@ -24,22 +24,21 @@ 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; +import fiji.plugin.trackmate.Model; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.util.Threads; import net.imglib2.algorithm.MultiThreaded; -import net.imglib2.mesh.Meshes; +import net.imglib2.mesh.Mesh; 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; +import net.imglib2.mesh.view.TranslateMesh; public class MeshSmoother implements MultiThreaded { @@ -48,44 +47,19 @@ public class MeshSmoother implements MultiThreaded 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; - private final Logger logger; private int numThreads; + private final Model model; - public MeshSmoother( final Logger logger ) + public MeshSmoother( final Model model, final Logger logger ) { + this.model = model; this.logger = logger; - this.undoMap = new ConcurrentHashMap<>(); setNumThreads(); } - - 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 List< Spot > smooth( final MeshSmootherModel smootherModel, final Iterable< Spot > spots ) { final double mu = smootherModel.getMu(); @@ -101,27 +75,30 @@ public List< Spot > smooth( final MeshSmootherModel smootherModel, final Iterabl 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 ) + model.beginUpdate(); + try { - if ( SpotMesh.class.isInstance( spot ) ) + final AtomicInteger ai = new AtomicInteger( 0 ); + final ExecutorService executors = Threads.newFixedThreadPool( numThreads ); + final List< Spot > modifiedSpots = new ArrayList<>(); + for ( final Spot spot : spots ) { - final SpotMesh sm = ( SpotMesh ) spot; - executors.execute( process( sm, nIters, mu, lambda, weightType, ai, nSpots ) ); - modifiedSpots.add( sm ); + if ( SpotMesh.class.isInstance( spot ) ) + { + final SpotMesh sm = ( SpotMesh ) spot; + model.beforeEdit( sm ); + executors.execute( process( sm, nIters, mu, lambda, weightType, ai, nSpots ) ); + modifiedSpots.add( sm ); + } } - } - executors.shutdown(); - try - { + executors.shutdown(); 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" ); + return modifiedSpots; } catch ( final InterruptedException e ) { @@ -132,8 +109,9 @@ public List< Spot > smooth( final MeshSmootherModel smootherModel, final Iterabl { logger.setProgress( 1 ); logger.setStatus( "" ); + model.endUpdate(); } - return modifiedSpots; + return null; } private static final int count( final Iterable< Spot > spots ) @@ -162,21 +140,9 @@ private Runnable process( @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 Mesh mesh = sm.getMesh(); final BufferMesh smoothedMesh = TaubinSmoothing.smooth( mesh, nIters, lambda, mu, weightType ); - sm.setMesh( smoothedMesh ); + sm.setMesh( TranslateMesh.translate( smoothedMesh, sm ) ); logger.setProgress( ( double ) ai.incrementAndGet() / nSpots ); } 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 25da09913..4ed8a3a12 100644 --- a/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java +++ b/src/main/java/fiji/plugin/trackmate/action/meshtools/MeshSmootherController.java @@ -22,14 +22,12 @@ 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; @@ -48,18 +46,15 @@ public class MeshSmootherController implements MultiThreaded 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; this.gui = new MeshSmootherPanel(); - this.smoother = new MeshSmoother( logger ); + this.smoother = new MeshSmoother( model, logger ); gui.btnRun.addActionListener( e -> run( gui.getModel() ) ); - gui.btnUndo.addActionListener( e -> undo() ); + gui.btnUndo.addActionListener( e -> model.undo() ); } public void show( final Component parent ) @@ -85,8 +80,7 @@ private void run( final MeshSmootherModel smootherModel ) try { enabler.disable(); - final Collection< Spot > modifiedSpots = smoother.smooth( smootherModel, spots ); - fireEvent( modifiedSpots ); + smoother.smooth( smootherModel, spots ); } catch ( final Exception err ) { @@ -99,33 +93,6 @@ private void run( final MeshSmootherModel smootherModel ) }, "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 ) - { - 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" ); - } - @Override public void setNumThreads() { From 1bc63722678ad66110c9956134637857afa4be02 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 17:54:27 +0200 Subject: [PATCH 360/371] An abstract class for TrackMate views that are based on BVV. We need this class because we want to attach our actions and behaviours to the InputActionBindings and TriggerBehaviourBindings of the BVV, so that we can override actions defined in the BVV. This is important e.g. to show OUR Preferences dialog. --- .../AbstractTrackMateModelBvvView.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java new file mode 100644 index 000000000..b788223c5 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java @@ -0,0 +1,69 @@ +package fiji.plugin.trackmate.visualization; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.scijava.ui.behaviour.MouseAndKeyHandler; +import org.scijava.ui.behaviour.util.Actions; +import org.scijava.ui.behaviour.util.Behaviours; +import org.scijava.ui.behaviour.util.InputActionBindings; +import org.scijava.ui.behaviour.util.TriggerBehaviourBindings; +import org.scijava.ui.behaviour.util.WrappedActionMap; +import org.scijava.ui.behaviour.util.WrappedInputMap; + +import bdv.ui.keymap.Keymap; +import bdv.ui.keymap.Keymap.UpdateListener; +import bvv.vistools.BvvHandle; +import fiji.plugin.trackmate.gui.GuiModel; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; + +public abstract class AbstractTrackMateModelBvvView extends AbstractTrackMateModelView +{ + + protected final Behaviours behaviours; + + protected final Actions actions; + + protected AbstractTrackMateModelBvvView( final GuiModel guiModel, final String... keyConfigContexts ) + { + super( guiModel ); + final Set< String > c = new LinkedHashSet<>( Arrays.asList( KeyConfigContexts.TRACKMATE ) ); + c.addAll( Arrays.asList( keyConfigContexts ) ); + final String[] kccs = c.toArray( new String[] {} ); + final Keymap keymap = TrackMateKeymapManager.keymapManager.getForwardSelectedKeymap(); + this.behaviours = new Behaviours( keymap.getConfig(), kccs ); + this.actions = new Actions( keymap.getConfig(), kccs ); + } + + protected void setHandle( final BvvHandle handle ) + { + // We add our actions and behaviours to the following object, so that + // they can override those defined in the BVV. + final InputActionBindings keybindings = handle.getKeybindings(); + final TriggerBehaviourBindings triggerbindings = handle.getTriggerbindings(); + actions.install( keybindings, "view" ); + behaviours.install( triggerbindings, "view" ); + + final Keymap keymap = TrackMateKeymapManager.keymapManager.getForwardSelectedKeymap(); + final UpdateListener updateListener = () -> { + behaviours.updateKeyConfig( keymap.getConfig() ); + actions.updateKeyConfig( keymap.getConfig() ); + }; + keymap.updateListeners().add( updateListener ); + onClose( () -> keymap.updateListeners().remove( updateListener ) ); + + final MouseAndKeyHandler mouseAndKeyHandler = new MouseAndKeyHandler(); + mouseAndKeyHandler.setInputMap( triggerbindings.getConcatenatedInputTriggerMap() ); + mouseAndKeyHandler.setBehaviourMap( triggerbindings.getConcatenatedBehaviourMap() ); + + // Register global actions, if any. + final Actions globalActions = guiModel.getGlobalActions(); + if ( globalActions != null ) + { + keybindings.addActionMap( "global", new WrappedActionMap( globalActions.getActionMap() ) ); + keybindings.addInputMap( "global", new WrappedInputMap( globalActions.getInputMap() ) ); + } + } +} From 68603dacd3044cc0486f76a1e24cfcbca2527756 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 17:55:17 +0200 Subject: [PATCH 361/371] Use the abstract BVV view class with the TrackMate BVV --- .../plugin/trackmate/visualization/bvv/TrackMateBVV.java | 9 ++++++--- .../trackmate/visualization/ui/KeyConfigContexts.java | 7 ++++++- 2 files changed, 12 insertions(+), 4 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 93fdc87b7..e72ec5045 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -44,14 +44,15 @@ import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings.UpdateListener; -import fiji.plugin.trackmate.visualization.AbstractTrackMateModelView; +import fiji.plugin.trackmate.visualization.AbstractTrackMateModelBvvView; import fiji.plugin.trackmate.visualization.FeatureColorGenerator; +import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import ij.ImagePlus; import net.imglib2.RealLocalizable; import net.imglib2.realtransform.AffineTransform3D; import net.imglib2.type.Type; -public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelView +public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelBvvView { private static final String KEY = "BIGVOLUMEVIEWER"; @@ -64,7 +65,7 @@ public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelV public TrackMateBVV( final GuiModel guiModel, final ImagePlus imp ) { - super( guiModel ); + super( guiModel, KeyConfigContexts.BIGVOLUMEVIEWER ); this.imp = imp; this.meshMap = new HashMap<>(); @@ -99,7 +100,9 @@ public BvvHandle getBvvHandle() public void render() { this.handle = BVVUtils.createViewer( imp ); + setHandle( handle ); final VolumeViewerPanel viewer = handle.getViewerPanel(); + viewer.setRenderScene( ( gl, data ) -> { if ( guiModel.getDisplaySettings().isSpotVisible() ) { diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/KeyConfigContexts.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/KeyConfigContexts.java index b9b80c79b..1b1858ffe 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/KeyConfigContexts.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/KeyConfigContexts.java @@ -38,4 +38,9 @@ public interface KeyConfigContexts */ String TRACK_TABLE = "track-table"; -} \ No newline at end of file + /** + * The action or behaviour applies to the BVV views. + */ + String BIGVOLUMEVIEWER = "bigvolumeviewer"; + +} From 2f2fc60c0a9cc8ae5965448febb4d87348622bb0 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 22:38:36 +0200 Subject: [PATCH 362/371] The BVV view uses BigVolumeViewer rather than the BVVHandle Desperately try to integrate with the key bindings defined in TrackMate. --- .../AbstractTrackMateModelBvvView.java | 17 +- .../trackmate/visualization/bvv/BVVUtils.java | 251 ++++++++++++------ .../visualization/bvv/TrackMateBVV.java | 89 ++++--- 3 files changed, 234 insertions(+), 123 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java index b788223c5..5cfc2a563 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java @@ -14,10 +14,9 @@ import bdv.ui.keymap.Keymap; import bdv.ui.keymap.Keymap.UpdateListener; -import bvv.vistools.BvvHandle; +import bvv.core.VolumeViewerFrame; import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; -import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; public abstract class AbstractTrackMateModelBvvView extends AbstractTrackMateModelView { @@ -32,21 +31,21 @@ protected AbstractTrackMateModelBvvView( final GuiModel guiModel, final String.. final Set< String > c = new LinkedHashSet<>( Arrays.asList( KeyConfigContexts.TRACKMATE ) ); c.addAll( Arrays.asList( keyConfigContexts ) ); final String[] kccs = c.toArray( new String[] {} ); - final Keymap keymap = TrackMateKeymapManager.keymapManager.getForwardSelectedKeymap(); + final Keymap keymap = guiModel.getKeymapManager().getForwardSelectedKeymap(); this.behaviours = new Behaviours( keymap.getConfig(), kccs ); this.actions = new Actions( keymap.getConfig(), kccs ); } - protected void setHandle( final BvvHandle handle ) + protected void setWindow( final VolumeViewerFrame viewerFrame ) { // We add our actions and behaviours to the following object, so that // they can override those defined in the BVV. - final InputActionBindings keybindings = handle.getKeybindings(); - final TriggerBehaviourBindings triggerbindings = handle.getTriggerbindings(); - actions.install( keybindings, "view" ); - behaviours.install( triggerbindings, "view" ); + final InputActionBindings keybindings = viewerFrame.getKeybindings(); + final TriggerBehaviourBindings triggerbindings = viewerFrame.getTriggerbindings(); + actions.install( keybindings, "view2" ); + behaviours.install( triggerbindings, "view2" ); - final Keymap keymap = TrackMateKeymapManager.keymapManager.getForwardSelectedKeymap(); + final Keymap keymap = guiModel.getKeymapManager().getForwardSelectedKeymap(); final UpdateListener updateListener = () -> { behaviours.updateKeyConfig( keymap.getConfig() ); actions.updateKeyConfig( keymap.getConfig() ); 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 cea8ef42b..da55f96f0 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -21,27 +21,45 @@ */ package fiji.plugin.trackmate.visualization.bvv; -import bvv.vistools.Bvv; -import bvv.vistools.BvvFunctions; -import bvv.vistools.BvvHandle; -import bvv.vistools.BvvSource; +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; + +import org.scijava.ui.behaviour.io.InputTriggerConfig; + +import bdv.BigDataViewer; +import bdv.cache.CacheControl.CacheControls; +import bdv.tools.brightness.ConverterSetup; +import bdv.ui.appearance.AppearanceManager; +import bdv.util.RandomAccessibleIntervalSource; +import bdv.util.RandomAccessibleIntervalSource4D; +import bdv.viewer.ConverterSetups; +import bdv.viewer.DisplayMode; +import bdv.viewer.Source; +import bdv.viewer.SourceAndConverter; +import bvv.core.BigVolumeViewer; +import bvv.core.VolumeViewerOptions; import fiji.plugin.trackmate.Spot; import fiji.plugin.trackmate.SpotMesh; +import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; import ij.CompositeImage; import ij.ImagePlus; -import ij.process.ImageProcessor; +import ij.measure.Calibration; import ij.process.LUT; import net.imagej.ImgPlus; import net.imagej.axis.Axes; -import net.imglib2.img.display.imagej.ImgPlusViews; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.converter.Converter; import net.imglib2.mesh.Mesh; import net.imglib2.mesh.Meshes; 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.realtransform.AffineTransform3D; import net.imglib2.type.numeric.ARGBType; +import net.imglib2.type.numeric.RealType; public class BVVUtils { @@ -59,86 +77,169 @@ public static final StupidMesh createMesh( final Spot spot ) return new StupidMesh( Icosahedron.sphere( spot, spot.getFeature( Spot.RADIUS ).doubleValue() ) ); } - public static final < T extends Type< T > > BvvHandle createViewer( final ImagePlus imp ) + public static final < T extends RealType< T > > BigVolumeViewer createBvv( final GuiModel guiModel ) { - final double[] cal = TMUtils.getSpatialCalibration( imp ); - // Convert and split by channels. + /* + * Wire BVV options to TrackMate config objects. + */ + + final ImagePlus imp = guiModel.getSettings().imp; + final TrackMateKeymapManager keymapManager = guiModel.getKeymapManager(); + final InputTriggerConfig config = keymapManager.getForwardSelectedKeymap().getConfig(); + final AppearanceManager appearanceManager = guiModel.getAppearanceManager(); + + final VolumeViewerOptions options = VolumeViewerOptions.options() + .inputTriggerConfig( config ) + .maxAllowedStepInVoxels( 0 ) + .renderWidth( 1024 ) + .renderHeight( 1024 ) + .keymapManager( keymapManager ) + .appearanceManager( appearanceManager ) + .height( 512 ) + .width( 512 ); + + /* + * Create BVV sources + */ + + // Scaling + final Calibration cal = imp.getCalibration(); + final AffineTransform3D sourceTransform = new AffineTransform3D(); + sourceTransform.set( + cal.pixelWidth, 0, 0, 0, + 0, cal.pixelHeight, 0, 0, + 0, 0, cal.pixelDepth, 0 ); + + // Image data final ImgPlus< T > img = TMUtils.rawWraps( imp ); final int cAxis = img.dimensionIndex( Axes.CHANNEL ); - final BvvHandle bvvHandle; - if ( cAxis < 0 ) + final int nChannels = ( int ) ( ( cAxis < 0 ) ? 1 : img.dimension( cAxis ) ); + final int tAxis = img.dimensionIndex( Axes.TIME ); + final int nTimePoints = ( int ) ( ( tAxis < 0 ) ? 0 : img.dimension( tAxis ) ); + + // Source and converter setup + final List< SourceAndConverter< ? > > sources = new ArrayList<>( nChannels ); + final List< ConverterSetup > setups = new ArrayList< ConverterSetup >( nChannels ); + for ( int c = 0; c < nChannels; c++ ) { - final BvvSource source = BvvFunctions.show( img, imp.getShortTitle(), - Bvv.options() - .maxAllowedStepInVoxels( 0 ) - .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 ) + final RandomAccessibleInterval< T > channelRai = + ( cAxis < 0 ) + ? img + : img.view().slice( cAxis, c ); + + final String sourceName = ( cAxis < 0 ) ? "" : "Ch " + ( c + 1 ); + final Source< T > source; + if ( nTimePoints > 1 ) + { + source = new RandomAccessibleIntervalSource4D<>( + channelRai, + channelRai.getType(), + sourceTransform, + sourceName ); + } + else { - final LUT lut = imp.getLuts()[ 0 ]; - final int rgb = lut.getColorModel().getRGB( ( int ) imp.getDisplayRangeMax() ); - source.setColor( new ARGBType( rgb ) ); + source = new RandomAccessibleIntervalSource<>( + channelRai, + channelRai.getType(), + sourceTransform, + sourceName ); } - bvvHandle = source.getBvvHandle(); + + final Converter< T, ARGBType > converterToARGB = BigDataViewer.createConverterToARGB( channelRai.getType() ); + final SourceAndConverter< T > soc = new SourceAndConverter< T >( source, converterToARGB ); + sources.add( soc ); + final ConverterSetup setup = BigDataViewer.createConverterSetup( soc, c ); + setups.add( setup ); } - else + + final CacheControls cacheControl = new CacheControls(); + final String title = "3D view " + imp.getShortTitle(); + final BigVolumeViewer bvv = new BigVolumeViewer( setups, sources, nTimePoints, cacheControl, title, options ); + syncDisplayAndLUTs( bvv, sources, imp ); + return bvv; + } + + public static void syncDisplayAndLUTs( + final BigVolumeViewer bvv, + final List< SourceAndConverter< ? > > sources, + final ImagePlus imp ) + { + // Display mode. + final DisplayMode displayMode = getDisplayMode( imp ); + bvv.getViewer().setDisplayMode( displayMode ); + + // LUT & min max + final ConverterSetups converterSetups = bvv.getConverterSetups(); + final int nChannels = sources.size(); + for ( int c = 0; c < nChannels; c++ ) { - BvvHandle h = null; - final long nChannels = img.dimension( cAxis ); - final String st = imp.getShortTitle(); - for ( int c = 0; c < nChannels; c++ ) + final List< ConverterSetup > css = converterSetups.getConverterSetups( sources ); + final ConverterSetup setup = css.get( c ); + + double minRange; + double maxRange; + Color channelColor; + if ( imp instanceof CompositeImage ) { - 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 ) - .frameTitle( "3D view " + imp.getShortTitle() ) - .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 ); - } + final CompositeImage ci = ( CompositeImage ) imp; + final LUT lut = ci.getChannelLut( c + 1 ); + minRange = lut.min; + maxRange = lut.max; + channelColor = new Color( lut.getRGB( 255 ) ); + } + else + { + imp.setPosition( c + 1, 1, 1 ); + minRange = imp.getDisplayRangeMin(); + maxRange = imp.getDisplayRangeMax(); + + final LUT lut = imp.getProcessor().getLut(); + if ( lut != null ) + channelColor = new Color( lut.getRGB( 255 ) ); 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 ) ); - } + channelColor = Color.WHITE; } - bvvHandle = h; + + // Apply + setup.setDisplayRange( minRange, maxRange ); + final int argb = ARGBType.rgba( + channelColor.getRed(), + channelColor.getGreen(), + channelColor.getBlue(), + 255 ); + setup.setColor( new ARGBType( argb ) ); } - return bvvHandle; + } + + /** + * Determines the BVV DisplayMode based on an ImagePlus instance. + */ + public static DisplayMode getDisplayMode( final ImagePlus imp ) + { + // Check if the ImagePlus is a CompositeImage (multi-channel UI mode) + if ( imp instanceof CompositeImage ) + { + final CompositeImage ci = ( CompositeImage ) imp; + + switch ( ci.getMode() ) + { + case CompositeImage.COMPOSITE: + return DisplayMode.FUSED; + + case CompositeImage.COLOR: + case CompositeImage.GRAYSCALE: + return DisplayMode.SINGLE; + + default: + return DisplayMode.FUSED; + } + } + + if ( imp.getNChannels() > 1 ) + return DisplayMode.FUSED; + else + return DisplayMode.SINGLE; } } 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 e72ec5045..6e6b91094 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -22,19 +22,21 @@ package fiji.plugin.trackmate.visualization.bvv; import java.awt.Color; +import java.awt.Dimension; import java.awt.Window; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import javax.swing.SwingUtilities; - import org.joml.Matrix4f; +import org.scijava.ui.config.utils.GuiUtils; +import bdv.tools.InitializeViewerState; import bdv.viewer.animate.TranslationAnimator; +import bvv.core.BigVolumeViewer; +import bvv.core.VolumeViewerFrame; 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.SelectionChangeListener; @@ -57,16 +59,13 @@ public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelB private static final String KEY = "BIGVOLUMEVIEWER"; - private final ImagePlus imp; - - private BvvHandle handle; - private final Map< Spot, StupidMesh > meshMap; + private final BigVolumeViewer bvvInstance; + public TrackMateBVV( final GuiModel guiModel, final ImagePlus imp ) { - super( guiModel, KeyConfigContexts.BIGVOLUMEVIEWER ); - this.imp = imp; + super( guiModel, KeyConfigContexts.BIGVOLUMEVIEWER, bvv.core.KeyConfigContexts.BIGVOLUMEVIEWER ); this.meshMap = new HashMap<>(); final Model model = guiModel.getModel(); @@ -83,26 +82,10 @@ public TrackMateBVV( final GuiModel guiModel, final ImagePlus imp ) displaySettings.listeners().remove( colorUpdater ); selectionModel.removeSelectionChangeListener( refresher ); } ); - } - /** - * 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() - { - this.handle = BVVUtils.createViewer( imp ); - setHandle( handle ); - final VolumeViewerPanel viewer = handle.getViewerPanel(); + this.bvvInstance = BVVUtils.createBvv( guiModel ); + final VolumeViewerPanel viewer = bvvInstance.getViewer(); viewer.setRenderScene( ( gl, data ) -> { if ( guiModel.getDisplaySettings().isSpotVisible() ) { @@ -111,33 +94,45 @@ public void render() final Matrix4f vm = MatrixMath.screen( data.getDCam(), data.getScreenWidth(), data.getScreenHeight(), new Matrix4f() ).mul( view ); final int t = data.getTimepoint(); - final Iterable< Spot > it = guiModel.getModel().getSpots().iterable( t, true ); - it.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm, guiModel.getSelectionModel().getSpotSelection().contains( s ) ) ); + final Iterable< Spot > its = guiModel.getModel().getSpots().iterable( t, true ); + its.forEach( s -> meshMap.computeIfAbsent( s, BVVUtils::createMesh ).draw( gl, pvm, vm, guiModel.getSelectionModel().getSpotSelection().contains( s ) ) ); } } ); + synchronized ( this ) + { + initTransformPending = true; + tryInitTransform( viewer ); + } + + final VolumeViewerFrame frame = bvvInstance.getViewerFrame(); + setWindow( frame ); + GuiUtils.positionWindow( frame, guiModel.getSettings().imp.getWindow() ); } @Override - public void refresh() + public void render() { - if ( handle != null ) - handle.getViewerPanel().requestRepaint(); + bvvInstance.getViewerFrame().setVisible( true ); } @Override - public void clear() + public void refresh() { - // TODO Auto-generated method stub - + if ( bvvInstance != null ) + bvvInstance.getViewer().requestRepaint(); } + @Override + public void clear() + {} + @Override public void centerViewOn( final Spot spot ) { - if ( handle == null ) + if ( bvvInstance == null ) return; - final VolumeViewerPanel panel = handle.getViewerPanel(); + final VolumeViewerPanel panel = bvvInstance.getViewer(); panel.setTimepoint( spot.getFeature( Spot.FRAME ).intValue() ); final AffineTransform3D c = panel.state().getViewerTransform(); @@ -230,7 +225,23 @@ private void updateColor() @Override public Window getWindow() { - final VolumeViewerPanel viewerPanel = handle.getViewerPanel(); - return SwingUtilities.getWindowAncestor( viewerPanel ); + return bvvInstance.getViewerFrame(); + } + + private boolean initTransformPending; + + private synchronized void tryInitTransform( final VolumeViewerPanel viewer ) + { + if ( viewer.getDisplay().getWidth() <= 0 || viewer.getDisplay().getHeight() <= 0 ) + return; + + if ( initTransformPending ) + { + initTransformPending = false; + + final Dimension dim = viewer.getDisplay().getSize(); + final AffineTransform3D viewerTransform = InitializeViewerState.initTransform( dim.width, dim.height, false, viewer.state().snapshot() ); + viewer.state().setViewerTransform( viewerTransform ); + } } } From 2b6ca377d5b2f4a98ba9036443b4146796cc3e2f Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 22:40:02 +0200 Subject: [PATCH 363/371] Don't use a static instance in TrackMateKeymapManager. --- .../visualization/AbstractTrackMateModelJFrameView.java | 3 +-- .../trackmate/visualization/ui/TrackMateKeymapManager.java | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java index 1b90892e0..23b7b2759 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java @@ -43,7 +43,6 @@ import bdv.ui.keymap.Keymap.UpdateListener; import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; -import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; /** * An abstract class for TrackMate views that display content in a @@ -74,7 +73,7 @@ protected AbstractTrackMateModelJFrameView( final GuiModel guiModel, final Strin this.keybindings = new InputActionBindings(); this.triggerbindings = new TriggerBehaviourBindings(); - final Keymap keymap = TrackMateKeymapManager.keymapManager.getForwardSelectedKeymap(); + final Keymap keymap = guiModel.getKeymapManager().getForwardSelectedKeymap(); this.actions = new Actions( keymap.getConfig(), kccs ); actions.install( keybindings, "view" ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java index 7afc7575f..6d4d2f8cb 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java @@ -13,8 +13,6 @@ public class TrackMateKeymapManager extends KeymapManager private static final String KEYMAP_HOME = new File( System.getProperty( "user.home" ), ".trackmate" ).getAbsolutePath(); - public static final TrackMateKeymapManager keymapManager = new TrackMateKeymapManager(); - public TrackMateKeymapManager() { super( KEYMAP_HOME ); From d9d431354659af06b35437face2a8bf62e4f4d56 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 22:40:32 +0200 Subject: [PATCH 364/371] Integrate BVV context in the keymap config page. --- .../java/fiji/plugin/trackmate/gui/WindowManager.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java index 0335be4f8..7b216f382 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java @@ -1,6 +1,7 @@ package fiji.plugin.trackmate.gui; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.ALL_SPOTS_TABLE; +import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.BIGVOLUMEVIEWER; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.HYPERSTACK_DISPLAYER; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.KEY_CONFIG_SCOPE; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACKMATE; @@ -14,8 +15,6 @@ import java.util.List; import java.util.Objects; -import javax.swing.SwingUtilities; - import org.scijava.plugin.Plugin; import org.scijava.ui.behaviour.io.gui.CommandDescriptionProvider; import org.scijava.ui.behaviour.io.gui.CommandDescriptions; @@ -28,7 +27,6 @@ import bdv.ui.keymap.Keymap; import bdv.ui.keymap.KeymapSettingsPage; import bdv.util.InvokeOnEDT; -import bvv.vistools.BvvHandle; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsConfigPage; import fiji.plugin.trackmate.gui.editor.LabkitLauncher; import fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame; @@ -65,7 +63,7 @@ public WindowManager( final GuiModel guiModel ) final Actions globalActions = guiModel.getGlobalActions(); // Preferences dialog - final PreferencesDialog preferencesDialog = new PreferencesDialog( null, keymap, new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE } ); + final PreferencesDialog preferencesDialog = new PreferencesDialog( null, keymap, new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE, BIGVOLUMEVIEWER } ); preferencesDialog.setTitle( "TrackMate Preferences" ); preferencesDialog.setLocationRelativeTo( null ); BigDataViewerActions.toggleDialogAction( globalActions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); @@ -116,8 +114,6 @@ public TrackScheme createTrackScheme() final TrackMateBVV< ? > tbvv = new TrackMateBVV<>( guiModel, imp ); registerView( tbvv ); tbvv.render(); - final BvvHandle bvvHandle = tbvv.getBvvHandle(); - SwingUtilities.getWindowAncestor( bvvHandle.getViewerPanel() ).setLocationRelativeTo( null ); return tbvv; } return null; From d01d64fe0920c63286dd14eabfaa4bda9c6a32ee Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Mon, 10 Aug 2026 22:40:55 +0200 Subject: [PATCH 365/371] Disable BVV button while a BVV is being launched. --- .../trackmate/gui/components/ConfigureViewsPanel.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 f60a66d2a..afe09ce76 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/components/ConfigureViewsPanel.java @@ -521,7 +521,11 @@ private LaunchBVVAction() @Override public void actionPerformed( final ActionEvent e ) { - Threads.run( "Launching BVV thread", () -> windowManager.createBVV() ); + Threads.run( "Launching BVV thread", () -> { + setEnabled( false ); + windowManager.createBVV(); + setEnabled( true ); + } ); } } From 268e613289ba9210c2b8ce90effc75d2ef0cbf04 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 11 Aug 2026 10:52:49 +0200 Subject: [PATCH 366/371] Add BDV and BVV scope to key config discovery. Otherwise they these bindings get erased when we edit a keymap and the BVV views loose the corresponding actions. --- src/main/java/fiji/plugin/trackmate/gui/WindowManager.java | 4 +++- .../trackmate/visualization/ui/TrackMateKeymapManager.java | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java index 7b216f382..3cf9505d0 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java @@ -27,6 +27,7 @@ import bdv.ui.keymap.Keymap; import bdv.ui.keymap.KeymapSettingsPage; import bdv.util.InvokeOnEDT; +import bvv.core.KeyConfigContexts; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsConfigPage; import fiji.plugin.trackmate.gui.editor.LabkitLauncher; import fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame; @@ -63,7 +64,8 @@ public WindowManager( final GuiModel guiModel ) final Actions globalActions = guiModel.getGlobalActions(); // Preferences dialog - final PreferencesDialog preferencesDialog = new PreferencesDialog( null, keymap, new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE, BIGVOLUMEVIEWER } ); + final PreferencesDialog preferencesDialog = new PreferencesDialog( null, keymap, + new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE, BIGVOLUMEVIEWER, KeyConfigContexts.BIGVOLUMEVIEWER } ); preferencesDialog.setTitle( "TrackMate Preferences" ); preferencesDialog.setLocationRelativeTo( null ); BigDataViewerActions.toggleDialogAction( globalActions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java index 6d4d2f8cb..6c13fab3f 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java @@ -24,7 +24,10 @@ public synchronized void discoverCommandDescriptions() final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); final Context context = TMUtils.getContext(); context.inject( builder ); - builder.discoverProviders( KeyConfigContexts.KEY_CONFIG_SCOPE ); + builder.discoverProviders( + KeyConfigContexts.KEY_CONFIG_SCOPE, // TrackMate scope + bvv.core.KeyConfigScopes.BIGVOLUMEVIEWER, // BVV + bdv.KeyConfigScopes.BIGDATAVIEWER ); // BDV, required by BVV setCommandDescriptions( builder.build() ); } } From b53b333cf64a24edadbb7362fb2a9551d42ea43a Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 11 Aug 2026 22:27:00 +0200 Subject: [PATCH 367/371] Add separate BVVKeymapManager for 3D view key bindings Otherwise the global key config page is super crowded. --- .../fiji/plugin/trackmate/gui/GuiModel.java | 9 ++++ .../visualization/bvv/BVVKeymapManager.java | 42 +++++++++++++++++++ .../ui/TrackMateKeymapManager.java | 5 +-- 3 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVKeymapManager.java diff --git a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java index d714c9e54..e60042f5c 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java +++ b/src/main/java/fiji/plugin/trackmate/gui/GuiModel.java @@ -18,6 +18,7 @@ import fiji.plugin.trackmate.util.TMUtils; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SemiAutoTrackingParams; import fiji.plugin.trackmate.visualization.hyperstack.behaviours.semiautotracking.SemiAutoTrackingParamsIO; +import fiji.plugin.trackmate.visualization.bvv.BVVKeymapManager; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; import fiji.plugin.trackmate.visualization.ui.TrackMateActions; import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; @@ -44,6 +45,8 @@ public class GuiModel private final TrackMateKeymapManager keymapManager; + private final BVVKeymapManager bvvKeymapManager; + private final EditorKeymapManager editorKeymapManager; private final AppearanceManager appearanceManager; @@ -78,6 +81,7 @@ public GuiModel( final Model model, final Settings settings, final DisplaySettin // Keymap and actions this.editorKeymapManager = new EditorKeymapManager(); + this.bvvKeymapManager = new BVVKeymapManager(); this.appearanceManager = new AppearanceManager( EDITOR_KEYMAP_HOME ); this.keyPressedManager = new KeyPressedManager(); this.keymapManager = new TrackMateKeymapManager(); @@ -210,6 +214,11 @@ public EditorKeymapManager getEditorKeymapManager() return editorKeymapManager; } + public BVVKeymapManager getBvvKeymapManager() + { + return bvvKeymapManager; + } + public Model getModel() { return model; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVKeymapManager.java b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVKeymapManager.java new file mode 100644 index 000000000..b5e1411e4 --- /dev/null +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVKeymapManager.java @@ -0,0 +1,42 @@ +package fiji.plugin.trackmate.visualization.bvv; + +import java.io.File; + +import org.scijava.Context; +import org.scijava.ui.behaviour.io.gui.CommandDescriptionsBuilder; + +import bdv.ui.keymap.KeymapManager; +import fiji.plugin.trackmate.util.TMUtils; + +/** + * Keymap manager for BigVolumeViewer (BVV) actions in TrackMate. + *

    + * This manager is separate from the global TrackMate keymap and handles + * BVV-specific key bindings (navigation, rotation, zoom, etc.). + * It uses its own subdirectory to avoid conflicts with other keymap managers. + */ +public class BVVKeymapManager extends KeymapManager +{ + + private static final String KEYMAP_HOME = new File( + new File( System.getProperty( "user.home" ), ".trackmate" ), "bvv" + ).getAbsolutePath(); + + public BVVKeymapManager() + { + super( KEYMAP_HOME ); + } + + @Override + public synchronized void discoverCommandDescriptions() + { + final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); + final Context context = TMUtils.getContext(); + context.inject( builder ); + // Discover BVV-specific command descriptions + builder.discoverProviders( + bvv.core.KeyConfigScopes.BIGVOLUMEVIEWER, + bdv.KeyConfigScopes.BIGDATAVIEWER ); + setCommandDescriptions( builder.build() ); + } +} diff --git a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java index 6c13fab3f..6d4d2f8cb 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/ui/TrackMateKeymapManager.java @@ -24,10 +24,7 @@ public synchronized void discoverCommandDescriptions() final CommandDescriptionsBuilder builder = new CommandDescriptionsBuilder(); final Context context = TMUtils.getContext(); context.inject( builder ); - builder.discoverProviders( - KeyConfigContexts.KEY_CONFIG_SCOPE, // TrackMate scope - bvv.core.KeyConfigScopes.BIGVOLUMEVIEWER, // BVV - bdv.KeyConfigScopes.BIGDATAVIEWER ); // BDV, required by BVV + builder.discoverProviders( KeyConfigContexts.KEY_CONFIG_SCOPE ); setCommandDescriptions( builder.build() ); } } From 331c9591b16bc48f61decd2e79ef986d661579f8 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 11 Aug 2026 22:28:53 +0200 Subject: [PATCH 368/371] Add BVV keymap page to preferences dialog --- .../java/fiji/plugin/trackmate/gui/WindowManager.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java index 3cf9505d0..343f4dafd 100644 --- a/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java +++ b/src/main/java/fiji/plugin/trackmate/gui/WindowManager.java @@ -1,7 +1,6 @@ package fiji.plugin.trackmate.gui; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.ALL_SPOTS_TABLE; -import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.BIGVOLUMEVIEWER; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.HYPERSTACK_DISPLAYER; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.KEY_CONFIG_SCOPE; import static fiji.plugin.trackmate.visualization.ui.KeyConfigContexts.TRACKMATE; @@ -27,7 +26,6 @@ import bdv.ui.keymap.Keymap; import bdv.ui.keymap.KeymapSettingsPage; import bdv.util.InvokeOnEDT; -import bvv.core.KeyConfigContexts; import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsConfigPage; import fiji.plugin.trackmate.gui.editor.LabkitLauncher; import fiji.plugin.trackmate.gui.editor.labkit.component.TMLabKitFrame; @@ -65,15 +63,17 @@ public WindowManager( final GuiModel guiModel ) // Preferences dialog final PreferencesDialog preferencesDialog = new PreferencesDialog( null, keymap, - new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE, BIGVOLUMEVIEWER, KeyConfigContexts.BIGVOLUMEVIEWER } ); + new String[] { TRACKMATE, HYPERSTACK_DISPLAYER, TRACKSCHEME, ALL_SPOTS_TABLE, TRACK_TABLE } ); + preferencesDialog.setTitle( "TrackMate Preferences" ); preferencesDialog.setLocationRelativeTo( null ); BigDataViewerActions.toggleDialogAction( globalActions, preferencesDialog, BigDataViewerActions.PREFERENCES_DIALOG, BigDataViewerActions.PREFERENCES_DIALOG_KEYS ); preferencesDialog.addPage( new DisplaySettingsConfigPage( "Display settings", guiModel.getDisplaySettingsManager() ) ); preferencesDialog.addPage( new KeymapSettingsPage( "Global keymap", keymapManager, keymapManager.getCommandDescriptions() ) ); - preferencesDialog.addPage( new KeymapSettingsPage( "Editor keymap", guiModel.getEditorKeymapManager(), guiModel.getEditorKeymapManager().getCommandDescriptions() ) ); + preferencesDialog.addPage( new KeymapSettingsPage( "Spot editor keymap", guiModel.getEditorKeymapManager(), guiModel.getEditorKeymapManager().getCommandDescriptions() ) ); + preferencesDialog.addPage( new KeymapSettingsPage( "3D View keymap", guiModel.getBvvKeymapManager(), guiModel.getBvvKeymapManager().getCommandDescriptions() ) ); preferencesDialog.addPage( new SpotEditToolSettingsPage( "Semi-auto tracking", guiModel.getSemiAutoTrackingParams() ) ); - preferencesDialog.addPage( new AppearanceSettingsPage( "Editor appearance", guiModel.getAppearanceManager() ) ); + preferencesDialog.addPage( new AppearanceSettingsPage( "BDVs appearance", guiModel.getAppearanceManager() ) ); } public HyperStackDisplayer createHyperStackDisplayer() From bdd72b854981520b960e4522bd50a1c50ace1d23 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 11 Aug 2026 22:30:46 +0200 Subject: [PATCH 369/371] Wire the BVV view to use its own keymap manager --- .../visualization/AbstractTrackMateModelBvvView.java | 8 +++++--- .../plugin/trackmate/visualization/bvv/BVVUtils.java | 10 +++++----- .../trackmate/visualization/bvv/TrackMateBVV.java | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java index 5cfc2a563..66f6abea5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelBvvView.java @@ -14,6 +14,7 @@ import bdv.ui.keymap.Keymap; import bdv.ui.keymap.Keymap.UpdateListener; +import bdv.ui.keymap.KeymapManager; import bvv.core.VolumeViewerFrame; import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.visualization.ui.KeyConfigContexts; @@ -25,13 +26,13 @@ public abstract class AbstractTrackMateModelBvvView extends AbstractTrackMateMod protected final Actions actions; - protected AbstractTrackMateModelBvvView( final GuiModel guiModel, final String... keyConfigContexts ) + protected AbstractTrackMateModelBvvView( final GuiModel guiModel, final KeymapManager keymapManager, final String... keyConfigContexts ) { super( guiModel ); final Set< String > c = new LinkedHashSet<>( Arrays.asList( KeyConfigContexts.TRACKMATE ) ); c.addAll( Arrays.asList( keyConfigContexts ) ); final String[] kccs = c.toArray( new String[] {} ); - final Keymap keymap = guiModel.getKeymapManager().getForwardSelectedKeymap(); + final Keymap keymap = keymapManager.getForwardSelectedKeymap(); this.behaviours = new Behaviours( keymap.getConfig(), kccs ); this.actions = new Actions( keymap.getConfig(), kccs ); } @@ -45,7 +46,7 @@ protected void setWindow( final VolumeViewerFrame viewerFrame ) actions.install( keybindings, "view2" ); behaviours.install( triggerbindings, "view2" ); - final Keymap keymap = guiModel.getKeymapManager().getForwardSelectedKeymap(); + final Keymap keymap = guiModel.getBvvKeymapManager().getForwardSelectedKeymap(); final UpdateListener updateListener = () -> { behaviours.updateKeyConfig( keymap.getConfig() ); actions.updateKeyConfig( keymap.getConfig() ); @@ -56,6 +57,7 @@ protected void setWindow( final VolumeViewerFrame viewerFrame ) final MouseAndKeyHandler mouseAndKeyHandler = new MouseAndKeyHandler(); mouseAndKeyHandler.setInputMap( triggerbindings.getConcatenatedInputTriggerMap() ); mouseAndKeyHandler.setBehaviourMap( triggerbindings.getConcatenatedBehaviourMap() ); + mouseAndKeyHandler.setKeypressManager( guiModel.getKeyPressedManager(), viewerFrame.getViewerPanel().getDisplay().getComponent() ); // Register global actions, if any. final Actions globalActions = guiModel.getGlobalActions(); 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 da55f96f0..580dec9b2 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/BVVUtils.java @@ -43,7 +43,6 @@ import fiji.plugin.trackmate.SpotMesh; import fiji.plugin.trackmate.gui.GuiModel; import fiji.plugin.trackmate.util.TMUtils; -import fiji.plugin.trackmate.visualization.ui.TrackMateKeymapManager; import ij.CompositeImage; import ij.ImagePlus; import ij.measure.Calibration; @@ -85,17 +84,18 @@ public static final < T extends RealType< T > > BigVolumeViewer createBvv( final */ final ImagePlus imp = guiModel.getSettings().imp; - final TrackMateKeymapManager keymapManager = guiModel.getKeymapManager(); - final InputTriggerConfig config = keymapManager.getForwardSelectedKeymap().getConfig(); + final BVVKeymapManager bvvKeymapManager = guiModel.getBvvKeymapManager(); + final InputTriggerConfig config = bvvKeymapManager.getForwardSelectedKeymap().getConfig(); final AppearanceManager appearanceManager = guiModel.getAppearanceManager(); - + final VolumeViewerOptions options = VolumeViewerOptions.options() .inputTriggerConfig( config ) .maxAllowedStepInVoxels( 0 ) .renderWidth( 1024 ) .renderHeight( 1024 ) - .keymapManager( keymapManager ) + .keymapManager( bvvKeymapManager ) .appearanceManager( appearanceManager ) + .shareKeyPressedEvents( guiModel.getKeyPressedManager() ) .height( 512 ) .width( 512 ); 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 6e6b91094..b94df3ea5 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/bvv/TrackMateBVV.java @@ -65,7 +65,7 @@ public class TrackMateBVV< T extends Type< T > > extends AbstractTrackMateModelB public TrackMateBVV( final GuiModel guiModel, final ImagePlus imp ) { - super( guiModel, KeyConfigContexts.BIGVOLUMEVIEWER, bvv.core.KeyConfigContexts.BIGVOLUMEVIEWER ); + super( guiModel, guiModel.getBvvKeymapManager(), KeyConfigContexts.BIGVOLUMEVIEWER, bvv.core.KeyConfigContexts.BIGVOLUMEVIEWER ); this.meshMap = new HashMap<>(); final Model model = guiModel.getModel(); From c001f4127abb81ad65ae5598d9742d66bc13e480 Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 11 Aug 2026 22:31:08 +0200 Subject: [PATCH 370/371] Share KeyPressedManager across windows --- .../visualization/AbstractTrackMateModelJFrameView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java index 23b7b2759..bf7a2fa40 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/AbstractTrackMateModelJFrameView.java @@ -118,6 +118,7 @@ private void attachKeybindings( final JComponent component ) { SwingUtilities.replaceUIActionMap( component, keybindings.getConcatenatedActionMap() ); SwingUtilities.replaceUIInputMap( component, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, keybindings.getConcatenatedInputMap() ); + mouseAndKeyHandler.setKeypressManager( guiModel.getKeyPressedManager(), component ); component.addKeyListener( mouseAndKeyHandler ); component.addMouseListener( mouseAndKeyHandler ); component.addMouseMotionListener( mouseAndKeyHandler ); From bc5d960d9ca9dc8f0a399e6f79b4296baa6f67fd Mon Sep 17 00:00:00 2001 From: Jean-Yves Tinevez Date: Tue, 11 Aug 2026 22:33:10 +0200 Subject: [PATCH 371/371] A demo to reproduce the nighmare with the bvv keybindings. Help of Claude code there. --- .../visualization/bvv/BVVKeymapErrorDemo.java | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/test/java/fiji/plugin/trackmate/visualization/bvv/BVVKeymapErrorDemo.java diff --git a/src/test/java/fiji/plugin/trackmate/visualization/bvv/BVVKeymapErrorDemo.java b/src/test/java/fiji/plugin/trackmate/visualization/bvv/BVVKeymapErrorDemo.java new file mode 100644 index 000000000..2a5e83933 --- /dev/null +++ b/src/test/java/fiji/plugin/trackmate/visualization/bvv/BVVKeymapErrorDemo.java @@ -0,0 +1,119 @@ +package fiji.plugin.trackmate.visualization.bvv; + +import java.awt.EventQueue; +import java.io.File; + +import org.scijava.ui.behaviour.io.gui.CommandDescriptions; + +import bdv.ui.keymap.Keymap; +import fiji.plugin.trackmate.Model; +import fiji.plugin.trackmate.Settings; +import fiji.plugin.trackmate.gui.GuiModel; +import fiji.plugin.trackmate.gui.WindowManager; +import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings; +import fiji.plugin.trackmate.io.TmXmlReader; +import ij.ImageJ; +import ij.ImagePlus; + +/** + * Demo class to reproduce the BVV keymap error issue. + */ +public class BVVKeymapErrorDemo +{ + + public static void main( final String[] args ) + { + try + { + fiji.plugin.trackmate.gui.GuiUtils.setSystemLookAndFeel(); + ImageJ.main( args ); + Thread.sleep( 1000 ); + + // Run the demo + EventQueue.invokeLater( () -> runDemo() ); + } + catch ( final Throwable t ) + { + t.printStackTrace(); + } + } + + private static void runDemo() + { + System.out.println( "=== BVV Keymap Error Demo ===" ); + System.out.println( "" ); + System.out.println( "This demo shows the 'Could not assign InputTrigger' errors" ); + System.out.println( "that occur when the BVV keymap is missing or corrupted." ); + System.out.println( "" ); + + // Load a TrackMate file (3D data required for BVV) + final String filePath = "samples/CElegans3D-smoothed-mask-orig-02.xml"; + final TmXmlReader reader = new TmXmlReader( new File( filePath ) ); + if ( !reader.isReadingOk() ) + { + System.err.println( "Error reading TrackMate file: " + reader.getErrorMessage() ); + return; + } + + final Model model = reader.getModel(); + final ImagePlus imp = reader.readImage(); + final Settings settings = reader.readSettings( imp ); + final DisplaySettings ds = reader.getDisplaySettings(); + final GuiModel guiModel = new GuiModel( model, settings, ds ); + final WindowManager wm = guiModel.getWindowManager(); + + System.out.println( "=== Step 1: Check initial BVV keymap state ===" ); + final BVVKeymapManager bvvKeymapManager = guiModel.getBvvKeymapManager(); + System.out.println( "BVV Keymap user styles: " + bvvKeymapManager.getUserStyles().size() ); + System.out.println( "BVV Keymap selected: " + bvvKeymapManager.getForwardSelectedKeymap().getName() ); + + // Check if keymap has BVV bindings + final var config = bvvKeymapManager.getForwardSelectedKeymap().getConfig(); + final var rotateLeftTriggers = config.getInputs( "rotate left", "bvv" ); + System.out.println( "rotate left triggers in bvv context: " + rotateLeftTriggers ); + System.out.println( "" ); + + if ( rotateLeftTriggers.isEmpty() ) + { + System.out.println( ">>> WARNING: BVV keymap is empty! Errors will occur. <<<" ); + System.out.println( "" ); + } + else + { + System.out.println( ">>> BVV keymap has bindings. To see errors, the keymap needs to be corrupted. <<<" ); + System.out.println( "" ); + } + + System.out.println( "=== Step 2: Launch BVV view ===" ); + System.out.println( "Watch for 'Could not assign InputTrigger' errors below:" ); + System.out.println( "---" ); + + wm.createBVV().render(); + + System.out.println( "---" ); + + // Simulate what happens when PreferencesDialog is opened + // This is where the errors actually occur - when the VisualEditorPanel + // tries to populate the table with all commands for the BVV context + System.out.println( "" ); + System.out.println( "=== Step 3: Simulate PreferencesDialog opening ===" ); + System.out.println( "The PreferencesDialog creates a VisualEditorPanel which" ); + System.out.println( "calls InputTriggerConfig.put() for all BVV commands." ); + System.out.println( "" ); + + // Get the BVV keymap config and simulate what the PreferencesDialog does + bvvKeymapManager.getForwardSelectedKeymap().getConfig(); + final CommandDescriptions bvvDescriptions = bvvKeymapManager.getCommandDescriptions(); + + System.out.println( "BVV command descriptions discovered: " + + (bvvDescriptions != null ? bvvDescriptions.toString() : "null") ); + + System.out.println( "=== Changing keymap ===" ); + final BVVKeymapManager keymapManager = guiModel.getBvvKeymapManager(); + final Keymap keymap = keymapManager.getUserStyles().get( 0 ); + keymapManager.setSelectedStyle( keymap ); + System.out.println( "" ); + System.out.println( "=== Demo complete ===" ); + System.out.println( "" ); + } +}