Skip to content

Commit 6528928

Browse files
committed
Merge branch 'trackmate' into devel
2 parents 0be62b7 + 173e9e1 commit 6528928

2 files changed

Lines changed: 409 additions & 0 deletions

File tree

src/imcflibs/imagej/trackmate.py

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
import os
2+
import sys
3+
4+
from ij import IJ
5+
6+
from fiji.plugin.trackmate import Logger, Model, SelectionModel, Settings, TrackMate
7+
from fiji.plugin.trackmate.action import LabelImgExporter
8+
from fiji.plugin.trackmate.detection import LogDetectorFactory
9+
from fiji.plugin.trackmate.cellpose import CellposeDetectorFactory
10+
from fiji.plugin.trackmate.stardist import StarDistDetectorFactory
11+
from fiji.plugin.trackmate.cellpose.CellposeSettings import PretrainedModel
12+
13+
from fiji.plugin.trackmate.features import FeatureFilter
14+
from fiji.plugin.trackmate.tracking.jaqaman import LAPUtils, SparseLAPTrackerFactory
15+
from java.lang import Double
16+
17+
from .. import pathtools
18+
19+
20+
def cellpose_detector(
21+
imageplus,
22+
cellpose_env_path,
23+
model_to_use,
24+
obj_diameter,
25+
target_channel,
26+
optional_channel=0,
27+
use_gpu=True,
28+
simplify_contours=True,
29+
):
30+
"""Create a dictionary with all settings for TrackMate using Cellpose.
31+
32+
Parameters
33+
----------
34+
imageplus : ij.ImagePlus
35+
ImagePlus on which to apply the detector.
36+
cellpose_env_path : str
37+
Path to the Cellpose environment.
38+
model_to_use : str
39+
Name of the model to use for the segmentation (CYTO, NUCLEI, CYTO2).
40+
obj_diameter : float
41+
Diameter of the objects to detect in the image.
42+
This will be calibrated to the unit used in the image.
43+
target_channel : int
44+
Index of the channel to use for segmentation.
45+
optional_channel : int, optional
46+
Index of the secondary channel to use for segmentation, by default 0.
47+
use_gpu : bool, optional
48+
Boolean for GPU usage, by default True.
49+
simplify_contours : bool, optional
50+
Boolean for simplifying the contours, by default True.
51+
52+
Returns
53+
-------
54+
fiji.plugin.trackmate.Settings
55+
Dictionary containing all the settings to use for TrackMate.
56+
57+
Example
58+
-------
59+
>>> settings = cellpose_detector(
60+
... imageplus=imp,
61+
... cellpose_env_path="D:/CondaEnvs/cellpose",
62+
... model_to_use="NUCLEI",
63+
... obj_diameter=23.0,
64+
... target_channel=1,
65+
... optional_channel=0
66+
... )
67+
"""
68+
settings = Settings(imageplus)
69+
70+
settings.detectorFactory = CellposeDetectorFactory()
71+
settings.detectorSettings["TARGET_CHANNEL"] = target_channel
72+
# set optional channel to 0, will be overwritten if needed:
73+
settings.detectorSettings["OPTIONAL_CHANNEL_2"] = optional_channel
74+
75+
settings.detectorSettings["CELLPOSE_PYTHON_FILEPATH"] = pathtools.join2(
76+
cellpose_env_path, "python.exe"
77+
)
78+
settings.detectorSettings["CELLPOSE_MODEL_FILEPATH"] = os.path.join(
79+
os.environ["USERPROFILE"], ".cellpose", "models"
80+
)
81+
input_to_model = {
82+
"nuclei": PretrainedModel.NUCLEI,
83+
"cyto": PretrainedModel.CYTO,
84+
"cyto2": PretrainedModel.CYTO2,
85+
}
86+
if model_to_use.lower() in input_to_model:
87+
selected_model = input_to_model[model_to_use.lower()]
88+
else:
89+
print("Selected Model Does Not Exist")
90+
return
91+
92+
settings.detectorSettings["CELLPOSE_MODEL"] = selected_model
93+
settings.detectorSettings["CELL_DIAMETER"] = obj_diameter
94+
settings.detectorSettings["USE_GPU"] = use_gpu
95+
settings.detectorSettings["SIMPLIFY_CONTOURS"] = simplify_contours
96+
97+
return settings
98+
99+
100+
def stardist_detector(imageplus, target_chnl):
101+
"""Create a dictionary with all settings for TrackMate using StarDist.
102+
103+
Parameters
104+
----------
105+
imageplus : ij.ImagePlus
106+
Image on which to do the segmentation.
107+
target_chnl : int
108+
Index of the channel on which to do the segmentation.
109+
110+
Returns
111+
-------
112+
fiji.plugin.trackmate.Settings
113+
Dictionary containing all the settings to use for TrackMate.
114+
"""
115+
116+
settings = Settings(imageplus)
117+
settings.detectorFactory = StarDistDetectorFactory()
118+
settings.detectorSettings["TARGET_CHANNEL"] = target_chnl
119+
120+
return settings
121+
122+
123+
def log_detector(
124+
imageplus,
125+
radius,
126+
target_channel,
127+
quality_threshold=0.0,
128+
median_filtering=True,
129+
subpix_localization=True,
130+
):
131+
"""Create a dictionary with all settings for TrackMate using the LogDetector.
132+
133+
Parameters
134+
----------
135+
imageplus : ij.ImagePlus
136+
Image on which to do the segmentation.
137+
radius : float
138+
Radius of the objects to detect.
139+
target_channel : int
140+
Index of the channel on which to do the segmentation.
141+
quality_threshold : int, optional
142+
Threshold to use for excluding the spots by quality, by default 0.
143+
median_filtering : bool, optional
144+
Boolean to do median filtering, by default True.
145+
subpix_localization : bool, optional
146+
Boolean to do subpixel localization, by default True.
147+
148+
Returns
149+
-------
150+
fiji.plugin.trackmate.Settings
151+
Dictionary containing all the settings to use for TrackMate.
152+
"""
153+
154+
settings = Settings(imageplus)
155+
settings.detectorFactory = LogDetectorFactory()
156+
157+
settings.detectorSettings["RADIUS"] = Double(radius)
158+
settings.detectorSettings["TARGET_CHANNEL"] = target_channel
159+
settings.detectorSettings["THRESHOLD"] = Double(quality_threshold)
160+
settings.detectorSettings["DO_MEDIAN_FILTERING"] = median_filtering
161+
settings.detectorSettings["DO_SUBPIXEL_LOCALIZATION"] = subpix_localization
162+
163+
return settings
164+
165+
166+
def spot_filtering(
167+
settings,
168+
quality_thresh=None,
169+
area_thresh=None,
170+
circularity_thresh=None,
171+
intensity_dict_thresh=None,
172+
):
173+
"""Add spot filtering for different features to the settings dictionary.
174+
175+
Parameters
176+
----------
177+
settings : fiji.plugin.trackmate.Settings
178+
Dictionary containing all the settings to use for TrackMate.
179+
quality_thresh : float, optional
180+
Threshold to use for quality filtering of the spots, by default None.
181+
If the threshold is positive, will exclude everything below the value.
182+
If the threshold is negative, will exclude everything above the value.
183+
area_thresh : float, optional
184+
Threshold to use for area filtering of the spots, keep None with LoG Detector -
185+
by default also None.
186+
If the threshold is positive, will exclude everything below the value.
187+
If the threshold is negative, will exclude everything above the value.
188+
circularity_thresh : float, optional
189+
Threshold to use for circularity thresholding (needs to be between 0 and 1, keep None with LoG Detector)
190+
- by default None.
191+
If the threshold is positive, will exclude everything below the value.
192+
If the threshold is negative, will exclude everything above the value.
193+
intensity_dict_thresh : dict, optional
194+
Threshold to use for intensity filtering of the spots, by default None.
195+
Dictionary needs to contain the channel index as key and the filter as value.
196+
If the threshold is positive, will exclude everything below the value.
197+
If the threshold is negative, will exclude everything above the value.
198+
199+
Returns
200+
-------
201+
fiji.plugin.trackmate.Settings
202+
Dictionary containing all the settings to use for TrackMate.
203+
"""
204+
205+
settings.initialSpotFilterValue = -1.0
206+
settings.addAllAnalyzers()
207+
208+
# Here 'true' takes everything ABOVE the mean_int value
209+
if quality_thresh:
210+
filter_spot = FeatureFilter(
211+
"QUALITY",
212+
Double(abs(quality_thresh)),
213+
quality_thresh >= 0,
214+
)
215+
settings.addSpotFilter(filter_spot)
216+
if area_thresh: # Keep none for log detector
217+
filter_spot = FeatureFilter("AREA", Double(abs(area_thresh)), area_thresh >= 0)
218+
settings.addSpotFilter(filter_spot)
219+
if circularity_thresh: # has to be between 0 and 1, keep none for log detector
220+
filter_spot = FeatureFilter(
221+
"CIRCULARITY", Double(abs(circularity_thresh)), circularity_thresh >= 0
222+
)
223+
settings.addSpotFilter(filter_spot)
224+
if intensity_dict_thresh:
225+
for key, value in intensity_dict_thresh.items():
226+
filter_spot = FeatureFilter(
227+
"MEAN_INTENSITY_CH" + str(key), abs(value), value >= 0
228+
)
229+
settings.addSpotFilter(filter_spot)
230+
231+
return settings
232+
233+
234+
def sparse_lap_tracker(settings):
235+
"""Create a sparse LAP tracker with default settings.
236+
237+
Parameters
238+
----------
239+
settings : fiji.plugin.trackmate.Settings
240+
Dictionary containing all the settings to use for TrackMate.
241+
242+
Returns
243+
-------
244+
fiji.plugin.trackmate.Settings
245+
Dictionary containing all the settings to use for TrackMate.
246+
"""
247+
248+
settings.trackerFactory = SparseLAPTrackerFactory()
249+
settings.trackerSettings = settings.trackerFactory.getDefaultSettings()
250+
251+
return settings
252+
253+
254+
def track_filtering(
255+
settings,
256+
link_max_dist=15.0,
257+
gap_closing_dist=15.0,
258+
max_frame_gap=3,
259+
track_splitting_max_dist=None,
260+
track_merging_max_distance=None,
261+
):
262+
"""Add track filtering for different features to the settings dictionary.
263+
264+
Parameters
265+
----------
266+
settings : fiji.plugin.trackmate.Settings
267+
Dictionary containing all the settings to use for TrackMate.
268+
link_max_dist : float, optional
269+
Maximal displacement of the spots, by default 0.5.
270+
gap_closing_dist : float, optional
271+
Maximal distance for gap closing, by default 0.5.
272+
max_frame_gap : int, optional
273+
Maximal frame interval between spots to be bridged, by default 2.
274+
track_splitting_max_dist : int, optional
275+
Maximal frame interval for splitting tracks, by default None.
276+
track_merging_max_distance : int, optional
277+
Maximal frame interval for merging tracks , by default None.
278+
279+
Returns
280+
-------
281+
fiji.plugin.trackmate.Settings
282+
Dictionary containing all the settings to use for TrackMate.
283+
"""
284+
# NOTE: `link_max_dist` and `gap_closing_dist` must be double!
285+
settings.trackerSettings["LINKING_MAX_DISTANCE"] = link_max_dist
286+
settings.trackerSettings["GAP_CLOSING_MAX_DISTANCE"] = gap_closing_dist
287+
settings.trackerSettings["MAX_FRAME_GAP"] = max_frame_gap
288+
if track_splitting_max_dist:
289+
settings.trackerSettings["ALLOW_TRACK_SPLITTING"] = True
290+
settings.trackerSettings["SPLITTING_MAX_DISTANCE"] = track_splitting_max_dist
291+
if track_merging_max_distance:
292+
settings.trackerSettings["ALLOW_TRACK_MERGING"] = True
293+
settings.trackerSettings["MERGING_MAX_DISTANCE"] = track_merging_max_distance
294+
295+
return settings
296+
297+
298+
def run_trackmate(
299+
implus,
300+
settings,
301+
crop_roi=None,
302+
):
303+
# sourcery skip: merge-else-if-into-elif, swap-if-else-branches
304+
"""Function to run TrackMate on already opened data.
305+
306+
Parameters
307+
----------
308+
implus : ij.ImagePlus
309+
ImagePlus image on which to run Trackmate.
310+
settings : fiji.plugin.trackmate.Settings
311+
Settings to use for TrackMate, see detector methods for different settings.
312+
crop_roi : ij.gui.Roi, optional
313+
ROI to crop on the image, by default None.
314+
315+
Returns
316+
-------
317+
ij.ImagePlus
318+
Labeled image with all the objects belonging to the same tracks having
319+
the same label.
320+
"""
321+
322+
dims = implus.getDimensions()
323+
cal = implus.getCalibration()
324+
325+
if implus.getNSlices() > 1:
326+
implus.setDimensions(dims[2], dims[4], dims[3])
327+
328+
if crop_roi is not None:
329+
implus.setRoi(crop_roi)
330+
331+
model = Model()
332+
333+
model.setLogger(Logger.IJTOOLBAR_LOGGER)
334+
335+
# Configure tracker
336+
# settings.addTrackAnalyzer(TrackDurationAnalyzer())
337+
settings.initialSpotFilterValue = -1.0
338+
339+
trackmate = TrackMate(model, settings)
340+
trackmate.computeSpotFeatures(True)
341+
trackmate.computeTrackFeatures(True)
342+
343+
if not settings.trackerFactory:
344+
# Create a Sparse LAP Tracker if no Tracker has been created
345+
settings = sparseLAP_tracker(settings)
346+
347+
ok = trackmate.checkInput()
348+
if not ok:
349+
sys.exit(str(trackmate.getErrorMessage()))
350+
351+
ok = trackmate.process()
352+
if not ok:
353+
if "[SparseLAPTracker] The spot collection is empty." in str(
354+
trackmate.getErrorMessage()
355+
):
356+
new_imp = IJ.createImage(
357+
"Untitled",
358+
str(implus.getBitDepth()) + "-bit black",
359+
implus.getWidth(),
360+
implus.getHeight(),
361+
implus.getNFrames(),
362+
)
363+
new_imp.setCalibration(cal)
364+
365+
return new_imp
366+
367+
else:
368+
sys.exit(str(trackmate.getErrorMessage()))
369+
370+
SelectionModel(model)
371+
372+
exportSpotsAsDots = False
373+
exportTracksOnly = False
374+
# implus2.close()
375+
label_imp = LabelImgExporter.createLabelImagePlus(
376+
trackmate, exportSpotsAsDots, exportTracksOnly, False
377+
)
378+
label_imp.setCalibration(cal)
379+
label_imp.setDimensions(dims[2], dims[3], dims[4])
380+
implus.setDimensions(dims[2], dims[3], dims[4])
381+
382+
return label_imp

0 commit comments

Comments
 (0)