Skip to content

Commit 447bd3a

Browse files
Merge pull request #149 from mastodon-sc/re-use-envs
Re use already loaded python environments
2 parents a07f913 + 5d7ffb3 commit 447bd3a

15 files changed

Lines changed: 212 additions & 218 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@
147147
<dependency>
148148
<groupId>org.apposed</groupId>
149149
<artifactId>appose</artifactId>
150-
<version>0.8.0</version>
150+
<version>0.8.1</version>
151151
</dependency>
152152

153153
<!-- mastodon tracking as base for custom detectors (cellpose, stardist) -->

src/main/java/org/mastodon/mamut/detection/DeepLearningDetector.java

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@
4141
import net.imglib2.view.IntervalView;
4242
import net.imglib2.view.Views;
4343

44-
import org.mastodon.mamut.util.appose.ApposeProcess;
45-
import org.mastodon.mamut.util.ByteFormatter;
44+
import org.apposed.appose.Appose;
45+
import org.apposed.appose.Environment;
46+
import org.apposed.appose.Service;
4647
import org.mastodon.mamut.detection.util.SpimImageProperties;
4748
import org.mastodon.mamut.model.ModelGraph;
49+
import org.mastodon.mamut.util.ByteFormatter;
4850
import org.mastodon.mamut.util.ImgUtils;
4951
import org.mastodon.mamut.util.LabelImageUtils;
5052
import org.mastodon.tracking.detection.AbstractDetectorOp;
@@ -78,13 +80,13 @@ public abstract class DeepLearningDetector extends AbstractSpotDetectorOp
7880
@Parameter
7981
protected Context context;
8082

83+
protected Service pythonService;
84+
8185
/**
8286
* Represents the maximum allowable size in bytes for datasets handle via the appose java-python bridge.
8387
*/
8488
private static final int MAX_SIZE_IN_BYTES = Integer.MAX_VALUE - 1;
8589

86-
protected ApposeProcess apposeProcess;
87-
8890
@Override
8991
public void compute( final List< SourceAndConverter< ? > > sources, final ModelGraph graph )
9092
{
@@ -111,17 +113,39 @@ public void compute( final List< SourceAndConverter< ? > > sources, final ModelG
111113
log.info( "Initialize python environment." );
112114
log.info( "On first time use this requires internet connection and may take a couple of minutes." );
113115
log.info( "Progress can be observed using FIJI console: FIJI > Window > Console." );
114-
for ( int timepoint = minTimepoint; timepoint <= maxTimepoint; timepoint++ )
116+
Environment environment = Appose.mamba().scheme( "environment.yml" ).content( getPythonEnvContent() ).logDebug()
117+
.subscribeProgress( ( title, cur, max ) -> logger.info( "{}: {}/{}", title, cur, max ) )
118+
.subscribeOutput( logger::info )
119+
.subscribeError( logger::error ).build();
120+
if ( logger.isInfoEnabled() )
121+
logger.info( "Set up environment. Path: {}", environment.base() );
122+
try (Service python = environment.python())
115123
{
116-
// We use the `statusService to show progress.
117-
statusService.showProgress( timepoint - minTimepoint + 1, maxTimepoint - minTimepoint + 1 );
118-
119-
if ( isCanceled() )
120-
break; // Exit but don't fail.
121-
122-
if ( DetectionUtil.isPresent( sources, settings.getSetupId(), timepoint ) )
123-
detectAndAddSpots( sources, graph, settings.getSetupId(), timepoint, settings.getResolutionLevel() );
124+
if ( isWindows() )
125+
python.init( getPythonEnvInit() );
126+
127+
// First, get the source for the current channel (or setup) at the desired time-point. In BDV jargon, this is a source.
128+
final Source< ? > source = sources.get( settings.getSetupId() ).getSpimSource();
129+
RandomAccessibleInterval< ? > image = source.getSource( 0, 0 );
130+
131+
Service.Task importTask = python.task( getImportScript( !is3D( image ) ), "main" );
132+
importTask.waitFor();
133+
this.pythonService = python;
134+
for ( int timepoint = minTimepoint; timepoint <= maxTimepoint; timepoint++ )
135+
{
136+
// We use the `statusService to show progress.
137+
statusService.showProgress( timepoint - minTimepoint + 1, maxTimepoint - minTimepoint + 1 );
138+
139+
if ( isCanceled() )
140+
break; // Exit but don't fail.
141+
142+
if ( DetectionUtil.isPresent( sources, settings.getSetupId(), timepoint ) )
143+
detectAndAddSpots( sources, graph, settings.getSetupId(), timepoint, settings.getResolutionLevel(), python );
144+
}
145+
if ( logger.isInfoEnabled() )
146+
logger.info( "Finished python process." );
124147
}
148+
125149
}
126150
catch ( Exception e )
127151
{
@@ -174,7 +198,7 @@ private SpimImageProperties extractSettings( final List< SourceAndConverter< ? >
174198
}
175199

176200
private void detectAndAddSpots( final List< SourceAndConverter< ? > > sources, final ModelGraph graph, final int setup,
177-
final int timepoint, final int level )
201+
final int timepoint, final int level, final Service python )
178202
{
179203
// First, get the source for the current channel (or setup) at the desired time-point. In BDV jargon, this is a source.
180204
final Source< ? > source = sources.get( setup ).getSpimSource();
@@ -212,7 +236,8 @@ private void detectAndAddSpots( final List< SourceAndConverter< ? > > sources, f
212236
}
213237

214238
final Img< ? > segmentation =
215-
performSegmentation( Views.dropSingletonDimensions( image ), source.getVoxelDimensions().dimensionsAsDoubleArray() );
239+
performSegmentation( Views.dropSingletonDimensions( image ), source.getVoxelDimensions().dimensionsAsDoubleArray(),
240+
python );
216241

217242
if ( segmentation != null )
218243
{
@@ -301,20 +326,36 @@ public Map< String, Object > getDefaultSettings()
301326

302327
protected abstract boolean validateSettings( final StringBuilder errorHolder );
303328

304-
protected abstract Img< ? > performSegmentation( final RandomAccessibleInterval< ? > image, final double[] voxelDimensions );
329+
protected abstract Img< ? > performSegmentation( final RandomAccessibleInterval< ? > image, final double[] voxelDimensions,
330+
final Service python );
305331

306332
protected abstract void addSpecificDefaultSettings( final Map< String, Object > defaultSettings );
307333

308334
protected abstract String getDetectorName();
309335

336+
protected abstract String getPythonEnvContent();
337+
338+
protected String getPythonEnvInit()
339+
{
340+
return "import numpy\n";
341+
}
342+
343+
protected abstract String getImportScript( final boolean dataIs2D );
344+
345+
private static boolean isWindows()
346+
{
347+
String os = System.getProperty( "os.name" ).toLowerCase();
348+
return os.contains( "win" );
349+
}
350+
310351
/** Cancels the command execution, with the given reason for doing so. */
311352
@Override
312353
public void cancel( final String reason )
313354
{
314355
// this is a workaround to avoid a null pointer exception during the cancel operation
315356
detector = new DummyDetectorOp();
316-
if ( apposeProcess != null )
317-
apposeProcess.cancel();
357+
if ( pythonService != null )
358+
pythonService.kill();
318359
super.cancel( reason );
319360
}
320361

src/main/java/org/mastodon/mamut/detection/Segmentation.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ public abstract class Segmentation extends ApposeProcess
5454
{
5555
private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );
5656

57-
protected Segmentation() throws IOException
57+
protected Segmentation( final Service pythonService )
5858
{
59-
super();
59+
super( pythonService );
6060
}
6161

6262
/**
63-
* Segments the input image using the configured Python environment and
63+
* Segments the input image using the configured Python runtime environment and
6464
* returns the segmented image as an {@link Img}.
6565
*
6666
* @param inputImage the input image to be segmented.

src/main/java/org/mastodon/mamut/detection/cellpose/Cellpose.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import java.io.IOException;
3232

33+
import org.apposed.appose.Service;
3334
import org.mastodon.mamut.detection.Segmentation;
3435

3536
/**
@@ -58,9 +59,9 @@ public abstract class Cellpose extends Segmentation
5859

5960
public static final double DEFAULT_DIAMETER = 0d;
6061

61-
protected Cellpose() throws IOException
62+
protected Cellpose( final Service python ) throws IOException
6263
{
63-
super();
64+
super( python );
6465
}
6566

6667
public void setCellProbThreshold( final double cellProbThreshold )
@@ -151,8 +152,7 @@ protected String generateScript()
151152
+ "task.outputs['label_image'] = shared" + "\n";
152153
}
153154

154-
@Override
155-
protected String generateImportStatements()
155+
public static String generateImportStatements()
156156
{
157157
return "import numpy as np" + "\n"
158158
+ "from cellpose import models" + "\n"

src/main/java/org/mastodon/mamut/detection/cellpose/Cellpose3.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
import java.io.IOException;
3232

33+
import org.apposed.appose.Service;
34+
3335
/**
3436
* Cellpose3 is a specialized implementation of the {@link Cellpose} class, specifically
3537
* designed to use Cellpose version 3 model for cell segmentation tasks.<br>
@@ -56,11 +58,12 @@ public class Cellpose3 extends Cellpose
5658
+ " - numpy\n";
5759

5860
private final ModelType modelType;
61+
5962
private double anisotropy = 1;
6063

61-
public Cellpose3( final ModelType modelType ) throws IOException
64+
public Cellpose3( final ModelType modelType, final Service python ) throws IOException
6265
{
63-
super();
66+
super( python );
6467
this.modelType = modelType;
6568
}
6669

@@ -102,12 +105,6 @@ protected String getEvaluateModelCommand()
102105
+ "cellprob_threshold=" + cellProbThreshold + ")" + "\n";
103106
}
104107

105-
@Override
106-
protected String generateEnvFileContent()
107-
{
108-
return ENV_FILE_CONTENT;
109-
}
110-
111108
public enum ModelType
112109
{
113110
CYTO3( "cyto3", true ),

src/main/java/org/mastodon/mamut/detection/cellpose/Cellpose3Detector.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@
5151
import net.imglib2.RandomAccessibleInterval;
5252
import net.imglib2.img.Img;
5353
import net.imglib2.util.Cast;
54-
import net.imglib2.view.Views;
5554

55+
import org.apposed.appose.Service;
5656
import org.mastodon.mamut.detection.DeepLearningDetector;
5757
import org.mastodon.tracking.mamut.detection.SpotDetectorOp;
5858
import org.scijava.Priority;
@@ -99,11 +99,13 @@ && checkParameter( settings, KEY_GPU_ID, Integer.class, errorHolder )
9999
}
100100

101101
@Override
102-
protected Img< ? > performSegmentation( final RandomAccessibleInterval< ? > image, final double[] voxelDimensions )
102+
protected Img< ? > performSegmentation( final RandomAccessibleInterval< ? > image, final double[] voxelDimensions,
103+
final Service python )
103104
{
104-
try (Cellpose3 cellpose = new Cellpose3( ( Cellpose3.ModelType ) settings.get( KEY_MODEL_TYPE ) ))
105+
106+
try
105107
{
106-
this.apposeProcess = cellpose;
108+
Cellpose3 cellpose = new Cellpose3( ( Cellpose3.ModelType ) settings.get( KEY_MODEL_TYPE ), python );
107109
boolean is3D = is3D( image );
108110
cellpose.set3D( is3D );
109111
cellpose.setCellProbThreshold( ( double ) settings.get( KEY_CELL_PROBABILITY_THRESHOLD ) );
@@ -143,4 +145,16 @@ protected String getDetectorName()
143145
{
144146
return "Cellpose3";
145147
}
148+
149+
@Override
150+
protected String getPythonEnvContent()
151+
{
152+
return Cellpose3.ENV_FILE_CONTENT;
153+
}
154+
155+
@Override
156+
protected String getImportScript( final boolean dataIs2D )
157+
{
158+
return Cellpose3.generateImportStatements();
159+
}
146160
}

src/main/java/org/mastodon/mamut/detection/cellpose/Cellpose4.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
import java.io.IOException;
3232

33+
import org.apposed.appose.Service;
34+
3335
/**
3436
* Cellpose3 is a specialized implementation of the {@link Cellpose} class, specifically
3537
* designed to use Cellpose version 4 model for cell segmentation tasks.<br>
@@ -54,9 +56,9 @@ public class Cellpose4 extends Cellpose
5456
+ " - pip:\n"
5557
+ " - appose==" + APPOSE_PYTHON_VERSION + "\n";
5658

57-
public Cellpose4() throws IOException
59+
public Cellpose4( final Service python ) throws IOException
5860
{
59-
super();
61+
super( python );
6062
}
6163

6264
@Override
@@ -80,10 +82,4 @@ protected String getEvaluateModelCommand()
8082
+ "flow_threshold=" + flowThreshold + ", "
8183
+ "cellprob_threshold=" + cellProbThreshold + ")" + "\n";
8284
}
83-
84-
@Override
85-
protected String generateEnvFileContent()
86-
{
87-
return ENV_FILE_CONTENT;
88-
}
8985
}

src/main/java/org/mastodon/mamut/detection/cellpose/Cellpose4Detector.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import net.imglib2.img.Img;
4949
import net.imglib2.util.Cast;
5050

51+
import org.apposed.appose.Service;
5152
import org.mastodon.mamut.detection.DeepLearningDetector;
5253
import org.mastodon.tracking.mamut.detection.SpotDetectorOp;
5354
import org.mastodon.tracking.mamut.trackmate.wizard.descriptors.cellpose.Cellpose4DetectorDescriptor;
@@ -90,11 +91,12 @@ && checkParameter( settings, KEY_GPU_ID, Integer.class, errorHolder )
9091
}
9192

9293
@Override
93-
protected Img< ? > performSegmentation( final RandomAccessibleInterval< ? > image, final double[] voxelDimensions )
94+
protected Img< ? > performSegmentation( final RandomAccessibleInterval< ? > image, final double[] voxelDimensions,
95+
final Service python )
9496
{
95-
try (Cellpose4 cellpose = new Cellpose4())
97+
try
9698
{
97-
this.apposeProcess = cellpose;
99+
Cellpose4 cellpose = new Cellpose4( python );
98100
cellpose.set3D( is3D( image ) );
99101
cellpose.setCellProbThreshold( ( double ) settings.get( KEY_CELL_PROBABILITY_THRESHOLD ) );
100102
cellpose.setFlowThreshold( ( double ) settings.get( KEY_FLOW_THRESHOLD ) );
@@ -128,4 +130,16 @@ protected String getDetectorName()
128130
{
129131
return "Cellpose4";
130132
}
133+
134+
@Override
135+
protected String getPythonEnvContent()
136+
{
137+
return Cellpose4.ENV_FILE_CONTENT;
138+
}
139+
140+
@Override
141+
protected String getImportScript( final boolean dataIs2D )
142+
{
143+
return Cellpose.generateImportStatements();
144+
}
131145
}

0 commit comments

Comments
 (0)