Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate LabKit in TrackMate to edit segmentation results #302

Closed
wants to merge 53 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
dc27c90
Add LbKit as a dependency.
tinevez Sep 1, 2023
4bbf665
A LabKit launcher made to edit spots in the current frame.
tinevez Sep 1, 2023
43f8687
Add a button to launch LabKit in the configure views panel of the wiz…
tinevez Sep 1, 2023
c1e6c09
Do not use a yet non-existing method in MaskUtils.
tinevez Jul 7, 2024
bb139c2
Wrap the button line of the configure views panel.
tinevez Sep 2, 2023
2815d63
Rework and improve the import method of the spot editor.
tinevez Sep 2, 2023
0fd4193
Utility to know whether a class is present at runtime.
tinevez Sep 2, 2023
a73f832
Make the editor button visible only if LabKit is available.
tinevez Sep 2, 2023
d7b25d6
After editing in LbKit, message the user
tinevez Sep 3, 2023
6c23066
Copy the channel color, min & max, etc to the editor BDV window.
tinevez Sep 3, 2023
cf486c4
Directly store the index image as previous labels.
tinevez Sep 3, 2023
73d096a
Modifed and new spots get a QUALITY value of -1.
tinevez Sep 4, 2023
9de374b
Fix editing spots in 3D.
tinevez Sep 12, 2023
3f7dc78
ImpBdvShowable wraps around the ImgPlusBdvShowable.
tinevez Sep 17, 2023
e263f83
The Labkit editor can be launched on all time-points at once.
tinevez Sep 17, 2023
3757a83
Add tooltip for the editor button.
tinevez Sep 17, 2023
f9c0b73
In Labkit editor, labels receive name and color from spots.
tinevez Sep 17, 2023
ee3b86a
Better editor frame title.
tinevez Sep 17, 2023
8aaf763
Properly handle 2D and 3D with or without time in the editor.
tinevez Sep 19, 2023
544b3df
Always start the editor in FUSED mode,
tinevez Sep 19, 2023
48594c2
Don't crash when editing on images with 1 channel.
tinevez Sep 22, 2023
a788cf1
If we crash when launching the editor, unfreeze TrackMate.
tinevez Sep 22, 2023
b503a63
Store labels on ints, not on shorts.
tinevez Sep 22, 2023
4d3a326
Fix reimporting labels with large IDs in 2D.
tinevez Jul 7, 2024
39e16fc
Fix javadoc errors.
tinevez Oct 8, 2023
2d9edce
Make the spot editor work with 1-timepoint images.
tinevez Feb 8, 2024
e4eb1bc
Fix import of spots in LabKit for 2D single time-point images.
tinevez Mar 11, 2024
8c9961b
Protect against weird error caused by empty labels.
tinevez Mar 11, 2024
e65687c
setFont() utility.
tinevez Nov 28, 2023
1d95bea
The detection preview is cancelable.
tinevez Mar 28, 2024
0bd86ec
Fix preview panel size in free-size layouts.
tinevez May 21, 2024
d6fdfb5
Detection preview panel tweak.
tinevez Jul 9, 2024
3b93eed
Merge remote-tracking branch 'origin/master' into labkit-main
tinevez Jul 10, 2024
de5de59
Fixed a difficult bug with the LabKit editor reimporter.
tinevez Jul 10, 2024
2c56943
Another fix for the LabKit reimporter bug.
tinevez Jul 10, 2024
fc19713
Label image to spots: also returns the label corresponding to created…
tinevez Jul 10, 2024
f886b65
Do not execute the LabKit reimporter in the AWT thread.
tinevez Jul 10, 2024
d1ca968
Simplify the LabKit importer.
tinevez Jul 11, 2024
aa9dc52
In LabKit editor, offer to simplify the contour of modified spots.
tinevez Jul 11, 2024
73c85db
Let the user edit a ROI in the image with the LabKit editor.
tinevez Jul 12, 2024
186520e
Fix panning not working when the TrackMate tool is selected.
tinevez Jul 17, 2024
28666c3
In LabKit editor, import spots at the border of the image.
tinevez Jul 18, 2024
faa87b9
Fix bug in LabKit editor: avoid spot ID and label conflicts.
tinevez Jul 18, 2024
8ccd3ee
Custom LabKit frame and segmentation component.
tinevez Jul 19, 2024
2f9583e
Fix another bug with the LabKit importer.
tinevez Jul 19, 2024
9d3c58e
Fixed one more bug with the labkit importer.
tinevez Jul 22, 2024
5e9643d
Don't crash when displaying an empty model.
tinevez Sep 30, 2024
cff5746
Compatibility with command lines args that ends in '='
tinevez Oct 1, 2024
a030b71
A new CLI configurator for executables that live in a conda env.
tinevez Oct 1, 2024
6cd0cc3
Smooth icon when resizing.
tinevez Oct 2, 2024
a33a1c3
In the CLI configurator, when a key is null, let it be null.
tinevez Oct 2, 2024
a23c307
Don't set quality value for preview to - infinity.
tinevez Oct 4, 2024
b3f4d87
In the CLI GUI builder, avoid long paths deforming new panels.
tinevez Oct 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@
<groupId>sc.fiji</groupId>
<artifactId>fiji-lib</artifactId>
</dependency>
<dependency>
<groupId>sc.fiji</groupId>
<artifactId>labkit-ui</artifactId>
<version>0.3.12-SNAPSHOT</version>
</dependency>

<!-- ImageJ dependencies -->
<dependency>
Expand Down Expand Up @@ -208,10 +213,10 @@
<groupId>com.github.vlsi.mxgraph</groupId>
<artifactId>jgraphx</artifactId>
</dependency>
<dependency>
<!--<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependency> -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/fiji/plugin/trackmate/LoadTrackMatePlugIn.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import static fiji.plugin.trackmate.gui.Icons.TRACKMATE_ICON;

import java.awt.Color;
import java.awt.Dimension;
import java.io.File;

import javax.swing.JFrame;
Expand Down Expand Up @@ -65,8 +66,6 @@ public class LoadTrackMatePlugIn extends TrackMatePlugIn
@Override
public void run( final String filePath )
{
// GuiUtils.setSystemLookAndFeel();

final Logger logger = Logger.IJ_LOGGER;
File file;
if ( null == filePath || filePath.length() == 0 )
Expand Down Expand Up @@ -215,6 +214,8 @@ public void run( final String filePath )
frame.setIconImage( TRACKMATE_ICON.getImage() );
GuiUtils.positionWindow( frame, settings.imp.getWindow() );
frame.setVisible( true );
final Dimension size = frame.getSize();
frame.setSize( size.width, size.height + 1 );

// Text
final LogPanelDescriptor2 logDescriptor = ( LogPanelDescriptor2 ) sequence.logDescriptor();
Expand All @@ -230,7 +231,7 @@ public void run( final String filePath )
final String warning = reader.getErrorMessage();
if ( !warning.isEmpty() )
{
logger2.log( "Warnings occured during reading the file:\n"
logger2.log( "Warnings occurred during reading the file:\n"
+ "--------------------\n"
+ warning
+ "--------------------\n",
Expand Down Expand Up @@ -292,11 +293,12 @@ protected TmXmlReader createReader( final File lFile )

public static void main( final String[] args )
{
GuiUtils.setSystemLookAndFeel();
ImageJ.main( args );
final LoadTrackMatePlugIn plugIn = new LoadTrackMatePlugIn();
plugIn.run( null );
// plugIn.run( null );
// plugIn.run( "samples/FakeTracks.xml" );
// plugIn.run( "samples/MAX_Merged.xml" );
plugIn.run( "samples/MAX_Merged.xml" );
// plugIn.run( "c:/Users/tinevez/Development/TrackMateWS/TrackMate-Cellpose/samples/R2_multiC.xml" );
// plugIn.run( "/Users/tinevez/Desktop/230901_DeltaRcsB-ZipA-mCh_timestep5min_Stage9_reg/230901_DeltaRcsB-ZipA-mCh_timestep5min_Stage9_reg_merge65.xml" );
}
Expand Down
171 changes: 122 additions & 49 deletions src/main/java/fiji/plugin/trackmate/detection/MaskUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,23 @@

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.SpotRoi;
import fiji.plugin.trackmate.util.SpotUtil;
import fiji.plugin.trackmate.util.Threads;
import ij.ImagePlus;
import ij.gui.PolygonRoi;
import ij.measure.Measurements;
import ij.process.FloatPolygon;
import net.imagej.ImgPlus;
import net.imagej.axis.Axes;
import net.imagej.axis.AxisType;
import net.imglib2.Interval;
import net.imglib2.IterableInterval;
import net.imglib2.RandomAccess;
import net.imglib2.RandomAccessible;
import net.imglib2.RandomAccessibleInterval;
Expand All @@ -45,15 +51,13 @@
import net.imglib2.histogram.Real1dBinMapper;
import net.imglib2.img.Img;
import net.imglib2.img.ImgFactory;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.roi.labeling.ImgLabeling;
import net.imglib2.roi.labeling.LabelRegion;
import net.imglib2.roi.labeling.LabelRegionCursor;
import net.imglib2.roi.labeling.LabelRegions;
import net.imglib2.type.BooleanType;
import net.imglib2.type.logic.BitType;
import net.imglib2.type.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;
Expand Down Expand Up @@ -428,7 +432,7 @@ public static < T extends RealType< T >, R extends RealType< R > > List< Spot >
* the image in which to read the quality value.
* @return a list of spots, with ROI.
*/
public static final < T extends RealType< T >, S extends NumericType< S > > List< Spot > fromThresholdWithROI(
public static final < T extends RealType< T >, S extends RealType< S > > List< Spot > fromThresholdWithROI(
final RandomAccessible< T > input,
final Interval interval,
final double[] calibration,
Expand All @@ -439,15 +443,15 @@ public static final < T extends RealType< T >, S extends NumericType< S > > List
{
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 );
}

/**
* Creates spots <b>with ROIs</b> from a <b>2D</b> label image. The quality
* value is read from a secondary image, byt taking the max value in each
* value is read from a secondary image, by taking the max value in each
* ROI.
*
* @param <R>
Expand All @@ -468,7 +472,51 @@ public static final < T extends RealType< T >, S extends NumericType< S > > List
* the image in which to read the quality value.
* @return a list of spots, with ROI.
*/
public static < R extends IntegerType< R >, S extends NumericType< S > > List< Spot > fromLabelingWithROI(
public static < R extends IntegerType< R >, S extends RealType< S > > List< Spot > fromLabelingWithROI(
final ImgLabeling< Integer, R > labeling,
final Interval interval,
final double[] calibration,
final boolean simplify,
final RandomAccessibleInterval< S > qualityImage )
{
final Map< Integer, List< Spot > > map = fromLabelingWithROIMap( 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 <b>with ROIs</b> from a <b>2D</b> label image. The quality
* value is read from a secondary image, by taking the max value in each
* ROI.
* <p>
* 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 <R>
* the type that backs-up the labeling.
* @param <S>
* 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 <code>true</code> the polygon will be post-processed to be
* smoother and contain less points.
* @param qualityImage
* the image in which to read the quality value.
* @return a map linking the label integer value to the list of spots, with
* ROI, it corresponds to.
*/
public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > fromLabelingWithROIMap(
final ImgLabeling< Integer, R > labeling,
final Interval interval,
final double[] calibration,
Expand All @@ -480,9 +528,14 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S

final LabelRegions< Integer > regions = new LabelRegions< Integer >( labeling );

// Parse regions to create polygons on boundaries.
final List< Polygon > polygons = new ArrayList<>( regions.getExistingLabels().size() );
/*
* Map of label in the label image to a collection of polygons around
* this label. Because 1 polygon correspond to 1 connected component,
* there might be several polygons for a label.
*/
final Map< Integer, List< Polygon > > polygonsMap = new HashMap<>( regions.getExistingLabels().size() );
final Iterator< LabelRegion< Integer > > iterator = regions.iterator();
// Parse regions to create polygons on boundaries.
while ( iterator.hasNext() )
{
final LabelRegion< Integer > region = iterator.next();
Expand All @@ -492,55 +545,75 @@ public static < R extends IntegerType< R >, S extends NumericType< S > > List< S
for ( final Polygon polygon : pp )
polygon.translate( ( int ) region.min( 0 ), ( int ) region.min( 1 ) );

polygons.addAll( pp );
final Integer label = region.getLabel();
polygonsMap.put( label, pp );
}

// Quality image.
final List< Spot > spots = new ArrayList<>( polygons.size() );
final ImagePlus qualityImp = ( null == qualityImage )
? null
: ImageJFunctions.wrap( qualityImage, "QualityImage" );

// Storage for results.
final Map< Integer, List< Spot > > output = new HashMap<>( polygonsMap.size() );

// Simplify them and compute a quality.
for ( final Polygon polygon : polygons )
for ( final Integer label : polygonsMap.keySet() )
{
final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON );
final List< Spot > spots = new ArrayList<>( polygonsMap.size() );
output.put( label, spots );

final List< Polygon > polygons = polygonsMap.get( label );
for ( final Polygon polygon : polygons )
{
final PolygonRoi roi = new PolygonRoi( polygon, PolygonRoi.POLYGON );

// Create Spot ROI.
final PolygonRoi fRoi;
if ( simplify )
fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE );
else
fRoi = roi;
// Create Spot ROI.
final PolygonRoi fRoi;
if ( simplify )
fRoi = simplify( roi, SMOOTH_INTERVAL, DOUGLAS_PEUCKER_MAX_DISTANCE );
else
fRoi = roi;

// Don't include ROIs that have been shrunk to < 1 pixel.
if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. )
continue;
// Don't include ROIs that have been shrunk to < 1 pixel.
if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. )
continue;

// 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 );
}

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. );

spots.add( SpotRoi.createSpot( xpoly, ypoly, quality ) );
// Measure quality.
final double quality;
if ( null == qualityImage )
{
quality = fRoi.getStatistics().area;
}
else
{
final String name = "QualityImage";
final AxisType[] axes = new AxisType[] { Axes.X, Axes.Y };
final double[] cal = new double[] { calibration[ 0 ], calibration[ 1 ] };
final String[] units = new String[] { "unitX", "unitY" };
final ImgPlus< S > qualityImgPlus = new ImgPlus<>( ImgPlus.wrapToImg( qualityImage ), name, axes, cal, units );
final IterableInterval< S > iterable = SpotUtil.iterable( spot, qualityImgPlus );
double max = Double.NEGATIVE_INFINITY;
for ( final S s : iterable )
{
final double val = s.getRealDouble();
if ( val > max )
max = val;
}
quality = max;
}
spot.putFeature( Spot.QUALITY, quality );
spots.add( spot );
}
}
return spots;
return output;
}

private static final double distanceSquaredBetweenPoints( final double vx, final double vy, final double wx, final double wy )
Expand Down Expand Up @@ -636,7 +709,7 @@ private static final void douglasPeucker( final List< double[] > list, final int
*/
public static final List< double[] > douglasPeucker( final List< double[] > list, final double epsilon )
{
final List< double[] > resultList = new ArrayList< >();
final List< double[] > resultList = new ArrayList<>();
douglasPeucker( list, 0, list.size(), epsilon, resultList );
return resultList;
}
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/fiji/plugin/trackmate/gui/GuiUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ public static final void selectAllOnFocus( final JTextField tf )
tf.addFocusListener( selectAllFocusListener );
}

public static final void setFont( final JComponent panel, final Font font )
{
for ( final Component c : panel.getComponents() )
c.setFont( font );
}

/**
* Returns the black color or white color depending on the specified
* background color, to ensure proper readability of the text on said
Expand Down Expand Up @@ -394,7 +400,7 @@ public static final ImageIcon scaleImage( final ImageIcon icon, final int w, fin
nw = ( icon.getIconWidth() * nh ) / icon.getIconHeight();
}

return new ImageIcon( icon.getImage().getScaledInstance( nw, nh, Image.SCALE_DEFAULT ) );
return new ImageIcon( icon.getImage().getScaledInstance( nw, nh, Image.SCALE_SMOOTH ) );
}

public static URL getResource( final String name, final Class< ? > clazz )
Expand Down
Loading