Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e870bea
Add method for dataset definition for angles and illuminations
lguerard Feb 11, 2025
77138ff
Start WIP for refactor using latest BigStitcher
lguerard Feb 11, 2025
c5528dd
Add method for fusion using BDV Playground
lguerard Feb 11, 2025
e1845e3
Refactor to work with latest BigStitcher
lguerard Mar 6, 2025
9aa57db
Add method for fusion using BIOP Kheops to OME-TIFF
lguerard Mar 6, 2025
b47ab1b
Use the multi view dataset definition command instead
lguerard Mar 11, 2025
4233176
Update tests to latest modifications
lguerard Mar 11, 2025
cdbcaf4
Update method for fusion using BDV playground
lguerard Mar 11, 2025
28b319e
Add method for associating label images using 3DImageJSuite
lguerard Mar 11, 2025
aba06fa
Fix the filtering of objects to use the calibrated method
lguerard Mar 11, 2025
2c58ea9
Use the morpholibj package to 2D dilate labels
lguerard Mar 11, 2025
2ab85e9
Format imports
lguerard Mar 11, 2025
07fa81b
Add method to write results to CSV
lguerard Mar 11, 2025
1ec6f37
Add method to return the median value of a list
lguerard Mar 11, 2025
7fffba7
Add methods for 3D Maxima Finder and 3D Watershed
lguerard Mar 11, 2025
d2bfd09
Update tests to work with Multi-View dataset definition
lguerard Mar 11, 2025
44a77fe
Add processing library to do basic methods
lguerard Mar 11, 2025
af09a4b
Revert "Add processing library to do basic methods"
lguerard Mar 12, 2025
1294ede
Revert "Add method to return the median value of a list"
lguerard Mar 12, 2025
f160c59
Revert "Format imports"
lguerard Mar 12, 2025
dc87aad
Revert "Use the morpholibj package to 2D dilate labels"
lguerard Mar 12, 2025
dc3382e
Revert "Fix the filtering of objects to use the calibrated method"
lguerard Mar 12, 2025
93dadc9
Revert "Add method for associating label images using 3DImageJSuite"
lguerard Mar 12, 2025
201a6c7
Add missing check function
lguerard Mar 12, 2025
af3616a
Fix docstring to reflect real string
lguerard Mar 12, 2025
ab8e5aa
Formatting
lguerard Mar 12, 2025
5cc36d3
Fix string
lguerard Mar 12, 2025
678596e
Fix string
lguerard Mar 12, 2025
09c790c
Use same input for test to work
lguerard Mar 12, 2025
c03f792
Formatting
lguerard Mar 12, 2025
8f67cb0
Fix input for angle to only support input options
lguerard Mar 12, 2025
7c63de8
Merge branch 'devel' into laurent/devel
ehrenfeu Mar 20, 2025
2e9ad4a
Fix linting
lguerard Mar 19, 2025
0d5581d
Formatting
lguerard Mar 19, 2025
f5b4f86
Fix linting issues
lguerard Mar 19, 2025
c8fcff3
Add missing docstring
lguerard Mar 24, 2025
41b301b
Format using Ruff
lguerard Mar 24, 2025
3876612
Shorten docstring explanation
lguerard Mar 24, 2025
72ebf11
Fix D100 error for undocumented public module
lguerard Mar 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
383 changes: 282 additions & 101 deletions src/imcflibs/imagej/bdv.py

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions src/imcflibs/imagej/misc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Miscellaneous ImageJ related functions, mostly convenience wrappers."""

import csv
import sys
import time
import smtplib
Expand Down Expand Up @@ -417,3 +418,33 @@
threshold_value = int(round(threshold_value.get()))

return threshold_value


def write_results(out_file, content):
"""Write the results to a csv file.

Parameters
----------
out_file : str
Path to the output file.
content : list of OrderedDict
List of dictionaries representing the results.

"""

# Check if the output file exists
if not os.path.exists(out_file):

Check warning on line 436 in src/imcflibs/imagej/misc.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/misc.py#L436

Added line #L436 was not covered by tests
# If the file does not exist, create it and write the header
with open(out_file, "wb") as f:
dict_writer = csv.DictWriter(

Check warning on line 439 in src/imcflibs/imagej/misc.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/misc.py#L438-L439

Added lines #L438 - L439 were not covered by tests
f, content[0].keys(), delimiter=";"
)
dict_writer.writeheader()
dict_writer.writerows(content)

Check warning on line 443 in src/imcflibs/imagej/misc.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/misc.py#L442-L443

Added lines #L442 - L443 were not covered by tests
else:
# If the file exists, append the results
with open(out_file, "ab") as f:
dict_writer = csv.DictWriter(

Check warning on line 447 in src/imcflibs/imagej/misc.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/misc.py#L446-L447

Added lines #L446 - L447 were not covered by tests
f, content[0].keys(), delimiter=";"
)
dict_writer.writerows(content)

Check warning on line 450 in src/imcflibs/imagej/misc.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/misc.py#L450

Added line #L450 was not covered by tests
94 changes: 94 additions & 0 deletions src/imcflibs/imagej/objects3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
[mcib3d]: https://mcib3d.frama.io/3d-suite-imagej/
"""

from de.mpicbg.scf.imgtools.image.create.image import ImageCreationUtilities
from de.mpicbg.scf.imgtools.image.create.labelmap import WatershedLabeling

Check warning on line 9 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L8-L9

Added lines #L8 - L9 were not covered by tests
from ij import IJ
from mcib3d.geom import Objects3DPopulation
from mcib3d.image3d import ImageHandler, ImageLabeller
from mcib3d.image3d.processing import MaximaFinder
from net.imglib2.img import ImagePlusAdapter

Check warning on line 14 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L13-L14

Added lines #L13 - L14 were not covered by tests


def population3d_to_imgplus(imp, population):
Expand Down Expand Up @@ -145,3 +149,93 @@

# Return the new population with the filtered objects
return Objects3DPopulation(objects_within_intensity)
def maxima_finder_3D(imageplus, min_threshold=0, noise=100, rxy=1.5, rz=1.5):

Check warning on line 152 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L152

Added line #L152 was not covered by tests
"""
Find local maxima in a 3D image.

This function identifies local maxima in a 3D image using a specified minimum threshold and noise level.
The radii for the maxima detection can be set independently for the x/y and z dimensions.

Parameters
----------
imageplus : ij.ImagePlus
The input 3D image in which to find local maxima.
min_threshold : int, optional
The minimum intensity threshold for maxima detection. Default is 0.
noise : int, optional
The noise tolerance level for maxima detection. Default is 100.
rxy : float, optional
The radius for maxima detection in the x and y dimensions. Default is 1.5.
rz : float, optional
The radius for maxima detection in the z dimension. Default is 1.5.

Returns
-------
ij.ImagePlus
An ImagePlus object containing the detected maxima as peaks.
"""

Check failure on line 176 in src/imcflibs/imagej/objects3d.py

View workflow job for this annotation

GitHub Actions / Ruff ⚡🕵

Ruff (D212)

src/imcflibs/imagej/objects3d.py:153:5: D212 Multi-line docstring summary should start at the first line
# Wrap the input ImagePlus into an ImageHandler
img = ImageHandler.wrap(imageplus)

Check warning on line 178 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L178

Added line #L178 was not covered by tests

# Duplicate the image and apply a threshold cut-off
thresholded = img.duplicate()
thresholded.thresholdCut(min_threshold, False, True)

Check warning on line 182 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L181-L182

Added lines #L181 - L182 were not covered by tests

# Initialize the MaximaFinder with the thresholded image and noise level
maxima_finder = MaximaFinder(thresholded, noise)

Check warning on line 185 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L185

Added line #L185 was not covered by tests

# Set the radii for maxima detection in x/y and z dimensions
maxima_finder.setRadii(rxy, rz)

Check warning on line 188 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L188

Added line #L188 was not covered by tests

# Retrieve the image peaks as an ImageHandler
img_peaks = maxima_finder.getImagePeaks()

Check warning on line 191 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L191

Added line #L191 was not covered by tests

# Convert the ImageHandler peaks to an ImagePlus
imp_peaks = img_peaks.getImagePlus()

Check warning on line 194 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L194

Added line #L194 was not covered by tests

# Set the calibration of the peaks image to match the input image
imp_peaks.setCalibration(imageplus.getCalibration())

Check warning on line 197 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L197

Added line #L197 was not covered by tests

# Set the title of the peaks image
imp_peaks.setTitle("Peaks")

Check warning on line 200 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L200

Added line #L200 was not covered by tests

return imp_peaks

Check warning on line 202 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L202

Added line #L202 was not covered by tests


def seeded_watershed(imp_binary, imp_peaks, threshold=10):

Check warning on line 205 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L205

Added line #L205 was not covered by tests
"""
Perform a seeded watershed segmentation on a binary image using seed points.

This function applies a watershed segmentation to a binary image using seed points provided in another image.
An optional threshold can be specified to control the segmentation process.

Parameters
----------
imp_binary : ij.ImagePlus
The binary image to segment.
imp_peaks : ij.ImagePlus
The image containing the seed points for the watershed segmentation.
threshold : float, optional
The threshold value to use for the segmentation. Default is 10.

Returns
-------
ij.ImagePlus
The segmented image with labels.
"""

Check failure on line 225 in src/imcflibs/imagej/objects3d.py

View workflow job for this annotation

GitHub Actions / Ruff ⚡🕵

Ruff (D212)

src/imcflibs/imagej/objects3d.py:206:5: D212 Multi-line docstring summary should start at the first line

img = ImagePlusAdapter.convertFloat(imp_binary)
img_seed = ImagePlusAdapter.convertFloat(imp_peaks).copy()

Check warning on line 228 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L227-L228

Added lines #L227 - L228 were not covered by tests

if threshold:
watersheded_result = WatershedLabeling.watershed(img, img_seed, threshold)

Check warning on line 231 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L230-L231

Added lines #L230 - L231 were not covered by tests
else:
watersheded_result = WatershedLabeling.watershed(img, img_seed)

Check warning on line 233 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L233

Added line #L233 was not covered by tests

return ImageCreationUtilities.convertImgToImagePlus(

Check warning on line 235 in src/imcflibs/imagej/objects3d.py

View check run for this annotation

Codecov / codecov/patch

src/imcflibs/imagej/objects3d.py#L235

Added line #L235 was not covered by tests
watersheded_result,
"Label image",
"",
imp_binary.getDimensions(),
imp_binary.getCalibration(),
)
49 changes: 35 additions & 14 deletions tests/bdv/test_define_dataset_auto.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
"""Tests for the automatic dataset definition functionality in the BDV module."""

import logging

from imcflibs import pathtools
from imcflibs.imagej import bdv


def set_default_values(project_filename, file_path):
def set_default_values(
project_filename, file_path, series_type="Tiles"
):
"""Set the default values for dataset definitions.

Parameters
Expand All @@ -13,9 +17,11 @@ def set_default_values(project_filename, file_path):
Name of the project
file_path : pathlib.Path
Path to a temporary folder
series_type : str, optional
Type of Bioformats series (default is "Tiles")

Returns
----------
-------
str
Start of the options for dataset definitions.
"""
Expand All @@ -32,15 +38,17 @@ def set_default_values(project_filename, file_path):
+ file_info["path"]
+ "] "
+ "exclude=10 "
+ "bioformats_series_are?="
+ series_type
+ " "
+ "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] "
)

return options


def test_define_dataset_auto_tile(tmp_path, caplog):
"""
Test automatic dataset definition method for tile series.
"""Test automatic dataset definition method for tile series.

Parameters
----------
Expand Down Expand Up @@ -69,18 +77,22 @@ def test_define_dataset_auto_tile(tmp_path, caplog):
bf_series_type = "Tiles"

# Define the ImageJ command
cmd = "Define dataset ..."
cmd = "Define Multi-View Dataset"

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

# Construct the options for dataset definitions
options = (
options
+ "how_to_load_images=["
+ "how_to_store_input_images=["
+ "Re-save as multiresolution HDF5"
+ "] "
+ "dataset_save_path=["
+ "load_raw_data_virtually "
+ "metadata_save_path=["
+ result_folder
+ "] "
+ "image_data_save_path=["
+ result_folder
+ "] "
+ "check_stack_sizes "
Expand All @@ -94,14 +106,15 @@ def test_define_dataset_auto_tile(tmp_path, caplog):
final_call = "IJ.run(cmd=[%s], params=[%s])" % (cmd, options)

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


def test_define_dataset_auto_angle(tmp_path, caplog):
"""
Test automatic dataset definition method for angle series.
"""Test automatic dataset definition method for angle series.

Parameters
----------
Expand Down Expand Up @@ -133,15 +146,21 @@ def test_define_dataset_auto_angle(tmp_path, caplog):
cmd = "Define Multi-View Dataset"

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

# Construct the options for dataset definitions
options = (
options
+ "how_to_load_images=["
+ "how_to_store_input_images=["
+ "Re-save as multiresolution HDF5"
+ "] "
+ "dataset_save_path=["
+ "load_raw_data_virtually "
+ "metadata_save_path=["
+ result_folder
+ "] "
+ "image_data_save_path=["
+ result_folder
+ "] "
+ "check_stack_sizes "
Expand All @@ -156,6 +175,8 @@ def test_define_dataset_auto_angle(tmp_path, caplog):
final_call = "IJ.run(cmd=[%s], params=[%s])" % (cmd, options)

# Define the dataset using the "Auto-Loader" option
bdv.define_dataset_auto(project_filename, file_path, bf_series_type)
bdv.define_dataset_auto(
project_filename, file_info["path"], bf_series_type
)
# Check if the final call is in the log
assert final_call == caplog.messages[0]
23 changes: 16 additions & 7 deletions tests/bdv/test_definitionoptions.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""Tests for the imcflibs.imagej.bdv.DefinitionOptions class."""

import pytest

from imcflibs.imagej.bdv import DefinitionOptions


def test_defaults():
"""Test the default options by calling all formatters on a "raw" objects."""
acitt_options = (
"multiple_angles=[NO (one angle)] "
"multiple_channels=[YES (all channels in one file)] "
"multiple_illuminations=[NO (one illumination direction)] "
"multiple_illuminations_directions=[NO (one illumination direction)] "
"multiple_tiles=[YES (one file per tile)] "
"multiple_timepoints=[NO (one time-point)] "
)
Expand All @@ -16,6 +19,7 @@ def test_defaults():

assert def_opts.fmt_acitt_options() == acitt_options


def test__definition_option():
"""Test an example with wrong setting for definition option."""

Expand All @@ -24,15 +28,19 @@ def test__definition_option():
def_opts = DefinitionOptions()
with pytest.raises(ValueError) as excinfo:
def_opts.set_angle_definition(test_value)
assert str(excinfo.value) == "Value must be one of single, multi_multi or multi_single"
assert (
str(excinfo.value)
== "Value must be one of single, multi_multi. Support for multi_single is not available for angles and illuminations"
)


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

acitt_options = (
"multiple_angles=[NO (one angle)] "
"multiple_channels=[YES (all channels in one file)] "
"multiple_illuminations=[NO (one illumination direction)] "
"multiple_illuminations_directions=[NO (one illumination direction)] "
"multiple_tiles=[YES (one file per tile)] "
"multiple_timepoints=[YES (one file per time-point)] "
)
Expand All @@ -42,13 +50,14 @@ def test__multiple_timepoints_files():

assert def_opts.fmt_acitt_options() == acitt_options


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

acitt_options = (
"multiple_angles=[NO (one angle)] "
"multiple_channels=[YES (one file per channel)] "
"multiple_illuminations=[NO (one illumination direction)] "
"multiple_illuminations_directions=[NO (one illumination direction)] "
"multiple_tiles=[YES (one file per tile)] "
"multiple_timepoints=[YES (all time-points in one file)] "
)
Expand All @@ -59,14 +68,14 @@ def test__multiple_channels_files_multiple_timepoints():

assert def_opts.fmt_acitt_options() == acitt_options


def test_single_tile_multiple_angles_files():
"""Test an example setting how to treat single tile and multiple angle
files"""
"""Test an example on with one tile and multiple angle files."""

acitt_options = (
"multiple_angles=[YES (one file per angle)] "
"multiple_channels=[YES (all channels in one file)] "
"multiple_illuminations=[NO (one illumination direction)] "
"multiple_illuminations_directions=[NO (one illumination direction)] "
"multiple_tiles=[NO (one tile)] "
"multiple_timepoints=[NO (one time-point)] "
)
Expand Down
12 changes: 10 additions & 2 deletions tests/bdv/test_processingoptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Tests for the ProcessingOptions class from the imcflibs.imagej.bdv module."""

from imcflibs.imagej.bdv import ProcessingOptions


Expand All @@ -18,7 +20,10 @@ def test_defaults():
"how_to_treat_tiles=compare "
"how_to_treat_timepoints=[treat individually] "
)
use_acitt = "channels=[Average Channels] " "illuminations=[Average Illuminations] "
use_acitt = (
"channels=[Average Channels] "
"illuminations=[Average Illuminations] "
)

proc_opts = ProcessingOptions()

Expand Down Expand Up @@ -47,7 +52,10 @@ def test__treat_tc_ti__ref_c1():
"how_to_treat_tiles=compare "
"how_to_treat_timepoints=[treat individually] "
)
use_acitt = "channels=[use Channel 1] " "illuminations=[Average Illuminations] "
use_acitt = (
"channels=[use Channel 1] "
"illuminations=[Average Illuminations] "
)

proc_opts = ProcessingOptions()
proc_opts.treat_tiles("compare")
Expand Down
1 change: 1 addition & 0 deletions tests/bdv/test_processingoptions_example3.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Tests for ProcessingOptions class with multiple reference channels configuration."""

from imcflibs.imagej.bdv import ProcessingOptions

Expand Down
Loading
Loading