true
if the specified image is 2D. It can have
* multiple channels and multiple time-points; this method only looks at
* whether several Z-slices can be found.
- *
+ *
* @param img
* the image.
* @return true
if the image is 2D, regardless of time and
@@ -189,7 +188,7 @@ public static final boolean is2D( final ImagePlus imp )
* radius specified using calibrated units. The specified calibration
* is used to determine the dimensionality of the kernel and to map it on a
* pixel grid.
- *
+ *
* @param radius
* the blob radius (in image unit).
* @param nDims
@@ -277,7 +276,7 @@ public static final < T extends RealType< T > > Img< FloatType > copyToFloatImg(
final RandomAccess< T > in = Views.zeroMin( Views.interval( img, interval ) ).randomAccess();
final Cursor< FloatType > out = output.cursor();
final RealFloatConverter< T > c = new RealFloatConverter<>();
-
+
while ( out.hasNext() )
{
out.fwd();
@@ -344,7 +343,7 @@ public static final < T extends RealType< T > > List< Spot > findLocalMaxima(
* Find maxima.
*/
- final T val = Util.getTypeFromInterval( source ).createVariable();
+ final T val = source.getType().createVariable();
val.setReal( threshold );
final LocalNeighborhoodCheck< Point, T > localNeighborhoodCheck = new LocalExtrema.MaximumCheck<>( val );
final IntervalView< T > dogWithBorder = Views.interval( Views.extendMirrorSingle( source ), Intervals.expand( source, 1 ) );
@@ -492,7 +491,7 @@ else if ( source.numDimensions() > 1 )
/**
* Return a view of the specified input image, at the specified channel
* (0-based) and the specified frame (0-based too).
- *
+ *
* @param
+ * 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
- *
+ *
* @see Ramer–Douglas–Peucker
* Algorithm (Wikipedia)
@@ -637,7 +710,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;
}
@@ -665,7 +738,7 @@ public static final PolygonRoi simplify( final PolygonRoi roi, final double smoo
/**
* Start at 1.
- *
+ *
* @return a new iterator that goes like 1, 2, 3, ...
*/
public static final Iterator< Integer > labelGenerator()
@@ -697,7 +770,7 @@ public boolean hasNext()
* Warning: cannot deal with holes, they are simply ignored.
*
* Copied and adapted from ImageJ1 code by Wayne Rasband.
- *
+ *
* @param
@@ -55,4 +58,31 @@ public String toString()
return str;
}
+ @Override
+ public boolean equals( final Object obj )
+ {
+ if ( obj == null )
+ return false;
+ if ( obj == this )
+ return true;
+ if ( obj.getClass() != getClass() )
+ return false;
+ final FeatureFilter other = ( FeatureFilter ) obj;
+ return new EqualsBuilder()
+ .append( feature, other.feature )
+ .append( value, other.value )
+ .append( isAbove, other.isAbove )
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return new HashCodeBuilder( 17, 37 )
+ .append( feature )
+ .append( value )
+ .append( isAbove )
+ .toHashCode();
+ }
+
}
diff --git a/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java b/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java
index 2bfbee9f9..a9055ee1c 100644
--- a/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.java
+++ b/src/main/java/fiji/plugin/trackmate/graph/GraphUtils.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
* "
+ + "Shift + click will launch the editor on all the ", " " ).replace( "", " " ) );
- add( labelTrackerDescription );
+ final GridBagConstraints gbcLabelTracker = new GridBagConstraints();
+ gbcLabelTracker.fill = GridBagConstraints.BOTH;
+ gbcLabelTracker.insets = new Insets( 0, 0, 5, 0 );
+ gbcLabelTracker.gridwidth = 3;
+ gbcLabelTracker.gridx = 0;
+ gbcLabelTracker.gridy = 1;
+ add( labelTracker, gbcLabelTracker );
+
+ final GridBagConstraints gbcLabelTrackerDescription = new GridBagConstraints();
+ gbcLabelTrackerDescription.anchor = GridBagConstraints.NORTH;
+ gbcLabelTrackerDescription.fill = GridBagConstraints.BOTH;
+ gbcLabelTrackerDescription.insets = new Insets( 0, 0, 5, 0 );
+ gbcLabelTrackerDescription.gridwidth = 3;
+ gbcLabelTrackerDescription.gridx = 0;
+ gbcLabelTrackerDescription.gridy = 2;
+ add( GuiUtils.textInScrollPanel( GuiUtils.infoDisplay( infoText ) ), gbcLabelTrackerDescription );
final JLabel lblMaximalLinkingDistance = new JLabel( "Maximal linking distance: " );
lblMaximalLinkingDistance.setFont( FONT );
- lblMaximalLinkingDistance.setBounds( 10, 314, 164, 20 );
- add( lblMaximalLinkingDistance );
+ final GridBagConstraints gbcLblMaximalLinkingDistance = new GridBagConstraints();
+ gbcLblMaximalLinkingDistance.fill = GridBagConstraints.BOTH;
+ gbcLblMaximalLinkingDistance.insets = new Insets( 0, 0, 0, 5 );
+ gbcLblMaximalLinkingDistance.gridx = 0;
+ gbcLblMaximalLinkingDistance.gridy = 3;
+ add( lblMaximalLinkingDistance, gbcLblMaximalLinkingDistance );
maxDistField = new JFormattedTextField( 15. );
+ maxDistField.setHorizontalAlignment( JTextField.CENTER );
maxDistField.setFont( FONT );
- maxDistField.setBounds( 184, 316, 62, 16 );
- maxDistField.setSize( TEXTFIELD_DIMENSION );
- add( maxDistField );
-
- labelUnits = new JLabel( spaceUnits );
- labelUnits.setFont( FONT );
- labelUnits.setBounds( 236, 314, 34, 20 );
- add( labelUnits );
+ final GridBagConstraints gbcMaxDistField = new GridBagConstraints();
+ gbcMaxDistField.fill = GridBagConstraints.BOTH;
+ gbcMaxDistField.insets = new Insets( 0, 0, 0, 5 );
+ gbcMaxDistField.gridx = 1;
+ gbcMaxDistField.gridy = 3;
+ add( maxDistField, gbcMaxDistField );
// Select text-fields content on focus.
GuiUtils.selectAllOnFocus( maxDistField );
+
+ final JLabel labelUnits = new JLabel( spaceUnits );
+ labelUnits.setFont( FONT );
+ final GridBagConstraints gbcLabelUnits = new GridBagConstraints();
+ gbcLabelUnits.anchor = GridBagConstraints.WEST;
+ gbcLabelUnits.fill = GridBagConstraints.VERTICAL;
+ gbcLabelUnits.gridx = 2;
+ gbcLabelUnits.gridy = 3;
+ add( labelUnits, gbcLabelUnits );
}
@Override
diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java
new file mode 100644
index 000000000..6d2e6fde6
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/gui/editor/ImpBdvShowable.java
@@ -0,0 +1,257 @@
+package fiji.plugin.trackmate.gui.editor;
+
+import java.awt.Color;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import bdv.tools.brightness.ConverterSetup;
+import bdv.util.AxisOrder;
+import bdv.util.BdvFunctions;
+import bdv.util.BdvOptions;
+import bdv.util.BdvStackSource;
+import bdv.viewer.DisplayMode;
+import bdv.viewer.SourceAndConverter;
+import bdv.viewer.SynchronizedViewerState;
+import bdv.viewer.ViewerState;
+import fiji.plugin.trackmate.util.TMUtils;
+import ij.CompositeImage;
+import ij.IJ;
+import ij.ImagePlus;
+import ij.gui.Roi;
+import ij.process.LUT;
+import net.imagej.ImgPlus;
+import net.imagej.axis.Axes;
+import net.imagej.axis.AxisType;
+import net.imglib2.FinalInterval;
+import net.imglib2.Interval;
+import net.imglib2.realtransform.AffineTransform3D;
+import net.imglib2.type.numeric.ARGBType;
+import net.imglib2.type.numeric.NumericType;
+import net.imglib2.view.Views;
+import sc.fiji.labkit.ui.bdv.BdvShowable;
+import sc.fiji.labkit.ui.inputimage.ImgPlusViewsOld;
+
+/**
+ * A {@link BdvShowable} from a {@link ImgPlus}, but with channel colors, min
+ * and max, channel visibility and display mode taken from a specified
+ * {@link ImagePlus}.
+ *
+ * Adapted from Matthias Arzt' ImgPlusBdvShowable, reusing code I made for
+ * Mastodon support of IJ ImagePlus.
+ *
+ * @author Jean-Yves Tinevez
+ */
+public class ImpBdvShowable implements BdvShowable
+{
+
+ /**
+ * Returns a new {@link BdvShowable} that wraps the specified
+ * {@link ImagePlus}. The LUT and display settings are read from the
+ * {@link ImagePlus}.
+ *
+ * @param
+ * The index image after modification is compared with the original one, and
+ * modifications are detected. Spots corresponding to modifications are
+ * added, removed or edited in the TrackMate model.
+ *
+ * To properly detect modifications, the indices in the label images must
+ * correspond to the spot ID + 1 (
+ * The UI consist of a Big Data Viewer panel, with brush tools and a side bar.
+ * The side bar lists panels and segmentation algorithms.
+ *
+ * A main menu that contains many actions for open and saving data, can be
+ * accessed by using {@link #getMenuBar()}.
+ */
+public class TrackMateLabKitSegmentationComponent extends JPanel implements AutoCloseable
+{
+
+ private static final long serialVersionUID = 1L;
+
+ private final boolean unmodifiableLabels;
+
+ private final DefaultExtensible extensible;
+
+ private final BasicLabelingComponent labelingComponent;
+
+ private final SegmentationModel segmentationModel;
+
+ public TrackMateLabKitSegmentationComponent( final JFrame dialogBoxOwner,
+ final SegmentationModel segmentationModel, final boolean unmodifiableLabels )
+ {
+ this.extensible = new DefaultExtensible( segmentationModel.context(), dialogBoxOwner );
+ this.unmodifiableLabels = unmodifiableLabels;
+ this.segmentationModel = segmentationModel;
+ final ImageLabelingModel imageLabelingModel = segmentationModel.imageLabelingModel();
+ labelingComponent = new BasicLabelingComponent( dialogBoxOwner, imageLabelingModel );
+ labelingComponent.addBdvLayer( PredictionLayer.createPredictionLayer( segmentationModel ) );
+ initActions();
+ setLayout( new BorderLayout() );
+ add( initGui() );
+ }
+
+ private void initActions()
+ {
+// final Holder< SegmentationItem > selectedSegmenter = segmentationModel.segmenterList().selectedSegmenter();
+ final ImageLabelingModel labelingModel = segmentationModel.imageLabelingModel();
+// new TrainClassifier( extensible, segmentationModel.segmenterList() );
+// new ClassifierSettingsAction( extensible, segmentationModel.segmenterList() );
+// new ClassifierIoAction( extensible, segmentationModel.segmenterList() );
+ new LabelingIoAction( extensible, labelingModel );
+ new AddLabelingIoAction( extensible, labelingModel.labeling() );
+// new SegmentationExportAction( extensible, labelingModel );
+ new ResetViewAction( extensible, labelingModel );
+// new BatchSegmentAction( extensible, selectedSegmenter );
+// new SegmentationAsLabelAction( extensible, segmentationModel );
+// new BitmapImportExportAction( extensible, labelingModel );
+ new LabelEditAction( extensible, unmodifiableLabels, new ColoredLabelsModel( labelingModel ) );
+// MeasureConnectedComponents.addAction( extensible, labelingModel );
+ new ShowHelpAction( extensible );
+ labelingComponent.addShortcuts( extensible.getShortCuts() );
+ }
+
+ private JPanel initLeftPanel()
+ {
+ final JPanel panel = new JPanel();
+ panel.setLayout( new MigLayout( "", "[grow]", "[][grow]" ) );
+ panel.add( ImageInfoPanel.newFramedImageInfoPanel( segmentationModel.imageLabelingModel(), labelingComponent ), "grow, wrap" );
+ panel.add( LabelPanel.newFramedLabelPanel( segmentationModel.imageLabelingModel(), extensible, unmodifiableLabels ), "grow, wrap, height 0:5000" );
+// panel.add( SegmenterPanel.newFramedSegmeterPanel( segmentationModel.segmenterList(), extensible ), "grow, height 0:50" );
+ panel.invalidate();
+ panel.repaint();
+ return panel;
+ }
+
+ private JSplitPane initGui()
+ {
+ final JSplitPane panel = new JSplitPane();
+ panel.setOneTouchExpandable( true );
+ panel.setLeftComponent( initLeftPanel() );
+ panel.setRightComponent( labelingComponent );
+ panel.setBorder( BorderFactory.createEmptyBorder() );
+ return panel;
+ }
+
+ @Deprecated
+ public JComponent getComponent()
+ {
+ return this;
+ }
+
+ public JMenu createMenu( final MenuKey< Void > key )
+ {
+ if ( key == MenuBar.SEGMENTER_MENU )
+ return extensible.createMenu(
+ SegmentationItem.SEGMENTER_MENU, segmentationModel
+ .segmenterList().selectedSegmenter()::get );
+ return extensible.createMenu( key, () -> null );
+ }
+
+ @Override
+ public void close()
+ {
+ labelingComponent.close();
+ }
+
+ public JMenuBar getMenuBar()
+ {
+ return new MenuBar( this::createMenu );
+ }
+
+ public void autoContrast()
+ {
+ labelingComponent.autoContrast();
+ }
+}
diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabkitFrame.java b/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabkitFrame.java
new file mode 100644
index 000000000..f76cf3c9c
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabkitFrame.java
@@ -0,0 +1,152 @@
+/*-
+ * #%L
+ * The Labkit image segmentation tool for Fiji.
+ * %%
+ * Copyright (C) 2017 - 2024 Matthias Arzt
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+package fiji.plugin.trackmate.gui.editor;
+
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+
+import javax.swing.JFrame;
+
+import org.scijava.Context;
+
+import fiji.plugin.trackmate.gui.Icons;
+import io.scif.services.DatasetIOService;
+import net.imagej.Dataset;
+import sc.fiji.labkit.pixel_classification.utils.SingletonContext;
+import sc.fiji.labkit.ui.InitialLabeling;
+import sc.fiji.labkit.ui.LabkitFrame;
+import sc.fiji.labkit.ui.inputimage.DatasetInputImage;
+import sc.fiji.labkit.ui.inputimage.InputImage;
+import sc.fiji.labkit.ui.models.DefaultSegmentationModel;
+import sc.fiji.labkit.ui.models.SegmentationModel;
+import sc.fiji.labkit.ui.utils.Notifier;
+
+/**
+ * This class is copied and adapted from {@link LabkitFrame}.
+ *
+ * The main Labkit window. (This window allows to segment a single image. It has
+ * to be distinguished from the LabkitProjectFrame, which allows to operation on
+ * multiple images.) The window only contains a
+ * {@link TrackMateLabKitSegmentationComponent} and shows the associated main
+ * menu.
+ *
+ * @author Matthias Arzt
+ */
+public class TrackMateLabkitFrame
+{
+
+ private final JFrame frame = initFrame();
+
+ private final Notifier onCloseListeners = new Notifier();
+
+ public static TrackMateLabkitFrame showForFile( Context context,
+ final String filename )
+ {
+ if ( context == null )
+ context = SingletonContext.getInstance();
+ final Dataset dataset = openDataset( context, filename );
+ return showForImage( context, new DatasetInputImage( dataset ) );
+ }
+
+ private static Dataset openDataset( final Context context, final String filename )
+ {
+ try
+ {
+ return context.service( DatasetIOService.class ).open( filename );
+ }
+ catch ( final IOException e )
+ {
+ throw new RuntimeException( e );
+ }
+ }
+
+ public static TrackMateLabkitFrame showForImage( Context context, final InputImage inputImage )
+ {
+ if ( context == null )
+ context = SingletonContext.getInstance();
+ final SegmentationModel model = new DefaultSegmentationModel( context, inputImage );
+ model.imageLabelingModel().labeling().set( InitialLabeling.initialLabeling( context, inputImage ) );
+ return show( model, inputImage.imageForSegmentation().getName() );
+ }
+
+ public static TrackMateLabkitFrame show( final SegmentationModel model, final String title )
+ {
+ return new TrackMateLabkitFrame( model, title );
+ }
+
+ private TrackMateLabkitFrame( final SegmentationModel model, final String title )
+ {
+ @SuppressWarnings( "unused" )
+ final TrackMateLabKitSegmentationComponent segmentationComponent = initSegmentationComponent( model );
+ setTitle( title );
+ frame.setIconImage( Icons.TRACKMATE_ICON.getImage() );
+// frame.setJMenuBar( new MenuBar( segmentationComponent::createMenu ) );
+ frame.setVisible( true );
+ }
+
+ private TrackMateLabKitSegmentationComponent initSegmentationComponent( final SegmentationModel segmentationModel )
+ {
+ final TrackMateLabKitSegmentationComponent segmentationComponent = new TrackMateLabKitSegmentationComponent( frame, segmentationModel, false );
+ frame.add( segmentationComponent );
+ frame.addWindowListener( new WindowAdapter()
+ {
+
+ @Override
+ public void windowClosed( final WindowEvent e )
+ {
+ segmentationComponent.close();
+ onCloseListeners.notifyListeners();
+ }
+ } );
+ return segmentationComponent;
+ }
+
+ private JFrame initFrame()
+ {
+ final JFrame frame = new JFrame();
+ frame.setBounds( 50, 50, 1200, 900 );
+ frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
+ return frame;
+ }
+
+ private void setTitle( final String name )
+ {
+ if ( name == null || name.isEmpty() )
+ frame.setTitle( "Labkit" );
+ else
+ frame.setTitle( "Labkit - " + name );
+ }
+
+ public Notifier onCloseListeners()
+ {
+ return onCloseListeners;
+ }
+}
diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java
index 63103ea39..9ab663389 100644
--- a/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java
+++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/TrackMateWizardSequence.java
@@ -50,6 +50,7 @@
import fiji.plugin.trackmate.gui.components.FeatureDisplaySelector;
import fiji.plugin.trackmate.gui.components.LogPanel;
import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings;
+import fiji.plugin.trackmate.gui.editor.LabkitLauncher;
import fiji.plugin.trackmate.gui.wizard.descriptors.ActionChooserDescriptor;
import fiji.plugin.trackmate.gui.wizard.descriptors.ChooseDetectorDescriptor;
import fiji.plugin.trackmate.gui.wizard.descriptors.ChooseTrackerDescriptor;
@@ -146,7 +147,14 @@ public TrackMateWizardSequence( final TrackMate trackmate, final SelectionModel
chooseTrackerDescriptor = new ChooseTrackerDescriptor( new TrackerProvider(), trackmate );
executeTrackingDescriptor = new ExecuteTrackingDescriptor( trackmate, logPanel );
trackFilterDescriptor = new TrackFilterDescriptor( trackmate, trackFilters, featureSelector );
- configureViewsDescriptor = new ConfigureViewsDescriptor( displaySettings, featureSelector, new LaunchTrackSchemeAction(), new ShowTrackTablesAction(), new ShowSpotTableAction(), model.getSpaceUnits() );
+ configureViewsDescriptor = new ConfigureViewsDescriptor(
+ displaySettings,
+ featureSelector,
+ new LaunchTrackSchemeAction(),
+ new ShowTrackTablesAction(),
+ new ShowSpotTableAction(),
+ LabkitLauncher.getLaunchAction( trackmate, displaySettings ),
+ model.getSpaceUnits() );
grapherDescriptor = new GrapherDescriptor( trackmate, selectionModel, displaySettings );
actionChooserDescriptor = new ActionChooserDescriptor( new ActionProvider(), trackmate, selectionModel, displaySettings );
saveDescriptor = new SaveDescriptor( trackmate, displaySettings, this );
@@ -175,7 +183,6 @@ public WizardPanelDescriptor next()
return current;
}
-
@Override
public WizardPanelDescriptor previous()
{
@@ -201,7 +208,6 @@ public WizardPanelDescriptor current()
return current;
}
-
@Override
public WizardPanelDescriptor logDescriptor()
{
diff --git a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java
index 1d849386c..7856dd874 100644
--- a/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java
+++ b/src/main/java/fiji/plugin/trackmate/gui/wizard/descriptors/ConfigureViewsDescriptor.java
@@ -39,6 +39,7 @@ public ConfigureViewsDescriptor(
final Action launchTrackSchemeAction,
final Action showTrackTablesAction,
final Action showSpotTableAction,
+ final Action launchLabkitAction,
final String spaceUnits )
{
super( KEY );
@@ -48,6 +49,7 @@ public ConfigureViewsDescriptor(
spaceUnits,
launchTrackSchemeAction,
showTrackTablesAction,
- showSpotTableAction );
+ showSpotTableAction,
+ launchLabkitAction );
}
}
diff --git a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java b/src/main/java/fiji/plugin/trackmate/io/IOUtils.java
index 3a5b6cab7..9a7c82a8c 100644
--- a/src/main/java/fiji/plugin/trackmate/io/IOUtils.java
+++ b/src/main/java/fiji/plugin/trackmate/io/IOUtils.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
* "
- + "The nearest neighbor search relies upon the KD-tree technique implemented "
+ + "The nearest neighbor search relies upon the KD-tree technique implemented "
+ + "in imglib2. This ensure a very efficient "
+ + "tracking and makes this tracker suitable for situation where a huge number "
+ + "of particles are to be tracked over a very large number of frames. However, "
+ + "because of the naiveness of its principles, it can result in pathological "
+ + "tracks. It can only do frame-to-frame linking; there cannot be any track "
+ "merging or splitting, and gaps will not be closed. Also, the end results are non-"
- + "deterministic."
+ + "deterministic, as the links created depend in the order in which the source spots "
+ + "are iterated."
+ " ";
private String errorMessage;
diff --git a/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java b/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java
index d0edab222..43298c3e3 100644
--- a/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java
+++ b/src/main/java/fiji/plugin/trackmate/util/DetectionPreview.java
@@ -48,6 +48,8 @@ public class DetectionPreview
private final DetectionPreviewPanel panel;
+ private TrackMate trackmate;
+
protected DetectionPreview(
final Model model,
final Settings settings,
@@ -66,6 +68,7 @@ protected DetectionPreview(
detectionSettingsSupplier.get(),
currentFrameSupplier.get(),
thresholdKey ) );
+ panel.btnCancel.addActionListener( l -> cancel() );
}
public DetectionPreviewPanel getPanel()
@@ -86,7 +89,9 @@ protected void preview(
final int frame,
final String thresholdKey )
{
- panel.btnPreview.setEnabled( false );
+ panel.btnPreview.setVisible( false );
+ panel.btnCancel.setVisible( true );
+ panel.btnCancel.setEnabled( true );
Threads.run( "TrackMate preview detection thread", () ->
{
try
@@ -115,7 +120,8 @@ protected void preview(
}
finally
{
- panel.btnPreview.setEnabled( true );
+ panel.btnPreview.setVisible( true );
+ panel.btnCancel.setVisible( false );
}
} );
}
@@ -161,38 +167,54 @@ protected Pair< Model, Double > runPreviewDetection(
lSettings.detectorFactory = detectorFactory;
lSettings.detectorSettings = new HashMap<>( detectorSettings );
- // Does this detector have a THRESHOLD parameter?
- final boolean hasThreshold = ( thresholdKey != null ) && ( detectorSettings.containsKey( thresholdKey ) );
- final double threshold;
- if ( hasThreshold )
- {
- threshold = ( ( Double ) detectorSettings.get( thresholdKey ) ).doubleValue();
- lSettings.detectorSettings.put( thresholdKey, Double.valueOf( Double.NEGATIVE_INFINITY ) );
- }
- else
+ this.trackmate = new TrackMate( lSettings );
+
+ try
{
- threshold = Double.NaN;
- }
+ // Does this detector have a THRESHOLD parameter?
+ final boolean hasThreshold = ( thresholdKey != null ) && ( detectorSettings.containsKey( thresholdKey ) );
+ final double threshold;
+ if ( hasThreshold )
+ {
+ threshold = ( ( Double ) detectorSettings.get( thresholdKey ) ).doubleValue();
+// lSettings.detectorSettings.put( thresholdKey, Double.valueOf( Double.NEGATIVE_INFINITY ) );
+ lSettings.detectorSettings.put( thresholdKey, Double.valueOf( 0. ) );
+ }
+ else
+ {
+ threshold = Double.NaN;
+ }
- // Execute preview.
- final TrackMate trackmate = new TrackMate( lSettings );
- trackmate.getModel().setLogger( panel.logger );
+ // Execute preview.
+ trackmate.getModel().setLogger( panel.logger );
- final boolean detectionOk = trackmate.execDetection();
- if ( !detectionOk )
+ final boolean detectionOk = trackmate.execDetection();
+ if ( !detectionOk )
+ {
+ panel.logger.error( trackmate.getErrorMessage() );
+ return null;
+ }
+
+ if ( hasThreshold )
+ // Filter by the initial threshold value.
+ trackmate.getModel().getSpots().filter( new FeatureFilter( Spot.QUALITY, threshold, true ) );
+ else
+ // Make them all visible.
+ trackmate.getModel().getSpots().setVisible( true );
+
+ return new ValuePair< Model, Double >( trackmate.getModel(), Double.valueOf( threshold ) );
+ }
+ finally
{
- panel.logger.error( trackmate.getErrorMessage() );
- return null;
+ this.trackmate = null;
}
+ }
- if ( hasThreshold )
- // Filter by the initial threshold value.
- trackmate.getModel().getSpots().filter( new FeatureFilter( Spot.QUALITY, threshold, true ) );
- else
- // Make them all visible.
- trackmate.getModel().getSpots().setVisible( true );
-
- return new ValuePair< Model, Double >( trackmate.getModel(), Double.valueOf( threshold ) );
+ private void cancel()
+ {
+ panel.btnCancel.setEnabled( false );
+ if ( null != trackmate )
+ trackmate.cancel( "User canceled preview" );
}
protected void updateModelAndHistogram( final Model targetModel, final Model sourceModel, final int frame, final double threshold )
diff --git a/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java b/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java
index f3adad062..e2b36983b 100644
--- a/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java
+++ b/src/main/java/fiji/plugin/trackmate/util/DetectionPreviewPanel.java
@@ -22,14 +22,17 @@
package fiji.plugin.trackmate.util;
import static fiji.plugin.trackmate.gui.Fonts.SMALL_FONT;
+import static fiji.plugin.trackmate.gui.Icons.CANCEL_ICON;
import static fiji.plugin.trackmate.gui.Icons.PREVIEW_ICON;
+import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.function.DoubleConsumer;
import javax.swing.JButton;
+import javax.swing.JLabel;
import javax.swing.JPanel;
import fiji.plugin.trackmate.Logger;
@@ -48,28 +51,37 @@ public class DetectionPreviewPanel extends JPanel
+ "get rid of them later."
+ "";
+ private static final String TOOLTIP_CANCEL = ""
+ + "Cancel the current preview."
+ + "";
+
final Logger logger;
final JButton btnPreview;
+ final JButton btnCancel;
+
final QualityHistogramChart chart;
+
public DetectionPreviewPanel( final DoubleConsumer thresholdUpdater, final String axisLabel )
{
final GridBagLayout gridBagLayout = new GridBagLayout();
gridBagLayout.columnWeights = new double[] { 1.0, 0.0 };
- gridBagLayout.rowWeights = new double[] { 0., 0. };
- gridBagLayout.rowHeights = new int[] { 120, 20 };
+ gridBagLayout.rowWeights = new double[] { 1., 0., 0. };
+ gridBagLayout.rowHeights = new int[] { 0, 120, 20 };
setLayout( gridBagLayout );
+ add( new JLabel() );
+
this.chart = new QualityHistogramChart( thresholdUpdater, axisLabel );
final GridBagConstraints gbcHistogram = new GridBagConstraints();
gbcHistogram.gridwidth = 2;
gbcHistogram.insets = new Insets( 0, 0, 5, 0 );
gbcHistogram.fill = GridBagConstraints.BOTH;
gbcHistogram.gridx = 0;
- gbcHistogram.gridy = 0;
+ gbcHistogram.gridy = 1;
add( chart, gbcHistogram );
final JLabelLogger labelLogger = new JLabelLogger();
@@ -78,18 +90,29 @@ public DetectionPreviewPanel( final DoubleConsumer thresholdUpdater, final Strin
gbcLabelLogger.insets = new Insets( 5, 5, 0, 5 );
gbcLabelLogger.fill = GridBagConstraints.BOTH;
gbcLabelLogger.gridx = 0;
- gbcLabelLogger.gridy = 1;
+ gbcLabelLogger.gridy = 2;
add( labelLogger, gbcLabelLogger );
this.logger = labelLogger.getLogger();
this.btnPreview = new JButton( "Preview", PREVIEW_ICON );
btnPreview.setToolTipText( TOOLTIP_PREVIEW );
+ btnPreview.setFont( SMALL_FONT );
+ this.btnCancel = new JButton( "Cancel", CANCEL_ICON );
+ btnCancel.setToolTipText( TOOLTIP_CANCEL );
+ btnCancel.setVisible( false );
+ btnCancel.setFont( SMALL_FONT );
+
+ final JPanel btnPanel = new JPanel();
+ btnPanel.add( btnPreview );
+ btnPanel.add( btnCancel );
+
final GridBagConstraints gbcBtnPreview = new GridBagConstraints();
gbcBtnPreview.anchor = GridBagConstraints.NORTHEAST;
gbcBtnPreview.insets = new Insets( 5, 5, 0, 0 );
gbcBtnPreview.gridx = 1;
- gbcBtnPreview.gridy = 1;
- this.add( btnPreview, gbcBtnPreview );
- btnPreview.setFont( SMALL_FONT );
+ gbcBtnPreview.gridy = 2;
+ this.add( btnPanel, gbcBtnPreview );
+
+ setPreferredSize( new Dimension( 240, 100 ) );
}
}
diff --git a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java
index cd4c2ee05..4acca565c 100644
--- a/src/main/java/fiji/plugin/trackmate/util/TMUtils.java
+++ b/src/main/java/fiji/plugin/trackmate/util/TMUtils.java
@@ -865,6 +865,30 @@ public static String getImagePathWithoutExtension( final Settings settings )
}
}
+ /**
+ * Returns
+ * 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 )
{
@@ -511,6 +635,16 @@ public ChoiceAdder defaultValue( final String defaultChoice )
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() )
@@ -544,31 +678,73 @@ public ChoiceArgument get()
* 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
+ * the type of the quality image. Must be real, scalar.
+ * @param labeling
+ * the labeling, must be zero-min and 2D..
+ * @param interval
+ * the interval, used to reposition the spots from the zero-min
+ * labeling to the proper coordinates.
+ * @param calibration
+ * the physical calibration.
+ * @param simplify
+ * if true
the polygon will be post-processed to be
+ * smoother and contain less points.
+ * @param qualityImage
+ * the image in which to read the quality value.
+ * @return a map linking the label integer value to the list of spots, with
+ * ROI, it corresponds to.
+ */
+ public static < R extends IntegerType< R >, S extends RealType< S > > Map< Integer, List< Spot > > fromLabelingWithROIMap(
final ImgLabeling< Integer, R > labeling,
final Interval interval,
final double[] calibration,
@@ -481,9 +529,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();
@@ -493,55 +546,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 );
- // 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;
- // Measure quality.
- final double quality;
- if ( null == qualityImp )
- {
- quality = fRoi.getStatistics().area;
- }
- else
- {
- qualityImp.setRoi( fRoi );
- quality = qualityImp.getStatistics( Measurements.MIN_MAX ).max;
- }
+ // Don't include ROIs that have been shrunk to < 1 pixel.
+ if ( fRoi.getNCoordinates() < 3 || fRoi.getStatistics().area <= 0. )
+ continue;
- final Polygon fPolygon = fRoi.getPolygon();
- final double[] xpoly = new double[ fPolygon.npoints ];
- final double[] ypoly = new double[ fPolygon.npoints ];
- for ( int i = 0; i < fPolygon.npoints; i++ )
- {
- xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 );
- ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 );
- }
+ final Polygon fPolygon = fRoi.getPolygon();
+ final double[] xpoly = new double[ fPolygon.npoints ];
+ final double[] ypoly = new double[ fPolygon.npoints ];
+ for ( int i = 0; i < fPolygon.npoints; i++ )
+ {
+ xpoly[ i ] = calibration[ 0 ] * ( interval.min( 0 ) + fPolygon.xpoints[ i ] - 0.5 );
+ ypoly[ i ] = calibration[ 1 ] * ( interval.min( 1 ) + fPolygon.ypoints[ i ] - 0.5 );
+ }
+
+ final Spot spot = SpotRoi.createSpot( xpoly, ypoly, -1. );
- 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 )
@@ -624,7 +697,7 @@ private static final void douglasPeucker( final List< double[] > list, final int
* the number of points in a curve that is approximated by a series of
* points.
*
"
+ + "on the time-point currently displayed in the main
"
+ + "view."
+ + "
"
+ + "time-points in the movie." );
+ panelButtons.add( btnLabKit );
+ }
+ panelButtons.setSize( new Dimension( 300, 1 ) );
final GridBagConstraints gbcPanelButtons = new GridBagConstraints();
gbcPanelButtons.gridwidth = 2;
gbcPanelButtons.anchor = GridBagConstraints.SOUTH;
@@ -371,6 +395,7 @@ public ConfigureViewsPanel(
gbcPanelButtons.gridx = 0;
gbcPanelButtons.gridy = 7;
add( panelButtons, gbcPanelButtons );
+ setSize( new Dimension( 300, 1 ) );
/*
* Listeners & co.
diff --git a/src/main/java/fiji/plugin/trackmate/gui/components/tracker/NearestNeighborTrackerSettingsPanel.java b/src/main/java/fiji/plugin/trackmate/gui/components/tracker/NearestNeighborTrackerSettingsPanel.java
index 379accbbb..9f9447723 100644
--- a/src/main/java/fiji/plugin/trackmate/gui/components/tracker/NearestNeighborTrackerSettingsPanel.java
+++ b/src/main/java/fiji/plugin/trackmate/gui/components/tracker/NearestNeighborTrackerSettingsPanel.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
*
", "" ).replace( "[dx, dy dz]
.
+ * @param dt
+ * the frame interval.
+ * @param simplifyContours
+ * if true
the contours of the spots imported and
+ * modified will be simplified. If false
their
+ * contour will follow pixel edges.
+ */
+ public LabkitImporter(
+ final Model model,
+ final double[] calibration,
+ final double dt,
+ final boolean simplifyContours )
+ {
+ this.model = model;
+ this.calibration = calibration;
+ this.dt = dt;
+ this.simplify = simplifyContours;
+ }
+
+ /**
+ * Re-import the specified label image (specified by its index image) into
+ * the TrackMate model. The label images must corresponds to one time-point.
+ * index = id + 1
).
+ *
+ * @param novelIndexImg
+ * the new index image of the labeling model, that represents the
+ * TrackMate model in the specified time-point after
+ * modification.
+ * @param previousIndexImg
+ * the previous index image, that represents the TrackMate model
+ * before modification.
+ * @param currentTimePoint
+ * the time-point in the TrackMate model that corresponds to the
+ * index image.
+ * @param previousSpotLabels
+ * the map of spots (vs the label value in the previous labeling)
+ * that were written in the previous index image.
+ */
+ public void reimport(
+ final RandomAccessibleInterval< T > novelIndexImg,
+ final RandomAccessibleInterval< T > previousIndexImg,
+ final int currentTimePoint,
+ final Map< Integer, Spot > previousSpotLabels )
+ {
+ // Collect labels corresponding to spots that have been modified.
+ final Set< Integer > modifiedLabels = getModifiedLabels( novelIndexImg, previousIndexImg );
+ final int nModified = modifiedLabels.size();
+ if ( nModified == 0 )
+ return;
+
+ model.beginUpdate();
+ try
+ {
+ /*
+ * Get all the spots present in the new image, as a map against the
+ * label in the novel index image.
+ */
+ final Map< Integer, List< Spot > > novelSpots = getSpots( novelIndexImg );
+
+ // Update model for those spots.
+ for ( final int labelValue : modifiedLabels )
+ {
+ final Spot previousSpot = previousSpotLabels.get( labelValue );
+ final List< Spot > novelSpotList = novelSpots.get( labelValue );
+ if ( previousSpot == null )
+ {
+ /*
+ * A new one (possible several) I cannot find in the
+ * previous list -> add as a new spot.
+ */
+ if ( novelSpotList != null )
+ addNewSpot( novelSpotList, currentTimePoint );
+ }
+ else if ( novelSpotList == null || novelSpotList.isEmpty() )
+ {
+ /*
+ * One I had in the previous spot list, but that has
+ * disappeared. Remove it.
+ */
+ IJ.log( " - Removed spot " + str( previousSpot ) );
+ model.removeSpot( previousSpot );
+ }
+ else
+ {
+ /*
+ * I know of them both. Treat the case as if the previous
+ * spot was modified.
+ */
+ modifySpot( novelSpotList, previousSpot, currentTimePoint );
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+
+ private void modifySpot( final List< Spot > novelSpotList, final Spot previousSpot, final int currentTimePoint )
+ {
+ /*
+ * Hopefully there is only one spot in the novel spot list. If not we
+ * privilege the one closest to the previous spots.
+ */
+ final Spot mainNovelSpot;
+ if ( novelSpotList.size() == 1 )
+ {
+ mainNovelSpot = novelSpotList.get( 0 );
+ }
+ else
+ {
+ Spot closest = null;
+ double minD2 = Double.POSITIVE_INFINITY;
+ for ( final Spot s : novelSpotList )
+ {
+ final double d2 = s.squareDistanceTo( previousSpot );
+ if ( d2 < minD2 )
+ {
+ minD2 = d2;
+ closest = s;
+ }
+ }
+ mainNovelSpot = closest;
+ }
+
+ // Add it properly.
+ mainNovelSpot.setName( previousSpot.getName() );
+ mainNovelSpot.putFeature( Spot.POSITION_T, currentTimePoint * dt );
+ mainNovelSpot.putFeature( Spot.QUALITY, -1. );
+ model.addSpotTo( mainNovelSpot, Integer.valueOf( currentTimePoint ) );
+ // Recreate links.
+ final Set< DefaultWeightedEdge > edges = model.getTrackModel().edgesOf( previousSpot );
+ for ( final DefaultWeightedEdge e : edges )
+ {
+ final double weight = model.getTrackModel().getEdgeWeight( e );
+ final Spot source = model.getTrackModel().getEdgeSource( e );
+ final Spot target = model.getTrackModel().getEdgeTarget( e );
+ if ( source == previousSpot )
+ model.addEdge( mainNovelSpot, target, weight );
+ else if ( target == previousSpot )
+ model.addEdge( source, mainNovelSpot, weight );
+ else
+ throw new IllegalArgumentException( "The edge of a spot does not have the spot as source or target?!?" );
+ }
+ if ( DEBUG )
+ IJ.log( " - Modified spot " + str( previousSpot ) + " -> " + str( mainNovelSpot ) );
+
+ model.removeSpot( previousSpot );
+
+ // Deal with the other ones.
+ final HashSet< Spot > extraSpots = new HashSet<>( novelSpotList );
+ extraSpots.remove( mainNovelSpot );
+
+ int i = 1;
+ for ( final Spot s : extraSpots )
+ {
+ s.setName( previousSpot.getName() + "_" + i++ );
+ s.putFeature( Spot.POSITION_T, currentTimePoint * dt );
+ s.putFeature( Spot.QUALITY, -1. );
+ model.addSpotTo( s, Integer.valueOf( currentTimePoint ) );
+
+ if ( DEBUG )
+ IJ.log( " - Added spot " + str( s ) );
+ }
+ }
+
+ private void addNewSpot( final Iterable< Spot > novelSpotList, final int currentTimePoint )
+ {
+ for ( final Spot spot : novelSpotList )
+ {
+ spot.putFeature( Spot.POSITION_T, currentTimePoint * dt );
+ spot.putFeature( Spot.QUALITY, -1. );
+ model.addSpotTo( spot, Integer.valueOf( currentTimePoint ) );
+
+ if ( DEBUG )
+ IJ.log( " - Added spot " + str( spot ) );
+ }
+ }
+
+ private final Set< Integer > getModifiedLabels(
+ final RandomAccessibleInterval< T > novelIndexImg,
+ final RandomAccessibleInterval< T > previousIndexImg )
+ {
+ final ConcurrentSkipListSet< Integer > modifiedIDs = new ConcurrentSkipListSet<>();
+ LoopBuilder.setImages( novelIndexImg, previousIndexImg )
+ .multiThreaded( false )
+ .forEachPixel( ( c, p ) -> {
+ final int ci = c.getInteger();
+ final int pi = p.getInteger();
+ if ( ci == 0 && pi == 0 )
+ return;
+ if ( ci != pi )
+ {
+ modifiedIDs.add( Integer.valueOf( pi ) );
+ modifiedIDs.add( Integer.valueOf( ci ) );
+ }
+ } );
+ modifiedIDs.remove( Integer.valueOf( -1 ) );
+ return modifiedIDs;
+ }
+
+ private Map< Integer, List< Spot > > getSpots( final RandomAccessibleInterval< T > rai )
+ {
+ // Get all labels.
+ final AtomicInteger max = new AtomicInteger( 0 );
+ rai.forEach( p -> {
+ final int val = p.getInteger();
+ if ( val != 0 && val > max.get() )
+ max.set( val );
+ } );
+ final List< Integer > indices = new ArrayList<>( max.get() );
+ for ( int i = 0; i < max.get(); i++ )
+ indices.add( Integer.valueOf( i + 1 ) );
+
+ final ImgLabeling< Integer, ? > labeling = ImgLabeling.fromImageAndLabels( rai, indices );
+ final Map< Integer, List< Spot > > spots = MaskUtils.fromLabelingWithROIMap(
+ labeling,
+ Views.zeroMin( labeling ),
+ calibration,
+ simplify,
+ rai );
+ return spots;
+ }
+
+ private static final String str( final Spot spot )
+ {
+ return spot.ID() + " (" +
+ roundToN( spot.getDoublePosition( 0 ), 1 ) + ", " +
+ roundToN( spot.getDoublePosition( 1 ), 1 ) + ", " +
+ roundToN( spot.getDoublePosition( 2 ), 1 ) + ") " +
+ "@ t=" + spot.getFeature( Spot.FRAME ).intValue();
+ }
+
+ private static double roundToN( final double num, final int n )
+ {
+ final double scale = Math.pow( 10, n );
+ return Math.round( num * scale ) / scale;
+ }
+}
diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java
new file mode 100644
index 000000000..c1654465b
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/gui/editor/LabkitLauncher.java
@@ -0,0 +1,532 @@
+package fiji.plugin.trackmate.gui.editor;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JRootPane;
+import javax.swing.JSeparator;
+import javax.swing.SwingUtilities;
+
+import org.scijava.Context;
+import org.scijava.ui.behaviour.util.AbstractNamedAction;
+
+import fiji.plugin.trackmate.Logger;
+import fiji.plugin.trackmate.Spot;
+import fiji.plugin.trackmate.SpotCollection;
+import fiji.plugin.trackmate.SpotRoi;
+import fiji.plugin.trackmate.TrackMate;
+import fiji.plugin.trackmate.detection.DetectionUtils;
+import fiji.plugin.trackmate.features.FeatureUtils;
+import fiji.plugin.trackmate.gui.Icons;
+import fiji.plugin.trackmate.gui.displaysettings.DisplaySettings;
+import fiji.plugin.trackmate.util.EverythingDisablerAndReenabler;
+import fiji.plugin.trackmate.util.SpotUtil;
+import fiji.plugin.trackmate.util.TMUtils;
+import fiji.plugin.trackmate.visualization.FeatureColorGenerator;
+import ij.ImagePlus;
+import ij.gui.Roi;
+import net.imagej.ImgPlus;
+import net.imagej.axis.Axes;
+import net.imagej.axis.AxisType;
+import net.imglib2.FinalInterval;
+import net.imglib2.RandomAccessibleInterval;
+import net.imglib2.img.Img;
+import net.imglib2.img.ImgFactory;
+import net.imglib2.img.ImgView;
+import net.imglib2.img.array.ArrayImgs;
+import net.imglib2.img.display.imagej.ImgPlusViews;
+import net.imglib2.loops.LoopBuilder;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.ARGBType;
+import net.imglib2.type.numeric.IntegerType;
+import net.imglib2.type.numeric.integer.UnsignedIntType;
+import net.imglib2.util.Intervals;
+import net.imglib2.util.Pair;
+import net.imglib2.util.Util;
+import net.imglib2.util.ValuePair;
+import net.imglib2.view.Views;
+import sc.fiji.labkit.ui.inputimage.DatasetInputImage;
+import sc.fiji.labkit.ui.labeling.Label;
+import sc.fiji.labkit.ui.labeling.Labeling;
+import sc.fiji.labkit.ui.models.DefaultSegmentationModel;
+
+public class LabkitLauncher< T extends IntegerType< T > & NativeType< T > >
+{
+
+ private final double[] calibration;
+
+ private final TrackMate trackmate;
+
+ private final EverythingDisablerAndReenabler disabler;
+
+ private int currentTimePoint;
+
+ private final DisplaySettings ds;
+
+ private final boolean is3D;
+
+ private final boolean isSingleTimePoint;
+
+ private static boolean simplify = true;
+
+ public LabkitLauncher( final TrackMate trackmate, final DisplaySettings ds, final EverythingDisablerAndReenabler disabler )
+ {
+ this.trackmate = trackmate;
+ this.ds = ds;
+ this.disabler = disabler;
+ final ImagePlus imp = trackmate.getSettings().imp;
+ this.calibration = TMUtils.getSpatialCalibration( imp );
+ this.is3D = !DetectionUtils.is2D( imp );
+ this.isSingleTimePoint = imp.getNFrames() <= 1;
+ }
+
+ /**
+ * Launches the Labkit editor.
+ *
+ * @param singleTimePoint
+ * if true
, will launch the editor using only the
+ * time-point currently displayed in the main view. Otherwise,
+ * will edit all time-points.
+ */
+ protected void launch( final boolean singleTimePoint )
+ {
+ final ImagePlus imp = trackmate.getSettings().imp;
+ final DatasetInputImage input = makeInput( imp, singleTimePoint );
+ final Pair< Labeling, Map< Integer, Spot > > pair = makeLabeling( imp, trackmate.getModel().getSpots(), singleTimePoint );
+ final Labeling labeling = pair.getA();
+ final Map< Integer, Spot > spotLabels = pair.getB();
+
+ // Make a labeling model from it.
+ final Context context = TMUtils.getContext();
+ final DefaultSegmentationModel model = new DefaultSegmentationModel( context, input );
+ model.imageLabelingModel().labeling().set( labeling );
+
+ // Store a copy.
+ @SuppressWarnings( "unchecked" )
+ final RandomAccessibleInterval< T > previousIndexImg = copy( ( RandomAccessibleInterval< T > ) labeling.getIndexImg() );
+
+ // Show LabKit.
+ String title = "Editing TrackMate data for " + imp.getShortTitle();
+ if ( singleTimePoint )
+ title += "at frame " + ( currentTimePoint + 1 );
+ final TrackMateLabkitFrame labkit = TrackMateLabkitFrame.show( model, title );
+
+ // Prepare re-importer.
+ final double dt = imp.getCalibration().frameInterval;
+ labkit.onCloseListeners().addListener( () -> {
+ @SuppressWarnings( "unchecked" )
+ final RandomAccessibleInterval< T > indexImg = ( RandomAccessibleInterval< T > ) model.imageLabelingModel().labeling().get().getIndexImg();
+ reimport( indexImg, previousIndexImg, spotLabels, dt );
+ } );
+ }
+
+ private void reimport(
+ final RandomAccessibleInterval< T > indexImg,
+ final RandomAccessibleInterval< T > previousIndexImg,
+ final Map< Integer, Spot > spotLabels,
+ final double dt )
+ {
+ new Thread( "TrackMate-LabKit-Importer-thread" )
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ // Do we have something to reimport?
+ final AtomicBoolean modified = new AtomicBoolean( false );
+ LoopBuilder.setImages( previousIndexImg, indexImg )
+ .multiThreaded()
+ .forEachChunk( chunk -> {
+ if ( modified.get() )
+ return null;
+ chunk.forEachPixel( ( p1, p2 ) -> {
+ if ( p1.getInteger() != p2.getInteger() )
+ {
+ modified.set( true );
+ return;
+ }
+ } );
+ return null;
+ } );
+ if ( !modified.get() )
+ return;
+
+ // Message the user.
+ final String msg = ( isSingleTimePoint )
+ ? "Commit the changes made to the\n"
+ + "segmentation in the image?"
+ : ( currentTimePoint < 0 )
+ ? "Commit the changes made to the\n"
+ + "segmentation in whole movie?"
+ : "Commit the changes made to the\n"
+ + "segmentation in frame " + ( currentTimePoint + 1 ) + "?";
+ final String title = "Commit edits to TrackMate";
+ final JCheckBox chkbox = new JCheckBox( "Simplify the contours of modified spots" );
+ chkbox.setSelected( simplify );
+ final Object[] objs = new Object[] { msg, new JSeparator(), chkbox };
+ final int returnedValue = JOptionPane.showConfirmDialog(
+ null,
+ objs,
+ title,
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ Icons.TRACKMATE_ICON );
+ if ( returnedValue != JOptionPane.YES_OPTION )
+ return;
+
+ simplify = chkbox.isSelected();
+ final LabkitImporter< T > reimporter = new LabkitImporter<>( trackmate.getModel(), calibration, dt, simplify );
+
+ // Possibly determine the number of time-points to parse.
+ final int timeDim = ( isSingleTimePoint )
+ ? -1
+ : ( is3D ) ? 3 : 2;
+ final long nTimepoints = ( timeDim < 0 )
+ ? 0
+ : indexImg.numDimensions() > timeDim ? indexImg.dimension( timeDim ) : 0;
+
+ if ( currentTimePoint < 0 && nTimepoints > 1 )
+ {
+ // All time-points.
+ final Logger log = Logger.IJ_LOGGER;
+ log.setStatus( "Re-importing from Labkit..." );
+ for ( int t = 0; t < nTimepoints; t++ )
+ {
+ // The spots of this time-point:
+ final Map< Integer, Spot > spotLabelsThisFrame = new HashMap<>();
+ for ( final Integer label : spotLabels.keySet() )
+ {
+ final Spot spot = spotLabels.get( label );
+ if ( spot.getFeature( Spot.FRAME ).intValue() == t )
+ spotLabelsThisFrame.put( label, spot );
+ }
+
+ final RandomAccessibleInterval< T > novelIndexImgThisFrame = Views.hyperSlice( indexImg, timeDim, t );
+ final RandomAccessibleInterval< T > previousIndexImgThisFrame = Views.hyperSlice( previousIndexImg, timeDim, t );
+ reimporter.reimport( novelIndexImgThisFrame, previousIndexImgThisFrame, t, spotLabelsThisFrame );
+ log.setProgress( t / ( double ) nTimepoints );
+ }
+ log.setStatus( "" );
+ log.setProgress( 0. );
+ }
+ else
+ {
+ // Only one.
+ final int localT = Math.max( 0, currentTimePoint );
+ reimporter.reimport( indexImg, previousIndexImg, localT, spotLabels );
+ }
+ }
+ catch ( final Exception e )
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ disabler.reenable();
+ }
+ }
+ }.start();
+ }
+
+ private Img< T > copy( final RandomAccessibleInterval< T > in )
+ {
+ final ImgFactory< T > factory = Util.getArrayOrCellImgFactory( in, in.getType() );
+ final Img< T > out = factory.create( in );
+ LoopBuilder.setImages( in, out )
+ .multiThreaded()
+ .forEachPixel( ( i, o ) -> o.setInteger( i.getInteger() ) );
+ return out;
+ }
+
+ /**
+ * Creates a new {@link DatasetInputImage} from the specified
+ * {@link ImagePlus}. The embedded label image is empty.
+ *
+ * @param imp
+ * the input {@link ImagePlus}.
+ * @param singleTimePoint
+ * if true
, then the dataset will be created only
+ * for the time-point currently displayed in the
+ * {@link ImagePlus}. This time-point is then stored in the
+ * {@link #currentTimePoint} field. If false
, the
+ * dataset is created for the whole movie and the
+ * {@link #currentTimePoint} takes the value -1.
+ * @return a new {@link DatasetInputImage}.
+ */
+ @SuppressWarnings( { "rawtypes", "unchecked" } )
+ private final DatasetInputImage makeInput( final ImagePlus imp, final boolean singleTimePoint )
+ {
+ final ImgPlus src = TMUtils.rawWraps( imp );
+ // Crop if we have a ROI.
+ final Roi roi = imp.getRoi();
+ final RandomAccessibleInterval crop;
+ if ( roi != null )
+ {
+ final long[] min = src.minAsLongArray();
+ final long[] max = src.maxAsLongArray();
+ min[ 0 ] = roi.getBounds().x;
+ min[ 1 ] = roi.getBounds().y;
+// max[ 0 ] = roi.getBounds().x + roi.getBounds().width;
+// max[ 1 ] = roi.getBounds().y + roi.getBounds().height;
+ max[ 0 ] = roi.getBounds().x + roi.getBounds().width - 1;
+ max[ 1 ] = roi.getBounds().y + roi.getBounds().height - 1;
+ crop = Views.interval( src, min, max );
+ }
+ else
+ {
+ crop = src;
+ }
+ final ImgPlus srcCropped = new ImgPlus<>( ImgView.wrap( crop ), src );
+
+ // Possibly reslice for current time-point.
+ final ImpBdvShowable showable;
+ final ImgPlus inputImg;
+ final int timeAxis = src.dimensionIndex( Axes.TIME );
+ if ( singleTimePoint && timeAxis >= 0 )
+ {
+ this.currentTimePoint = imp.getFrame() - 1;
+ inputImg = ImgPlusViews.hyperSlice( srcCropped, timeAxis, currentTimePoint );
+ showable = ImpBdvShowable.fromImp( inputImg, imp );
+ }
+ else
+ {
+ this.currentTimePoint = -1;
+ inputImg = srcCropped;
+ showable = ImpBdvShowable.fromImp( inputImg, imp );
+ }
+ return new DatasetInputImage( inputImg, showable );
+ }
+
+ /**
+ * Prepare the label image for annotation. The labeling is created and each
+ * of its labels receive the name and the color from the spot it is created
+ * from. Only the spots fully included in the bounding box of the ROI of the
+ * source image are written in the labeling.
+ *
+ * @param imp
+ * the source image plus.
+ * @param spots
+ * the spot collection.
+ * @param singleTimePoint
+ * if true
we only annotate one time-point.
+ * @return the pair of: A. a new {@link Labeling}, B. the map of spots that
+ * were written in the labeling. The keys are the label value in the
+ * labeling.
+ */
+ private Pair< Labeling, Map< Integer, Spot > > makeLabeling( final ImagePlus imp, final SpotCollection spots, final boolean singleTimePoint )
+ {
+ // Axes.
+ final AxisType[] axes = ( is3D )
+ ? ( singleTimePoint )
+ ? new AxisType[] { Axes.X, Axes.Y, Axes.Z }
+ : new AxisType[] { Axes.X, Axes.Y, Axes.Z, Axes.TIME }
+ : ( singleTimePoint )
+ ? new AxisType[] { Axes.X, Axes.Y }
+ : new AxisType[] { Axes.X, Axes.Y, Axes.TIME };
+
+ // N dimensions.
+ final int nDims = is3D
+ ? singleTimePoint ? 3 : 4
+ : singleTimePoint ? 2 : 3;
+
+ // Dimensions.
+ final long[] dims = new long[ nDims ];
+ int dim = 0;
+ dims[ dim++ ] = imp.getWidth();
+ dims[ dim++ ] = imp.getHeight();
+ if ( is3D )
+ dims[ dim++ ] = imp.getNSlices();
+ if ( !singleTimePoint )
+ dims[ dim++ ] = imp.getNFrames();
+
+ // Possibly crop.
+ final Roi roi = imp.getRoi();
+ final long[] origin;
+ if ( roi != null )
+ {
+ dims[ 0 ] = roi.getBounds().width + 1;
+ dims[ 1 ] = roi.getBounds().height + 1;
+ origin = new long[ dims.length ];
+ origin[ 0 ] = roi.getBounds().x;
+ origin[ 1 ] = roi.getBounds().y;
+ }
+ else
+ {
+ origin = null;
+ }
+
+ // Raw image.
+ Img< UnsignedIntType > lblImg = ArrayImgs.unsignedInts( dims );
+ if ( origin != null )
+ {
+ final RandomAccessibleInterval< UnsignedIntType > translated = Views.translate( lblImg, origin );
+ lblImg = ImgView.wrap( translated );
+ }
+
+ // Calibration.
+ final double[] c = TMUtils.getSpatialCalibration( imp );
+ final double[] calibration = new double[ nDims ];
+ dim = 0;
+ calibration[ dim++ ] = c[ 0 ];
+ calibration[ dim++ ] = c[ 1 ];
+ if ( is3D )
+ calibration[ dim++ ] = c[ 2 ];
+ if ( !singleTimePoint )
+ calibration[ dim++ ] = 1.;
+
+ // Label image holder.
+ final ImgPlus< UnsignedIntType > lblImgPlus = new ImgPlus<>( lblImg, "LblImg", axes, calibration );
+
+ // Write spots in it with index = id + 1 and build a map index -> spot.
+ final Map< Integer, Spot > spotLabels = new HashMap<>();
+ if ( singleTimePoint )
+ {
+ processFrame( lblImgPlus, spots, currentTimePoint, spotLabels, origin );
+ }
+ else
+ {
+ final int timeDim = lblImgPlus.dimensionIndex( Axes.TIME );
+ for ( int t = 0; t < imp.getNFrames(); t++ )
+ {
+ final ImgPlus< UnsignedIntType > lblImgPlusThisFrame = ImgPlusViews.hyperSlice( lblImgPlus, timeDim, t );
+ processFrame( lblImgPlusThisFrame, spots, t, spotLabels, origin );
+ }
+ }
+ final Labeling labeling = Labeling.fromImg( lblImgPlus );
+
+ // Fine tune labels name and color.
+ final FeatureColorGenerator< Spot > colorGen = FeatureUtils.createSpotColorGenerator( trackmate.getModel(), ds );
+ for ( final Label label : labeling.getLabels() )
+ {
+ final String name = label.name();
+ final int labelVal = Integer.parseInt( name );
+ final Spot spot = spotLabels.get( labelVal );
+ if ( spot == null )
+ {
+ System.out.println( "Spot is null for label " + labelVal + "!!" ); // DEBUG
+ continue;
+ }
+ label.setName( spot.getName() );
+ label.setColor( new ARGBType( colorGen.color( spot ).getRGB() ) );
+ }
+
+ return new ValuePair<>( labeling, spotLabels );
+ }
+
+ private final void processFrame(
+ final ImgPlus< UnsignedIntType > lblImgPlus,
+ final SpotCollection spots,
+ final int t,
+ final Map< Integer, Spot > spotLabels,
+ final long[] origin )
+ {
+ // If we have a single timepoint, don't use -1 to retrieve spots.
+ final int lt = t < 0 ? 0 : t;
+ final Iterable< Spot > spotsThisFrame = spots.iterable( lt, true );
+ if ( null == origin )
+ {
+ for ( final Spot spot : spotsThisFrame )
+ {
+ final int index = spot.ID() + 1;
+ SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) );
+ spotLabels.put( index, spot );
+ }
+ }
+ else
+ {
+ final long[] min = new long[ 2 ];
+ final long[] max = new long[ 2 ];
+ final FinalInterval spotBB = FinalInterval.wrap( min, max );
+ final FinalInterval imgBB = Intervals.createMinSize( origin[ 0 ], origin[ 1 ], lblImgPlus.dimension( 0 ), lblImgPlus.dimension( 1 ) );
+ for ( final Spot spot : spotsThisFrame )
+ {
+ boundingBox( spot, min, max );
+ // Inside? We skip if we touch the border.
+ final boolean isInside = Intervals.contains( imgBB, spotBB );
+ if ( !isInside )
+ continue;
+
+ final int index = spot.ID() + 1;
+ SpotUtil.iterable( spot, lblImgPlus ).forEach( p -> p.set( index ) );
+ spotLabels.put( index, spot );
+ }
+ }
+ }
+
+ private void boundingBox( final Spot spot, final long[] min, final long[] max )
+ {
+ final SpotRoi roi = spot.getRoi();
+ if ( roi == null )
+ {
+ final double cx = spot.getDoublePosition( 0 );
+ final double cy = spot.getDoublePosition( 1 );
+ final double r = spot.getFeature( Spot.RADIUS ).doubleValue();
+ min[ 0 ] = ( long ) Math.floor( ( cx - r ) / calibration[ 0 ] );
+ min[ 1 ] = ( long ) Math.floor( ( cy - r ) / calibration[ 1 ] );
+ max[ 0 ] = ( long ) Math.ceil( ( cx + r ) / calibration[ 0 ] );
+ max[ 1 ] = ( long ) Math.ceil( ( cy + r ) / calibration[ 1 ] );
+ }
+ else
+ {
+ final 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[ 0 ] = Math.max( 0, min[ 0 ] );
+ min[ 1 ] = Math.max( 0, min[ 1 ] );
+ final ImagePlus imp = trackmate.getSettings().imp;
+ max[ 0 ] = Math.min( imp.getWidth(), max[ 0 ] );
+ max[ 1 ] = Math.min( imp.getHeight(), max[ 1 ] );
+ }
+
+ public static final AbstractNamedAction getLaunchAction( final TrackMate trackmate, final DisplaySettings ds )
+ {
+ return new AbstractNamedAction( "launch labkit editor" )
+ {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void actionPerformed( final ActionEvent ae )
+ {
+ new Thread( "TrackMate editor thread" )
+ {
+ @Override
+ public void run()
+ {
+ // Is shift pressed?
+ final int mod = ae.getModifiers();
+ final boolean shiftPressed = ( mod & ActionEvent.SHIFT_MASK ) > 0;
+ final boolean singleTimepoint = !shiftPressed;
+
+ final JRootPane parent = SwingUtilities.getRootPane( ( Component ) ae.getSource() );
+ final EverythingDisablerAndReenabler disabler = new EverythingDisablerAndReenabler( parent, new Class[] { JLabel.class } );
+ disabler.disable();
+ try
+ {
+ @SuppressWarnings( "rawtypes" )
+ final LabkitLauncher launcher = new LabkitLauncher( trackmate, ds, disabler );
+ launcher.launch( singleTimepoint );
+ }
+ catch ( final Exception e )
+ {
+ disabler.reenable();
+ e.printStackTrace();
+ }
+ };
+ }.start();
+ }
+ };
+ }
+}
diff --git a/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabKitSegmentationComponent.java b/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabKitSegmentationComponent.java
new file mode 100644
index 000000000..c4d407694
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/gui/editor/TrackMateLabKitSegmentationComponent.java
@@ -0,0 +1,174 @@
+/*-
+ * #%L
+ * The Labkit image segmentation tool for Fiji.
+ * %%
+ * Copyright (C) 2017 - 2024 Matthias Arzt
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+package fiji.plugin.trackmate.gui.editor;
+
+import java.awt.BorderLayout;
+
+import javax.swing.BorderFactory;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+
+import net.miginfocom.swing.MigLayout;
+import sc.fiji.labkit.ui.BasicLabelingComponent;
+import sc.fiji.labkit.ui.DefaultExtensible;
+import sc.fiji.labkit.ui.MenuBar;
+import sc.fiji.labkit.ui.SegmentationComponent;
+import sc.fiji.labkit.ui.actions.AddLabelingIoAction;
+import sc.fiji.labkit.ui.actions.LabelEditAction;
+import sc.fiji.labkit.ui.actions.LabelingIoAction;
+import sc.fiji.labkit.ui.actions.ResetViewAction;
+import sc.fiji.labkit.ui.actions.ShowHelpAction;
+import sc.fiji.labkit.ui.menu.MenuKey;
+import sc.fiji.labkit.ui.models.ColoredLabelsModel;
+import sc.fiji.labkit.ui.models.ImageLabelingModel;
+import sc.fiji.labkit.ui.models.SegmentationItem;
+import sc.fiji.labkit.ui.models.SegmentationModel;
+import sc.fiji.labkit.ui.panel.ImageInfoPanel;
+import sc.fiji.labkit.ui.panel.LabelPanel;
+import sc.fiji.labkit.ui.segmentation.PredictionLayer;
+
+/**
+ * This class is copy/pasted and adapted from {@link SegmentationComponent} to
+ * control what is shown in the TrackMate UI. This could be revised to avoid
+ * class duplication.
+ *
+ * {@link SegmentationComponent} is the central Labkit UI component. Provides UI
+ * to display and modify a {@link SegmentationModel}.
+ *
"
- + "search. The spots in the target frame are searched for the nearest neighbor
"
- + "of each spot in the source frame. If the spots found are closer than the
"
- + "maximal allowed distance, a link between the two is created.
"
+ + "This tracker is the most simple one, and is based on nearest neighbor "
+ + "search. "
+ "
"
- + "in imglib by Johannes Schindelin and friends. This ensure a very efficient "
- + "tracking and makes this tracker suitable for situation where a huge number
"
- + "of particles are to be tracked over a very large number of frames. However,
"
- + "because of the naiveness of its principles, it can result in pathological
"
- + "tracks. It can only do frame-to-frame linking; there cannot be any track
"
+ + "For each pair of consecutive frames t1 and t2, it iterates through all "
+ + "spots in frame t1. For each source spot in t1, it searches for the nearest target spot "
+ + "in frame t2. If it is not already connected to a spot in frame t1, and is "
+ + "within the maximal linking distance, a link between the two spots is created.
"
+ + "true
if the class with the fully qualified name is
+ * present at runtime. This is useful to detect whether a certain update
+ * site has been activated in Fiji and to enable or disable component based
+ * on this.
+ *
+ * @param className
+ * the fully qualified class name, e.g.
+ * "sc.fiji.labkit.ui.LabkitFrame"
+ * @return true
if the the class with the specified name is
+ * present, false otherwise.
+ */
+ public static boolean isClassPresent( final String className )
+ {
+ try
+ {
+ Class.forName( className, false, TMUtils.class.getClassLoader() );
+ return true;
+ }
+ catch ( final ClassNotFoundException e1 )
+ {}
+ return false;
+ }
+
private TMUtils()
{}
}
diff --git a/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java b/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java
new file mode 100644
index 000000000..ccd1bac73
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/util/WrapLayout.java
@@ -0,0 +1,117 @@
+package fiji.plugin.trackmate.util;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Insets;
+
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+public class WrapLayout extends FlowLayout
+{
+ private static final long serialVersionUID = 1L;
+
+ public WrapLayout()
+ {
+ super();
+ }
+
+ public WrapLayout( final int align )
+ {
+ super( align );
+ }
+
+ public WrapLayout( final int align, final int hgap, final int vgap )
+ {
+ super( align, hgap, vgap );
+ }
+
+ @Override
+ public Dimension preferredLayoutSize( final Container target )
+ {
+ return layoutSize( target, true );
+ }
+
+ @Override
+ public Dimension minimumLayoutSize( final Container target )
+ {
+ final Dimension minimum = layoutSize( target, false );
+ minimum.width -= ( getHgap() + 1 );
+ return minimum;
+ }
+
+ private Dimension layoutSize( final Container target, final boolean preferred )
+ {
+ synchronized ( target.getTreeLock() )
+ {
+ int targetWidth = target.getSize().width;
+
+ if ( targetWidth == 0 )
+ targetWidth = Integer.MAX_VALUE;
+
+ final int hgap = getHgap();
+ final int vgap = getVgap();
+ final Insets insets = target.getInsets();
+ final int horizontalInsetsAndGap = insets.left + insets.right + ( hgap * 2 );
+ final int maxWidth = targetWidth - horizontalInsetsAndGap;
+
+ final Dimension dim = new Dimension( 0, 0 );
+ int rowWidth = 0;
+ int rowHeight = 0;
+
+ final int nmembers = target.getComponentCount();
+
+ for ( int i = 0; i < nmembers; i++ )
+ {
+ final Component m = target.getComponent( i );
+
+ if ( m.isVisible() )
+ {
+ final Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
+
+ if ( rowWidth + d.width > maxWidth )
+ {
+ addRow( dim, rowWidth, rowHeight );
+ rowWidth = 0;
+ rowHeight = 0;
+ }
+
+ if ( rowWidth != 0 )
+ {
+ rowWidth += hgap;
+ }
+
+ rowWidth += d.width;
+ rowHeight = Math.max( rowHeight, d.height );
+ }
+ }
+
+ addRow( dim, rowWidth, rowHeight );
+
+ dim.width += horizontalInsetsAndGap;
+ dim.height += insets.top + insets.bottom + vgap * 2;
+
+ final Container scrollPane = SwingUtilities.getAncestorOfClass( JScrollPane.class, target );
+ if ( scrollPane != null )
+ {
+ dim.width -= ( hgap + 1 );
+ }
+
+ return dim;
+ }
+ }
+
+ private void addRow( final Dimension dim, final int rowWidth, final int rowHeight )
+ {
+ dim.width = Math.max( dim.width, rowWidth );
+
+ if ( dim.height > 0 )
+ {
+ dim.height += getVgap();
+ }
+
+ dim.height += rowHeight;
+ }
+}
diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java b/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java
index db9b4a9fa..169f15326 100644
--- a/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.java
+++ b/src/main/java/fiji/plugin/trackmate/util/cli/CLIConfigurator.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
* addXYX()
+ * builders.
+ *
+ * @author Jean-Yves Tinevez
+ */
public abstract class CLIConfigurator
{
@@ -181,7 +189,7 @@ public void select( final String key )
{
for ( int i = 0; i < args.size(); i++ )
{
- if ( args.get( i ).getKey().equals( key ) )
+ if ( key.equals( args.get( i ).getKey() ) )
{
this.selected = i;
return;
@@ -282,60 +290,133 @@ abstract class Adder< A extends Argument< A, O >, T extends Adder< A, T, O >, O
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();
}
@@ -346,12 +427,28 @@ private abstract class BoundedAdder< A extends BoundedValueArgument< A, O >, T e
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;
@@ -487,6 +584,14 @@ private ChoiceAdder()
private final List< String > choices = 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 )
{
if ( !choices.contains( choice ) )
@@ -494,6 +599,14 @@ public ChoiceAdder addChoice( final String choice )
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 )
@@ -501,6 +614,17 @@ public Adder< ChoiceArgument, ChoiceAdder, String > addChoiceAll( final Collecti
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.
+ * --
, 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();
@@ -596,6 +772,9 @@ public static class Flag extends Argument< Flag, Boolean >
Flag()
{}
+ /**
+ * Sets this flag argument to true
.
+ */
public void set()
{
set( true );
@@ -708,7 +887,7 @@ public static class ChoiceArgument extends AbstractStringArgument< ChoiceArgumen
private ChoiceArgument()
{}
- ChoiceArgument addChoice( final String choice )
+ private ChoiceArgument addChoice( final String choice )
{
if ( !choices.contains( choice ) )
choices.add( choice );
@@ -1040,7 +1219,7 @@ public String getHelp()
public String getKey()
{
- return ( key == null ) ? getName() : key;
+ return key;
}
public abstract void accept( final ArgumentVisitor visitor );
diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CliGuiBuilder.java b/src/main/java/fiji/plugin/trackmate/util/cli/CliGuiBuilder.java
index 41af6992c..eca1de965 100644
--- a/src/main/java/fiji/plugin/trackmate/util/cli/CliGuiBuilder.java
+++ b/src/main/java/fiji/plugin/trackmate/util/cli/CliGuiBuilder.java
@@ -393,6 +393,8 @@ private void addPathToLayout( final String help, final JLabel lbl, final JTextFi
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 );
diff --git a/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java b/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java
index ebe01db0e..6aec3e860 100644
--- a/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java
+++ b/src/main/java/fiji/plugin/trackmate/util/cli/CommandBuilder.java
@@ -91,12 +91,22 @@ public void visit( final Flag flag )
val = flag.getValue();
else
val = flag.getDefaultValue();
- if ( val )
+
+ // 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( flag.getArgument() );
- tokens.addAll( translators.getOrDefault( flag, v -> Collections.singletonList( "" + v ) ).apply( val ) );
+ tokens.add( a + String.join( ",", vals ) );
+ }
+ else
+ {
+ if ( val )
+ {
+ tokens.add( a );
+ tokens.addAll( vals );
+ }
}
-
}
@Override
@@ -122,8 +132,19 @@ public void visit( final IntArgument arg )
if ( arg.getMax() != Integer.MAX_VALUE && ( val > arg.getMax() ) )
throw new IllegalArgumentException( "Value " + val + " for argument '" + arg.getName() + "' is larger than the max: " + arg.getMax() );
- tokens.add( arg.getArgument() );
- tokens.addAll( translators.getOrDefault( arg, v -> Collections.singletonList( "" + v ) ).apply( val ) );
+ 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
@@ -151,8 +172,19 @@ public void visit( final DoubleArgument arg )
throw new IllegalArgumentException( "Value " + val + " for argument '" + arg.getName()
+ "' is larger than the max: " + arg.getMax() );
- tokens.add( arg.getArgument() );
- tokens.addAll( translators.getOrDefault( arg, v -> Collections.singletonList( "" + v ) ).apply( val ) );
+ 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 )
@@ -171,8 +203,19 @@ private void visitString( final AbstractStringArgument< ? > arg )
? arg.getDefaultValue()
: arg.getValue();
- tokens.add( arg.getArgument() );
- tokens.addAll( translators.getOrDefault( arg, v -> Collections.singletonList( "" + v ) ).apply( val ) );
+ 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
@@ -198,9 +241,20 @@ public void visit( final ChoiceArgument arg )
// Is not set? -> skip
if ( !arg.isSet() )
return;
-
- tokens.add( arg.getArgument() );
- tokens.addAll( translators.getOrDefault( arg, v -> Collections.singletonList( "" + v ) ).apply( arg.getValue() ) );
+
+ final String a = arg.getArgument();
+ final List
+ *
+ */
+public abstract class CondaExecutableCLIConfigurator extends CondaCLIConfigurator
+{
+
+ public CondaExecutableCLIConfigurator()
+ {
+ super();
+
+ // Add the translator to make a proper cmd line calling conda first.
+ setTranslator( condaEnv, s -> {
+ final List< String > cmd = new ArrayList<>();
+ final String condaPath = CLIUtils.getCondaPath();
+ // Conda and executable stuff.
+ final String envname = ( String ) s;
+ if ( IJ.isWindows() )
+ {
+ cmd.addAll( Arrays.asList( "cmd.exe", "/c" ) );
+ cmd.addAll( Arrays.asList( condaPath, "activate", envname ) );
+ cmd.add( "&" );
+ // Add command name
+ final String executableCommand = getCommand();
+ // Split by spaces
+ final String[] split = executableCommand.split( " " );
+ cmd.addAll( Arrays.asList( split ) );
+ return cmd;
+
+ }
+ else
+ {
+ try
+ {
+ final String pythonPath = CLIUtils.getEnvMap().get( envname );
+ final int i = pythonPath.lastIndexOf( "python" );
+ final String binPath = pythonPath.substring( 0, i );
+ final String executablePath = binPath + getCommand();
+ 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();
+ }
+ }
+ return null;
+ } );
+ }
+
+}
diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java
index 1fc20c149..d091862d2 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/SpotEditTool.java
@@ -355,7 +355,6 @@ public void keyPressed( final KeyEvent e )
case KeyEvent.VK_SPACE:
{
actions.startMoveSpot();
- e.consume();
break;
}
diff --git a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackOverlay.java b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackOverlay.java
index 077456d15..ed6611395 100644
--- a/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackOverlay.java
+++ b/src/main/java/fiji/plugin/trackmate/visualization/hyperstack/TrackOverlay.java
@@ -211,7 +211,8 @@ public final synchronized void drawOverlay( final Graphics g )
final Set< DefaultWeightedEdge > track;
synchronized ( model )
{
- track = new HashSet<>( model.getTrackModel().trackEdges( trackID ) );
+ final Set< DefaultWeightedEdge > edges = model.getTrackModel().trackEdges( trackID );
+ track = ( edges == null ) ? new HashSet<>() : new HashSet<>( edges );
}
for ( final DefaultWeightedEdge edge : track )
{