Skip to content

Commit 6d5a936

Browse files
Merge pull request #140 from mastodon-sc/appose
Deep Learning Detectors Improvements
2 parents 6c3f5a1 + 4ea505a commit 6d5a936

34 files changed

Lines changed: 1466 additions & 294 deletions

pom.xml

Lines changed: 17 additions & 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.5.1</version>
150+
<version>0.7.0</version>
151151
</dependency>
152152

153153
<!-- mastodon tracking as base for custom detectors (cellpose, stardist) -->
@@ -164,6 +164,13 @@
164164
<version>0.5.10</version>
165165
</dependency>
166166

167+
<!-- oshi for system / GPU information (used to limit gpu usage of cellpose) -->
168+
<dependency>
169+
<groupId>com.github.oshi</groupId>
170+
<artifactId>oshi-core</artifactId>
171+
<version>6.8.3</version>
172+
</dependency>
173+
167174
<!-- Test dependencies -->
168175
<dependency>
169176
<groupId>org.junit.jupiter</groupId>
@@ -210,6 +217,10 @@
210217
<type>test-jar</type>
211218
<scope>test</scope>
212219
</dependency>
220+
<dependency>
221+
<groupId>org.slf4j</groupId>
222+
<artifactId>slf4j-api</artifactId>
223+
</dependency>
213224
</dependencies>
214225

215226
<mailingLists>
@@ -259,6 +270,11 @@
259270
<id>scijava.public</id>
260271
<url>https://maven.scijava.org/content/repositories/public/</url>
261272
</repository>
273+
<repository>
274+
<!-- mvn central -->
275+
<id>central</id>
276+
<url>https://repo.maven.apache.org/maven2</url>
277+
</repository>
262278
</repositories>
263279

264280
<profiles>

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

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,16 @@
4141
import net.imglib2.view.IntervalView;
4242
import net.imglib2.view.Views;
4343

44-
import org.apache.commons.lang3.tuple.ImmutableTriple;
45-
import org.apache.commons.lang3.tuple.Triple;
44+
import org.mastodon.mamut.util.ByteFormatter;
45+
import org.mastodon.mamut.util.ImgSizeUtils;
46+
import org.mastodon.mamut.detection.util.SpimImageProperties;
4647
import org.mastodon.mamut.model.ModelGraph;
4748
import org.mastodon.mamut.util.LabelImageUtils;
4849
import org.mastodon.tracking.detection.DetectionUtil;
4950
import org.mastodon.tracking.detection.DetectorKeys;
5051
import org.mastodon.tracking.mamut.detection.AbstractSpotDetectorOp;
52+
import org.scijava.Context;
53+
import org.scijava.plugin.Parameter;
5154
import org.slf4j.Logger;
5255
import org.slf4j.LoggerFactory;
5356

@@ -69,6 +72,14 @@ public abstract class DeepLearningDetector extends AbstractSpotDetectorOp
6972

7073
private static final Logger logger = LoggerFactory.getLogger( MethodHandles.lookup().lookupClass() );
7174

75+
@Parameter
76+
protected Context context;
77+
78+
/**
79+
* Represents the maximum allowable size in bytes for datasets handle via the appose java-python bridge.
80+
*/
81+
private static final int MAX_SIZE_IN_BYTES = Integer.MAX_VALUE - 1;
82+
7283
@Override
7384
public void compute( final List< SourceAndConverter< ? > > sources, final ModelGraph graph )
7485
{
@@ -80,14 +91,13 @@ public void compute( final List< SourceAndConverter< ? > > sources, final ModelG
8091
if ( !validateAndInitializeSettings() )
8192
return;
8293

83-
Triple< Integer, Integer, Integer > settings = extractSettings( sources );
94+
SpimImageProperties settings = extractSettings( sources );
8495
if ( settings == null )
8596
return;
8697
// Now we are sure the settings are valid.
8798

88-
int minTimepoint = settings.getLeft();
89-
int maxTimepoint = settings.getMiddle();
90-
int setup = settings.getRight();
99+
int minTimepoint = settings.getMinTimepoint();
100+
int maxTimepoint = settings.getMaxTimepoint();
91101

92102
// Perform detection.
93103
statusService.showStatus( "Detecting spots..." );
@@ -101,8 +111,8 @@ public void compute( final List< SourceAndConverter< ? > > sources, final ModelG
101111
if ( isCanceled() )
102112
break; // Exit but don't fail.
103113

104-
if ( DetectionUtil.isPresent( sources, setup, timepoint ) )
105-
detectAndAddSpots( sources, graph, setup, timepoint );
114+
if ( DetectionUtil.isPresent( sources, settings.getSetupId(), timepoint ) )
115+
detectAndAddSpots( sources, graph, settings.getSetupId(), timepoint, settings.getResolutionLevel() );
106116
}
107117
}
108118
catch ( Exception e )
@@ -119,14 +129,16 @@ public void compute( final List< SourceAndConverter< ? > > sources, final ModelG
119129
ok = true;
120130
}
121131

122-
private Triple< Integer, Integer, Integer > extractSettings( final List< SourceAndConverter< ? > > sources )
132+
private SpimImageProperties extractSettings( final List< SourceAndConverter< ? > > sources )
123133
{
124134
// Extract settings.
125135
final int minTimepoint = ( int ) settings.get( DetectorKeys.KEY_MIN_TIMEPOINT );
126136
final int maxTimepoint = ( int ) settings.get( DetectorKeys.KEY_MAX_TIMEPOINT );
127137
final int setup = ( int ) settings.get( DetectorKeys.KEY_SETUP_ID );
138+
final int level = ( int ) settings.get( DeepLearningDetectorKeys.KEY_LEVEL );
128139

129-
logger.info( "Settings contain, minTimepoint: {}, maxTimepoint: {} and setup {}", minTimepoint, maxTimepoint, setup );
140+
logger.info( "Settings contain, minTimepoint: {}, maxTimepoint: {}, setup: {} and level: {}", minTimepoint, maxTimepoint, setup,
141+
level );
130142

131143
if ( setup < 0 || setup >= sources.size() )
132144
{
@@ -142,27 +154,58 @@ private Triple< Integer, Integer, Integer > extractSettings( final List< SourceA
142154
logger.error( "Invalid time-point range: {}", errorMessage );
143155
return null;
144156
}
145-
return new ImmutableTriple<>( minTimepoint, maxTimepoint, setup );
157+
int availableLevels = sources.get( setup ).getSpimSource().getNumMipmapLevels();
158+
if ( level < 0 || level >= availableLevels )
159+
{
160+
errorMessage = "The parameter " + DeepLearningDetectorKeys.KEY_LEVEL
161+
+ " is not in the range of available resolution levels ( 0 to " + ( availableLevels - 1 ) + "): " + level;
162+
logger.error( "Invalid level: {}.", errorMessage );
163+
return null;
164+
}
165+
return new SpimImageProperties( minTimepoint, maxTimepoint, setup, level );
146166
}
147167

148168
private void detectAndAddSpots( final List< SourceAndConverter< ? > > sources, final ModelGraph graph, final int setup,
149-
final int timepoint )
169+
final int timepoint, final int level )
150170
{
151171
// First, get the source for the current channel (or setup) at the desired time-point. In BDV jargon, this is a source.
152172
final Source< ? > source = sources.get( setup ).getSpimSource();
153173

154-
// level 0 is always the highest resolution.
155-
final int level = 0;
156-
157174
// This is the 3D image of the current time-point and specified channel. It is always 3D. If the source is 2D, the 3rd dimension has a size of 1.
158175
RandomAccessibleInterval< ? > image = source.getSource( timepoint, level );
159176

177+
long theoreticalImageSize = 0;
178+
try
179+
{
180+
theoreticalImageSize = ImgSizeUtils.getSizeInBytes( image );
181+
}
182+
catch ( IllegalArgumentException e )
183+
{
184+
logger.info( "Could not estimate image size." );
185+
}
186+
if ( theoreticalImageSize > MAX_SIZE_IN_BYTES )
187+
{
188+
String actualSize = ByteFormatter.humanReadableByteCount( theoreticalImageSize );
189+
String maxSize = ByteFormatter.humanReadableByteCount( MAX_SIZE_IN_BYTES );
190+
logger.warn( "Size of image at timepoint {}, setup {} and level {} is {}, which is larger than the maximum size of {}.",
191+
timepoint, setup, level, actualSize, maxSize );
192+
String message = "Size of image at timepoint " + timepoint + ", setup " + setup + " and level " + level + " is " + actualSize
193+
+ ", which is larger than the supported maximum size of " + maxSize
194+
+ " per timepoint. Consider using a different resolution level or process only a region of interest.\n";
195+
throw new InferenceException( message );
196+
}
197+
160198
// Crop the image to the region of interest (ROI) if specified in the settings.
161199
final Interval roi = ( Interval ) settings.get( DetectorKeys.KEY_ROI );
162200
if ( roi != null )
201+
{
202+
logger.info( "Settings contained a roi: {}", roi );
163203
image = Views.interval( image, roi );
204+
}
164205

165-
final Img< ? > segmentation = performSegmentation( image, source.getVoxelDimensions().dimensionsAsDoubleArray() );
206+
System.err.println(); // show the FIJI console
207+
final Img< ? > segmentation =
208+
performSegmentation( Views.dropSingletonDimensions( image ), source.getVoxelDimensions().dimensionsAsDoubleArray() );
166209

167210
if ( segmentation != null )
168211
{
@@ -192,8 +235,11 @@ private boolean validateAndInitializeSettings()
192235
return true;
193236
}
194237

195-
protected double getAnisotropy( double[] voxelSizes )
238+
protected double getAnisotropy( double[] voxelSizes, boolean is3D )
196239
{
240+
if ( !is3D )
241+
return 1.0;
242+
197243
if ( voxelSizes == null || voxelSizes.length == 0 )
198244
{
199245
throw new IllegalArgumentException( "Array must not be empty" );
@@ -241,6 +287,7 @@ public Map< String, Object > getDefaultSettings()
241287
defaultSettings.put( DetectorKeys.KEY_MIN_TIMEPOINT, DetectorKeys.DEFAULT_MIN_TIMEPOINT );
242288
defaultSettings.put( DetectorKeys.KEY_MAX_TIMEPOINT, DetectorKeys.DEFAULT_MAX_TIMEPOINT );
243289
defaultSettings.put( DetectorKeys.KEY_ROI, null ); // No ROI by default.
290+
defaultSettings.put( DeepLearningDetectorKeys.KEY_LEVEL, DeepLearningDetectorKeys.DEFAULT_LEVEL );
244291
addSpecificDefaultSettings( defaultSettings );
245292
return defaultSettings;
246293
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.mastodon.mamut.detection;
2+
3+
public class DeepLearningDetectorKeys
4+
{
5+
private DeepLearningDetectorKeys()
6+
{
7+
// Prevent instantiation
8+
}
9+
10+
/**
11+
* Key for the parameter specifying what resolution level to perform on. Expected values must be
12+
* {@link Integer} larger or equal to 0.
13+
*/
14+
public static final String KEY_LEVEL = "LEVEL";
15+
16+
/**
17+
* Key for the parameter specifying what GPU to use. Expected values must be
18+
* {@link Integer} representing the GPU ID, or {@code null} to use the CPU.
19+
*/
20+
public static final String KEY_GPU_ID = "GPU_ID";
21+
22+
/**
23+
* Key for the parameter specifying what fraction of the GPU memory to allocate. Expected values must be
24+
* {@link Double} between 0 and 1.
25+
*/
26+
public static final String KEY_GPU_MEMORY_FRACTION = "GPU_MEMORY_FRACTION";
27+
28+
/**
29+
* Default value for the {@link #KEY_LEVEL} parameter.
30+
* <br>
31+
* The highest resolution level (0).
32+
*/
33+
public static final int DEFAULT_LEVEL = 0;
34+
35+
/**
36+
* Default value for the {@link #KEY_GPU_ID} parameter.
37+
* <br>
38+
* The first gpu (0).
39+
*/
40+
public static final int DEFAULT_GPU_ID = 0;
41+
42+
/**
43+
* Default value for the {@link #KEY_GPU_MEMORY_FRACTION} parameter.
44+
* <br>
45+
* Half of the GPU memory (0.5).
46+
*/
47+
public static final double DEFAULT_GPU_MEMORY_FRACTION = 0.5d;
48+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.mastodon.mamut.detection;
2+
3+
/**
4+
* Represents an exception that occurs during an inference process.
5+
* This exception extends RuntimeException and provides constructors
6+
* for initializing with a message, a cause, or both.
7+
*/
8+
public class InferenceException extends RuntimeException
9+
{
10+
public InferenceException( final String message )
11+
{
12+
super( message );
13+
}
14+
15+
public InferenceException( final String message, final Throwable cause )
16+
{
17+
super( message, cause );
18+
}
19+
20+
public InferenceException( final Throwable cause )
21+
{
22+
super( cause );
23+
}
24+
}

0 commit comments

Comments
 (0)