.
+ * #L%
+ */
+package fiji.plugin.trackmate.omnipose;
+
+import static fiji.plugin.trackmate.detection.DetectorKeys.DEFAULT_TARGET_CHANNEL;
+import static fiji.plugin.trackmate.detection.DetectorKeys.KEY_TARGET_CHANNEL;
+import static fiji.plugin.trackmate.detection.ThresholdDetectorFactory.KEY_SIMPLIFY_CONTOURS;
+import static fiji.plugin.trackmate.io.IOUtils.readBooleanAttribute;
+import static fiji.plugin.trackmate.io.IOUtils.readDoubleAttribute;
+import static fiji.plugin.trackmate.io.IOUtils.readIntegerAttribute;
+import static fiji.plugin.trackmate.io.IOUtils.readStringAttribute;
+import static fiji.plugin.trackmate.io.IOUtils.writeAttribute;
+import static fiji.plugin.trackmate.io.IOUtils.writeTargetChannel;
+import static fiji.plugin.trackmate.util.TMUtils.checkMapKeys;
+import static fiji.plugin.trackmate.util.TMUtils.checkParameter;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.jdom2.Element;
+import org.scijava.Priority;
+import org.scijava.plugin.Plugin;
+
+import fiji.plugin.trackmate.Logger;
+import fiji.plugin.trackmate.Model;
+import fiji.plugin.trackmate.Settings;
+import fiji.plugin.trackmate.cellpose.CellposeDetector;
+import fiji.plugin.trackmate.cellpose.CellposeDetectorFactory;
+import fiji.plugin.trackmate.detection.SpotDetectorFactory;
+import fiji.plugin.trackmate.detection.SpotDetectorFactoryBase;
+import fiji.plugin.trackmate.detection.SpotGlobalDetector;
+import fiji.plugin.trackmate.gui.components.ConfigurationPanel;
+import fiji.plugin.trackmate.io.IOUtils;
+import fiji.plugin.trackmate.omnipose.OmniposeSettings.PretrainedModelOmnipose;
+import fiji.plugin.trackmate.util.TMUtils;
+import net.imglib2.Interval;
+import net.imglib2.type.NativeType;
+import net.imglib2.type.numeric.RealType;
+
+@Plugin( type = SpotDetectorFactory.class, priority = Priority.LOW )
+public class OmniposeDetectorFactory< T extends RealType< T > & NativeType< T > > extends CellposeDetectorFactory< T >
+{
+
+ /*
+ * CONSTANTS
+ */
+
+ /**
+ * The key to the parameter that stores the path the omnipose model to use.
+ * Value can be {@link OmniposeSettings.PretrainedModelOmnipose}.
+ */
+ public static final String KEY_OMNIPOSE_MODEL = "OMNIPOSE_MODEL";
+
+ public static final PretrainedModelOmnipose DEFAULT_OMNIPOSE_MODEL = PretrainedModelOmnipose.BACT_PHASE;
+
+ /**
+ * The key to the parameter that stores the path to the Python instance that
+ * can run omnipose if you installed it via Conda or the omnipose executable
+ * if you have installed the standalone version. Something like
+ * '/opt/anaconda3/envs/omnipose/bin/python' or
+ * 'C:\Users\tinevez\Applications\omnipose.exe'.
+ */
+ public static final String KEY_OMNIPOSE_PYTHON_FILEPATH = "OMNIPOSE_PYTHON_FILEPATH";
+
+ public static final String DEFAULT_OMNIPOSE_PYTHON_FILEPATH = "/opt/anaconda3/envs/omnipose/bin/python";
+
+ /**
+ * The key to the parameter that stores the path to the custom model file to
+ * use with Omnipose. It must be an absolute file path.
+ */
+ public static final String KEY_OMNIPOSE_CUSTOM_MODEL_FILEPATH = "OMNIPOSE_MODEL_FILEPATH";
+
+ public static final String DEFAULT_OMNIPOSE_CUSTOM_MODEL_FILEPATH = "";
+
+ /** A string key identifying this factory. */
+ public static final String DETECTOR_KEY = "OMNIPOSE_DETECTOR";
+
+ /** The pretty name of the target detector. */
+ public static final String NAME = "Omnipose detector";
+
+ /** An html information text. */
+ public static final String INFO_TEXT = ""
+ + "This detector relies on omnipose to detect objects."
+ + ""
+ + "The detector simply calls an external omnipose installation. So for this "
+ + "to work, you must have a omnipose installation running on your computer. "
+ + "Please follow the instructions from the omnipose website: "
+ + "https://github.com/kevinjohncutler/omnipose"
+ + "
"
+ + "You will also need to specify the path to the Python executable that can run omnipose "
+ + "or the omnipose executable directly. "
+ + "For instance if you used anaconda to install omnipose, and that you have a "
+ + "Conda environment called 'omnipose', this path will be something along the line of "
+ + "'/opt/anaconda3/envs/omnipose/bin/python' or 'C:\\\\Users\\\\tinevez\\\\anaconda3\\\\envs\\\\omnipose_biop_gpu\\\\python.exe' "
+ + "If you installed the standalone version, the path to it would something like "
+ + "this on Windows: 'C:\\Users\\tinevez\\Applications\\omnipose.exe'. "
+ + "
"
+ + "If you use this detector for your work, please be so kind as to "
+ + "also cite the omnipose paper: Cutler, Kevin J., et al., "
+ + "'Omnipose: A High-Precision Morphology-Independent Solution for Bacterial Cell Segmentation.' "
+ + "Nature Methods 19, no. 11 (November 2022): 1438–48."
+ + "";
+
+ /*
+ * METHODS
+ */
+
+ @Override
+ public SpotGlobalDetector< T > getDetector( final Interval interval )
+ {
+ final String omniposePythonPath = ( String ) settings.get( KEY_OMNIPOSE_PYTHON_FILEPATH );
+ final PretrainedModelOmnipose model = ( PretrainedModelOmnipose ) settings.get( KEY_OMNIPOSE_MODEL );
+ final String customModelPath = ( String ) settings.get( KEY_OMNIPOSE_CUSTOM_MODEL_FILEPATH );
+ final boolean simplifyContours = ( boolean ) settings.get( KEY_SIMPLIFY_CONTOURS );
+ final boolean useGPU = ( boolean ) settings.get( KEY_USE_GPU );
+
+ // Channels are 0-based (0: grayscale, then R & G & B).
+ final int channel = ( Integer ) settings.get( KEY_TARGET_CHANNEL );
+ final int channel2 = ( Integer ) settings.get( KEY_OPTIONAL_CHANNEL_2 );
+
+ // Convert to diameter in pixels.
+ final double[] calibration = TMUtils.getSpatialCalibration( img );
+ final double diameter = ( double ) settings.get( KEY_CELL_DIAMETER ) / calibration[ 0 ];
+
+ final OmniposeSettings omniposeSettings = OmniposeSettings.create()
+ .omniposePythonPath( omniposePythonPath )
+ .customModel( customModelPath )
+ .model( model )
+ .channel1( channel )
+ .channel2( channel2 )
+ .diameter( diameter )
+ .useGPU( useGPU )
+ .simplifyContours( simplifyContours )
+ .get();
+
+ // Logger.
+ final Logger logger = ( Logger ) settings.get( KEY_LOGGER );
+ final CellposeDetector< T > detector = new CellposeDetector<>(
+ img,
+ interval,
+ omniposeSettings,
+ logger );
+ return detector;
+ }
+
+ @Override
+ public boolean marshall( final Map< String, Object > settings, final Element element )
+ {
+ final StringBuilder errorHolder = new StringBuilder();
+ boolean ok = writeTargetChannel( settings, element, errorHolder );
+ ok = ok && writeAttribute( settings, element, KEY_OMNIPOSE_PYTHON_FILEPATH, String.class, errorHolder );
+ ok = ok && writeAttribute( settings, element, KEY_OMNIPOSE_CUSTOM_MODEL_FILEPATH, String.class, errorHolder );
+ ok = ok && writeAttribute( settings, element, KEY_TARGET_CHANNEL, Integer.class, errorHolder );
+ ok = ok && writeAttribute( settings, element, KEY_OPTIONAL_CHANNEL_2, Integer.class, errorHolder );
+ ok = ok && writeAttribute( settings, element, KEY_CELL_DIAMETER, Double.class, errorHolder );
+ ok = ok && writeAttribute( settings, element, KEY_USE_GPU, Boolean.class, errorHolder );
+ ok = ok && writeAttribute( settings, element, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder );
+
+ final PretrainedModelOmnipose model = ( PretrainedModelOmnipose ) settings.get( KEY_OMNIPOSE_MODEL );
+ element.setAttribute( KEY_OMNIPOSE_MODEL, model.name() );
+
+ if ( !ok )
+ errorMessage = errorHolder.toString();
+
+ return ok;
+ }
+
+ @Override
+ public boolean unmarshall( final Element element, final Map< String, Object > settings )
+ {
+ settings.clear();
+ final StringBuilder errorHolder = new StringBuilder();
+ boolean ok = true;
+ ok = ok && readStringAttribute( element, settings, KEY_OMNIPOSE_PYTHON_FILEPATH, errorHolder );
+ ok = ok && readStringAttribute( element, settings, KEY_OMNIPOSE_CUSTOM_MODEL_FILEPATH, errorHolder );
+ ok = ok && readIntegerAttribute( element, settings, KEY_TARGET_CHANNEL, errorHolder );
+ ok = ok && readIntegerAttribute( element, settings, KEY_OPTIONAL_CHANNEL_2, errorHolder );
+ ok = ok && readDoubleAttribute( element, settings, KEY_CELL_DIAMETER, errorHolder );
+ ok = ok && readBooleanAttribute( element, settings, KEY_USE_GPU, errorHolder );
+ ok = ok && readBooleanAttribute( element, settings, KEY_SIMPLIFY_CONTOURS, errorHolder );
+
+ // Read model.
+ final String str = element.getAttributeValue( KEY_OMNIPOSE_MODEL );
+ if ( null == str )
+ {
+ errorHolder.append( "Attribute " + KEY_OMNIPOSE_MODEL + " could not be found in XML element.\n" );
+ ok = false;
+ }
+ settings.put( KEY_OMNIPOSE_MODEL, PretrainedModelOmnipose.valueOf( str ) );
+
+ return checkSettings( settings );
+ }
+
+ @Override
+ public ConfigurationPanel getDetectorConfigurationPanel( final Settings settings, final Model model )
+ {
+ return new OmniposeDetectorConfigurationPanel( settings, model );
+ }
+
+ @Override
+ public Map< String, Object > getDefaultSettings()
+ {
+ final Map< String, Object > settings = new HashMap<>();
+ settings.put( KEY_OMNIPOSE_PYTHON_FILEPATH, DEFAULT_OMNIPOSE_PYTHON_FILEPATH );
+ settings.put( KEY_OMNIPOSE_MODEL, DEFAULT_OMNIPOSE_MODEL );
+ settings.put( KEY_TARGET_CHANNEL, DEFAULT_TARGET_CHANNEL );
+ settings.put( KEY_OPTIONAL_CHANNEL_2, DEFAULT_OPTIONAL_CHANNEL_2 );
+ settings.put( KEY_CELL_DIAMETER, DEFAULT_CELL_DIAMETER );
+ settings.put( KEY_USE_GPU, DEFAULT_USE_GPU );
+ settings.put( KEY_SIMPLIFY_CONTOURS, true );
+ settings.put( KEY_LOGGER, Logger.DEFAULT_LOGGER );
+ settings.put( KEY_OMNIPOSE_CUSTOM_MODEL_FILEPATH, DEFAULT_OMNIPOSE_CUSTOM_MODEL_FILEPATH );
+ return settings;
+ }
+
+ @Override
+ public boolean checkSettings( final Map< String, Object > settings )
+ {
+ boolean ok = true;
+ final StringBuilder errorHolder = new StringBuilder();
+ ok = ok & checkParameter( settings, KEY_OMNIPOSE_PYTHON_FILEPATH, String.class, errorHolder );
+ ok = ok & checkParameter( settings, KEY_OMNIPOSE_CUSTOM_MODEL_FILEPATH, String.class, errorHolder );
+ ok = ok & checkParameter( settings, KEY_OMNIPOSE_MODEL, PretrainedModelOmnipose.class, errorHolder );
+ ok = ok & checkParameter( settings, KEY_TARGET_CHANNEL, Integer.class, errorHolder );
+ ok = ok & checkParameter( settings, KEY_OPTIONAL_CHANNEL_2, Integer.class, errorHolder );
+ ok = ok & checkParameter( settings, KEY_CELL_DIAMETER, Double.class, errorHolder );
+ ok = ok & checkParameter( settings, KEY_USE_GPU, Boolean.class, errorHolder );
+ ok = ok & checkParameter( settings, KEY_SIMPLIFY_CONTOURS, Boolean.class, errorHolder );
+
+ // If we have a logger, test it is of the right class.
+ final Object loggerObj = settings.get( KEY_LOGGER );
+ if ( loggerObj != null && !Logger.class.isInstance( loggerObj ) )
+ {
+ errorHolder.append( "Value for parameter " + KEY_LOGGER + " is not of the right class. "
+ + "Expected " + Logger.class.getName() + ", got " + loggerObj.getClass().getName() + ".\n" );
+ ok = false;
+ }
+
+ final List< String > mandatoryKeys = Arrays.asList(
+ KEY_OMNIPOSE_PYTHON_FILEPATH,
+ KEY_OMNIPOSE_MODEL,
+ KEY_TARGET_CHANNEL,
+ KEY_OPTIONAL_CHANNEL_2,
+ KEY_CELL_DIAMETER,
+ KEY_USE_GPU,
+ KEY_SIMPLIFY_CONTOURS );
+ final List< String > optionalKeys = Arrays.asList(
+ KEY_OMNIPOSE_CUSTOM_MODEL_FILEPATH,
+ KEY_LOGGER );
+ ok = ok & checkMapKeys( settings, mandatoryKeys, optionalKeys, errorHolder );
+ if ( !ok )
+ errorMessage = errorHolder.toString();
+
+ // Extra test to make sure we can read the classifier file.
+ if ( ok )
+ {
+ final Object obj = settings.get( KEY_OMNIPOSE_PYTHON_FILEPATH );
+ if ( obj == null )
+ {
+ errorMessage = "The path to the Omnipose python executable is not set.";
+ return false;
+ }
+
+ if ( !IOUtils.canReadFile( ( String ) obj, errorHolder ) )
+ {
+ errorMessage = "Problem with Omnipose python executable: " + errorHolder.toString();
+ return false;
+ }
+ }
+
+ return ok;
+ }
+
+ @Override
+ public String getInfoText()
+ {
+ return INFO_TEXT;
+ }
+
+ @Override
+ public String getKey()
+ {
+ return DETECTOR_KEY;
+ }
+
+ @Override
+ public String getName()
+ {
+ return NAME;
+ }
+
+ @Override
+ public SpotDetectorFactoryBase< T > copy()
+ {
+ return new OmniposeDetectorFactory<>();
+ }
+}
diff --git a/src/main/java/fiji/plugin/trackmate/omnipose/OmniposeSettings.java b/src/main/java/fiji/plugin/trackmate/omnipose/OmniposeSettings.java
new file mode 100644
index 00000000..068e3185
--- /dev/null
+++ b/src/main/java/fiji/plugin/trackmate/omnipose/OmniposeSettings.java
@@ -0,0 +1,175 @@
+/*-
+ * #%L
+ * TrackMate: your buddy for everyday tracking.
+ * %%
+ * Copyright (C) 2021 - 2023 TrackMate developers.
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+package fiji.plugin.trackmate.omnipose;
+
+import fiji.plugin.trackmate.cellpose.AbstractCellposeSettings;
+
+public class OmniposeSettings extends AbstractCellposeSettings
+{
+
+ public OmniposeSettings(
+ final String omniposePythonPath,
+ final PretrainedModelOmnipose model,
+ final String customModelPath,
+ final int chan,
+ final int chan2,
+ final double diameter,
+ final boolean useGPU,
+ final boolean simplifyContours )
+ {
+ super( omniposePythonPath, model, customModelPath, chan, chan2, diameter, useGPU, simplifyContours );
+ }
+
+ @Override
+ public String getExecutableName()
+ {
+ return "omnipose";
+ }
+
+ public static Builder create()
+ {
+ return new Builder();
+ }
+
+ public static final class Builder
+ {
+
+ private String omniposePythonPath = "/opt/anaconda3/envs/omnipose/bin/python";
+
+ private int chan = 0;
+
+ private int chan2 = -1;
+
+ private PretrainedModelOmnipose model = PretrainedModelOmnipose.BACT_PHASE;
+
+ private double diameter = 30.;
+
+ private boolean useGPU = true;
+
+ private boolean simplifyContours = true;
+
+ private String customModelPath = "";
+
+ public Builder channel1( final int ch )
+ {
+ this.chan = ch;
+ return this;
+ }
+
+ public Builder channel2( final int ch )
+ {
+ this.chan2 = ch;
+ return this;
+ }
+
+ public Builder omniposePythonPath( final String omniposePythonPath )
+ {
+ this.omniposePythonPath = omniposePythonPath;
+ return this;
+ }
+
+ public Builder model( final PretrainedModelOmnipose model )
+ {
+ this.model = model;
+ return this;
+ }
+
+ public Builder diameter( final double diameter )
+ {
+ this.diameter = diameter;
+ return this;
+ }
+
+ public Builder useGPU( final boolean useGPU )
+ {
+ this.useGPU = useGPU;
+ return this;
+ }
+
+ public Builder simplifyContours( final boolean simplifyContours )
+ {
+ this.simplifyContours = simplifyContours;
+ return this;
+ }
+
+ public Builder customModel( final String customModelPath )
+ {
+ this.customModelPath = customModelPath;
+ return this;
+ }
+
+ public OmniposeSettings get()
+ {
+ return new OmniposeSettings(
+ omniposePythonPath,
+ model,
+ customModelPath,
+ chan,
+ chan2,
+ diameter,
+ useGPU,
+ simplifyContours );
+ }
+
+ }
+
+ public enum PretrainedModelOmnipose implements PretrainedModel
+ {
+ BACT_PHASE( "Bacteria phase contrast", "bact_phase_omni", false ),
+ BACT_FLUO( "Bacteria fluorescence", "bact_fluor_omni", false ),
+ CUSTOM( "Custom", "", true );
+
+ private final String name;
+
+ private final String path;
+
+ private final boolean isCustom;
+
+ PretrainedModelOmnipose( final String name, final String path, final boolean isCustom )
+ {
+ this.name = name;
+ this.path = path;
+ this.isCustom = isCustom;
+ }
+
+ @Override
+ public String toString()
+ {
+ return name;
+ }
+
+ @Override
+ public boolean isCustom()
+ {
+ return isCustom;
+ }
+
+ @Override
+ public String getPath()
+ {
+ return path;
+ }
+
+ }
+
+ public static final OmniposeSettings DEFAULT = new Builder().get();
+}
diff --git a/src/main/resources/images/omniposelogo.png b/src/main/resources/images/omniposelogo.png
new file mode 100644
index 00000000..231cb06e
Binary files /dev/null and b/src/main/resources/images/omniposelogo.png differ
diff --git a/src/test/java/fiji/plugin/trackmate/CellPoseAttempt.java b/src/test/java/fiji/plugin/trackmate/CellPoseAttempt.java
index c4a82798..916e6745 100644
--- a/src/test/java/fiji/plugin/trackmate/CellPoseAttempt.java
+++ b/src/test/java/fiji/plugin/trackmate/CellPoseAttempt.java
@@ -21,9 +21,6 @@
*/
package fiji.plugin.trackmate;
-import javax.swing.UIManager;
-import javax.swing.UnsupportedLookAndFeelException;
-
import ij.ImageJ;
/**
@@ -32,12 +29,10 @@
public class CellPoseAttempt
{
- public static void main( final String[] args ) throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException
+ public static void main( final String[] args )
{
- UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
ImageJ.main( args );
-
-// new TrackMatePlugIn().run( "samples/R2_multiC.tif" );
- new TrackMatePlugIn().run( "D:/Projects/JYTinevez/TrackMate-StarDist/CTCMetrics/Brightfield/01.tif" );
+ new TrackMatePlugIn().run( "samples/R2_multiC.tif" );
+// new TrackMatePlugIn().run( "D:/Projects/JYTinevez/TrackMate-StarDist/CTCMetrics/Brightfield/01.tif" );
}
}
diff --git a/src/test/java/fiji/plugin/trackmate/CellPoseAttempt2.java b/src/test/java/fiji/plugin/trackmate/CellPoseAttempt2.java
index e17ca3f0..773caec8 100644
--- a/src/test/java/fiji/plugin/trackmate/CellPoseAttempt2.java
+++ b/src/test/java/fiji/plugin/trackmate/CellPoseAttempt2.java
@@ -23,6 +23,7 @@
import java.io.IOException;
+import fiji.plugin.trackmate.cellpose.AbstractCellposeSettings;
import fiji.plugin.trackmate.cellpose.CellposeDetector;
import fiji.plugin.trackmate.cellpose.CellposeSettings;
import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO;
@@ -48,7 +49,7 @@ public static void main( final String[] args ) throws IOException, InterruptedEx
imp.show();
// Cellpose command line options.
- final CellposeSettings cp = CellposeSettings.DEFAULT;
+ final AbstractCellposeSettings cp = CellposeSettings.DEFAULT;
final ImgPlus img = TMUtils.rawWraps( imp );
final CellposeDetector detector = new CellposeDetector( img, img, cp, Logger.DEFAULT_LOGGER );
diff --git a/src/test/java/fiji/plugin/trackmate/LoadCellposeResultsDemo.java b/src/test/java/fiji/plugin/trackmate/LoadCellposeResultsDemo.java
new file mode 100644
index 00000000..6fd0af03
--- /dev/null
+++ b/src/test/java/fiji/plugin/trackmate/LoadCellposeResultsDemo.java
@@ -0,0 +1,13 @@
+package fiji.plugin.trackmate;
+
+import ij.ImageJ;
+
+public class LoadCellposeResultsDemo
+{
+
+ public static void main( final String[] args )
+ {
+ ImageJ.main( args );
+ new LoadTrackMatePlugIn().run( "" );
+ }
+}
diff --git a/src/test/java/fiji/plugin/trackmate/OmniPoseAttempt.java b/src/test/java/fiji/plugin/trackmate/OmniPoseAttempt.java
new file mode 100644
index 00000000..ec5ace33
--- /dev/null
+++ b/src/test/java/fiji/plugin/trackmate/OmniPoseAttempt.java
@@ -0,0 +1,37 @@
+/*-
+ * #%L
+ * TrackMate: your buddy for everyday tracking.
+ * %%
+ * Copyright (C) 2021 - 2023 TrackMate developers.
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+package fiji.plugin.trackmate;
+
+import ij.ImageJ;
+
+/**
+ * Inspired by the BIOP approach.
+ */
+public class OmniPoseAttempt
+{
+
+ public static void main( final String[] args )
+ {
+ ImageJ.main( args );
+ new TrackMatePlugIn().run( "samples/20230331_washed_XY1.ome-1_stabilized_cropped.tif" );
+ }
+}
diff --git a/src/test/java/fiji/plugin/trackmate/OmniPoseAttempt2.java b/src/test/java/fiji/plugin/trackmate/OmniPoseAttempt2.java
new file mode 100644
index 00000000..ba0c789a
--- /dev/null
+++ b/src/test/java/fiji/plugin/trackmate/OmniPoseAttempt2.java
@@ -0,0 +1,79 @@
+/*-
+ * #%L
+ * TrackMate: your buddy for everyday tracking.
+ * %%
+ * Copyright (C) 2021 - 2023 TrackMate developers.
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program. If not, see
+ * .
+ * #L%
+ */
+package fiji.plugin.trackmate;
+
+import java.io.IOException;
+
+import fiji.plugin.trackmate.cellpose.CellposeDetector;
+import fiji.plugin.trackmate.gui.displaysettings.DisplaySettingsIO;
+import fiji.plugin.trackmate.omnipose.OmniposeSettings;
+import fiji.plugin.trackmate.util.TMUtils;
+import fiji.plugin.trackmate.visualization.hyperstack.HyperStackDisplayer;
+import ij.IJ;
+import ij.ImageJ;
+import ij.ImagePlus;
+import net.imagej.ImgPlus;
+
+/**
+ * Inspired by the BIOP approach.
+ */
+public class OmniPoseAttempt2
+{
+
+ @SuppressWarnings( { "rawtypes", "unchecked" } )
+ public static void main( final String[] args ) throws IOException, InterruptedException
+ {
+ ImageJ.main( args );
+
+ final ImagePlus imp = IJ.openImage( "samples/P31-crop-2.tif" );
+ imp.show();
+
+ // Omnipose command line options.
+ final OmniposeSettings cp = OmniposeSettings.DEFAULT;
+
+ final ImgPlus img = TMUtils.rawWraps( imp );
+ final CellposeDetector detector = new CellposeDetector( img, img, cp, Logger.DEFAULT_LOGGER );
+ if ( !detector.checkInput() )
+ {
+ System.err.println( detector.getErrorMessage() );
+ return;
+ }
+
+ if ( !detector.process() )
+ {
+ System.err.println( detector.getErrorMessage() );
+ return;
+ }
+
+ System.out.println( String.format( "Done in %.1f s.", detector.getProcessingTime() / 1000. ) );
+ final SpotCollection spots = detector.getResult();
+ spots.setVisible( true );
+ System.out.println( spots );
+
+ final Model model = new Model();
+ model.setSpots( spots, false );
+ final SelectionModel selectionModel = new SelectionModel( model );
+
+ final HyperStackDisplayer displayer = new HyperStackDisplayer( model, selectionModel, imp, DisplaySettingsIO.readUserDefault() );
+ displayer.render();
+ }
+}