diff --git a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java index faaad638a..25890c4ae 100644 --- a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java +++ b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporter.java @@ -38,9 +38,11 @@ import fiji.plugin.trackmate.SelectionModel; 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.util.SpotUtil; import fiji.plugin.trackmate.util.TMUtils; +import fiji.plugin.trackmate.visualization.GlasbeyLut; import ij.ImagePlus; import net.imagej.ImgPlus; import net.imagej.axis.Axes; @@ -62,12 +64,11 @@ public class LabelImgExporter extends AbstractTMAction public static final String INFO_TEXT = "" + "This action creates a label image from the tracking results. " + "

" - + "A new 16-bit image is generated, of same dimension and size that " - + "of the input image. The label image has one channel, with black baground (0 value) " + + "A new 32-bit image is generated, of same dimension and size that " + + "of the input image. The label image has one channel, with black background (0 value) " + "everywhere, except where there are spots. Each spot is painted with " - + "a uniform integer value equal to the trackID it belongs to. " - + "Spots that do not belong to tracks are painted with a unique integer " - + "larger than the last trackID in the dataset. " + + "a uniform integer value, configurable to be the spot ID, the ID of the " + + "track it belongs to, or a simple index, unique to the frame or to the movie. " + "

" + "Only visible spots are painted. " + ""; @@ -85,7 +86,7 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo final boolean exportSpotsAsDots; final boolean exportTracksOnly; - final boolean useSpotIDsAsLabels; + final LabelIdPainting labelIdPainting; if ( gui != null ) { final LabelImgExporterPanel panel = new LabelImgExporterPanel(); @@ -102,20 +103,20 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo exportSpotsAsDots = panel.isExportSpotsAsDots(); exportTracksOnly = panel.isExportTracksOnly(); - useSpotIDsAsLabels = panel.isUseSpotIDsAsLabels();; + labelIdPainting = panel.labelIdPainting(); } else { exportSpotsAsDots = false; exportTracksOnly = false; - useSpotIDsAsLabels = false; + labelIdPainting = LabelIdPainting.LABEL_IS_INDEX_MOVIE_UNIQUE; } /* * Generate label image. */ - createLabelImagePlus( trackmate, exportSpotsAsDots, exportTracksOnly, useSpotIDsAsLabels ,logger ).show(); + createLabelImagePlus( trackmate, exportSpotsAsDots, exportTracksOnly, labelIdPainting, logger ).show(); } /** @@ -131,17 +132,15 @@ public void execute( final TrackMate trackmate, final SelectionModel selectionMo * 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 of ellipsoids. + * 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 useSpotIDsAsLabels - * if true, the label mask images will contain - * the spot ID. If false, the label mask images - * will contain the track ID. + * @param labeIdPainting + * specifies how to paint the label ID of spots. * * @return a new {@link ImagePlus}. */ @@ -149,10 +148,10 @@ public static final ImagePlus createLabelImagePlus( final TrackMate trackmate, final boolean exportSpotsAsDots, final boolean exportTracksOnly, - final boolean useSpotIDsAsLabels + final LabelIdPainting labeIdPainting ) { - return createLabelImagePlus( trackmate, exportSpotsAsDots, exportTracksOnly, useSpotIDsAsLabels, Logger.VOID_LOGGER ); + return createLabelImagePlus( trackmate, exportSpotsAsDots, exportTracksOnly, labeIdPainting, Logger.VOID_LOGGER ); } /** @@ -175,10 +174,8 @@ public static final ImagePlus createLabelImagePlus( * 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 useSpotIDsAsLabels - * if true, the label mask images will contain - * the spot ID. If false, the label mask images - * will contain the track ID. + * @param labelIdPainting + * specifies how to paint the label ID of spots. * @param logger * a {@link Logger} instance, to report progress of the export * process. @@ -189,10 +186,10 @@ public static final ImagePlus createLabelImagePlus( final TrackMate trackmate, final boolean exportSpotsAsDots, final boolean exportTracksOnly, - final boolean useSpotIDsAsLabels, + final LabelIdPainting labelIdPainting, final Logger logger ) { - return createLabelImagePlus( trackmate.getModel(), trackmate.getSettings().imp, exportSpotsAsDots, exportTracksOnly, useSpotIDsAsLabels, logger ); + return createLabelImagePlus( trackmate.getModel(), trackmate.getSettings().imp, exportSpotsAsDots, exportTracksOnly, labelIdPainting, logger ); } /** @@ -215,10 +212,8 @@ public static final ImagePlus createLabelImagePlus( * 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 useSpotIDsAsLabels - * if true, the label mask images will contain - * the spot ID. If false, the label mask images - * will contain the track ID. + * @param labelIdPainting + * specifies how to paint the label ID of spots. * * @return a new {@link ImagePlus}. */ @@ -227,9 +222,9 @@ public static final ImagePlus createLabelImagePlus( final ImagePlus imp, final boolean exportSpotsAsDots, final boolean exportTracksOnly, - final boolean useSpotIDsAsLabels ) + final LabelIdPainting labelIdPainting ) { - return createLabelImagePlus( model, imp, exportSpotsAsDots, exportTracksOnly, useSpotIDsAsLabels, Logger.VOID_LOGGER ); + return createLabelImagePlus( model, imp, exportSpotsAsDots, exportTracksOnly, labelIdPainting, Logger.VOID_LOGGER ); } /** @@ -252,10 +247,8 @@ public static final ImagePlus createLabelImagePlus( * 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 useSpotIDsAsLabels - * if true, the label mask images will contain - * the spot ID. If false, the label mask images - * will contain the track ID. + * @param labelIdPainting + * specifies how to paint the label ID of spots. * @param logger * a {@link Logger} instance, to report progress of the export * process. @@ -267,7 +260,7 @@ public static final ImagePlus createLabelImagePlus( final ImagePlus imp, final boolean exportSpotsAsDots, final boolean exportTracksOnly, - boolean useSpotIDsAsLabels, + final LabelIdPainting labelIdPainting, final Logger logger ) { final int[] dimensions = imp.getDimensions(); @@ -278,7 +271,7 @@ public static final ImagePlus createLabelImagePlus( imp.getCalibration().pixelDepth, imp.getCalibration().frameInterval }; - final ImagePlus lblImp = createLabelImagePlus( model, dims, calibration, exportSpotsAsDots, exportTracksOnly, useSpotIDsAsLabels, logger ); + final ImagePlus lblImp = createLabelImagePlus( model, dims, calibration, exportSpotsAsDots, exportTracksOnly, labelIdPainting, logger ); lblImp.setCalibration( imp.getCalibration().copy() ); lblImp.setTitle( "LblImg_" + imp.getTitle() ); return lblImp; @@ -304,11 +297,9 @@ public static final ImagePlus createLabelImagePlus( * 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 useSpotIDsAsLabels - * if true, the label mask images will contain - * the spot ID. If false, the label mask images - * will contain the track ID. - * + * @param labelIdPainting + * specifies how to paint the label ID of spots. + * * @return a new {@link ImagePlus}. */ public static final ImagePlus createLabelImagePlus( @@ -317,9 +308,9 @@ public static final ImagePlus createLabelImagePlus( final double[] calibration, final boolean exportSpotsAsDots, final boolean exportTracksOnly, - final boolean useSpotIDsAsLabels) + final LabelIdPainting labelIdPainting ) { - return createLabelImagePlus( model, dimensions, calibration, exportSpotsAsDots, exportTracksOnly, useSpotIDsAsLabels, Logger.VOID_LOGGER ); + return createLabelImagePlus( model, dimensions, calibration, exportSpotsAsDots, exportTracksOnly, labelIdPainting, Logger.VOID_LOGGER ); } /** @@ -341,10 +332,8 @@ public static final ImagePlus createLabelImagePlus( * 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 useSpotIDsAsLabels - * if true, the label mask images will contain - * the spot ID. If false, the label mask images - * will contain the track ID. + * @param labelIdPainting + * specifies how to paint the label ID of spots. * @param logger * a {@link Logger} instance, to report progress of the export * process. @@ -357,17 +346,18 @@ public static final ImagePlus createLabelImagePlus( final double[] calibration, final boolean exportSpotsAsDots, final boolean exportTracksOnly, - final boolean useSpotIDsAsLabels, + final LabelIdPainting labelIdPainting, final Logger logger ) { final long[] dims = new long[ 4 ]; for ( int d = 0; d < dims.length; d++ ) dims[ d ] = dimensions[ d ]; - final ImagePlus lblImp = ImageJFunctions.wrap( createLabelImg( model, dims, calibration, exportSpotsAsDots, exportTracksOnly, useSpotIDsAsLabels, logger ), "LblImage" ); + final ImagePlus lblImp = ImageJFunctions.wrap( createLabelImg( model, dims, calibration, exportSpotsAsDots, exportTracksOnly, labelIdPainting, logger ), "LblImage" ); lblImp.setDimensions( 1, dimensions[ 2 ], dimensions[ 3 ] ); + lblImp.setLut( GlasbeyLut.toLUT() ); + lblImp.setDisplayRange( 0, 255 ); lblImp.setOpenAsHyperStack( true ); - lblImp.resetDisplayRange(); return lblImp; } @@ -390,11 +380,9 @@ public static final ImagePlus createLabelImagePlus( * 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 useSpotIDsAsLabels - * if true, the label mask images will contain - * the spot ID. If false, the label mask images - * will contain the track ID. - * + * @param labelIdPainting + * specifies how to paint the label ID of spots. + * * @return a new {@link Img}. */ public static final Img< FloatType > createLabelImg( @@ -403,9 +391,9 @@ public static final Img< FloatType > createLabelImg( final double[] calibration, final boolean exportSpotsAsDots, final boolean exportTracksOnly, - final boolean useSpotIDsAsLabels) + final LabelIdPainting labelIdPainting ) { - return createLabelImg( model, dimensions, calibration, exportSpotsAsDots, exportTracksOnly, useSpotIDsAsLabels, Logger.VOID_LOGGER ); + return createLabelImg( model, dimensions, calibration, exportSpotsAsDots, exportTracksOnly, labelIdPainting, Logger.VOID_LOGGER ); } /** @@ -427,10 +415,8 @@ public static final Img< FloatType > createLabelImg( * 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 useSpotIDsAsLabels - * if true, the label mask images will contain - * the spot ID. If false, the label mask images - * will contain the track ID. + * @param labelIdPainting + * specifies how to paint the label ID of spots. * @param logger * a {@link Logger} instance, to report progress of the export * process. @@ -443,7 +429,7 @@ public static final Img< FloatType > createLabelImg( final double[] calibration, final boolean exportSpotsAsDots, final boolean exportTracksOnly, - final boolean useSpotIDsAsLabels, + final LabelIdPainting labelIdPainting, final Logger logger ) { /* @@ -459,16 +445,10 @@ public static final Img< FloatType > createLabelImg( final ImgPlus< FloatType > imgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration ); /* - * Determine the starting id for spots not in tracks. + * How to assign an ID to spots. */ - int maxTrackID = -1; - final Set< Integer > trackIDs = model.getTrackModel().trackIDs( false ); - if ( null != trackIDs ) - for ( final Integer trackID : trackIDs ) - if ( trackID > maxTrackID ) - maxTrackID = trackID.intValue(); - final AtomicInteger lonelySpotID = new AtomicInteger( maxTrackID + 2 ); + final IdGenerator idGenerator = labelIdPainting.idGenerator( model.getTrackModel(), exportTracksOnly ); /* * Frame by frame iteration. @@ -481,29 +461,11 @@ public static final Img< FloatType > createLabelImg( final SpotWriter spotWriter = exportSpotsAsDots ? new SpotAsDotWriter<>( imgCT ) : new SpotRoiWriter<>( imgCT ); + idGenerator.nextFrame(); for ( final Spot spot : model.getSpots().iterable( frame, true ) ) { - final int id; - final Integer trackID = model.getTrackModel().trackIDOf( spot ); - if ( null == trackID || !model.getTrackModel().isVisible( trackID ) ) - { - if ( exportTracksOnly ) - continue; - - if ( useSpotIDsAsLabels ) - id = spot.ID() + 1; - else - id = lonelySpotID.getAndIncrement(); - } - else - { - if ( useSpotIDsAsLabels ) - id = spot.ID() + 1; - else - id = 1 + trackID.intValue(); - } - + final int id = idGenerator.id( spot ); spotWriter.write( spot, id ); } logger.setProgress( ( double ) ( 1 + frame ) / dimensions[ 3 ] ); @@ -600,4 +562,176 @@ public void write( final Spot spot, final int id ) ra.get().setReal( id ); } } + + /** + * Specifies how spots will be labeled in the exported label image. + */ + public static enum LabelIdPainting + { + + LABEL_IS_TRACK_ID( "Track ID", + "The spot label is the ID of the track it belongs to, plus one (+1). " + + "Spots that do not belong to tracks are painted with a unique integer " + + "larger than the last trackID in the dataset." ), + LABEL_IS_SPOT_ID( "Spot ID", + "The spot label is the spot ID, plus one (+1)." ), + LABEL_IS_INDEX( "Index unique in frame", + "The spot label is an index starting from 1. " + + "It is unique within a frame, " + + "but can be repeated across frames." ), + LABEL_IS_INDEX_MOVIE_UNIQUE( "Unique index", + "The spot label is an index starting from 1. " + + "It is unique within the whole movie." ); + + private final String info; + + private final String methodName; + + private LabelIdPainting( final String methodName, final String info ) + { + this.methodName = methodName; + this.info = info; + } + + @Override + public String toString() + { + return methodName; + } + + public String getInfo() + { + return info; + } + + public IdGenerator idGenerator( final TrackModel tm, final boolean visibleTracksOnly) + { + switch ( this ) + { + case LABEL_IS_INDEX: + return new SpotIndexGeneratorUniqueInFrame( tm, visibleTracksOnly ); + case LABEL_IS_INDEX_MOVIE_UNIQUE: + return new SpotIndexGeneratorUniqueInMovie( tm, visibleTracksOnly ); + case LABEL_IS_SPOT_ID: + return new SpotIdGenerator( tm, visibleTracksOnly ); + case LABEL_IS_TRACK_ID: + return new TrackIdGenerator( tm, visibleTracksOnly ); + default: + throw new IllegalArgumentException( "Unknown painting id mode: " + this ); + } + } + } + + private static interface IdGenerator + { + public int id( Spot spot ); + + public default void nextFrame() + {}; + } + + private static abstract class AbstractIdGenerator implements IdGenerator + { + protected final TrackModel tm; + + protected final boolean visibleTracksOnly; + + public AbstractIdGenerator( final TrackModel tm, final boolean visibleTracksOnly ) + { + this.tm = tm; + this.visibleTracksOnly = visibleTracksOnly; + } + } + + private static class TrackIdGenerator extends AbstractIdGenerator + { + + private final AtomicInteger lonelySpotID; + + public TrackIdGenerator( final TrackModel tm, final boolean visibleTracksOnly ) + { + super( tm, visibleTracksOnly ); + int maxTrackID = -1; + final Set< Integer > trackIDs = tm.trackIDs( false ); + if ( null != trackIDs ) + for ( final Integer trackID : trackIDs ) + if ( trackID > maxTrackID ) + maxTrackID = trackID.intValue(); + this.lonelySpotID = new AtomicInteger( maxTrackID + 2 ); + } + + @Override + public int id( final Spot spot ) + { + final Integer trackID = tm.trackIDOf( spot ); + if ( null == trackID || !tm.isVisible( trackID ) ) + { + if ( visibleTracksOnly ) + return -1; + return lonelySpotID.getAndIncrement(); + } + return trackID + 1; + } + } + + private static class SpotIdGenerator extends AbstractIdGenerator + { + + public SpotIdGenerator( final TrackModel tm, final boolean visibleTracksOnly ) + { + super( tm, visibleTracksOnly ); + } + + @Override + public int id( final Spot spot ) + { + final Integer trackID = tm.trackIDOf( spot ); + if ( null == trackID || !tm.isVisible( trackID ) ) + { + if ( visibleTracksOnly ) + return -1; + } + return spot.ID() + 1; + } + } + + private static class SpotIndexGeneratorUniqueInMovie extends AbstractIdGenerator + { + + protected final AtomicInteger index; + + public SpotIndexGeneratorUniqueInMovie( final TrackModel tm, final boolean visibleTracksOnly ) + { + super( tm, visibleTracksOnly ); + this.index = new AtomicInteger( 0 ); + } + + @Override + public int id( final Spot spot ) + { + final Integer trackID = tm.trackIDOf( spot ); + if ( null == trackID || !tm.isVisible( trackID ) ) + { + if ( visibleTracksOnly ) + return -1; + } + return index.incrementAndGet(); + } + } + + + private static class SpotIndexGeneratorUniqueInFrame extends SpotIndexGeneratorUniqueInMovie + { + + public SpotIndexGeneratorUniqueInFrame( final TrackModel tm, final boolean visibleTracksOnly ) + { + super( tm, visibleTracksOnly ); + } + + @Override + public void nextFrame() + { + index.set( 0 ); + } + } } diff --git a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporterPanel.java b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporterPanel.java index 9aebd3688..31366fd60 100644 --- a/src/main/java/fiji/plugin/trackmate/action/LabelImgExporterPanel.java +++ b/src/main/java/fiji/plugin/trackmate/action/LabelImgExporterPanel.java @@ -21,13 +21,19 @@ */ package fiji.plugin.trackmate.action; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; import javax.swing.JPanel; +import fiji.plugin.trackmate.action.LabelImgExporter.LabelIdPainting; +import fiji.plugin.trackmate.gui.Fonts; + public class LabelImgExporterPanel extends JPanel { @@ -37,15 +43,17 @@ public class LabelImgExporterPanel extends JPanel private final JCheckBox exportTracksOnly; - private final JCheckBox useSpotIdsAsLabels; + private final JComboBox< LabelIdPainting > labelIdPainting; public LabelImgExporterPanel() { + setPreferredSize( new Dimension( 250, 200 ) ); final GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.rowWeights = new double[] { 0., 0., 0., 1. }; setLayout( gridBagLayout ); final GridBagConstraints gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.WEST; + gbc.anchor = GridBagConstraints.NORTHWEST; gbc.insets = new Insets( 5, 5, 5, 5 ); gbc.gridx = 0; gbc.gridy = 0; @@ -57,9 +65,24 @@ public LabelImgExporterPanel() gbc.gridy++; add( exportTracksOnly, gbc ); - useSpotIdsAsLabels = new JCheckBox( "Use spots IDs as labels", false ); gbc.gridy++; - add( useSpotIdsAsLabels, gbc ); + add( new JLabel( "Label ID is:" ), gbc ); + + labelIdPainting = new JComboBox<>( LabelIdPainting.values() ); + gbc.gridy++; + add( labelIdPainting, gbc ); + + final JLabel info = new JLabel(); + info.setFont( Fonts.SMALL_FONT ); + gbc.gridy++; + gbc.fill = GridBagConstraints.BOTH; + add( info, gbc ); + + labelIdPainting.addItemListener( e -> info.setText( "" + + ( ( LabelIdPainting ) labelIdPainting.getSelectedItem() ).getInfo() + + "" ) ); + labelIdPainting.setSelectedIndex( 1 ); + labelIdPainting.setSelectedIndex( 0 ); } public boolean isExportSpotsAsDots() @@ -72,8 +95,8 @@ public boolean isExportTracksOnly() return exportTracksOnly.isSelected(); } - public boolean isUseSpotIDsAsLabels() + public LabelIdPainting labelIdPainting() { - return useSpotIdsAsLabels.isSelected(); + return ( LabelIdPainting ) labelIdPainting.getSelectedItem(); } } diff --git a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTrackerFactory.java b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTrackerFactory.java index a1420fa27..5bc05f791 100644 --- a/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTrackerFactory.java +++ b/src/main/java/fiji/plugin/trackmate/tracking/overlap/OverlapTrackerFactory.java @@ -106,8 +106,9 @@ public class OverlapTrackerFactory implements SpotTrackerFactory + "spot polygon. " + "

" + "

" - + "Careful: this tracker is only suited to 2D images. It treats all the spots " - + "as 2D objects. The Z dimension is ignored. " + + "This tracker works in 2D and 3D. However in 3D, the IoU is computed from the " + + "bounding-boxes regardless of the choice of the IoU computation method. " + + "The Precise method is not implemented." + ""; private String errorMessage; diff --git a/src/main/java/fiji/plugin/trackmate/visualization/GlasbeyLut.java b/src/main/java/fiji/plugin/trackmate/visualization/GlasbeyLut.java index 0602b1264..2b7e841a7 100644 --- a/src/main/java/fiji/plugin/trackmate/visualization/GlasbeyLut.java +++ b/src/main/java/fiji/plugin/trackmate/visualization/GlasbeyLut.java @@ -23,6 +23,8 @@ import java.awt.Color; +import ij.process.LUT; + public class GlasbeyLut { @@ -301,4 +303,18 @@ public static final Color next() return colors[ index ]; } + public static LUT toLUT() + { + final byte[] r = new byte[ 256 ]; + final byte[] g = new byte[ 256 ]; + final byte[] b = new byte[ 256 ]; + for ( int i = 1; i < 256; i++ ) + { + final Color c = colors[ ( i - 1 + colors.length / 2 ) % colors.length ]; + r[ i ] = ( byte ) ( ( 0xFF ) & c.getRed() ); + g[ i ] = ( byte ) ( ( 0xFF ) & c.getGreen() ); + b[ i ] = ( byte ) ( ( 0xFF ) & c.getBlue() ); + } + return new LUT( r, g, b ); + } }