Skip to content

Commit 3601c20

Browse files
authored
Merge pull request #39 from lguerard/devel
Update for latest version BigStitcher, add fusion (BDV-playground)
2 parents f46758d + 72ebf11 commit 3601c20

8 files changed

Lines changed: 471 additions & 124 deletions

src/imcflibs/imagej/bdv.py

Lines changed: 282 additions & 101 deletions
Large diffs are not rendered by default.

src/imcflibs/imagej/misc.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Miscellaneous ImageJ related functions, mostly convenience wrappers."""
22

3+
import csv
34
import sys
45
import time
56
import smtplib
@@ -417,3 +418,33 @@ def get_threshold_value_from_method(imp, method, ops):
417418
threshold_value = int(round(threshold_value.get()))
418419

419420
return threshold_value
421+
422+
423+
def write_results(out_file, content):
424+
"""Write the results to a csv file.
425+
426+
Parameters
427+
----------
428+
out_file : str
429+
Path to the output file.
430+
content : list of OrderedDict
431+
List of dictionaries representing the results.
432+
433+
"""
434+
435+
# Check if the output file exists
436+
if not os.path.exists(out_file):
437+
# If the file does not exist, create it and write the header
438+
with open(out_file, "wb") as f:
439+
dict_writer = csv.DictWriter(
440+
f, content[0].keys(), delimiter=";"
441+
)
442+
dict_writer.writeheader()
443+
dict_writer.writerows(content)
444+
else:
445+
# If the file exists, append the results
446+
with open(out_file, "ab") as f:
447+
dict_writer = csv.DictWriter(
448+
f, content[0].keys(), delimiter=";"
449+
)
450+
dict_writer.writerows(content)

src/imcflibs/imagej/objects3d.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
[mcib3d]: https://mcib3d.frama.io/3d-suite-imagej/
66
"""
77

8+
from de.mpicbg.scf.imgtools.image.create.image import ImageCreationUtilities
9+
from de.mpicbg.scf.imgtools.image.create.labelmap import WatershedLabeling
810
from ij import IJ
911
from mcib3d.geom import Objects3DPopulation
1012
from mcib3d.image3d import ImageHandler, ImageLabeller
13+
from mcib3d.image3d.processing import MaximaFinder
14+
from net.imglib2.img import ImagePlusAdapter
1115

1216

1317
def population3d_to_imgplus(imp, population):
@@ -145,3 +149,93 @@ def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity):
145149

146150
# Return the new population with the filtered objects
147151
return Objects3DPopulation(objects_within_intensity)
152+
def maxima_finder_3D(imageplus, min_threshold=0, noise=100, rxy=1.5, rz=1.5):
153+
"""
154+
Find local maxima in a 3D image.
155+
156+
This function identifies local maxima in a 3D image using a specified minimum threshold and noise level.
157+
The radii for the maxima detection can be set independently for the x/y and z dimensions.
158+
159+
Parameters
160+
----------
161+
imageplus : ij.ImagePlus
162+
The input 3D image in which to find local maxima.
163+
min_threshold : int, optional
164+
The minimum intensity threshold for maxima detection. Default is 0.
165+
noise : int, optional
166+
The noise tolerance level for maxima detection. Default is 100.
167+
rxy : float, optional
168+
The radius for maxima detection in the x and y dimensions. Default is 1.5.
169+
rz : float, optional
170+
The radius for maxima detection in the z dimension. Default is 1.5.
171+
172+
Returns
173+
-------
174+
ij.ImagePlus
175+
An ImagePlus object containing the detected maxima as peaks.
176+
"""
177+
# Wrap the input ImagePlus into an ImageHandler
178+
img = ImageHandler.wrap(imageplus)
179+
180+
# Duplicate the image and apply a threshold cut-off
181+
thresholded = img.duplicate()
182+
thresholded.thresholdCut(min_threshold, False, True)
183+
184+
# Initialize the MaximaFinder with the thresholded image and noise level
185+
maxima_finder = MaximaFinder(thresholded, noise)
186+
187+
# Set the radii for maxima detection in x/y and z dimensions
188+
maxima_finder.setRadii(rxy, rz)
189+
190+
# Retrieve the image peaks as an ImageHandler
191+
img_peaks = maxima_finder.getImagePeaks()
192+
193+
# Convert the ImageHandler peaks to an ImagePlus
194+
imp_peaks = img_peaks.getImagePlus()
195+
196+
# Set the calibration of the peaks image to match the input image
197+
imp_peaks.setCalibration(imageplus.getCalibration())
198+
199+
# Set the title of the peaks image
200+
imp_peaks.setTitle("Peaks")
201+
202+
return imp_peaks
203+
204+
205+
def seeded_watershed(imp_binary, imp_peaks, threshold=10):
206+
"""
207+
Perform a seeded watershed segmentation on a binary image using seed points.
208+
209+
This function applies a watershed segmentation to a binary image using seed points provided in another image.
210+
An optional threshold can be specified to control the segmentation process.
211+
212+
Parameters
213+
----------
214+
imp_binary : ij.ImagePlus
215+
The binary image to segment.
216+
imp_peaks : ij.ImagePlus
217+
The image containing the seed points for the watershed segmentation.
218+
threshold : float, optional
219+
The threshold value to use for the segmentation. Default is 10.
220+
221+
Returns
222+
-------
223+
ij.ImagePlus
224+
The segmented image with labels.
225+
"""
226+
227+
img = ImagePlusAdapter.convertFloat(imp_binary)
228+
img_seed = ImagePlusAdapter.convertFloat(imp_peaks).copy()
229+
230+
if threshold:
231+
watersheded_result = WatershedLabeling.watershed(img, img_seed, threshold)
232+
else:
233+
watersheded_result = WatershedLabeling.watershed(img, img_seed)
234+
235+
return ImageCreationUtilities.convertImgToImagePlus(
236+
watersheded_result,
237+
"Label image",
238+
"",
239+
imp_binary.getDimensions(),
240+
imp_binary.getCalibration(),
241+
)

tests/bdv/test_define_dataset_auto.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
"""Tests for the automatic dataset definition functionality in the BDV module."""
2+
13
import logging
24

35
from imcflibs import pathtools
46
from imcflibs.imagej import bdv
57

68

7-
def set_default_values(project_filename, file_path):
9+
def set_default_values(
10+
project_filename, file_path, series_type="Tiles"
11+
):
812
"""Set the default values for dataset definitions.
913
1014
Parameters
@@ -13,9 +17,11 @@ def set_default_values(project_filename, file_path):
1317
Name of the project
1418
file_path : pathlib.Path
1519
Path to a temporary folder
20+
series_type : str, optional
21+
Type of Bioformats series (default is "Tiles")
1622
1723
Returns
18-
----------
24+
-------
1925
str
2026
Start of the options for dataset definitions.
2127
"""
@@ -32,15 +38,17 @@ def set_default_values(project_filename, file_path):
3238
+ file_info["path"]
3339
+ "] "
3440
+ "exclude=10 "
41+
+ "bioformats_series_are?="
42+
+ series_type
43+
+ " "
3544
+ "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] "
3645
)
3746

3847
return options
3948

4049

4150
def test_define_dataset_auto_tile(tmp_path, caplog):
42-
"""
43-
Test automatic dataset definition method for tile series.
51+
"""Test automatic dataset definition method for tile series.
4452
4553
Parameters
4654
----------
@@ -69,18 +77,22 @@ def test_define_dataset_auto_tile(tmp_path, caplog):
6977
bf_series_type = "Tiles"
7078

7179
# Define the ImageJ command
72-
cmd = "Define dataset ..."
80+
cmd = "Define Multi-View Dataset"
7381

7482
# Set the default values for dataset definitions
7583
options = set_default_values(project_filename, file_path)
7684

7785
# Construct the options for dataset definitions
7886
options = (
7987
options
80-
+ "how_to_load_images=["
88+
+ "how_to_store_input_images=["
8189
+ "Re-save as multiresolution HDF5"
8290
+ "] "
83-
+ "dataset_save_path=["
91+
+ "load_raw_data_virtually "
92+
+ "metadata_save_path=["
93+
+ result_folder
94+
+ "] "
95+
+ "image_data_save_path=["
8496
+ result_folder
8597
+ "] "
8698
+ "check_stack_sizes "
@@ -94,14 +106,15 @@ def test_define_dataset_auto_tile(tmp_path, caplog):
94106
final_call = "IJ.run(cmd=[%s], params=[%s])" % (cmd, options)
95107

96108
# Define the dataset using the "Auto-Loader" option
97-
bdv.define_dataset_auto(project_filename, file_path, bf_series_type)
109+
bdv.define_dataset_auto(
110+
project_filename, file_info["path"], bf_series_type
111+
)
98112
# Check if the final call is in the log
99113
assert final_call == caplog.messages[0]
100114

101115

102116
def test_define_dataset_auto_angle(tmp_path, caplog):
103-
"""
104-
Test automatic dataset definition method for angle series.
117+
"""Test automatic dataset definition method for angle series.
105118
106119
Parameters
107120
----------
@@ -133,15 +146,21 @@ def test_define_dataset_auto_angle(tmp_path, caplog):
133146
cmd = "Define Multi-View Dataset"
134147

135148
# Set the default values for dataset definitions
136-
options = set_default_values(project_filename, file_path)
149+
options = set_default_values(
150+
project_filename, file_path, bf_series_type
151+
)
137152

138153
# Construct the options for dataset definitions
139154
options = (
140155
options
141-
+ "how_to_load_images=["
156+
+ "how_to_store_input_images=["
142157
+ "Re-save as multiresolution HDF5"
143158
+ "] "
144-
+ "dataset_save_path=["
159+
+ "load_raw_data_virtually "
160+
+ "metadata_save_path=["
161+
+ result_folder
162+
+ "] "
163+
+ "image_data_save_path=["
145164
+ result_folder
146165
+ "] "
147166
+ "check_stack_sizes "
@@ -156,6 +175,8 @@ def test_define_dataset_auto_angle(tmp_path, caplog):
156175
final_call = "IJ.run(cmd=[%s], params=[%s])" % (cmd, options)
157176

158177
# Define the dataset using the "Auto-Loader" option
159-
bdv.define_dataset_auto(project_filename, file_path, bf_series_type)
178+
bdv.define_dataset_auto(
179+
project_filename, file_info["path"], bf_series_type
180+
)
160181
# Check if the final call is in the log
161182
assert final_call == caplog.messages[0]

tests/bdv/test_definitionoptions.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
"""Tests for the imcflibs.imagej.bdv.DefinitionOptions class."""
2+
13
import pytest
24

35
from imcflibs.imagej.bdv import DefinitionOptions
46

7+
58
def test_defaults():
69
"""Test the default options by calling all formatters on a "raw" objects."""
710
acitt_options = (
811
"multiple_angles=[NO (one angle)] "
912
"multiple_channels=[YES (all channels in one file)] "
10-
"multiple_illuminations=[NO (one illumination direction)] "
13+
"multiple_illuminations_directions=[NO (one illumination direction)] "
1114
"multiple_tiles=[YES (one file per tile)] "
1215
"multiple_timepoints=[NO (one time-point)] "
1316
)
@@ -16,6 +19,7 @@ def test_defaults():
1619

1720
assert def_opts.fmt_acitt_options() == acitt_options
1821

22+
1923
def test__definition_option():
2024
"""Test an example with wrong setting for definition option."""
2125

@@ -24,15 +28,19 @@ def test__definition_option():
2428
def_opts = DefinitionOptions()
2529
with pytest.raises(ValueError) as excinfo:
2630
def_opts.set_angle_definition(test_value)
27-
assert str(excinfo.value) == "Value must be one of single, multi_multi or multi_single"
31+
assert (
32+
str(excinfo.value)
33+
== "Value must be one of single, multi_multi. Support for multi_single is not available for angles and illuminations"
34+
)
35+
2836

2937
def test__multiple_timepoints_files():
3038
"""Test an example setting how to treat multiple time-points."""
3139

3240
acitt_options = (
3341
"multiple_angles=[NO (one angle)] "
3442
"multiple_channels=[YES (all channels in one file)] "
35-
"multiple_illuminations=[NO (one illumination direction)] "
43+
"multiple_illuminations_directions=[NO (one illumination direction)] "
3644
"multiple_tiles=[YES (one file per tile)] "
3745
"multiple_timepoints=[YES (one file per time-point)] "
3846
)
@@ -42,13 +50,14 @@ def test__multiple_timepoints_files():
4250

4351
assert def_opts.fmt_acitt_options() == acitt_options
4452

53+
4554
def test__multiple_channels_files_multiple_timepoints():
4655
"""Test an example setting how to treat multiple channels and multiple time-points."""
4756

4857
acitt_options = (
4958
"multiple_angles=[NO (one angle)] "
5059
"multiple_channels=[YES (one file per channel)] "
51-
"multiple_illuminations=[NO (one illumination direction)] "
60+
"multiple_illuminations_directions=[NO (one illumination direction)] "
5261
"multiple_tiles=[YES (one file per tile)] "
5362
"multiple_timepoints=[YES (all time-points in one file)] "
5463
)
@@ -59,14 +68,14 @@ def test__multiple_channels_files_multiple_timepoints():
5968

6069
assert def_opts.fmt_acitt_options() == acitt_options
6170

71+
6272
def test_single_tile_multiple_angles_files():
63-
"""Test an example setting how to treat single tile and multiple angle
64-
files"""
73+
"""Test an example on with one tile and multiple angle files."""
6574

6675
acitt_options = (
6776
"multiple_angles=[YES (one file per angle)] "
6877
"multiple_channels=[YES (all channels in one file)] "
69-
"multiple_illuminations=[NO (one illumination direction)] "
78+
"multiple_illuminations_directions=[NO (one illumination direction)] "
7079
"multiple_tiles=[NO (one tile)] "
7180
"multiple_timepoints=[NO (one time-point)] "
7281
)

tests/bdv/test_processingoptions.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Tests for the ProcessingOptions class from the imcflibs.imagej.bdv module."""
2+
13
from imcflibs.imagej.bdv import ProcessingOptions
24

35

@@ -18,7 +20,10 @@ def test_defaults():
1820
"how_to_treat_tiles=compare "
1921
"how_to_treat_timepoints=[treat individually] "
2022
)
21-
use_acitt = "channels=[Average Channels] " "illuminations=[Average Illuminations] "
23+
use_acitt = (
24+
"channels=[Average Channels] "
25+
"illuminations=[Average Illuminations] "
26+
)
2227

2328
proc_opts = ProcessingOptions()
2429

@@ -47,7 +52,10 @@ def test__treat_tc_ti__ref_c1():
4752
"how_to_treat_tiles=compare "
4853
"how_to_treat_timepoints=[treat individually] "
4954
)
50-
use_acitt = "channels=[use Channel 1] " "illuminations=[Average Illuminations] "
55+
use_acitt = (
56+
"channels=[use Channel 1] "
57+
"illuminations=[Average Illuminations] "
58+
)
5159

5260
proc_opts = ProcessingOptions()
5361
proc_opts.treat_tiles("compare")

tests/bdv/test_processingoptions_example3.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Tests for ProcessingOptions class with multiple reference channels configuration."""
12

23
from imcflibs.imagej.bdv import ProcessingOptions
34

0 commit comments

Comments
 (0)