From e870bea6b36e5cc3de9686ee48e16ee5a0e517b5 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Feb 2025 17:23:15 +0100 Subject: [PATCH 01/38] Add method for dataset definition for angles and illuminations These only have 2 options --- src/imcflibs/imagej/bdv.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7dfa1325..9d100de1 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -543,6 +543,32 @@ def check_definition_option(self, value): "multi_multi": MULTI_MULTI_FILE, } + def check_definition_option_ang_ill(self, value): + """Check if the value is a valid definition option. + + This is needed for angles and illuminations because support is not + available for multiple angles and illuminations in a single file. + + Parameters + ---------- + value : str + Entered value by the user. + + Returns + ------- + dict(str, str): dictionary containing the correct string definition. + """ + if value not in [ + "single", + "multi_multi", + ]: + raise ValueError("Value must be one of single, multi_multi. Support for multi_single is not available for angles and illuminations") + + return { + "single": SINGLE_FILE, + "multi_multi": MULTI_MULTI_FILE, + } + def set_angle_definition(self, value): """Set the value for the angle definition @@ -551,7 +577,7 @@ def set_angle_definition(self, value): value : str One of `single`, `multi_single` or `multi_multi`. """ - choices = self.check_definition_option(value) + choices = self.check_definition_option_ang_ill(value) self._angle_definition = choices[value] % "angle" log.debug("New 'angle_definition' setting: %s", self._angle_definition) @@ -575,7 +601,7 @@ def set_illumination_definition(self, value): value : str One of `single`, `multi_single` or `multi_multi`. """ - choices = self.check_definition_option(value) + choices = self.check_definition_option_ang_ill(value) self._illumination_definition = choices[value] % "illumination direction" log.debug( "New 'illumination_definition' setting: %s", self._illumination_definition From 77138ffd88c8bbc71ed2f447816e16f18c079bbd Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Feb 2025 17:23:54 +0100 Subject: [PATCH 02/38] Start WIP for refactor using latest BigStitcher --- src/imcflibs/imagej/bdv.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9d100de1..e6d469e1 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -645,7 +645,7 @@ def fmt_acitt_options(self): parameters = [ "multiple_angles=" + self._angle_definition, "multiple_channels=" + self._channel_definition, - "multiple_illuminations=" + self._illumination_definition, + "multiple_illuminations_directions=" + self._illumination_definition, "multiple_tiles=" + self._tile_definition, "multiple_timepoints=" + self._timepoint_definition, ] @@ -842,14 +842,19 @@ def define_dataset_auto( + file_info["path"] + "] " + "exclude=10 " - # + "bioformats_series_are?=" - # + bf_series_type - # + " " + + "bioformats_series_are?=" + + bf_series_type + + " " + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " - + "how_to_load_images=[" + + "how_to_store_input_images=[" + resave + "] " - + "dataset_save_path=[" + + load_raw_data_virtually + + " " + + "metadata_save_path=[" + + dataset_save_path + + "] " + + "image_data_save_path=[" + dataset_save_path + "] " + "check_stack_sizes " @@ -862,16 +867,13 @@ def define_dataset_auto( + " " + "setups_per_partition=0 " + "use_deflate_compression " - # + "export_path=[" - # + dataset_save_path - # + "]", ) log.debug(options) if bf_series_type == "Tiles": log.debug("Doing tiled dataset definition") - IJ.run("Define dataset ...", str(options)) + IJ.run("BigSticher", "select=define " + str(options)) elif bf_series_type == "Angles": log.debug("Doing multi-view dataset definition") IJ.run("Define Multi-View Dataset", str(options)) @@ -914,10 +916,12 @@ def define_dataset_manual( os.path.join(temp, project_filename) options = ( - "define_dataset=[Manual Loader (Bioformats based)] " + "select=define " + + "define_dataset=[Manual Loader (Bioformats based)] " + "project_filename=[" + xml_filename + "] " + + "_____" + definition_opts.fmt_acitt_options() + " " + "image_file_directory=" @@ -929,11 +933,11 @@ def define_dataset_manual( + " " + "calibration_type=[Same voxel-size for all views] " + "calibration_definition=[Load voxel-size(s) from file(s)] " - + "imglib2_data_container=[ArrayImg (faster)]" + # + "imglib2_data_container=[ArrayImg (faster)]" ) - log.debug("Manual dataset defintion options: <%s>", options) - IJ.run("Define dataset ...", str(options)) + log.debug("Manual dataset definition options: <%s>", options) + IJ.run("BigStitcher", str(options)) def resave_as_h5( From c5528ddbd382bb0c43bce119b59f397ed37c26ad Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Feb 2025 17:24:18 +0100 Subject: [PATCH 03/38] Add method for fusion using BDV Playground --- src/imcflibs/imagej/bdv.py | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index e6d469e1..7a98c058 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1558,3 +1558,55 @@ def fuse_dataset( log.debug("Dataset fusion options: <%s>", options) IJ.run("Fuse dataset ...", str(options)) + +def fuse_dataset_bdvp( + project_path, + command, + processing_opts=None, + result_path=None, + compression="LZW", +): + """Export a BigDataViewer project using the BIOP Kheops exporter. + + This function uses the BIOP Kheops exporter to convert a BigDataViewer project into a + OME-TIFF files, with optional compression. + + Parameters + ---------- + project_path : str + Full path to the BigDataViewer XML project file. + command : CommandService + The Scijava CommandService instance to execute the export command. + processing_opts : ProcessingOptions, optional + Options defining which parts of the dataset to process. If None, default processing + options will be used (process all angles, channels, etc.). + result_path : str, optional + Path where to store the exported files. If None, files will be saved in the same + directory as the input project. + compression : str, optional + Compression method to use for the TIFF files. Default is "LZW". + + Notes + ----- + This function requires the PTBIOP update site to be enabled in Fiji/ImageJ. + """ + if processing_opts is None: + processing_opts = ProcessingOptions() + + file_info = pathtools.parse_path(project_path) + if not result_path: + result_path = file_info["path"] + # if not os.path.exists(result_path): + # os.makedirs(result_path) + + command.run( + KheopsExportImagePlusCommand, + True, + "image", project_path, + "output_dir", result_path, + "compression", compression, + "subset_channels", "", + "subset_slices", "", + "subset_frames", "", + "compress_temp_files", False + ) \ No newline at end of file From e1845e37b9e73767e55ca40db8a4b7e4f0ab20f3 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 6 Mar 2025 17:23:36 +0100 Subject: [PATCH 04/38] Refactor to work with latest BigStitcher --- src/imcflibs/imagej/bdv.py | 109 ++++++------------------------------- 1 file changed, 17 insertions(+), 92 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7a98c058..2435bea8 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -543,32 +543,6 @@ def check_definition_option(self, value): "multi_multi": MULTI_MULTI_FILE, } - def check_definition_option_ang_ill(self, value): - """Check if the value is a valid definition option. - - This is needed for angles and illuminations because support is not - available for multiple angles and illuminations in a single file. - - Parameters - ---------- - value : str - Entered value by the user. - - Returns - ------- - dict(str, str): dictionary containing the correct string definition. - """ - if value not in [ - "single", - "multi_multi", - ]: - raise ValueError("Value must be one of single, multi_multi. Support for multi_single is not available for angles and illuminations") - - return { - "single": SINGLE_FILE, - "multi_multi": MULTI_MULTI_FILE, - } - def set_angle_definition(self, value): """Set the value for the angle definition @@ -833,13 +807,16 @@ def define_dataset_auto( angle_rotation = "" options = ( - "define_dataset=[Automatic Loader (Bioformats based)] " + "select=define" + + " " + + "define_dataset=[Automatic Loader (Bioformats based)]" + + " " + "project_filename=[" + project_filename + ".xml" + "] " + "path=[" - + file_info["path"] + + file_info["full"] + "] " + "exclude=10 " + "bioformats_series_are?=" @@ -849,7 +826,7 @@ def define_dataset_auto( + "how_to_store_input_images=[" + resave + "] " - + load_raw_data_virtually + + "load_raw_data_virtually" + " " + "metadata_save_path=[" + dataset_save_path @@ -871,14 +848,7 @@ def define_dataset_auto( log.debug(options) - if bf_series_type == "Tiles": - log.debug("Doing tiled dataset definition") - IJ.run("BigSticher", "select=define " + str(options)) - elif bf_series_type == "Angles": - log.debug("Doing multi-view dataset definition") - IJ.run("Define Multi-View Dataset", str(options)) - else: - raise ValueError("Wrong answer for series type") + IJ.run("BigStitcher", str(options)) def define_dataset_manual( @@ -1454,7 +1424,9 @@ def fuse_dataset( downsampling=1, interpolation="[Linear Interpolation]", pixel_type="[16-bit unsigned integer]", + fusion_type="Avg, Blending", export="HDF5", + compression="Zstandard", ): """Call BigStitcher's "Fuse Dataset" command. @@ -1505,11 +1477,13 @@ def fuse_dataset( + "interpolation=" + interpolation + " " + + "fusion_type=[" + + fusion_type + + "] " + "pixel_type=" + pixel_type + " " + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " - + "blend " + "preserve_original " + "produce=[Each timepoint & channel] " ) @@ -1536,9 +1510,12 @@ def fuse_dataset( options = ( options - + "fused_image=[ZARR/N5/HDF5 export using N5-API] " + + "fused_image=[OME-ZARR/N5/HDF5 export using N5-API] " + "define_input=[Auto-load from input data (values shown below)] " + "export=HDF5 " + + "compression=" + + compression + + " " + "create " + "create_0 " + "hdf5_file=[" @@ -1557,56 +1534,4 @@ def fuse_dataset( ) log.debug("Dataset fusion options: <%s>", options) - IJ.run("Fuse dataset ...", str(options)) - -def fuse_dataset_bdvp( - project_path, - command, - processing_opts=None, - result_path=None, - compression="LZW", -): - """Export a BigDataViewer project using the BIOP Kheops exporter. - - This function uses the BIOP Kheops exporter to convert a BigDataViewer project into a - OME-TIFF files, with optional compression. - - Parameters - ---------- - project_path : str - Full path to the BigDataViewer XML project file. - command : CommandService - The Scijava CommandService instance to execute the export command. - processing_opts : ProcessingOptions, optional - Options defining which parts of the dataset to process. If None, default processing - options will be used (process all angles, channels, etc.). - result_path : str, optional - Path where to store the exported files. If None, files will be saved in the same - directory as the input project. - compression : str, optional - Compression method to use for the TIFF files. Default is "LZW". - - Notes - ----- - This function requires the PTBIOP update site to be enabled in Fiji/ImageJ. - """ - if processing_opts is None: - processing_opts = ProcessingOptions() - - file_info = pathtools.parse_path(project_path) - if not result_path: - result_path = file_info["path"] - # if not os.path.exists(result_path): - # os.makedirs(result_path) - - command.run( - KheopsExportImagePlusCommand, - True, - "image", project_path, - "output_dir", result_path, - "compression", compression, - "subset_channels", "", - "subset_slices", "", - "subset_frames", "", - "compress_temp_files", False - ) \ No newline at end of file + IJ.run("Image Fusion", str(options)) From 9aa57db64459645a260b4dd8fbb3661215306c35 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 6 Mar 2025 17:23:58 +0100 Subject: [PATCH 05/38] Add method for fusion using BIOP Kheops to OME-TIFF --- src/imcflibs/imagej/bdv.py | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 2435bea8..ebaaf8ad 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -13,6 +13,7 @@ import shutil import sys +from ch.epfl.biop.kheops import KheopsExportImagePlusCommand from ij import IJ from .. import pathtools @@ -1535,3 +1536,63 @@ def fuse_dataset( log.debug("Dataset fusion options: <%s>", options) IJ.run("Image Fusion", str(options)) + + +def fuse_dataset_bdvp( + project_path, + command, + processing_opts=None, + result_path=None, + compression="LZW", +): + """Export a BigDataViewer project using the BIOP Kheops exporter. + + This function uses the BIOP Kheops exporter to convert a BigDataViewer project into a + OME-TIFF files, with optional compression. + + Parameters + ---------- + project_path : str + Full path to the BigDataViewer XML project file. + command : CommandService + The Scijava CommandService instance to execute the export command. + processing_opts : ProcessingOptions, optional + Options defining which parts of the dataset to process. If None, default processing + options will be used (process all angles, channels, etc.). + result_path : str, optional + Path where to store the exported files. If None, files will be saved in the same + directory as the input project. + compression : str, optional + Compression method to use for the TIFF files. Default is "LZW". + + Notes + ----- + This function requires the PTBIOP update site to be enabled in Fiji/ImageJ. + """ + if processing_opts is None: + processing_opts = ProcessingOptions() + + file_info = pathtools.parse_path(project_path) + if not result_path: + result_path = file_info["path"] + # if not os.path.exists(result_path): + # os.makedirs(result_path) + + command.run( + KheopsExportImagePlusCommand, + True, + "image", + project_path, + "output_dir", + result_path, + "compression", + compression, + "subset_channels", + "", + "subset_slices", + "", + "subset_frames", + "", + "compress_temp_files", + False, + ) From b47ab1be2fddba9f248e9be84e7b3db53c4ea1f8 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 13:35:05 +0100 Subject: [PATCH 06/38] Use the multi view dataset definition command instead --- src/imcflibs/imagej/bdv.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index ebaaf8ad..93beb693 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -808,9 +808,7 @@ def define_dataset_auto( angle_rotation = "" options = ( - "select=define" - + " " - + "define_dataset=[Automatic Loader (Bioformats based)]" + "define_dataset=[Automatic Loader (Bioformats based)]" + " " + "project_filename=[" + project_filename @@ -849,7 +847,7 @@ def define_dataset_auto( log.debug(options) - IJ.run("BigStitcher", str(options)) + IJ.run("Define Multi-View Dataset", str(options)) def define_dataset_manual( @@ -887,8 +885,7 @@ def define_dataset_manual( os.path.join(temp, project_filename) options = ( - "select=define " - + "define_dataset=[Manual Loader (Bioformats based)] " + "define_dataset=[Manual Loader (Bioformats based)] " + "project_filename=[" + xml_filename + "] " @@ -908,7 +905,7 @@ def define_dataset_manual( ) log.debug("Manual dataset definition options: <%s>", options) - IJ.run("BigStitcher", str(options)) + IJ.run("Define Multi-View Dataset", str(options)) def resave_as_h5( From 4233176a76cee3a3e93d11040efcd73ffe3df4a3 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 13:35:13 +0100 Subject: [PATCH 07/38] Update tests to latest modifications --- tests/bdv/test_define_dataset_auto.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index 914a8ae9..fb6aa47e 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -69,7 +69,7 @@ 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) @@ -77,10 +77,15 @@ def test_define_dataset_auto_tile(tmp_path, caplog): # Construct the options for dataset definitions options = ( options - + "how_to_load_images=[" + + "bioformats_series_are?=Tiles " + + "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 " From cdbcaf4c2c20014e5c716263efce1098f64daac4 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 16:56:50 +0100 Subject: [PATCH 08/38] Update method for fusion using BDV playground --- src/imcflibs/imagej/bdv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 93beb693..52a1364d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -13,7 +13,7 @@ import shutil import sys -from ch.epfl.biop.kheops import KheopsExportImagePlusCommand +from ch.epfl.biop.scijava.command.spimdata import FuseBigStitcherDatasetIntoOMETiffCommand from ij import IJ from .. import pathtools @@ -1576,7 +1576,7 @@ def fuse_dataset_bdvp( # os.makedirs(result_path) command.run( - KheopsExportImagePlusCommand, + FuseBigStitcherDatasetIntoOMETiffCommand, True, "image", project_path, From 28b319eeab7c6be16faa576ae091b9fd19dd14bb Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 16:57:18 +0100 Subject: [PATCH 09/38] Add method for associating label images using 3DImageJSuite --- src/imcflibs/imagej/labelimage.py | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 5d800b48..6391fe60 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -97,6 +97,51 @@ def relate_label_images(label_image_ref, label_image_to_relate): return ImageCalculator.run(label_image_ref, imp_dup, "Multiply create") +def associate_label_images_3d(outer_label_imp, inner_label_imp): + """ + Associate two label images. + + Uses the 3D Association plugin from the 3DImageJSuite. + + Parameters + ---------- + outer_label_imp : ij.ImagePlus + The outer label image + inner_label_imp : ij.ImagePlus + The inner label image + + Returns + ------- + related_inner_imp : ij.ImagePlus + The related inner label image + """ + + outer_label_imp.show() + inner_label_imp.show() + + outer_title = outer_label_imp.getTitle() + inner_title = inner_label_imp.getTitle() + + IJ.run( + "3D Association", + "image_a=" + + outer_title + + " " + + "image_b=" + + inner_title + + " " + + "method=Colocalisation min=1 max=0.000", + ) + + related_inner_imp = IJ.getImage() + + outer_label_imp.hide() + inner_label_imp.hide() + related_inner_imp.hide() + + return related_inner_imp + + def filter_objects(label_image, table, string, min_val, max_val): """Filter labels based on specific min and max values. From aba06fa5f35a187534a513f2681495b04a7add37 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:01:08 +0100 Subject: [PATCH 10/38] Fix the filtering of objects to use the calibrated method --- src/imcflibs/imagej/labelimage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 6391fe60..9a29183b 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -227,11 +227,11 @@ def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): # Set the minimum size for labeling if provided if min_vol: - labeler.setMinSize(min_vol) + labeler.setMinSizeCalibrated(min_vol) # Set the maximum size for labeling if provided if max_vol: - labeler.setMaxSize(max_vol) + labeler.setMinSizeCalibrated(max_vol) # Get the labeled image seg = labeler.getLabels(img) From 2c58ea9db50775362fb634226b2fb911d26ddcf6 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:01:28 +0100 Subject: [PATCH 11/38] Use the morpholibj package to 2D dilate labels --- src/imcflibs/imagej/labelimage.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 9a29183b..1b996424 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -274,17 +274,7 @@ def dilate_labels_2d(imp, dilation_radius): current_imp = Duplicator().run(imp, 1, 1, i, imp.getNSlices(), 1, 1) # Perform a dilation of the labels in the current slice - IJ.run( - current_imp, - "Label Morphological Filters", - "operation=Dilation radius=" + str(dilation_radius) + " from_any_label", - ) - - # Get the dilated labels - dilated_labels_imp = IJ.getImage() - - # Hide the dilated labels to avoid visual clutter - dilated_labels_imp.hide() + dilated_labels_imp = li.dilateLabels(current_imp, dilation_radius) # Append the dilated labels to the list dilated_labels_list.append(dilated_labels_imp) From 2ab85e93e103cd474e3532f4fe4372ef4fbeb509 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:01:59 +0100 Subject: [PATCH 12/38] Format imports --- src/imcflibs/imagej/labelimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 1b996424..b68c4de8 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -2,7 +2,7 @@ """Functions to work with ImageJ label images.""" -from ij import IJ, ImagePlus, Prefs, ImageStack +from ij import IJ, ImagePlus, ImageStack, Prefs from ij.plugin import Duplicator, ImageCalculator from ij.plugin.filter import ThresholdToSelection from ij.process import FloatProcessor, ImageProcessor From 07fa81b4b89a24e13e73effed349dc413518a25f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:02:27 +0100 Subject: [PATCH 13/38] Add method to write results to CSV --- src/imcflibs/imagej/misc.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 1ccf35cc..6392c550 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -1,5 +1,6 @@ """Miscellaneous ImageJ related functions, mostly convenience wrappers.""" +import csv import sys import time import smtplib @@ -417,3 +418,33 @@ def get_threshold_value_from_method(imp, method, ops): 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): + # If the file does not exist, create it and write the header + with open(out_file, "wb") as f: + dict_writer = csv.DictWriter( + f, content[0].keys(), delimiter=";" + ) + dict_writer.writeheader() + dict_writer.writerows(content) + else: + # If the file exists, append the results + with open(out_file, "ab") as f: + dict_writer = csv.DictWriter( + f, content[0].keys(), delimiter=";" + ) + dict_writer.writerows(content) From 1ec6f371e430b3988efcc8974f068ad4ef2062ab Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:02:42 +0100 Subject: [PATCH 14/38] Add method to return the median value of a list --- src/imcflibs/imagej/misc.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 6392c550..6f8a5e9f 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -96,6 +96,30 @@ def percentage(part, whole): """ return 100 * float(part) / float(whole) +def median(array): + """ + Calculate the median of a list of numbers. + + Parameters + ---------- + array : list + The list of numbers to calculate the median of. + + Returns + ------- + float + The median of the list of numbers. + """ + sorted_array = sorted(array) + half, odd = divmod(len(sorted_array), 2) + if odd: + # If the length of the array is odd, the median is the middle element + return sorted_array[half] + else: + # If the length of the array is even, the median is the average of the two middle elements + return (sorted_array[half - 1] + sorted_array[half]) / 2.0 + + def calculate_mean_and_stdv(float_values): """Calculate mean and standard deviation from a list of floats. From 7fffba7f36ff6ab69f1a9787f8eac3396d8df32d Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:03:02 +0100 Subject: [PATCH 15/38] Add methods for 3D Maxima Finder and 3D Watershed --- src/imcflibs/imagej/objects3d.py | 94 ++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index cf738743..44e1d605 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -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 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 def population3d_to_imgplus(imp, population): @@ -145,3 +149,93 @@ def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): # 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): + """ + 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. + """ + # Wrap the input ImagePlus into an ImageHandler + img = ImageHandler.wrap(imageplus) + + # Duplicate the image and apply a threshold cut-off + thresholded = img.duplicate() + thresholded.thresholdCut(min_threshold, False, True) + + # Initialize the MaximaFinder with the thresholded image and noise level + maxima_finder = MaximaFinder(thresholded, noise) + + # Set the radii for maxima detection in x/y and z dimensions + maxima_finder.setRadii(rxy, rz) + + # Retrieve the image peaks as an ImageHandler + img_peaks = maxima_finder.getImagePeaks() + + # Convert the ImageHandler peaks to an ImagePlus + imp_peaks = img_peaks.getImagePlus() + + # Set the calibration of the peaks image to match the input image + imp_peaks.setCalibration(imageplus.getCalibration()) + + # Set the title of the peaks image + imp_peaks.setTitle("Peaks") + + return imp_peaks + + +def seeded_watershed(imp_binary, imp_peaks, threshold=10): + """ + 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. + """ + + img = ImagePlusAdapter.convertFloat(imp_binary) + img_seed = ImagePlusAdapter.convertFloat(imp_peaks).copy() + + if threshold: + watersheded_result = WatershedLabeling.watershed(img, img_seed, threshold) + else: + watersheded_result = WatershedLabeling.watershed(img, img_seed) + + return ImageCreationUtilities.convertImgToImagePlus( + watersheded_result, + "Label image", + "", + imp_binary.getDimensions(), + imp_binary.getCalibration(), + ) From d2bfd090ed72d6bc39d2bef3177033f12f65f143 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:04:02 +0100 Subject: [PATCH 16/38] Update tests to work with Multi-View dataset definition --- tests/bdv/test_define_dataset_auto.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index fb6aa47e..cf7be2bd 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -4,7 +4,7 @@ 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 @@ -32,6 +32,7 @@ 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)] " ) @@ -77,7 +78,6 @@ def test_define_dataset_auto_tile(tmp_path, caplog): # Construct the options for dataset definitions options = ( options - + "bioformats_series_are?=Tiles " + "how_to_store_input_images=[" + "Re-save as multiresolution HDF5" + "] " @@ -138,15 +138,19 @@ 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 " From 44a77fe03ad24117bbec1f6452e2087bb1c78d36 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:04:33 +0100 Subject: [PATCH 17/38] Add processing library to do basic methods Add one to apply a filter, to do rolling ball background subtraction and thresholding --- src/imcflibs/imagej/processing.py | 135 ++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/imcflibs/imagej/processing.py diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py new file mode 100644 index 00000000..de34a348 --- /dev/null +++ b/src/imcflibs/imagej/processing.py @@ -0,0 +1,135 @@ +from ij import IJ + +from ..log import LOG as log + +def apply_filter(imp, filter_method, filter_radius, do_3D=False): + """ + Make a specific filter followed by a threshold method of choice + + Parameters + ---------- + imp : ImagePlus + Input ImagePlus to filter and threshold + filter_method : str + Name of the filter method to use. Must be one of: + - Median + - Mean + - Gaussian Blur + - Minimum + - Maximum + filter_radius : int + Radius of the filter filter to use + do_3d : bool, optional + If set to True, will do a 3D filtering, by default False + + + Returns + ------- + ij.ImagePlus + Filtered ImagePlus + """ + log.info("Applying filter %s with radius %d" % (filter_method, filter_radius)) + + if filter_method not in ["Median", "Mean", "Gaussian Blur", "Minimum", "Maximum"]: + raise ValueError( + "filter_method must be one of: Median, Mean, Gaussian Blur, Minimum, Maximum" + ) + + if do_3d: + filter = filter_method + " 3D..." + else: + filter = filter_method + "..." + + options = ( + "sigma=" if filter_method == "Gaussian Blur" else "radius=" + + str(filter_radius) + + " stack" + ) + + log.debug("Filter: <%s> with options <%s>" % (filter, options)) + + imageplus = imp.duplicate() + IJ.run(imageplus, filter, options) + + return imageplus + +def apply_background_subtraction(imp, rolling_ball_radius, do_3D=False): + """ + Perform background subtraction using a rolling ball method + + Parameters + ---------- + imp : ij.ImagePlus + Input ImagePlus to filter and threshold + rolling_ball_radius : int + Radius of the rolling ball filter to use + do_3d : bool, optional + If set to True, will do a 3D filtering, by default False + + Returns + ------- + ij.ImagePlus + Filtered ImagePlus + """ + log.info("Applying rolling ball with radius %d" % rolling_ball_radius) + + options = ( + "rolling=" + str(rolling_ball_radius) + + " stack" if do_3D else "" + ) + + log.debug("Background subtraction options: %s" % options) + + imageplus = imp.duplicate() + IJ.run(imageplus, "Substract Background...", options) + + return imageplus + +def apply_threshold(imp, threshold_method): + """ + Apply a threshold method to the input ImagePlus + + Parameters + ---------- + imp : ij.ImagePlus + Input ImagePlus to filter and threshold + threshold_method : str + Name of the threshold method to use + do_3d : bool, optional + If set to True, the automatic threshold will be done on a 3D stack, by default True + + Returns + ------- + ij.ImagePlus + Thresholded ImagePlus + """ + + log.info("Applying threshold method %s" % threshold_method) + + imageplus = imp.duplicate() + + auto_threshold_options = ( + threshold_method + + " " + + "dark" + + " " + + "stack" if do_3D else "" + ) + + log.debug("Auto threshold options: %s" % auto_threshold_options) + + IJ.setAutoThreshold(imageplus, auto_threshold_options) + + convert_to_binary_options = ( + "method=" + threshold_method + + " " + + "background=Dark" + + " " + + "black" + ) + + log.debug("Convert to binary options: %s" % convert_to_binary_options) + + IJ.run(imageplus, "Convert to Mask", convert_to_binary_options) + + return imageplus \ No newline at end of file From af09a4bfe6a57eac58e661ae38536e424f18c5c2 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 09:20:52 +0100 Subject: [PATCH 18/38] Revert "Add processing library to do basic methods" This reverts commit 868563f2507f8a290037af43c83d5feca44b10a3. --- src/imcflibs/imagej/processing.py | 135 ------------------------------ 1 file changed, 135 deletions(-) delete mode 100644 src/imcflibs/imagej/processing.py diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py deleted file mode 100644 index de34a348..00000000 --- a/src/imcflibs/imagej/processing.py +++ /dev/null @@ -1,135 +0,0 @@ -from ij import IJ - -from ..log import LOG as log - -def apply_filter(imp, filter_method, filter_radius, do_3D=False): - """ - Make a specific filter followed by a threshold method of choice - - Parameters - ---------- - imp : ImagePlus - Input ImagePlus to filter and threshold - filter_method : str - Name of the filter method to use. Must be one of: - - Median - - Mean - - Gaussian Blur - - Minimum - - Maximum - filter_radius : int - Radius of the filter filter to use - do_3d : bool, optional - If set to True, will do a 3D filtering, by default False - - - Returns - ------- - ij.ImagePlus - Filtered ImagePlus - """ - log.info("Applying filter %s with radius %d" % (filter_method, filter_radius)) - - if filter_method not in ["Median", "Mean", "Gaussian Blur", "Minimum", "Maximum"]: - raise ValueError( - "filter_method must be one of: Median, Mean, Gaussian Blur, Minimum, Maximum" - ) - - if do_3d: - filter = filter_method + " 3D..." - else: - filter = filter_method + "..." - - options = ( - "sigma=" if filter_method == "Gaussian Blur" else "radius=" - + str(filter_radius) - + " stack" - ) - - log.debug("Filter: <%s> with options <%s>" % (filter, options)) - - imageplus = imp.duplicate() - IJ.run(imageplus, filter, options) - - return imageplus - -def apply_background_subtraction(imp, rolling_ball_radius, do_3D=False): - """ - Perform background subtraction using a rolling ball method - - Parameters - ---------- - imp : ij.ImagePlus - Input ImagePlus to filter and threshold - rolling_ball_radius : int - Radius of the rolling ball filter to use - do_3d : bool, optional - If set to True, will do a 3D filtering, by default False - - Returns - ------- - ij.ImagePlus - Filtered ImagePlus - """ - log.info("Applying rolling ball with radius %d" % rolling_ball_radius) - - options = ( - "rolling=" + str(rolling_ball_radius) - + " stack" if do_3D else "" - ) - - log.debug("Background subtraction options: %s" % options) - - imageplus = imp.duplicate() - IJ.run(imageplus, "Substract Background...", options) - - return imageplus - -def apply_threshold(imp, threshold_method): - """ - Apply a threshold method to the input ImagePlus - - Parameters - ---------- - imp : ij.ImagePlus - Input ImagePlus to filter and threshold - threshold_method : str - Name of the threshold method to use - do_3d : bool, optional - If set to True, the automatic threshold will be done on a 3D stack, by default True - - Returns - ------- - ij.ImagePlus - Thresholded ImagePlus - """ - - log.info("Applying threshold method %s" % threshold_method) - - imageplus = imp.duplicate() - - auto_threshold_options = ( - threshold_method - + " " - + "dark" - + " " - + "stack" if do_3D else "" - ) - - log.debug("Auto threshold options: %s" % auto_threshold_options) - - IJ.setAutoThreshold(imageplus, auto_threshold_options) - - convert_to_binary_options = ( - "method=" + threshold_method - + " " - + "background=Dark" - + " " - + "black" - ) - - log.debug("Convert to binary options: %s" % convert_to_binary_options) - - IJ.run(imageplus, "Convert to Mask", convert_to_binary_options) - - return imageplus \ No newline at end of file From 1294ede21ac55eb3eade0cb0791f4fe3cd034330 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 09:21:14 +0100 Subject: [PATCH 19/38] Revert "Add method to return the median value of a list" This reverts commit b18f9ba41f47fa2873879a4b2fc8d6e6cd715477. --- src/imcflibs/imagej/misc.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 6f8a5e9f..6392c550 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -96,30 +96,6 @@ def percentage(part, whole): """ return 100 * float(part) / float(whole) -def median(array): - """ - Calculate the median of a list of numbers. - - Parameters - ---------- - array : list - The list of numbers to calculate the median of. - - Returns - ------- - float - The median of the list of numbers. - """ - sorted_array = sorted(array) - half, odd = divmod(len(sorted_array), 2) - if odd: - # If the length of the array is odd, the median is the middle element - return sorted_array[half] - else: - # If the length of the array is even, the median is the average of the two middle elements - return (sorted_array[half - 1] + sorted_array[half]) / 2.0 - - def calculate_mean_and_stdv(float_values): """Calculate mean and standard deviation from a list of floats. From f160c59a3aeeabc0f6d4f7ad7ff6112aa21dffa0 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 09:21:31 +0100 Subject: [PATCH 20/38] Revert "Format imports" This reverts commit d8bafb42a26974caf2d2323574895358593a1bc8. --- src/imcflibs/imagej/labelimage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index b68c4de8..1b996424 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -2,7 +2,7 @@ """Functions to work with ImageJ label images.""" -from ij import IJ, ImagePlus, ImageStack, Prefs +from ij import IJ, ImagePlus, Prefs, ImageStack from ij.plugin import Duplicator, ImageCalculator from ij.plugin.filter import ThresholdToSelection from ij.process import FloatProcessor, ImageProcessor From dc87aadfe3b6fd47965e4ac93813208af42ae934 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 09:21:57 +0100 Subject: [PATCH 21/38] Revert "Use the morpholibj package to 2D dilate labels" This reverts commit 636862f43f06582e675dcaae5b9ae506869af362. --- src/imcflibs/imagej/labelimage.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 1b996424..9a29183b 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -274,7 +274,17 @@ def dilate_labels_2d(imp, dilation_radius): current_imp = Duplicator().run(imp, 1, 1, i, imp.getNSlices(), 1, 1) # Perform a dilation of the labels in the current slice - dilated_labels_imp = li.dilateLabels(current_imp, dilation_radius) + IJ.run( + current_imp, + "Label Morphological Filters", + "operation=Dilation radius=" + str(dilation_radius) + " from_any_label", + ) + + # Get the dilated labels + dilated_labels_imp = IJ.getImage() + + # Hide the dilated labels to avoid visual clutter + dilated_labels_imp.hide() # Append the dilated labels to the list dilated_labels_list.append(dilated_labels_imp) From dc3382e74237521628f438eeea684626226f2992 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 09:22:04 +0100 Subject: [PATCH 22/38] Revert "Fix the filtering of objects to use the calibrated method" This reverts commit 5228f485bc968852742af9ea3dcc5513a12b5564. --- src/imcflibs/imagej/labelimage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 9a29183b..6391fe60 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -227,11 +227,11 @@ def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): # Set the minimum size for labeling if provided if min_vol: - labeler.setMinSizeCalibrated(min_vol) + labeler.setMinSize(min_vol) # Set the maximum size for labeling if provided if max_vol: - labeler.setMinSizeCalibrated(max_vol) + labeler.setMaxSize(max_vol) # Get the labeled image seg = labeler.getLabels(img) From 93dadc94bb6ef4ae03da592464d7cf91ca50f867 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 09:22:12 +0100 Subject: [PATCH 23/38] Revert "Add method for associating label images using 3DImageJSuite" This reverts commit b06f29fd7f742ebb4456bcd1daaf34f0f178d4a6. --- src/imcflibs/imagej/labelimage.py | 45 ------------------------------- 1 file changed, 45 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 6391fe60..5d800b48 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -97,51 +97,6 @@ def relate_label_images(label_image_ref, label_image_to_relate): return ImageCalculator.run(label_image_ref, imp_dup, "Multiply create") -def associate_label_images_3d(outer_label_imp, inner_label_imp): - """ - Associate two label images. - - Uses the 3D Association plugin from the 3DImageJSuite. - - Parameters - ---------- - outer_label_imp : ij.ImagePlus - The outer label image - inner_label_imp : ij.ImagePlus - The inner label image - - Returns - ------- - related_inner_imp : ij.ImagePlus - The related inner label image - """ - - outer_label_imp.show() - inner_label_imp.show() - - outer_title = outer_label_imp.getTitle() - inner_title = inner_label_imp.getTitle() - - IJ.run( - "3D Association", - "image_a=" - + outer_title - + " " - + "image_b=" - + inner_title - + " " - + "method=Colocalisation min=1 max=0.000", - ) - - related_inner_imp = IJ.getImage() - - outer_label_imp.hide() - inner_label_imp.hide() - related_inner_imp.hide() - - return related_inner_imp - - def filter_objects(label_image, table, string, min_val, max_val): """Filter labels based on specific min and max values. From 201a6c751ecf9f591758de9c0074ca1cd919ca1e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 12:16:39 +0100 Subject: [PATCH 24/38] Add missing check function --- src/imcflibs/imagej/bdv.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 52a1364d..b30d5a5b 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -544,6 +544,34 @@ def check_definition_option(self, value): "multi_multi": MULTI_MULTI_FILE, } + def check_definition_option_ang_ill(self, value): + """Check if the value is a valid definition option. + + This is needed for angles and illuminations because support is not + available for multiple angles and illuminations in a single file. + + Parameters + ---------- + value : str + Entered value by the user. + + Returns + ------- + dict(str, str): dictionary containing the correct string definition. + """ + if value not in [ + "single", + "multi_multi", + ]: + raise ValueError( + "Value must be one of single, multi_multi. Support for multi_single is not available for angles and illuminations" + ) + + return { + "single": SINGLE_FILE, + "multi_multi": MULTI_MULTI_FILE, + } + def set_angle_definition(self, value): """Set the value for the angle definition From af3616aa1828f38c401e678de26ed348c785bc42 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 12:17:04 +0100 Subject: [PATCH 25/38] Fix docstring to reflect real string --- src/imcflibs/imagej/bdv.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b30d5a5b..d9d5dc1d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -507,7 +507,7 @@ class DefinitionOptions(object): >>> opts.fmt_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 (all tiles in one file)] ... multiple_timepoints=[NO (one time-point)] """ @@ -638,7 +638,7 @@ def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. Build a string providing the `multiple_angles`, `multiple_channels`, - `multiple_illuminations`, `multiple_tiles` and `multiple_timepoints` options + `multiple_illuminations_directions`, `multiple_tiles` and `multiple_timepoints` options that can be used in a BDV-related `IJ.run` call. Returns From ab8e5aa0c4391f0d0adc73f1dc5ce1f6ddcc856d Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 12:17:20 +0100 Subject: [PATCH 26/38] Formatting --- src/imcflibs/imagej/bdv.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index d9d5dc1d..1493de22 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -13,7 +13,9 @@ import shutil import sys -from ch.epfl.biop.scijava.command.spimdata import FuseBigStitcherDatasetIntoOMETiffCommand +from ch.epfl.biop.scijava.command.spimdata import ( + FuseBigStitcherDatasetIntoOMETiffCommand, +) from ij import IJ from .. import pathtools From 5cc36d3a5c6076ec1fbfc620e758b85d491dc36a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 12:17:51 +0100 Subject: [PATCH 27/38] Fix string --- tests/bdv/test_definitionoptions.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/bdv/test_definitionoptions.py b/tests/bdv/test_definitionoptions.py index d3674f65..888fd94b 100644 --- a/tests/bdv/test_definitionoptions.py +++ b/tests/bdv/test_definitionoptions.py @@ -2,12 +2,13 @@ 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)] " ) @@ -32,7 +33,7 @@ def test__multiple_timepoints_files(): 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)] " ) @@ -42,13 +43,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)] " ) @@ -59,6 +61,7 @@ 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""" @@ -66,7 +69,7 @@ def test_single_tile_multiple_angles_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)] " ) From 678596e8c6ff92b768345f178dc08016c111170c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 12:18:10 +0100 Subject: [PATCH 28/38] Fix string --- tests/bdv/test_define_dataset_auto.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index cf7be2bd..7e4424d0 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -32,7 +32,9 @@ def set_default_values(project_filename, file_path, series_type = "Tiles"): + file_info["path"] + "] " + "exclude=10 " - + "bioformats_series_are?=[" + series_type + "] " + + "bioformats_series_are?=" + + series_type + + " " + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " ) From 09c790c374e5f4f7108f14e9a1be172c51e65a2e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 12:18:23 +0100 Subject: [PATCH 29/38] Use same input for test to work --- tests/bdv/test_define_dataset_auto.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index 7e4424d0..ef384e09 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -101,7 +101,7 @@ 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] @@ -167,6 +167,6 @@ 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] From c03f792ffbc7ffc33e04ce352af63dc9ad2bc693 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 12:18:41 +0100 Subject: [PATCH 30/38] Formatting --- tests/bdv/test_define_dataset_auto.py | 2 +- tests/bdv/test_definitionoptions.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index ef384e09..240f2b87 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -4,7 +4,7 @@ from imcflibs.imagej import bdv -def set_default_values(project_filename, file_path, series_type = "Tiles"): +def set_default_values(project_filename, file_path, series_type="Tiles"): """Set the default values for dataset definitions. Parameters diff --git a/tests/bdv/test_definitionoptions.py b/tests/bdv/test_definitionoptions.py index 888fd94b..3363a4cd 100644 --- a/tests/bdv/test_definitionoptions.py +++ b/tests/bdv/test_definitionoptions.py @@ -1,5 +1,4 @@ import pytest - from imcflibs.imagej.bdv import DefinitionOptions @@ -17,6 +16,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.""" @@ -25,7 +25,10 @@ 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 or multi_single" + ) + def test__multiple_timepoints_files(): """Test an example setting how to treat multiple time-points.""" From 8f67cb02865dce17ad44bee1fd549b2df22be806 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 12 Mar 2025 12:22:54 +0100 Subject: [PATCH 31/38] Fix input for angle to only support input options --- src/imcflibs/imagej/bdv.py | 2 +- tests/bdv/test_definitionoptions.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 1493de22..2471b59f 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -580,7 +580,7 @@ def set_angle_definition(self, value): Parameters ---------- value : str - One of `single`, `multi_single` or `multi_multi`. + One of `single` or `multi_multi`. """ choices = self.check_definition_option_ang_ill(value) self._angle_definition = choices[value] % "angle" diff --git a/tests/bdv/test_definitionoptions.py b/tests/bdv/test_definitionoptions.py index 3363a4cd..ec659910 100644 --- a/tests/bdv/test_definitionoptions.py +++ b/tests/bdv/test_definitionoptions.py @@ -26,7 +26,8 @@ def test__definition_option(): 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" + str(excinfo.value) + == "Value must be one of single, multi_multi. Support for multi_single is not available for angles and illuminations" ) From 2e9ad4a5a75dbf5d12e64848db7a5a66726cb817 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 15:02:15 +0100 Subject: [PATCH 32/38] Fix linting --- src/imcflibs/imagej/bdv.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 2471b59f..f8ff55c1 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -202,7 +202,7 @@ def process_angle(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : angle_select(). """ @@ -228,7 +228,7 @@ def process_channel(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : channel_select(). """ @@ -254,7 +254,7 @@ def process_illumination(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : illumination_select(). """ @@ -280,7 +280,7 @@ def process_tile(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : tile_select(). """ @@ -306,7 +306,7 @@ def process_timepoint(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : timepoint_select(). """ From 0d5581daae716d898d92970005bfefd4304f8af0 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 15:02:27 +0100 Subject: [PATCH 33/38] Formatting --- src/imcflibs/imagej/bdv.py | 164 +++++++++++++++++++++++++++---------- 1 file changed, 123 insertions(+), 41 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f8ff55c1..4fffe879 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -133,7 +133,9 @@ def reference_channel(self, value): """ # channel = int(value) - 1 # will raise a ValueError if cast fails self._use_channel = "channels=[use Channel %s]" % int(value) - log.debug("New reference channel setting: %s", self._use_channel) + log.debug( + "New reference channel setting: %s", self._use_channel + ) def reference_illumination(self, value): """Set the reference illumination when using *Expert Grouping Options*. @@ -149,8 +151,13 @@ def reference_illumination(self, value): value : int or int-like The illumination number to use for the grouping. """ - self._use_illumination = "illuminations=[use Illumination %s]" % value - log.debug("New reference illumination setting: %s", self._use_illumination) + self._use_illumination = ( + "illuminations=[use Illumination %s]" % value + ) + log.debug( + "New reference illumination setting: %s", + self._use_illumination, + ) def reference_tile(self, value): """Set the reference tile when using *Expert Grouping Options*. @@ -184,7 +191,9 @@ def reference_timepoint(self, value): The timepoint number to use for the grouping. """ self._use_timepoint = "timepoints=[use Timepoint %s]" % value - log.debug("New reference timepoint setting: %s", self._use_timepoint) + log.debug( + "New reference timepoint setting: %s", self._use_timepoint + ) ### process-X methods @@ -406,16 +415,22 @@ def fmt_acitt_options(self, input="process"): """ input_type = ["process", "resave"] if input not in input_type: - raise ValueError("Invalue input type. Expected one of: %s" % input_type) + raise ValueError( + "Invalue input type. Expected one of: %s" % input_type + ) parameters = [ input + "_angle=" + self._angle_processing_option, input + "_channel=" + self._channel_processing_option, - input + "_illumination=" + self._illumination_processing_option, + input + + "_illumination=" + + self._illumination_processing_option, input + "_tile=" + self._tile_processing_option, input + "_timepoint=" + self._timepoint_processing_option, ] parameter_string = " ".join(parameters).strip() - log.debug("Formatted 'process_X' options: <%s>", parameter_string) + log.debug( + "Formatted 'process_X' options: <%s>", parameter_string + ) return parameter_string + " " def fmt_acitt_selectors(self): @@ -435,12 +450,16 @@ def fmt_acitt_selectors(self): parameters = [ self._angle_select if self._angle_select else "", self._channel_select if self._channel_select else "", - self._illumination_select if self._illumination_select else "", + self._illumination_select + if self._illumination_select + else "", self._tile_select if self._tile_select else "", self._timepoint_select if self._timepoint_select else "", ] parameter_string = " ".join(parameters).strip() - log.debug("Formatted 'processing_X' selectors: <%s>", parameter_string) + log.debug( + "Formatted 'processing_X' selectors: <%s>", parameter_string + ) return parameter_string + " " def fmt_how_to_treat(self): @@ -458,7 +477,9 @@ def fmt_how_to_treat(self): "how_to_treat_timepoints=" + self._treat_timepoints, ] parameter_string = " ".join(parameters).strip() - log.debug("Formatted 'how_to_treat_X' options: <%s>", parameter_string) + log.debug( + "Formatted 'how_to_treat_X' options: <%s>", parameter_string + ) return parameter_string + " " def fmt_use_acitt(self): @@ -473,13 +494,22 @@ def fmt_use_acitt(self): """ parameters = [ self._use_angle if self._treat_angles == "group" else "", - self._use_channel if self._treat_channels == "group" else "", - self._use_illumination if self._treat_illuminations == "group" else "", + self._use_channel + if self._treat_channels == "group" + else "", + self._use_illumination + if self._treat_illuminations == "group" + else "", self._use_tile if self._treat_tiles == "group" else "", - self._use_timepoint if self._treat_timepoints == "group" else "", + self._use_timepoint + if self._treat_timepoints == "group" + else "", ] parameter_string = " ".join(parameters).strip() - log.debug("Formatted expert grouping 'use' options: <%s>", parameter_string) + log.debug( + "Formatted expert grouping 'use' options: <%s>", + parameter_string, + ) return parameter_string + " " @@ -517,7 +547,9 @@ class DefinitionOptions(object): def __init__(self): self._angle_definition = SINGLE_FILE % "angle" self._channel_definition = MULTI_SINGLE_FILE % "channel" - self._illumination_definition = SINGLE_FILE % "illumination direction" + self._illumination_definition = ( + SINGLE_FILE % "illumination direction" + ) self._tile_definition = MULTI_MULTI_FILE % "tile" self._timepoint_definition = SINGLE_FILE % "time-point" @@ -538,7 +570,9 @@ def check_definition_option(self, value): "multi_single", "multi_multi", ]: - raise ValueError("Value must be one of single, multi_multi or multi_single") + raise ValueError( + "Value must be one of single, multi_multi or multi_single" + ) return { "single": SINGLE_FILE, @@ -584,7 +618,9 @@ def set_angle_definition(self, value): """ choices = self.check_definition_option_ang_ill(value) self._angle_definition = choices[value] % "angle" - log.debug("New 'angle_definition' setting: %s", self._angle_definition) + log.debug( + "New 'angle_definition' setting: %s", self._angle_definition + ) def set_channel_definition(self, value): """Set the value for the channel definition @@ -596,7 +632,10 @@ def set_channel_definition(self, value): """ choices = self.check_definition_option(value) self._channel_definition = choices[value] % "channel" - log.debug("New 'channel_definition' setting: %s", self._channel_definition) + log.debug( + "New 'channel_definition' setting: %s", + self._channel_definition, + ) def set_illumination_definition(self, value): """Set the value for the illumination definition @@ -607,9 +646,12 @@ def set_illumination_definition(self, value): One of `single`, `multi_single` or `multi_multi`. """ choices = self.check_definition_option_ang_ill(value) - self._illumination_definition = choices[value] % "illumination direction" + self._illumination_definition = ( + choices[value] % "illumination direction" + ) log.debug( - "New 'illumination_definition' setting: %s", self._illumination_definition + "New 'illumination_definition' setting: %s", + self._illumination_definition, ) def set_tile_definition(self, value): @@ -622,7 +664,9 @@ def set_tile_definition(self, value): """ choices = self.check_definition_option(value) self._tile_definition = choices[value] % "tile" - log.debug("New 'tile_definition' setting: %s", self._tile_definition) + log.debug( + "New 'tile_definition' setting: %s", self._tile_definition + ) def set_timepoint_definition(self, value): """Set the value for the time_point_definition @@ -634,7 +678,10 @@ def set_timepoint_definition(self, value): """ choices = self.check_definition_option(value) self._timepoint_definition = choices[value] % "time-point" - log.debug("New 'timepoint_definition' setting: %s", self._timepoint_definition) + log.debug( + "New 'timepoint_definition' setting: %s", + self._timepoint_definition, + ) def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. @@ -650,12 +697,15 @@ def fmt_acitt_options(self): parameters = [ "multiple_angles=" + self._angle_definition, "multiple_channels=" + self._channel_definition, - "multiple_illuminations_directions=" + self._illumination_definition, + "multiple_illuminations_directions=" + + self._illumination_definition, "multiple_tiles=" + self._tile_definition, "multiple_timepoints=" + self._timepoint_definition, ] parameter_string = " ".join(parameters).strip() - log.debug("Formatted 'multiple_X' options: <%s>", parameter_string) + log.debug( + "Formatted 'multiple_X' options: <%s>", parameter_string + ) return parameter_string + " " @@ -679,10 +729,14 @@ def check_processing_input(value, range_end): value = [value] # Check if all the elements of the value list are of the same type if not all(isinstance(x, type(value[0])) for x in value): - raise TypeError("Invalid input type. All the values should be of the same type") + raise TypeError( + "Invalid input type. All the values should be of the same type" + ) if type(range_end) is int: if type(value[0]) is not int: - raise TypeError("Invalid input type. Expected an int for the range start") + raise TypeError( + "Invalid input type. Expected an int for the range start" + ) elif len(value) != 1: raise ValueError( "Invalid input type. Expected a single number for the range start" @@ -720,7 +774,13 @@ def get_processing_settings(dimension, selection, value, range_end): if selection == "single": processing_option = SINGLE % dimension - dimension_select = "processing_" + dimension + "=[" + dimension + " %s]" % value + dimension_select = ( + "processing_" + + dimension + + "=[" + + dimension + + " %s]" % value + ) if selection == "multiple": processing_option = MULTIPLE % dimension @@ -763,7 +823,9 @@ def backup_xml_files(source_directory, subfolder_name): pathtools.create_directory(xml_backup_directory) backup_subfolder = xml_backup_directory + "/%s" % (subfolder_name) pathtools.create_directory(backup_subfolder) - all_xml_files = pathtools.listdir_matching(source_directory, ".*\\.xml", regex=True) + all_xml_files = pathtools.listdir_matching( + source_directory, ".*\\.xml", regex=True + ) os.chdir(source_directory) for xml_file in all_xml_files: shutil.copy2(xml_file, backup_subfolder) @@ -823,7 +885,9 @@ def define_dataset_auto( dataset_save_path = result_folder if subsampling_factors: subsampling_factors = ( - "manual_mipmap_setup subsampling_factors=" + subsampling_factors + " " + "manual_mipmap_setup subsampling_factors=" + + subsampling_factors + + " " ) else: subsampling_factors = "" @@ -987,7 +1051,9 @@ def resave_as_h5( split_hdf5 = "" if subsampling_factors: - subsampling_factors = "subsampling_factors=" + subsampling_factors + " " + subsampling_factors = ( + "subsampling_factors=" + subsampling_factors + " " + ) else: subsampling_factors = " " if hdf5_chunk_sizes: @@ -1077,10 +1143,13 @@ def phase_correlation_pairwise_shifts_calculation( file_info = pathtools.parse_path(project_path) if downsampling_xyz != "": - downsampling = "downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s " % ( - downsampling_xyz[0], - downsampling_xyz[1], - downsampling_xyz[2], + downsampling = ( + "downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s " + % ( + downsampling_xyz[0], + downsampling_xyz[1], + downsampling_xyz[2], + ) ) else: downsampling = "" @@ -1104,7 +1173,9 @@ def phase_correlation_pairwise_shifts_calculation( log.debug("Calculate pairwise shifts options: <%s>", options) IJ.run("Calculate pairwise shifts ...", str(options)) - backup_xml_files(file_info["path"], "phase_correlation_shift_calculation") + backup_xml_files( + file_info["path"], "phase_correlation_shift_calculation" + ) def filter_pairwise_shifts( @@ -1216,7 +1287,9 @@ def optimize_and_apply_shifts( + processing_opts.fmt_how_to_treat() ) - log.debug("Optimization and shifts application options: <%s>", options) + log.debug( + "Optimization and shifts application options: <%s>", options + ) IJ.run("Optimize globally and apply shifts ...", str(options)) backup_xml_files(file_info["path"], "optimize_and_apply_shifts") @@ -1394,8 +1467,12 @@ def duplicate_transformations( target = "[All Channels]" source = str(channel_source - 1) if tile_source: - tile_apply = "apply_to_tile=[Single tile (Select from List)] " - tile_process = "processing_tile=[tile " + str(tile_source) + "] " + tile_apply = ( + "apply_to_tile=[Single tile (Select from List)] " + ) + tile_process = ( + "processing_tile=[tile " + str(tile_source) + "] " + ) else: tile_apply = "apply_to_tile=[All tiles] " elif transformation_type == "tile": @@ -1403,9 +1480,13 @@ def duplicate_transformations( target = "[All Tiles]" source = str(tile_source) if channel_source: - chnl_apply = "apply_to_channel=[Single channel (Select from List)] " + chnl_apply = ( + "apply_to_channel=[Single channel (Select from List)] " + ) chnl_process = ( - "processing_channel=[channel " + str(channel_source - 1) + "] " + "processing_channel=[channel " + + str(channel_source - 1) + + "] " ) else: chnl_apply = "apply_to_channel=[All channels] " @@ -1441,7 +1522,8 @@ def duplicate_transformations( IJ.run("Duplicate Transformations", str(options)) backup_xml_files( - file_info["path"], "duplicate_transformation_" + transformation_type + file_info["path"], + "duplicate_transformation_" + transformation_type, ) From f5b4f86d35e5c72ae8ac624d932e342727d06a05 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 15:09:01 +0100 Subject: [PATCH 34/38] Fix linting issues --- src/imcflibs/imagej/bdv.py | 52 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 4fffe879..efd25b76 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -210,7 +210,7 @@ def process_angle(self, value, range_end=None): range_end : int, optional Contains the end of the range, by default None. - Notes: + Notes ----- Previous function name : angle_select(). """ @@ -236,7 +236,7 @@ def process_channel(self, value, range_end=None): range_end : int, optional Contains the end of the range, by default None. - Notes: + Notes ----- Previous function name : channel_select(). """ @@ -262,7 +262,7 @@ def process_illumination(self, value, range_end=None): range_end : int, optional Contains the end of the range, by default None. - Notes: + Notes ----- Previous function name : illumination_select(). """ @@ -288,7 +288,7 @@ def process_tile(self, value, range_end=None): range_end : int, optional Contains the end of the range, by default None. - Notes: + Notes ----- Previous function name : tile_select(). """ @@ -314,7 +314,7 @@ def process_timepoint(self, value, range_end=None): range_end : int, optional Contains the end of the range, by default None. - Notes: + Notes ----- Previous function name : timepoint_select(). """ @@ -609,7 +609,7 @@ def check_definition_option_ang_ill(self, value): } def set_angle_definition(self, value): - """Set the value for the angle definition + """Set the value for the angle definition. Parameters ---------- @@ -623,7 +623,7 @@ def set_angle_definition(self, value): ) def set_channel_definition(self, value): - """Set the value for the channel definition + """Set the value for the channel definition. Parameters ---------- @@ -638,7 +638,7 @@ def set_channel_definition(self, value): ) def set_illumination_definition(self, value): - """Set the value for the illumination definition + """Set the value for the illumination definition. Parameters ---------- @@ -655,7 +655,7 @@ def set_illumination_definition(self, value): ) def set_tile_definition(self, value): - """Set the value for the tile_definition + """Set the value for the tile_definition. Parameters ---------- @@ -669,7 +669,7 @@ def set_tile_definition(self, value): ) def set_timepoint_definition(self, value): - """Set the value for the time_point_definition + """Set the value for the time_point_definition. Parameters ---------- @@ -720,6 +720,7 @@ def check_processing_input(value, range_end): Contains the list of input dimensions, the first input dimension of a range or a single channel range_end : int or None Contains the end of the range if need be + Returns ------- str @@ -841,8 +842,8 @@ def define_dataset_auto( subsampling_factors=None, hdf5_chunk_sizes=None, ): - """Will run the corresponding "Define Dataset" using the "Auto-Loader" - option. + """Define a dataset using the Autoloader or Multi-View loader. + If the series is tiles, will run "Define Dataset...", otherwise will run "Define Multi-View Dataset...". @@ -1022,8 +1023,10 @@ def resave_as_h5( XML input file. output_h5_file_path : str Export path for the output file including the `.xml `extension. - timepoints : str, optional - The timepoints that should be exported, by default `All Timepoints`. + processing_opts : imcflibs.imagej.bdv.ProcessingOptions, optional + The `ProcessingOptions` object defining parameters for the run. Will + fall back to the defaults defined in the corresponding class if the + parameter is `None` or skipped. timepoints_per_partition : int, optional How many timepoints to export per partition, by default `1`. use_deflate_compression : bool, optional @@ -1308,10 +1311,10 @@ def detect_interest_points( ---------- project_path : str Path to the `.xml` project. - process_timepoint : str, optional - Timepoint to be processed, by default `All Timepoints`. - process_channel : str, optional - Channel to be processed, by default `All channels`. + processing_opts : imcflibs.imagej.bdv.ProcessingOptions, optional + The `ProcessingOptions` object defining parameters for the run. Will + fall back to the defaults defined in the corresponding class if the + parameter is `None` or skipped. sigma : float, optional Minimum sigma for interest points detection, by default `1.8`. threshold : float, optional @@ -1366,14 +1369,11 @@ def interest_points_registration( ---------- project_path : str Path to the `.xml` project. - process_timepoint : str, optional - Timepoint to be processed, by default `All Timepoints`. - process_channel : str, optional - Channels to be used for performing the registration. By default, all - channels are taken into account, however this behavior could be - undesirable if only one channel is adequate (e.g. beads or other useful - fiducials). To restrict registration to a specific channel, provide the - channel name using this parameter. By default `All channels`. + processing_opts : imcflibs.imagej.bdv.ProcessingOptions, optional + The `ProcessingOptions` object defining parameters for the run. Will + fall back to the defaults defined in the corresponding class if the + parameter is `None` or skipped. This controls which angles, channels, + illuminations, tiles and timepoints are processed. rigid_timepoints : bool, optional If set to `True` each timepoint will be considered as a rigid unit (useful e.g. if spatial registration has already been performed before). From c8fcff33d1d1129a9402ef2e7f246f41829c9c9c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:25:31 +0100 Subject: [PATCH 35/38] Add missing docstring --- src/imcflibs/imagej/bdv.py | 4 ++++ tests/bdv/test_define_dataset_auto.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index efd25b76..e5a67498 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1564,6 +1564,10 @@ def fuse_dataset( Pixel type to use during fusion, by default `[16-bit unsigned integer]`. export : str, optional Format of the output fused image, by default `HDF5`. + fusion_type : str, optional + Type of fusion algorithm to use, by default `Avg, Blending`. + compression : str, optional + Compression method to use when exporting as HDF5, by default `Zstandard`. """ if processing_opts is None: diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index 240f2b87..6fbd1799 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -13,6 +13,8 @@ def set_default_values(project_filename, file_path, series_type="Tiles"): 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 ---------- From 41b301b5d6d725814ae9214908f29fa9fb75bb1f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:26:08 +0100 Subject: [PATCH 36/38] Format using Ruff --- tests/bdv/test_define_dataset_auto.py | 24 +++++++++++++++--------- tests/bdv/test_definitionoptions.py | 1 + tests/bdv/test_processingoptions.py | 10 ++++++++-- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index 6fbd1799..fed52378 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -4,7 +4,9 @@ from imcflibs.imagej import bdv -def set_default_values(project_filename, file_path, series_type="Tiles"): +def set_default_values( + project_filename, file_path, series_type="Tiles" +): """Set the default values for dataset definitions. Parameters @@ -17,7 +19,7 @@ def set_default_values(project_filename, file_path, series_type="Tiles"): Type of Bioformats series (default is "Tiles") Returns - ---------- + ------- str Start of the options for dataset definitions. """ @@ -44,8 +46,7 @@ def set_default_values(project_filename, file_path, series_type="Tiles"): 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 ---------- @@ -103,14 +104,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_info["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 ---------- @@ -142,7 +144,9 @@ 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, bf_series_type) + options = set_default_values( + project_filename, file_path, bf_series_type + ) # Construct the options for dataset definitions options = ( @@ -169,6 +173,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_info["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] diff --git a/tests/bdv/test_definitionoptions.py b/tests/bdv/test_definitionoptions.py index ec659910..1caa1c79 100644 --- a/tests/bdv/test_definitionoptions.py +++ b/tests/bdv/test_definitionoptions.py @@ -1,4 +1,5 @@ import pytest + from imcflibs.imagej.bdv import DefinitionOptions diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index 57593241..08d9599b 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -18,7 +18,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() @@ -47,7 +50,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") From 3876612f1257dd3a9d496729bbb8e25213581249 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:27:31 +0100 Subject: [PATCH 37/38] Shorten docstring explanation --- tests/bdv/test_definitionoptions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/bdv/test_definitionoptions.py b/tests/bdv/test_definitionoptions.py index 1caa1c79..50c6ca36 100644 --- a/tests/bdv/test_definitionoptions.py +++ b/tests/bdv/test_definitionoptions.py @@ -68,8 +68,7 @@ def test__multiple_channels_files_multiple_timepoints(): 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)] " From 72ebf118d5b1c2ac9a8671483eb03b1b37f4d93d Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:27:55 +0100 Subject: [PATCH 38/38] Fix D100 error for undocumented public module --- tests/bdv/test_define_dataset_auto.py | 2 ++ tests/bdv/test_definitionoptions.py | 2 ++ tests/bdv/test_processingoptions.py | 2 ++ tests/bdv/test_processingoptions_example3.py | 1 + tests/bdv/test_processingoptions_example4.py | 2 ++ 5 files changed, 9 insertions(+) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index fed52378..08c53882 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -1,3 +1,5 @@ +"""Tests for the automatic dataset definition functionality in the BDV module.""" + import logging from imcflibs import pathtools diff --git a/tests/bdv/test_definitionoptions.py b/tests/bdv/test_definitionoptions.py index 50c6ca36..c8875d2d 100644 --- a/tests/bdv/test_definitionoptions.py +++ b/tests/bdv/test_definitionoptions.py @@ -1,3 +1,5 @@ +"""Tests for the imcflibs.imagej.bdv.DefinitionOptions class.""" + import pytest from imcflibs.imagej.bdv import DefinitionOptions diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index 08d9599b..218d3f31 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -1,3 +1,5 @@ +"""Tests for the ProcessingOptions class from the imcflibs.imagej.bdv module.""" + from imcflibs.imagej.bdv import ProcessingOptions diff --git a/tests/bdv/test_processingoptions_example3.py b/tests/bdv/test_processingoptions_example3.py index 124572b8..7aec8699 100644 --- a/tests/bdv/test_processingoptions_example3.py +++ b/tests/bdv/test_processingoptions_example3.py @@ -1,3 +1,4 @@ +"""Tests for ProcessingOptions class with multiple reference channels configuration.""" from imcflibs.imagej.bdv import ProcessingOptions diff --git a/tests/bdv/test_processingoptions_example4.py b/tests/bdv/test_processingoptions_example4.py index 0eab6db8..4331384f 100644 --- a/tests/bdv/test_processingoptions_example4.py +++ b/tests/bdv/test_processingoptions_example4.py @@ -1,3 +1,5 @@ +"""Tests for the ProcessingOptions class handling channel specific selection.""" + from imcflibs.imagej.bdv import ProcessingOptions