From 66b9a69c3c0a211f1ef98aadabd9e68c383511ba Mon Sep 17 00:00:00 2001 From: Sebastien Herbert Date: Mon, 6 Feb 2023 13:55:15 +0100 Subject: [PATCH 001/678] create a BDV helper module --- src/imcflibs/imagej/BDV.py | 190 +++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100755 src/imcflibs/imagej/BDV.py diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py new file mode 100755 index 00000000..10148ee8 --- /dev/null +++ b/src/imcflibs/imagej/BDV.py @@ -0,0 +1,190 @@ +"""BigDataViewer related functions, mostly convenience wrappers. with simplified calls""" + +from ij import IJ + +def run_defineMVD(project_filename, + cziPath, + dataset_save_path, + method="Automatic Loader (Bioformats based)", + timepoints_per_partition=1 + ): + """ + Run the Define Multi-View Dataset command + + Parameters + ---------- + project_filename : str + Name of the project (finishes with .xml) + cziPath : str + path to the first czi + dataset_save_path : str + output path for the .xml + method : str, optional + Image loader method, by default "Automatic Loader (Bioformats based)" + timepoints_per_partition : int, optional + split the output by timepoints. Use 0 for no split, by default 1 + """ + IJ.run( + "Define Multi-View Dataset", + "define_dataset=["+method+"]" + + "project_filename=["+project_filename+"] " + + "path=["+cziPath+"] " + + "exclude=10 bioformats_series_are?=Angles " + + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " + + "how_to_load_images=[Re-save as multiresolution HDF5] " + + "dataset_save_path=["+dataset_save_path+"] " + + "check_stack_sizes apply_angle_rotation " + + "subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }] " + + "hdf5_chunk_sizes=[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }] " + + "timepoints_per_partition="+str(timepoints_per_partition)+" " + + "setups_per_partition=0 " + + "use_deflate_compression " + + "export_path=["+dataset_save_path+"]" + ) + return + +def run_detectInterestPoints(project_path, + process_timepoint="All Timepoints", + sigma=1.8, + threshold=0.008, + maximum_number=3000 + ): + """ + run the detect interest points command for registration + + Parameters + ---------- + project_path : str + Path to the .xml project + process_timepoint : str, optional + Specify which timepoint should be processed, by default "All Timepoints" + sigma : float, optional + Minimum sigma for interest points detection, by default 1.8 + threshold : float, optional + Threshold value for the interest point detection, by default 0.008 + maximum_number : int, optional + maximum_number of interest points to use, by default 3000 + """ + + IJ.run( + "Detect Interest Points for Registration", + "select=["+project_path+"] " + + "process_angle=[All angles] " + + "process_channel=[All channels] " + + "process_illumination=[All illuminations] " + + "process_tile=[All tiles] " + + "process_timepoint=["+process_timepoint+"] " + + "type_of_interest_point_detection=Difference-of-Gaussian " + + "label_interest_points=beads " + + "limit_amount_of_detections " + + "group_tiles group_illuminations " + + "subpixel_localization=[3-dimensional quadratic fit] " + + "interest_point_specification=[Advanced ...] " + + "downsample_xy=[Match Z Resolution (less downsampling)] " + + "downsample_z=1x " + + "sigma="+str(sigma)+" " + + "threshold="+str(threshold)+" " + + "find_maxima " + + "maximum_number="+str(3000)+" " + + "type_of_detections_to_use=Brightest " + + "compute_on=[CPU (Java)]" + ) + return + +def run_registration(project_path, + process_timepoint="All Timepoints", + ): + """ + run the spatial registration command + + Parameters + ---------- + project_path : str + Path to the .xml project + process_timepoint : str, optional + Specify which timepoint should be processed, by default "All Timepoints" + """ + + # register using interest points + IJ.run( + "Register Dataset based on Interest Points", + "select=[" + project_path + "] " + + "process_angle=[All angles] " + + "process_channel=[All channels] " + + "process_illumination=[All illuminations] " + + "process_tile=[All tiles] " + + "process_timepoint=["+process_timepoint+"] " + + "registration_algorithm=[Precise descriptor-based (translation invariant)] " + + "registration_in_between_views=[Compare all views against each other] " + + "interest_points=beads " + + "group_tiles " + + "group_illuminations " + + "group_channels " + + "fix_views=[Fix first view] " + + "map_back_views=[Do not map back (use this if views are fixed)] " + + "transformation=Affine " + + "regularize_model " + + "model_to_regularize_with=Rigid " + + "lamba=0.10 " + + "number_of_neighbors=3 " + + "redundancy=3 " + + "significance=2 " + + "allowed_error_for_ransac=5 " + + "ransac_iterations=Normal " + + "interestpoint_grouping=[Group interest points (simply combine all in one virtual view)] " + + "interest=5" + ) + return + +def run_fusion(temp_path, + fused_xml_path, + process_timepoint="All Timepoints", + downsampling=1, + ram_handling="Virtual", + save_format="Save as new XML Project (HDF5)", + additional_option="" + ): + """ + run the image fusion command + + Parameters + ---------- + temp_path : str + temporary folder output (scratch) + fused_xml_path : str + final xml output path + process_timepoint : str, optional + Specify which timepoint should be processed, by default "All Timepoints" + downsampling : int, optional + Downsampling factor to use during the fusion, by default 1 (no downsampling) + ram_handling : str, optional + Which type of ram_handling do you require, by default "Virtual". This is the conservative (not likely to fail even if only a low amount of RAM is available) and slow approach + save_format : str, optional + file format of the new image, by default "Save as new XML Project (HDF5)", this is matching the conservative and slow approach + additional_option : str, optional="" + Any additional options that should be added to the fusion command. Do not forget to finish each additional option with a space + """ + + IJ.run( + "Fuse dataset ...", + "select=["+temp_path+"] " + + "process_angle=[All angles] " + + "process_channel=[All channels] " + + "process_illumination=["+process_timepoint+"] " + + "process_tile=[All tiles] " + + "process_timepoint=[All Timepoints] " + + "bounding_box=[Currently Selected Views] " + + "downsampling="+str(downsampling)+ " " + + "pixel_type=[16-bit unsigned integer] " + + "interpolation=[Linear Interpolation] " + + "image=["+ram_handling+"] " + + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " + + "blend " + + additional_option + + "produce=[Each timepoint & channel] " + + "fused_image=["+save_format+"] " + + "export_path=[" + + fused_xml_path + + "]", + ) + return From 9c8d7267f464ace4251f7a5a9615831ea9df2154 Mon Sep 17 00:00:00 2001 From: Sebastien Herbert Date: Thu, 9 Feb 2023 17:57:16 +0100 Subject: [PATCH 002/678] Add new optional arg to defineMVD --- src/imcflibs/imagej/BDV.py | 143 +++++++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 47 deletions(-) mode change 100755 => 100644 src/imcflibs/imagej/BDV.py diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py old mode 100755 new mode 100644 index 10148ee8..7f9ad826 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -2,12 +2,15 @@ from ij import IJ -def run_defineMVD(project_filename, - cziPath, - dataset_save_path, - method="Automatic Loader (Bioformats based)", - timepoints_per_partition=1 - ): + +def run_defineMVD( + project_filename, + cziPath, + dataset_save_path, + method="Automatic Loader (Bioformats based)", + timepoints_per_partition=1, + loadMethod="Re-save as multiresolution HDF5", +): """ Run the Define Multi-View Dataset command @@ -23,32 +26,50 @@ def run_defineMVD(project_filename, Image loader method, by default "Automatic Loader (Bioformats based)" timepoints_per_partition : int, optional split the output by timepoints. Use 0 for no split, by default 1 + loadMethod : str, optional + Allows this function to either re-save the images or simply create a merged xml. Use "Load raw data" to avoid re-saving, by default "Re-save as multiresolution HDF5" will resave the input data """ IJ.run( "Define Multi-View Dataset", - "define_dataset=["+method+"]" - + "project_filename=["+project_filename+"] " - + "path=["+cziPath+"] " + "define_dataset=[" + + method + + "]" + + "project_filename=[" + + project_filename + + "] " + + "path=[" + + cziPath + + "] " + "exclude=10 bioformats_series_are?=Angles " + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " - + "how_to_load_images=[Re-save as multiresolution HDF5] " - + "dataset_save_path=["+dataset_save_path+"] " + + "how_to_load_images=[" + + loadMethod + + "] " + + "dataset_save_path=[" + + dataset_save_path + + "] " + "check_stack_sizes apply_angle_rotation " + "subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }] " + "hdf5_chunk_sizes=[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }] " - + "timepoints_per_partition="+str(timepoints_per_partition)+" " + + "timepoints_per_partition=" + + str(timepoints_per_partition) + + " " + "setups_per_partition=0 " + "use_deflate_compression " - + "export_path=["+dataset_save_path+"]" - ) + + "export_path=[" + + dataset_save_path + + "]", + ) return -def run_detectInterestPoints(project_path, - process_timepoint="All Timepoints", - sigma=1.8, - threshold=0.008, - maximum_number=3000 - ): + +def run_detectInterestPoints( + project_path, + process_timepoint="All Timepoints", + sigma=1.8, + threshold=0.008, + maximum_number=3000, +): """ run the detect interest points command for registration @@ -65,15 +86,19 @@ def run_detectInterestPoints(project_path, maximum_number : int, optional maximum_number of interest points to use, by default 3000 """ - + IJ.run( "Detect Interest Points for Registration", - "select=["+project_path+"] " + "select=[" + + project_path + + "] " + "process_angle=[All angles] " + "process_channel=[All channels] " + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " - + "process_timepoint=["+process_timepoint+"] " + + "process_timepoint=[" + + process_timepoint + + "] " + "type_of_interest_point_detection=Difference-of-Gaussian " + "label_interest_points=beads " + "limit_amount_of_detections " @@ -82,18 +107,26 @@ def run_detectInterestPoints(project_path, + "interest_point_specification=[Advanced ...] " + "downsample_xy=[Match Z Resolution (less downsampling)] " + "downsample_z=1x " - + "sigma="+str(sigma)+" " - + "threshold="+str(threshold)+" " + + "sigma=" + + str(sigma) + + " " + + "threshold=" + + str(threshold) + + " " + "find_maxima " - + "maximum_number="+str(3000)+" " + + "maximum_number=" + + str(3000) + + " " + "type_of_detections_to_use=Brightest " - + "compute_on=[CPU (Java)]" - ) + + "compute_on=[CPU (Java)]", + ) return -def run_registration(project_path, - process_timepoint="All Timepoints", - ): + +def run_registration( + project_path, + process_timepoint="All Timepoints", +): """ run the spatial registration command @@ -108,12 +141,16 @@ def run_registration(project_path, # register using interest points IJ.run( "Register Dataset based on Interest Points", - "select=[" + project_path + "] " + "select=[" + + project_path + + "] " + "process_angle=[All angles] " + "process_channel=[All channels] " + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " - + "process_timepoint=["+process_timepoint+"] " + + "process_timepoint=[" + + process_timepoint + + "] " + "registration_algorithm=[Precise descriptor-based (translation invariant)] " + "registration_in_between_views=[Compare all views against each other] " + "interest_points=beads " @@ -132,18 +169,20 @@ def run_registration(project_path, + "allowed_error_for_ransac=5 " + "ransac_iterations=Normal " + "interestpoint_grouping=[Group interest points (simply combine all in one virtual view)] " - + "interest=5" + + "interest=5", ) return -def run_fusion(temp_path, - fused_xml_path, - process_timepoint="All Timepoints", - downsampling=1, - ram_handling="Virtual", - save_format="Save as new XML Project (HDF5)", - additional_option="" - ): + +def run_fusion( + temp_path, + fused_xml_path, + process_timepoint="All Timepoints", + downsampling=1, + ram_handling="Virtual", + save_format="Save as new XML Project (HDF5)", + additional_option="", +): """ run the image fusion command @@ -167,22 +206,32 @@ def run_fusion(temp_path, IJ.run( "Fuse dataset ...", - "select=["+temp_path+"] " + "select=[" + + temp_path + + "] " + "process_angle=[All angles] " + "process_channel=[All channels] " - + "process_illumination=["+process_timepoint+"] " + + "process_illumination=[" + + process_timepoint + + "] " + "process_tile=[All tiles] " + "process_timepoint=[All Timepoints] " + "bounding_box=[Currently Selected Views] " - + "downsampling="+str(downsampling)+ " " + + "downsampling=" + + str(downsampling) + + " " + "pixel_type=[16-bit unsigned integer] " + "interpolation=[Linear Interpolation] " - + "image=["+ram_handling+"] " + + "image=[" + + ram_handling + + "] " + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " + "blend " + additional_option + "produce=[Each timepoint & channel] " - + "fused_image=["+save_format+"] " + + "fused_image=[" + + save_format + + "] " + "export_path=[" + fused_xml_path + "]", From 3b6621cc74d82f6c0fc3e22a5b038fa6610846f6 Mon Sep 17 00:00:00 2001 From: Sebastien Herbert Date: Mon, 20 Feb 2023 15:38:04 +0100 Subject: [PATCH 003/678] Add rigid timepoints behavior to registration function --- src/imcflibs/imagej/BDV.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 7f9ad826..3380209f 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -124,11 +124,10 @@ def run_detectInterestPoints( def run_registration( - project_path, - process_timepoint="All Timepoints", + project_path, process_timepoint="All Timepoints", rigid_timepoints=False ): """ - run the spatial registration command + run the registration command Parameters ---------- @@ -136,8 +135,15 @@ def run_registration( Path to the .xml project process_timepoint : str, optional Specify which timepoint should be processed, by default "All Timepoints" + rigid_timepoints : bool, optional + If spatial registration has already been run, set this boolean to True to consider each timepoint as rigid unit, by default False """ + if rigid_timepoints: + rigid_timepointsArg = "consider_each_timepoint_as_rigid_unit " + else: + rigid_timepointsArg = "" + # register using interest points IJ.run( "Register Dataset based on Interest Points", @@ -157,6 +163,7 @@ def run_registration( + "group_tiles " + "group_illuminations " + "group_channels " + + rigid_timepointsArg + "fix_views=[Fix first view] " + "map_back_views=[Do not map back (use this if views are fixed)] " + "transformation=Affine " From 89f2737522a68857fc1b35ec807aaddc87df9933 Mon Sep 17 00:00:00 2001 From: Sebastien Herbert Date: Mon, 20 Feb 2023 15:38:22 +0100 Subject: [PATCH 004/678] Add resave function --- src/imcflibs/imagej/BDV.py | 71 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 3380209f..1edee396 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -63,6 +63,77 @@ def run_defineMVD( return +def run_resave( + sourceXMLFile, + outputH5FilePath, + timepoints="All Timepoints", + timepoints_per_partition=1, + use_deflate_compression=True, +): + """ + Resave the xml dataset in a new format. Only accepts all timepoints or single timepoints + + Parameters + ---------- + sourceXMLFile : File + XML input file + outputH5FilePath : str + Export path for the output file + timepoints : str, optional + Which timepoints should be exported, by default "All Timepoints" + timepoints_per_partition : int, optional + How many timepoints per partition should be exported, by default 1 + use_deflate_compression : bool, optional + Run deflate compression, by default True + """ + + # If all timepoints are nto to be saved at once + if timepoints == "All Timepoints": + timepoints = "resave_timepoint=[All Timepoints] " + else: + timepoints = ( + "resave_timepoint=[Single Timepoint (Select from List)] processing_timepoint=[Timepoint " + + str(timepoints) + + "] " + ) + + # If use_deflate_compression + if use_deflate_compression: + use_deflate_compressionArg = "use_deflate_compression " + else: + use_deflate_compressionArg = "" + + # If split_hdf5 option + if timepoints_per_partition != 0: + split_hdf5 = "split_hdf5 " + else: + split_hdf5 = "" + + IJ.run( + "As HDF5", + "select=" + + sourceXMLFile.getPath() + + " " + + "resave_angle=[All angles] " + + "resave_channel=[All channels] " + + "resave_illumination=[All illuminations] " + + "resave_tile=[All tiles] " + + timepoints + + "subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }] " + + "hdf5_chunk_sizes=[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }] " + + "timepoints_per_partition=" + + str(timepoints_per_partition) + + " " + + "setups_per_partition=0 " + + use_deflate_compressionArg + + split_hdf5 + + "export_path=" + + outputH5FilePath, + ) + + return + + def run_detectInterestPoints( project_path, process_timepoint="All Timepoints", From 64217617f8d1ed4e216a47e21461a1b6cf957649 Mon Sep 17 00:00:00 2001 From: Sebastien Herbert Date: Mon, 20 Feb 2023 17:55:42 +0100 Subject: [PATCH 005/678] Add possibility to register based on a single channel Registration using the descriptors is can be duplicated afterwards to the other channels using the run_duplicate function --- src/imcflibs/imagej/BDV.py | 104 +++++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 28 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 1edee396..8ecbc356 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -195,7 +195,10 @@ def run_detectInterestPoints( def run_registration( - project_path, process_timepoint="All Timepoints", rigid_timepoints=False + project_path, + process_timepoint="All Timepoints", + process_channel="All channels", + rigid_timepoints=False, ): """ run the registration command @@ -206,12 +209,26 @@ def run_registration( Path to the .xml project process_timepoint : str, optional Specify which timepoint should be processed, by default "All Timepoints" + process_channel : str, optional + Specify which channels should be processed. By default, all channels are processed together, however this behavior could be undesirable if only one channel is adequate (beads or nuclei). In that case provide the channel name instead. by default "All channels" rigid_timepoints : bool, optional If spatial registration has already been run, set this boolean to True to consider each timepoint as rigid unit, by default False """ + # If not process all channels at once, then adapt the option + if process_channel == "[All channels]": + process_channelArg = process_channel + else: + process_channelArg = ( + "[Single channel (Select from List)] processing_channel=[channel Cam2-T3] " + ) + # " + # + process_channel + # + "] " + # ) + if rigid_timepoints: - rigid_timepointsArg = "consider_each_timepoint_as_rigid_unit " + rigid_timepointsArg = " consider_each_timepoint_as_rigid_unit" else: rigid_timepointsArg = "" @@ -220,34 +237,65 @@ def run_registration( "Register Dataset based on Interest Points", "select=[" + project_path - + "] " - + "process_angle=[All angles] " - + "process_channel=[All channels] " - + "process_illumination=[All illuminations] " - + "process_tile=[All tiles] " - + "process_timepoint=[" + + "]" + + " process_angle=[All angles]" + + " process_channel=" + + process_channelArg + + " process_illumination=[All illuminations]" + + " process_tile=[All tiles]" + + " process_timepoint=[" + process_timepoint - + "] " - + "registration_algorithm=[Precise descriptor-based (translation invariant)] " - + "registration_in_between_views=[Compare all views against each other] " - + "interest_points=beads " - + "group_tiles " - + "group_illuminations " - + "group_channels " + + "]" + + " registration_algorithm=[Precise descriptor-based (translation invariant)]" + + " registration_in_between_views=[Compare all views against each other]" + + " interest_points=beads" + + " group_tiles" + + " group_illuminations" + + " group_channels" + rigid_timepointsArg - + "fix_views=[Fix first view] " - + "map_back_views=[Do not map back (use this if views are fixed)] " - + "transformation=Affine " - + "regularize_model " - + "model_to_regularize_with=Rigid " - + "lamba=0.10 " - + "number_of_neighbors=3 " - + "redundancy=3 " - + "significance=2 " - + "allowed_error_for_ransac=5 " - + "ransac_iterations=Normal " - + "interestpoint_grouping=[Group interest points (simply combine all in one virtual view)] " - + "interest=5", + + " fix_views=[Fix first view]" + + " map_back_views=[Do not map back (use this if views are fixed)]" + + " transformation=Affine" + + " regularize_model" + + " model_to_regularize_with=Rigid" + + " lamba=0.10" + + " number_of_neighbors=3" + + " redundancy=2" + + " significance=1" + + " allowed_error_for_ransac=5" + + " ransac_iterations=Normal" + + " interestpoint_grouping=[Group interest points (simply combine all in one virtual view)]" + + " interest=5", + ) + return + + +def run_duplicateReg(project_path, channel_source): + """ + If registration has been generated using a single channel, use this function to duplicate the registration parameters to the other channels + + Parameters + ---------- + project_path : str + Path to the .xml project + channel_source : str + Specify the channel name + """ + IJ.run( + "Duplicate Transformations", + "apply=[One channel to other channels] " + + "select=" + + project_path + + " " + + "apply_to_angle=[All angles] " + + "apply_to_illumination=[All illuminations] " + + "apply_to_tile=[All tiles] " + + "apply_to_timepoint=[All Timepoints] " + + "source=" + + channel_source + + " " + + "target=[All Channels] " + + "duplicate_which_transformations=[Replace all transformations]", ) return From 1cb5960b49904f793513195e9e1b409b2413eb16 Mon Sep 17 00:00:00 2001 From: Sebastien Herbert Date: Tue, 21 Feb 2023 09:29:04 +0100 Subject: [PATCH 006/678] Fix temporary bypass --- src/imcflibs/imagej/BDV.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 8ecbc356..6ba60922 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -220,12 +220,10 @@ def run_registration( process_channelArg = process_channel else: process_channelArg = ( - "[Single channel (Select from List)] processing_channel=[channel Cam2-T3] " + "[Single channel (Select from List)] processing_channel=[channel " + + process_channel + + "] " ) - # " - # + process_channel - # + "] " - # ) if rigid_timepoints: rigid_timepointsArg = " consider_each_timepoint_as_rigid_unit" From 8d9f4a5fb031dd7215ddcc5c2ab0222e5a9e2991 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 10:41:24 +0100 Subject: [PATCH 007/678] Docstring formatting --- src/imcflibs/imagej/BDV.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 6ba60922..93938bd6 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -11,8 +11,7 @@ def run_defineMVD( timepoints_per_partition=1, loadMethod="Re-save as multiresolution HDF5", ): - """ - Run the Define Multi-View Dataset command + """Run the Define Multi-View Dataset command. Parameters ---------- @@ -27,7 +26,9 @@ def run_defineMVD( timepoints_per_partition : int, optional split the output by timepoints. Use 0 for no split, by default 1 loadMethod : str, optional - Allows this function to either re-save the images or simply create a merged xml. Use "Load raw data" to avoid re-saving, by default "Re-save as multiresolution HDF5" will resave the input data + Allows this function to either re-save the images or simply create a merged xml. + Use "Load raw data" to avoid re-saving, by default "Re-save as multiresolution + HDF5" will resave the input data. """ IJ.run( "Define Multi-View Dataset", From bf89e711129106358151eacb8f0c41babc4f7a7f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:01:01 +0100 Subject: [PATCH 008/678] Allow subsampling and chunk size to be specified Fall back to the built-in defaults if not given. --- src/imcflibs/imagej/BDV.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 93938bd6..6e318158 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -10,6 +10,8 @@ def run_defineMVD( method="Automatic Loader (Bioformats based)", timepoints_per_partition=1, loadMethod="Re-save as multiresolution HDF5", + subsampling_factors=None, + hdf5_chunk_sizes=None, ): """Run the Define Multi-View Dataset command. @@ -29,7 +31,21 @@ def run_defineMVD( Allows this function to either re-save the images or simply create a merged xml. Use "Load raw data" to avoid re-saving, by default "Re-save as multiresolution HDF5" will resave the input data. + subsampling_factors: str, optional + Allow specifying subsampling factors explicitly, for example: + "[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }]" + hdf5_chunk_sizes: str, optional + Allow specifying hdf5_chunk_sizes factors explicitly, for example + "[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]" """ + if subsampling_factors: + subsampling_factors = "subsampling_factors=" + subsampling_factors + " " + else: + subsampling_factors = " " + if hdf5_chunk_sizes: + hdf5_chunk_sizes = "hdf5_chunk_sizes=" + hdf5_chunk_sizes + " " + else: + hdf5_chunk_sizes = " " IJ.run( "Define Multi-View Dataset", "define_dataset=[" @@ -50,8 +66,8 @@ def run_defineMVD( + dataset_save_path + "] " + "check_stack_sizes apply_angle_rotation " - + "subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }] " - + "hdf5_chunk_sizes=[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }] " + + subsampling_factors + + hdf5_chunk_sizes + "timepoints_per_partition=" + str(timepoints_per_partition) + " " From bdbaba55b58b7c8371940bdad0752703d049f9ca Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:02:37 +0100 Subject: [PATCH 009/678] Parameter naming conventions --- src/imcflibs/imagej/BDV.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 6e318158..33683802 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -5,7 +5,7 @@ def run_defineMVD( project_filename, - cziPath, + czi_path, dataset_save_path, method="Automatic Loader (Bioformats based)", timepoints_per_partition=1, @@ -19,7 +19,7 @@ def run_defineMVD( ---------- project_filename : str Name of the project (finishes with .xml) - cziPath : str + czi_path : str path to the first czi dataset_save_path : str output path for the .xml @@ -55,7 +55,7 @@ def run_defineMVD( + project_filename + "] " + "path=[" - + cziPath + + czi_path + "] " + "exclude=10 bioformats_series_are?=Angles " + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " From 6e21309033a2408b95db6bc95a482db63ed3d80f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:03:12 +0100 Subject: [PATCH 010/678] Hard-code loader method and rename function --- src/imcflibs/imagej/BDV.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 33683802..e78a7d3c 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -3,17 +3,16 @@ from ij import IJ -def run_defineMVD( +def run_define_dataset_autoloader( project_filename, czi_path, dataset_save_path, - method="Automatic Loader (Bioformats based)", timepoints_per_partition=1, loadMethod="Re-save as multiresolution HDF5", subsampling_factors=None, hdf5_chunk_sizes=None, ): - """Run the Define Multi-View Dataset command. + """Run the Define Multi-View Dataset command using the "Auto-Loader" option. Parameters ---------- @@ -23,8 +22,6 @@ def run_defineMVD( path to the first czi dataset_save_path : str output path for the .xml - method : str, optional - Image loader method, by default "Automatic Loader (Bioformats based)" timepoints_per_partition : int, optional split the output by timepoints. Use 0 for no split, by default 1 loadMethod : str, optional @@ -48,9 +45,7 @@ def run_defineMVD( hdf5_chunk_sizes = " " IJ.run( "Define Multi-View Dataset", - "define_dataset=[" - + method - + "]" + "define_dataset=[Automatic Loader (Bioformats based)] " + "project_filename=[" + project_filename + "] " From 9525de6af067f0331cf4e873a6a2cd3cdad0c935 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:03:50 +0100 Subject: [PATCH 011/678] Rename parameter --- src/imcflibs/imagej/BDV.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index e78a7d3c..1d47118e 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -8,7 +8,7 @@ def run_define_dataset_autoloader( czi_path, dataset_save_path, timepoints_per_partition=1, - loadMethod="Re-save as multiresolution HDF5", + resave="Re-save as multiresolution HDF5", subsampling_factors=None, hdf5_chunk_sizes=None, ): @@ -24,7 +24,7 @@ def run_define_dataset_autoloader( output path for the .xml timepoints_per_partition : int, optional split the output by timepoints. Use 0 for no split, by default 1 - loadMethod : str, optional + resave : str, optional Allows this function to either re-save the images or simply create a merged xml. Use "Load raw data" to avoid re-saving, by default "Re-save as multiresolution HDF5" will resave the input data. @@ -55,7 +55,7 @@ def run_define_dataset_autoloader( + "exclude=10 bioformats_series_are?=Angles " + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " + "how_to_load_images=[" - + loadMethod + + resave + "] " + "dataset_save_path=[" + dataset_save_path From 3f2f893cdac5afadbdd7ecf9bc4f736c31ef28be Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:04:23 +0100 Subject: [PATCH 012/678] Add mandatory parameter 'bf_series_type' --- src/imcflibs/imagej/BDV.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 1d47118e..0fc82018 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -7,6 +7,7 @@ def run_define_dataset_autoloader( project_filename, czi_path, dataset_save_path, + bf_series_type, timepoints_per_partition=1, resave="Re-save as multiresolution HDF5", subsampling_factors=None, @@ -22,6 +23,8 @@ def run_define_dataset_autoloader( path to the first czi dataset_save_path : str output path for the .xml + bf_series_type : str + One of "Angles" or "Tiles", specifying how Bio-Formats interprets the series. timepoints_per_partition : int, optional split the output by timepoints. Use 0 for no split, by default 1 resave : str, optional @@ -52,7 +55,9 @@ def run_define_dataset_autoloader( + "path=[" + czi_path + "] " - + "exclude=10 bioformats_series_are?=Angles " + + "exclude=10 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=[" + resave From 7b884caa41b67089f651d38548c2bfe05a70836b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:14:38 +0100 Subject: [PATCH 013/678] Split IJ.run arguments to be one per line --- src/imcflibs/imagej/BDV.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 0fc82018..679c9841 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -55,7 +55,8 @@ def run_define_dataset_autoloader( + "path=[" + czi_path + "] " - + "exclude=10 bioformats_series_are?=" + + "exclude=10 " + + "bioformats_series_are?=" + bf_series_type + " " + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " @@ -65,7 +66,8 @@ def run_define_dataset_autoloader( + "dataset_save_path=[" + dataset_save_path + "] " - + "check_stack_sizes apply_angle_rotation " + + "check_stack_sizes " + + "apply_angle_rotation " + subsampling_factors + hdf5_chunk_sizes + "timepoints_per_partition=" From b9e68680da755bfc9becf2feb4c7abff1031a896 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:18:18 +0100 Subject: [PATCH 014/678] Minor docstring fixes, WIP --- src/imcflibs/imagej/BDV.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/BDV.py index 679c9841..c67fb6a0 100644 --- a/src/imcflibs/imagej/BDV.py +++ b/src/imcflibs/imagej/BDV.py @@ -89,8 +89,7 @@ def run_resave( timepoints_per_partition=1, use_deflate_compression=True, ): - """ - Resave the xml dataset in a new format. Only accepts all timepoints or single timepoints + """Resave the xml dataset in a new format. Only accepts all timepoints or single timepoints Parameters ---------- @@ -160,8 +159,7 @@ def run_detectInterestPoints( threshold=0.008, maximum_number=3000, ): - """ - run the detect interest points command for registration + """run the detect interest points command for registration Parameters ---------- @@ -219,8 +217,7 @@ def run_registration( process_channel="All channels", rigid_timepoints=False, ): - """ - run the registration command + """run the registration command Parameters ---------- @@ -288,8 +285,7 @@ def run_registration( def run_duplicateReg(project_path, channel_source): - """ - If registration has been generated using a single channel, use this function to duplicate the registration parameters to the other channels + """If registration has been generated using a single channel, use this function to duplicate the registration parameters to the other channels Parameters ---------- @@ -326,8 +322,7 @@ def run_fusion( save_format="Save as new XML Project (HDF5)", additional_option="", ): - """ - run the image fusion command + """run the image fusion command Parameters ---------- From 67c291f97be50bc9b4b1cb9fbdbd9d30eac20849 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:19:43 +0100 Subject: [PATCH 015/678] Rename module from "BDV" to lowercase "bdv" Discussion has been about the naming as it is not all big-dataviewer related but agreement was to still keep this as the "common" name. --- src/imcflibs/imagej/{BDV.py => bdv.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/imcflibs/imagej/{BDV.py => bdv.py} (100%) diff --git a/src/imcflibs/imagej/BDV.py b/src/imcflibs/imagej/bdv.py similarity index 100% rename from src/imcflibs/imagej/BDV.py rename to src/imcflibs/imagej/bdv.py From 5a1fc320753efca65adc65ebdb4ed6d88b80e5a8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:25:36 +0100 Subject: [PATCH 016/678] Docstring improvements --- src/imcflibs/imagej/bdv.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index c67fb6a0..1cfc382c 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -89,20 +89,24 @@ def run_resave( timepoints_per_partition=1, use_deflate_compression=True, ): - """Resave the xml dataset in a new format. Only accepts all timepoints or single timepoints + """Resave the xml dataset in a new format (either all or single timepoints). + + Useful if it hasn't been done during dataset definition (see + `run_define_dataset_autoloader()`). Allows e.g. parallelization of HDF-5 + re-saving. Parameters ---------- sourceXMLFile : File - XML input file + XML input file. outputH5FilePath : str - Export path for the output file + Export path for the output file. timepoints : str, optional - Which timepoints should be exported, by default "All Timepoints" + Which timepoints should be exported, by default "All Timepoints". timepoints_per_partition : int, optional - How many timepoints per partition should be exported, by default 1 + How many timepoints per partition should be exported, by default 1. use_deflate_compression : bool, optional - Run deflate compression, by default True + Run deflate compression, by default True. """ # If all timepoints are nto to be saved at once From 94d1b76e4ed98635a13c141a403228933c96a1bb Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:25:44 +0100 Subject: [PATCH 017/678] Single arguments per line --- src/imcflibs/imagej/bdv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 1cfc382c..67ad6e87 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -114,7 +114,8 @@ def run_resave( timepoints = "resave_timepoint=[All Timepoints] " else: timepoints = ( - "resave_timepoint=[Single Timepoint (Select from List)] processing_timepoint=[Timepoint " + "resave_timepoint=[Single Timepoint (Select from List)] " + + "processing_timepoint=[Timepoint " + str(timepoints) + "] " ) From 5dbdb9a913aa7c74f70819e9d255101162372d96 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:34:38 +0100 Subject: [PATCH 018/678] Make it accept string paths as well --- 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 67ad6e87..570a593b 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -97,7 +97,7 @@ def run_resave( Parameters ---------- - sourceXMLFile : File + sourceXMLFile : File or str XML input file. outputH5FilePath : str Export path for the output file. @@ -135,7 +135,7 @@ def run_resave( IJ.run( "As HDF5", "select=" - + sourceXMLFile.getPath() + + str(sourceXMLFile) + " " + "resave_angle=[All angles] " + "resave_channel=[All channels] " From 6b1bc0c188630470c3c154b592c3fc894d71dbfa Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:35:07 +0100 Subject: [PATCH 019/678] Snake case conventions for parameters --- src/imcflibs/imagej/bdv.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 570a593b..1495844d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -83,8 +83,8 @@ def run_define_dataset_autoloader( def run_resave( - sourceXMLFile, - outputH5FilePath, + source_xml_file, + output_h5_file_path, timepoints="All Timepoints", timepoints_per_partition=1, use_deflate_compression=True, @@ -97,9 +97,9 @@ def run_resave( Parameters ---------- - sourceXMLFile : File or str + source_xml_file : File or str XML input file. - outputH5FilePath : str + output_h5_file_path : str Export path for the output file. timepoints : str, optional Which timepoints should be exported, by default "All Timepoints". @@ -135,7 +135,7 @@ def run_resave( IJ.run( "As HDF5", "select=" - + str(sourceXMLFile) + + str(source_xml_file) + " " + "resave_angle=[All angles] " + "resave_channel=[All channels] " @@ -151,7 +151,7 @@ def run_resave( + use_deflate_compressionArg + split_hdf5 + "export_path=" - + outputH5FilePath, + + output_h5_file_path, ) return From f32a91aa20178b5bc1426c060bd8e7a4f1fab95f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:45:25 +0100 Subject: [PATCH 020/678] Snake casing --- src/imcflibs/imagej/bdv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 1495844d..0b0b3ae6 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -122,9 +122,9 @@ def run_resave( # If use_deflate_compression if use_deflate_compression: - use_deflate_compressionArg = "use_deflate_compression " + use_deflate_compression_arg = "use_deflate_compression " else: - use_deflate_compressionArg = "" + use_deflate_compression_arg = "" # If split_hdf5 option if timepoints_per_partition != 0: @@ -148,7 +148,7 @@ def run_resave( + str(timepoints_per_partition) + " " + "setups_per_partition=0 " - + use_deflate_compressionArg + + use_deflate_compression_arg + split_hdf5 + "export_path=" + output_h5_file_path, From 93eefc151b8aaa1f1857df9d55cf30bc302191d8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:45:42 +0100 Subject: [PATCH 021/678] Comments / whitespace --- src/imcflibs/imagej/bdv.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 0b0b3ae6..a8cf3d67 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -38,6 +38,7 @@ def run_define_dataset_autoloader( Allow specifying hdf5_chunk_sizes factors explicitly, for example "[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]" """ + if subsampling_factors: subsampling_factors = "subsampling_factors=" + subsampling_factors + " " else: @@ -108,8 +109,7 @@ def run_resave( use_deflate_compression : bool, optional Run deflate compression, by default True. """ - - # If all timepoints are nto to be saved at once + # save all timepoints or a single one: if timepoints == "All Timepoints": timepoints = "resave_timepoint=[All Timepoints] " else: @@ -120,7 +120,6 @@ def run_resave( + "] " ) - # If use_deflate_compression if use_deflate_compression: use_deflate_compression_arg = "use_deflate_compression " else: From 121a97960c2ec0fdec907e86f58f4db5d89a9be5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:46:49 +0100 Subject: [PATCH 022/678] Allow subsampling and chunk size to be specified Fall back to the built-in defaults if not given. --- src/imcflibs/imagej/bdv.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index a8cf3d67..b37f5572 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -89,6 +89,8 @@ def run_resave( timepoints="All Timepoints", timepoints_per_partition=1, use_deflate_compression=True, + subsampling_factors=None, + hdf5_chunk_sizes=None, ): """Resave the xml dataset in a new format (either all or single timepoints). @@ -108,6 +110,12 @@ def run_resave( How many timepoints per partition should be exported, by default 1. use_deflate_compression : bool, optional Run deflate compression, by default True. + subsampling_factors: str, optional + Allow specifying subsampling factors explicitly, for example: + "[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }]" + hdf5_chunk_sizes: str, optional + Allow specifying hdf5_chunk_sizes factors explicitly, for example + "[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]" """ # save all timepoints or a single one: if timepoints == "All Timepoints": @@ -131,6 +139,15 @@ def run_resave( else: split_hdf5 = "" + if subsampling_factors: + subsampling_factors = "subsampling_factors=" + subsampling_factors + " " + else: + subsampling_factors = " " + if hdf5_chunk_sizes: + hdf5_chunk_sizes = "hdf5_chunk_sizes=" + hdf5_chunk_sizes + " " + else: + hdf5_chunk_sizes = " " + IJ.run( "As HDF5", "select=" @@ -141,8 +158,8 @@ def run_resave( + "resave_illumination=[All illuminations] " + "resave_tile=[All tiles] " + timepoints - + "subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }] " - + "hdf5_chunk_sizes=[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }] " + + subsampling_factors + + hdf5_chunk_sizes + "timepoints_per_partition=" + str(timepoints_per_partition) + " " From 7a646a86315a29fb44efbc069a85bc647948fc77 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 11:49:09 +0100 Subject: [PATCH 023/678] Rename to run_resave_as_h5() --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b37f5572..a0677063 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -83,7 +83,7 @@ def run_define_dataset_autoloader( return -def run_resave( +def run_resave_as_h5( source_xml_file, output_h5_file_path, timepoints="All Timepoints", From f3921ed0da0a75c863d6102369b45faefc6c24fa Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 12:40:13 +0100 Subject: [PATCH 024/678] Fix (non-) usage of parameter --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index a0677063..151b805f 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -224,7 +224,7 @@ def run_detectInterestPoints( + " " + "find_maxima " + "maximum_number=" - + str(3000) + + str(maximum_number) + " " + "type_of_detections_to_use=Brightest " + "compute_on=[CPU (Java)]", From 617d1e815bdfd941943e7376a40e3fbd5f01b093 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 12:40:22 +0100 Subject: [PATCH 025/678] Fix docstring --- 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 151b805f..f522b0e4 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -180,7 +180,7 @@ def run_detectInterestPoints( threshold=0.008, maximum_number=3000, ): - """run the detect interest points command for registration + """Run the "Detect Interest Points" command for registration. Parameters ---------- @@ -193,7 +193,7 @@ def run_detectInterestPoints( threshold : float, optional Threshold value for the interest point detection, by default 0.008 maximum_number : int, optional - maximum_number of interest points to use, by default 3000 + Maximum number of interest points to use, by default 3000. """ IJ.run( From a7c5623a323def56b72a82ce36f1418b487fb688 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 12:40:40 +0100 Subject: [PATCH 026/678] Rename functions to snake case --- 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 f522b0e4..936852a1 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -173,7 +173,7 @@ def run_resave_as_h5( return -def run_detectInterestPoints( +def run_detect_interest_points( project_path, process_timepoint="All Timepoints", sigma=1.8, @@ -305,7 +305,7 @@ def run_registration( return -def run_duplicateReg(project_path, channel_source): +def run_duplicate_reg(project_path, channel_source): """If registration has been generated using a single channel, use this function to duplicate the registration parameters to the other channels Parameters From bc213bc6bccfe3fb7577f50f79b8b1874566ac40 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 12:42:31 +0100 Subject: [PATCH 027/678] Space consistency --- 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 936852a1..8edbf8f9 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -272,35 +272,35 @@ def run_registration( "Register Dataset based on Interest Points", "select=[" + project_path - + "]" - + " process_angle=[All angles]" - + " process_channel=" + + "] " + + "process_angle=[All angles] " + + "process_channel=" + process_channelArg - + " process_illumination=[All illuminations]" - + " process_tile=[All tiles]" - + " process_timepoint=[" + + "process_illumination=[All illuminations] " + + "process_tile=[All tiles] " + + "process_timepoint=[" + process_timepoint - + "]" - + " registration_algorithm=[Precise descriptor-based (translation invariant)]" - + " registration_in_between_views=[Compare all views against each other]" - + " interest_points=beads" - + " group_tiles" - + " group_illuminations" - + " group_channels" + + "] " + + "registration_algorithm=[Precise descriptor-based (translation invariant)] " + + "registration_in_between_views=[Compare all views against each other] " + + "interest_points=beads " + + "group_tiles " + + "group_illuminations " + + "group_channels " + rigid_timepointsArg - + " fix_views=[Fix first view]" - + " map_back_views=[Do not map back (use this if views are fixed)]" - + " transformation=Affine" - + " regularize_model" - + " model_to_regularize_with=Rigid" - + " lamba=0.10" - + " number_of_neighbors=3" - + " redundancy=2" - + " significance=1" - + " allowed_error_for_ransac=5" - + " ransac_iterations=Normal" - + " interestpoint_grouping=[Group interest points (simply combine all in one virtual view)]" - + " interest=5", + + "fix_views=[Fix first view] " + + "map_back_views=[Do not map back (use this if views are fixed)] " + + "transformation=Affine " + + "regularize_model " + + "model_to_regularize_with=Rigid " + + "lamba=0.10 " + + "number_of_neighbors=3 " + + "redundancy=2 " + + "significance=1 " + + "allowed_error_for_ransac=5 " + + "ransac_iterations=Normal " + + "interestpoint_grouping=[Group interest points (simply combine all in one virtual view)] " + + "interest=5", ) return From b30c0eb58e0738aed259648a27c9c3d236590d40 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 12:43:05 +0100 Subject: [PATCH 028/678] Max one parameter per line for readability --- src/imcflibs/imagej/bdv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 8edbf8f9..18d8fe98 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -211,7 +211,8 @@ def run_detect_interest_points( + "type_of_interest_point_detection=Difference-of-Gaussian " + "label_interest_points=beads " + "limit_amount_of_detections " - + "group_tiles group_illuminations " + + "group_tiles " + + "group_illuminations " + "subpixel_localization=[3-dimensional quadratic fit] " + "interest_point_specification=[Advanced ...] " + "downsample_xy=[Match Z Resolution (less downsampling)] " From b94def24bdf81f256896031cf682428cea1b7afc Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 12:55:36 +0100 Subject: [PATCH 029/678] Add "process_channel" parameter --- src/imcflibs/imagej/bdv.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 18d8fe98..047578ff 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -176,6 +176,7 @@ def run_resave_as_h5( def run_detect_interest_points( project_path, process_timepoint="All Timepoints", + process_channel="All channels", sigma=1.8, threshold=0.008, maximum_number=3000, @@ -188,6 +189,8 @@ def run_detect_interest_points( Path to the .xml project process_timepoint : str, optional Specify which timepoint should be processed, by default "All Timepoints" + process_channel : str, optional + Specify which channel should be processed, by default "All channels" sigma : float, optional Minimum sigma for interest points detection, by default 1.8 threshold : float, optional @@ -196,13 +199,25 @@ def run_detect_interest_points( Maximum number of interest points to use, by default 3000. """ + # If not process all channels at once, then adapt the option + if process_channel == "All channels": + process_channel_arg = "[" + process_channel + "] " + else: + process_channel_arg = ( + "[Single channel (Select from List)] " + + "processing_channel=[channel " + + process_channel + + "] " + ) + IJ.run( "Detect Interest Points for Registration", "select=[" + project_path + "] " + "process_angle=[All angles] " - + "process_channel=[All channels] " + + "process_channel=" + + process_channel_arg + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " + "process_timepoint=[" From ad2d52f262e17738ac03cfbbdecdbbeb1b3cf274 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:00:34 +0100 Subject: [PATCH 030/678] Add fixme in regards of the timepoints processing --- src/imcflibs/imagej/bdv.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 047578ff..d03cbebf 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -209,6 +209,17 @@ def run_detect_interest_points( + process_channel + "] " ) + # FIXME look into the actual call! @sebastien + # save all timepoints or a single one: + if process_timepoint == "All Timepoints": + process_timepoint = "resave_timepoint=[All Timepoints] " + else: + process_timepoint = ( + "resave_timepoint=[Single Timepoint (Select from List)] " + + "processing_timepoint=[Timepoint " + + str(process_timepoint) + + "] " + ) IJ.run( "Detect Interest Points for Registration", From 498e24f49a84d247c3202dfc60bb7449f8dc9503 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:13:18 +0100 Subject: [PATCH 031/678] Snake-casing --- src/imcflibs/imagej/bdv.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index d03cbebf..b9c7c810 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -280,19 +280,19 @@ def run_registration( """ # If not process all channels at once, then adapt the option - if process_channel == "[All channels]": - process_channelArg = process_channel + if process_channel == "[All channels] ": + process_channel_arg = process_channel else: - process_channelArg = ( + process_channel_arg = ( "[Single channel (Select from List)] processing_channel=[channel " + process_channel + "] " ) if rigid_timepoints: - rigid_timepointsArg = " consider_each_timepoint_as_rigid_unit" + rigid_timepoints_arg = "consider_each_timepoint_as_rigid_unit " else: - rigid_timepointsArg = "" + rigid_timepoints_arg = " " # register using interest points IJ.run( @@ -302,7 +302,7 @@ def run_registration( + "] " + "process_angle=[All angles] " + "process_channel=" - + process_channelArg + + process_channel_arg + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " + "process_timepoint=[" @@ -314,7 +314,7 @@ def run_registration( + "group_tiles " + "group_illuminations " + "group_channels " - + rigid_timepointsArg + + rigid_timepoints_arg + "fix_views=[Fix first view] " + "map_back_views=[Do not map back (use this if views are fixed)] " + "transformation=Affine " From 37480c39a218f07d6b74c218e23ff7042968bbc8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:13:36 +0100 Subject: [PATCH 032/678] Docstring line lengths and other conventions --- src/imcflibs/imagej/bdv.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b9c7c810..69ba884d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -265,7 +265,7 @@ def run_registration( process_channel="All channels", rigid_timepoints=False, ): - """run the registration command + """Run the registration command. Parameters ---------- @@ -274,9 +274,13 @@ def run_registration( process_timepoint : str, optional Specify which timepoint should be processed, by default "All Timepoints" process_channel : str, optional - Specify which channels should be processed. By default, all channels are processed together, however this behavior could be undesirable if only one channel is adequate (beads or nuclei). In that case provide the channel name instead. by default "All channels" + Specify which channels should be processed. By default, all channels are + processed together, however this behavior could be undesirable if only + one channel is adequate (beads or nuclei). In that case provide the + channel name instead. by default "All channels" rigid_timepoints : bool, optional - If spatial registration has already been run, set this boolean to True to consider each timepoint as rigid unit, by default False + If spatial registration has already been run, set this boolean to True + to consider each timepoint as rigid unit, by default False """ # If not process all channels at once, then adapt the option @@ -333,7 +337,10 @@ def run_registration( def run_duplicate_reg(project_path, channel_source): - """If registration has been generated using a single channel, use this function to duplicate the registration parameters to the other channels + """Function to duplicate the registration parameters to the other channels. + + If registration has been generated using a single channel,this can be used + to propagate it to the others. Parameters ---------- @@ -381,13 +388,18 @@ def run_fusion( process_timepoint : str, optional Specify which timepoint should be processed, by default "All Timepoints" downsampling : int, optional - Downsampling factor to use during the fusion, by default 1 (no downsampling) + Downsampling factor to use during the fusion, by default 1 (no + downsampling) ram_handling : str, optional - Which type of ram_handling do you require, by default "Virtual". This is the conservative (not likely to fail even if only a low amount of RAM is available) and slow approach + Which type of ram_handling do you require, by default "Virtual". This is + the conservative (not likely to fail even if only a low amount of RAM is + available) and slow approach save_format : str, optional - file format of the new image, by default "Save as new XML Project (HDF5)", this is matching the conservative and slow approach + file format of the new image, by default "Save as new XML Project + (HDF5)", this is matching the conservative and slow approach additional_option : str, optional="" - Any additional options that should be added to the fusion command. Do not forget to finish each additional option with a space + Any additional options that should be added to the fusion command. Do + not forget to finish each additional option with a space """ IJ.run( From e1e3c868e9524529d129045c7696aa944a07f979 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:20:56 +0100 Subject: [PATCH 033/678] Fix "process_channel" parameter processing --- 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 69ba884d..d5c84610 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -284,8 +284,8 @@ def run_registration( """ # If not process all channels at once, then adapt the option - if process_channel == "[All channels] ": - process_channel_arg = process_channel + if process_channel == "All channels": + process_channel_arg = "[All channels] " else: process_channel_arg = ( "[Single channel (Select from List)] processing_channel=[channel " From b5bf51841506de9c8537c73928828d7b9b46bf28 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:21:04 +0100 Subject: [PATCH 034/678] Rename to run_interest_points_registration() --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index d5c84610..1928f809 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -259,7 +259,7 @@ def run_detect_interest_points( return -def run_registration( +def run_interest_points_registration( project_path, process_timepoint="All Timepoints", process_channel="All channels", From 5db1bf6e07b448bceaa8def684a72301fb802567 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:24:30 +0100 Subject: [PATCH 035/678] Cover for spaces in the project path --- 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 1928f809..3328a205 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -352,9 +352,9 @@ def run_duplicate_reg(project_path, channel_source): IJ.run( "Duplicate Transformations", "apply=[One channel to other channels] " - + "select=" + + "select=[" + project_path - + " " + + "] " + "apply_to_angle=[All angles] " + "apply_to_illumination=[All illuminations] " + "apply_to_tile=[All tiles] " From 1f9952bb99dfcf0704a968d901891cc1b7ca7ce6 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:28:51 +0100 Subject: [PATCH 036/678] Rename to run_duplicate_channel_transformations() --- 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 3328a205..db3a9818 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -336,8 +336,8 @@ def run_interest_points_registration( return -def run_duplicate_reg(project_path, channel_source): - """Function to duplicate the registration parameters to the other channels. +def run_duplicate_channel_transformations(project_path, channel_source): + """Duplicate the transformation parameters to the other channels. If registration has been generated using a single channel,this can be used to propagate it to the others. From 8e182c8da8825763d16ffb1701698eca375f03eb Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:37:07 +0100 Subject: [PATCH 037/678] Module docstring and pylint pragmas --- src/imcflibs/imagej/bdv.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index db3a9818..ff3d3095 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1,6 +1,14 @@ -"""BigDataViewer related functions, mostly convenience wrappers. with simplified calls""" +"""BigDataViewer related functions. -from ij import IJ +Mostly convenience wrappers with simplified calls and default values. +""" + +# The pylint on Python 2.7 is too old to play nicely with black: +# pylint: disable-msg=bad-continuation +# Some function names just need to be longer than 30 chars: +# pylint: disable-msg=invalid-name + +from ij import IJ # pylint: disable-msg=import-error def run_define_dataset_autoloader( From d9b22a3b88359f142d0d03d98add513ef35c4b66 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 21 Feb 2023 13:37:41 +0100 Subject: [PATCH 038/678] Comment only --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index ff3d3095..e9a6f306 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -410,6 +410,8 @@ def run_fusion( not forget to finish each additional option with a space """ + # Add preserve original data anisotropy tickbox: + # tiles=> true; angle=>False IJ.run( "Fuse dataset ...", "select=[" From 2450feb968ea82dbe066cbb3b23d0f7e8cc1f4a9 Mon Sep 17 00:00:00 2001 From: Sebastien Herbert Date: Tue, 18 Apr 2023 16:20:05 +0200 Subject: [PATCH 039/678] Add send_email function (WIP) --- src/imcflibs/imagej/misc.py | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 8d0c56c9..dc783950 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -2,6 +2,7 @@ import sys import time +import smtplib from ij import IJ # pylint: disable-msg=import-error from ij.plugin.frame import RoiManager # pylint: disable-msg=import-error @@ -135,6 +136,63 @@ def find_focus(imp): return focused_slice +def send_mail(job_name, recipient, filename, total_execution_time): + """Send an email via smtp.unibas.ch. + Will likely NOT work without connection to the unibas network. + + Parameters + ---------- + recipient : string + recipients email address + job_name : string + Job name to display in the email + filename : string + the name of the file to be passed in the email + total_execution_time : str + the time it took to process the file in the format [HH:MM:SS:ss] + """ + server = prefs.get("imcf.smtpserver", None) + if server is None: + print( + "Mail notifications only works if configured. Please use Plugins/IMCF_Utilities" + ) + return + + sender = prefs.get("imcf.sender_email", "") + if sender is "": + print( + "Mail notifications only works if configured. Please use Plugins/IMCF_Utilities" + ) + return + + header = "From: %s\n" + header += "To: %s\n" + header += "Subject: Your %s job finished successfully\n\n" + text = ( + "Dear recipient,\n\n" + "This is an automated message.\n" + "Your dataset %s has been successfully processed (%s [HH:MM:SS:ss]).\n\n" + "Kind regards,\n" + "The IMCF-team" + ) + + message = header + text + + try: + smtpObj = smtplib.SMTP(server) + smtpObj.sendmail( + sender, + job_name, + recipient, + message % (sender, recipient, job_name, filename, total_execution_time), + ) + print("Successfully sent email") + except smtplib.SMTPException: + print("Error: unable to send email") + + return + + def progressbar(progress, total, line_number, prefix=""): """Progress bar for the IJ log window. From 528233b0badd954bba1090fae167ec059ef56c6a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 20 Apr 2023 13:27:31 +0200 Subject: [PATCH 040/678] WIP --- src/imcflibs/imagej/trackmate.py | 143 +++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/imcflibs/imagej/trackmate.py diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py new file mode 100644 index 00000000..cf7a62a7 --- /dev/null +++ b/src/imcflibs/imagej/trackmate.py @@ -0,0 +1,143 @@ +import sys + +from fiji.plugin.trackmate import Logger, Model, SelectionModel, Settings, TrackMate +from fiji.plugin.trackmate.action import LabelImgExporter +from fiji.plugin.trackmate.cellpose import CellposeDetectorFactory +from fiji.plugin.trackmate.cellpose.CellposeSettings import PretrainedModel +from fiji.plugin.trackmate.features import FeatureFilter +from fiji.plugin.trackmate.tracking.jaqaman import LAPUtils, SparseLAPTrackerFactory +from java.lang import Double + + +def run_tm( + implus, + detector_type, + channel_number, + quality_thresh=None, + intensity_thresh=None, + circularity_thresh=None, + area_thresh=None, + crop_roi=None, +): + # sourcery skip: merge-else-if-into-elif, swap-if-else-branches + """Function to run TrackMate on open data. Has some specific input + + Parameters + ---------- + implus : ImagePlus + ImagePlus on which to run image + detector_type : str + Detector type to use + channel_number : int + Number of the channel of interest + quality_thresh : float, optional + Value to enter as threshold in the filters, by default None + intensity_thresh : float, optional + Value to enter as threshold in the filters, by default None + circularity_thresh : float, optional + Value to enter as threshold in the filters, by default None + area_thresh : float, optional + Value to enter as threshold in the filters, by default None + crop_roi : ij.gui.Roi, optional + ROI to crop on the image, by default None + """ + + dims = implus.getDimensions() + cal = implus.getCalibration() + + if implus.getNSlices() > 1: + implus.setDimensions(dims[2], dims[4], dims[3]) + + if crop_roi is not None: + implus.setRoi(crop_roi) + + model = Model() + + model.setLogger(Logger.IJTOOLBAR_LOGGER) + + settings = Settings(implus) + + set_detector(detector_type, channel_number, settings) + # settings.detectorFactory = CellposeDetectorFactory() + + # settings.detectorSettings["TARGET_CHANNEL"] = nuclei_chnl + # settings.detectorSettings["OPTIONAL_CHANNEL_2"] = 0 + # settings.detectorSettings["CELLPOSE_PYTHON_FILEPATH"] = os.path.join( + # cellpose_env, "python.exe" + # ) + # settings.detectorSettings["CELLPOSE_MODEL_FILEPATH"] = os.path.join( + # os.environ["USERPROFILE"], ".cellpose", "models" + # ) + # settings.detectorSettings["CELLPOSE_MODEL"] = PretrainedModel.CYTO2 + # settings.detectorSettings["CELL_DIAMETER"] = 11.0 + # settings.detectorSettings["USE_GPU"] = True + # settings.detectorSettings["SIMPLIFY_CONTOURS"] = True + + # settings.addAllAnalyzers() + # spotAnalyzerProvider = SpotAnalyzerProvider(1) + # spotMorphologyProvider = SpotMorphologyAnalyzerProvider(1) + + # #settings.initialSpotFilterValue=quality_thresh + + # # Add the filter on mean intensity + # # Here 'true' takes everything ABOVE the mean_int value + if quality_thresh: + filter1 = FeatureFilter("QUALITY", Double(quality_thresh), True) + settings.addSpotFilter(filter1) + if intensity_thresh: + filter2 = FeatureFilter( + "MEAN_INTENSITY_CH" + str(channel_number), Double(intensity_thresh), True + ) + settings.addSpotFilter(filter2) + if circularity_thresh: + filter3 = FeatureFilter("CIRCULARITY", Double(circularity_thresh), True) + settings.addSpotFilter(filter3) + if area_thresh: + filter4 = FeatureFilter("AREA", Double(area_thresh), False) + settings.addSpotFilter(filter4) + + # Configure tracker + settings.trackerFactory = SparseLAPTrackerFactory() + settings.trackerSettings = settings.trackerFactory.getDefaultSettings() + # settings.addTrackAnalyzer(TrackDurationAnalyzer()) + settings.trackerSettings["LINKING_MAX_DISTANCE"] = 15.0 + settings.trackerSettings["GAP_CLOSING_MAX_DISTANCE"] = 15.0 + settings.trackerSettings["MAX_FRAME_GAP"] = 3 + settings.initialSpotFilterValue = -1.0 + + trackmate = TrackMate(model, settings) + trackmate.computeSpotFeatures(True) + trackmate.computeTrackFeatures(True) + + ok = trackmate.checkInput() + if not ok: + sys.exit(str(trackmate.getErrorMessage())) + return + + ok = trackmate.process() + if not ok: + if "[SparseLAPTracker] The spot collection is empty." in str( + trackmate.getErrorMessage() + ): + return IJ.createImage( + "Untitled", + "8-bit black", + implus.getWidth(), + implus.getHeight(), + implus.getNFrames(), + ) + else: + sys.exit(str(trackmate.getErrorMessage())) + return + + sm = SelectionModel(model) + + exportSpotsAsDots = False + exportTracksOnly = False + # implus2.close() + result = LabelImgExporter.createLabelImagePlus( + trackmate, exportSpotsAsDots, exportTracksOnly, False + ) + result.setDimensions(dims[2], dims[3], dims[4]) + implus.setDimensions(dims[2], dims[3], dims[4]) + return result From e2e34b92d5e1b8fd095002461236d8de6ddc5c57 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 4 Jul 2023 15:58:05 +0200 Subject: [PATCH 041/678] Add methods creating the settings Multiple methods depending on the detector selected. --- src/imcflibs/imagej/trackmate.py | 121 +++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index cf7a62a7..4a15a766 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -13,6 +13,127 @@ def run_tm( implus, detector_type, channel_number, +def cellpose_detector( + imageplus, + cellpose_env_path, + model_to_use, + obj_diameter, + target_chnl, + optional_chnl=None, + use_gpu=True, + simplify_contours=True, +): + """Create a dictionary with all settings for TrackMate using Cellpose. + + Parameters + ---------- + imageplus : ij.ImagePlus + ImagePlus on which to apply the detector. + cellpose_env_path : str + Path to the Cellpose environment. + model_to_use : str + Name of the model to use for the segmentation (CYTO, NUCLEI, CYTO2). + obj_diameter : float + Diameter of the objects to detect in the image. + This will be calibrated to the unit used in the image. + target_chnl : int + Index of the channel to use for segmentation. + optional_chnl : int, optional + Index of the secondary channel to use for segmentation, by default None. + use_gpu : bool, optional + Boolean for GPU usage, by default True. + simplify_contours : bool, optional + Boolean for simplifying the contours, by default True. + + Returns + ------- + fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + """ + settings = Settings(imageplus) + + settings.detectorFactory = CellposeDetectorFactory() + settings.detectorSettings["TARGET_CHANNEL"] = target_chnl + if optional_chnl: + settings.detectorSettings["OPTIONAL_CHANNEL_2"] = optional_chnl + settings.detectorSettings["CELLPOSE_PYTHON_FILEPATH"] = pathtools.join2( + cellpose_env_path, "python.exe" + ) + settings.detectorSettings["CELLPOSE_MODEL_FILEPATH"] = os.path.join( + os.environ["USERPROFILE"], ".cellpose", "models" + ) + settings.detectorSettings["CELLPOSE_MODEL"] = model_to_use + settings.detectorSettings["CELL_DIAMETER"] = obj_diameter + settings.detectorSettings["USE_GPU"] = use_gpu + settings.detectorSettings["SIMPLIFY_CONTOURS"] = simplify_contours + + return settings + + +def stardist_detector(imageplus, target_chnl): + """Create a dictionary with all settings for TrackMate using StarDist. + + Parameters + ---------- + imageplus : ij.ImagePlus + Image on which to do the segmentation. + target_chnl : int + Index of the channel on which to do the segmentation. + + Returns + ------- + fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + """ + + settings = Settings(imageplus) + settings.detectorFactory = StarDistDetectorFactory() + settings.detectorSettings["TARGET_CHANNEL"] = target_chnl + + return settings + + +def log_detector( + imageplus, + radius, + target_chnl, + quality_threshold=0.0, + median_filtering=True, + subpix_localization=True, +): + """Create a dictionary with all settings for TrackMate using the LogDetector. + + Parameters + ---------- + imageplus : ij.ImagePlus + Image on which to do the segmentation. + radius : float + Radius of the objects to detect. + target_chnl : int + Index of the channel on which to do the segmentation. + quality_threshold : int, optional + Threshold to use for excluding the spots by quality, by default 0. + median_filtering : bool, optional + Boolean to do median filtering, by default True. + subpix_localization : bool, optional + Boolean to do subpixel localization, by default True. + + Returns + ------- + fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + """ + + settings = Settings(imageplus) + settings.detectorFactory = LogDetectorFactory() + + settings.detectorSettings["RADIUS"] = Double(radius) + settings.detectorSettings["TARGET_CHANNEL"] = target_chnl + settings.detectorSettings["THRESHOLD"] = Double(quality_threshold) + settings.detectorSettings["DO_MEDIAN_FILTERING"] = median_filtering + settings.detectorSettings["DO_SUBPIXEL_LOCALIZATION"] = subpix_localization + + return settings quality_thresh=None, intensity_thresh=None, circularity_thresh=None, From faab20c288a3df8d1dd5cc2737f80f7fb358a7e6 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 4 Jul 2023 15:58:33 +0200 Subject: [PATCH 042/678] Add method for spot filtering --- src/imcflibs/imagej/trackmate.py | 61 ++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 4a15a766..53db80b1 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -134,10 +134,67 @@ def log_detector( settings.detectorSettings["DO_SUBPIXEL_LOCALIZATION"] = subpix_localization return settings + +def spot_filtering( + settings, quality_thresh=None, - intensity_thresh=None, - circularity_thresh=None, area_thresh=None, + circularity_thresh=None, + intensity_dict_thresh=None, +): + """Add spot filtering for different features to the settings dictionary. + + Parameters + ---------- + settings : fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + quality_thresh : float, optional + Threshold to use for quality filtering of the spots, by default None. + If the threshold is positive, will exclude everything below the value. + If the threshold is negative, will exclude everything above the value. + area_thresh : float, optional + Threshold to use for area filtering of the spots, by default None. + If the threshold is positive, will exclude everything below the value. + If the threshold is negative, will exclude everything above the value. + circularity_thresh : float, optional + Threshold to use for circularity filtering of the spots, by default None. + If the threshold is positive, will exclude everything below the value. + If the threshold is negative, will exclude everything above the value. + intensity_dict_thresh : dict, optional + Threshold to use for intensity filtering of the spots, by default None. + Dictionary needs to contain the channel index as key and the filter as value. + If the threshold is positive, will exclude everything below the value. + If the threshold is negative, will exclude everything above the value. + + Returns + ------- + fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + """ + + settings.initialSpotFilterValue = -1.0 + settings.addAllAnalyzers() + + # Here 'true' takes everything ABOVE the mean_int value + if quality_thresh: + filter_spot = FeatureFilter("QUALITY", Double(abs(quality_thresh))) + settings.addSpotFilter(filter_spot) + if area_thresh: + filter_spot = FeatureFilter("AREA", Double(abs(area_thresh)), area_thresh >= 0) + settings.addSpotFilter(filter_spot) + if circularity_thresh: + filter_spot = FeatureFilter( + "CIRCULARITY", Double(abs(circularity_thresh)), circularity_thresh >= 0 + ) + settings.addSpotFilter(filter_spot) + if intensity_dict_thresh: + for key, value in intensity_dict_thresh.items(): + filter_spot = FeatureFilter( + "MEAN_INTENSITY_CH" + str(key), abs(value), value >= 0 + ) + settings.addSpotFilter(filter_spot) + + return settings crop_roi=None, ): # sourcery skip: merge-else-if-into-elif, swap-if-else-branches From 07db009025a25783d61f1eed874280a50a925159 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 4 Jul 2023 15:58:48 +0200 Subject: [PATCH 043/678] Add method for tracks filtering --- src/imcflibs/imagej/trackmate.py | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 53db80b1..a9ec2a4e 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -195,6 +195,54 @@ def spot_filtering( settings.addSpotFilter(filter_spot) return settings + +def track_filtering( + settings, + link_max_dist=0.5, + gap_closing_dist=0.5, + max_frame_gap=2, + track_splitting_max_dist=None, + track_merging_max_distance=None, +): + """Add track filtering for different features to the settings dictionary. + + Parameters + ---------- + settings : fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + link_max_dist : float, optional + Maximal displacement of the spots, by default 0.5. + gap_closing_dist : float, optional + Maximal distance for gap closing, by default 0.5. + max_frame_gap : int, optional + Maximal frame interval between spots to be bridged, by default 2. + track_splitting_max_dist : int, optional + Maximal frame interval for splitting tracks, by default None. + track_merging_max_distance : int, optional + Maximal frame interval for merging tracks , by default None. + + Returns + ------- + fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + """ + + settings.trackerFactory = SparseLAPTrackerFactory() + settings.trackerSettings = LAPUtils.getDefaultLAPSettingsMap() + settings.trackerSettings["LINKING_MAX_DISTANCE"] = link_max_dist # must be double + settings.trackerSettings[ + "GAP_CLOSING_MAX_DISTANCE" + ] = gap_closing_dist # must be double + settings.trackerSettings["MAX_FRAME_GAP"] = max_frame_gap + if track_splitting_max_dist: + settings.trackerSettings["ALLOW_TRACK_SPLITTING"] = True + settings.trackerSettings["SPLITTING_MAX_DISTANCE"] = track_splitting_max_dist + if track_merging_max_distance: + settings.trackerSettings["ALLOW_TRACK_MERGING"] = True + settings.trackerSettings["MERGING_MAX_DISTANCE"] = True + + return settings + crop_roi=None, ): # sourcery skip: merge-else-if-into-elif, swap-if-else-branches From 8c485dad196080e0a7065fa399a377cd11f1b00a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 4 Jul 2023 16:01:26 +0200 Subject: [PATCH 044/678] Clean up imports and code using settings as input --- src/imcflibs/imagej/trackmate.py | 116 +++++++++++-------------------- 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index a9ec2a4e..6ff6a0d6 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -1,18 +1,23 @@ +import os import sys +from ij import IJ + from fiji.plugin.trackmate import Logger, Model, SelectionModel, Settings, TrackMate from fiji.plugin.trackmate.action import LabelImgExporter -from fiji.plugin.trackmate.cellpose import CellposeDetectorFactory -from fiji.plugin.trackmate.cellpose.CellposeSettings import PretrainedModel +from fiji.plugin.trackmate.cellpose import ( + CellposeDetectorFactory, + LogDetectorFactory, + StarDistDetectorFactory, +) from fiji.plugin.trackmate.features import FeatureFilter -from fiji.plugin.trackmate.tracking.jaqaman import LAPUtils, SparseLAPTrackerFactory +from fiji.plugin.trackmate.tracking import LAPUtils +from fiji.plugin.trackmate.tracking.jaqaman import SparseLAPTrackerFactory from java.lang import Double +from . import pathtools + -def run_tm( - implus, - detector_type, - channel_number, def cellpose_detector( imageplus, cellpose_env_path, @@ -135,6 +140,7 @@ def log_detector( return settings + def spot_filtering( settings, quality_thresh=None, @@ -196,6 +202,7 @@ def spot_filtering( return settings + def track_filtering( settings, link_max_dist=0.5, @@ -243,6 +250,10 @@ def track_filtering( return settings + +def run_tm( + implus, + settings, crop_roi=None, ): # sourcery skip: merge-else-if-into-elif, swap-if-else-branches @@ -250,22 +261,18 @@ def track_filtering( Parameters ---------- - implus : ImagePlus + implus : ij.ImagePlus ImagePlus on which to run image - detector_type : str - Detector type to use - channel_number : int - Number of the channel of interest - quality_thresh : float, optional - Value to enter as threshold in the filters, by default None - intensity_thresh : float, optional - Value to enter as threshold in the filters, by default None - circularity_thresh : float, optional - Value to enter as threshold in the filters, by default None - area_thresh : float, optional - Value to enter as threshold in the filters, by default None + settings : fiji.plugin.trackmate.Settings + Settings to use for TrackMate crop_roi : ij.gui.Roi, optional ROI to crop on the image, by default None + + Returns + ------- + ij.ImagePlus + Labeled image with all the objects belonging to the same tracks having + the same label. """ dims = implus.getDimensions() @@ -281,56 +288,6 @@ def track_filtering( model.setLogger(Logger.IJTOOLBAR_LOGGER) - settings = Settings(implus) - - set_detector(detector_type, channel_number, settings) - # settings.detectorFactory = CellposeDetectorFactory() - - # settings.detectorSettings["TARGET_CHANNEL"] = nuclei_chnl - # settings.detectorSettings["OPTIONAL_CHANNEL_2"] = 0 - # settings.detectorSettings["CELLPOSE_PYTHON_FILEPATH"] = os.path.join( - # cellpose_env, "python.exe" - # ) - # settings.detectorSettings["CELLPOSE_MODEL_FILEPATH"] = os.path.join( - # os.environ["USERPROFILE"], ".cellpose", "models" - # ) - # settings.detectorSettings["CELLPOSE_MODEL"] = PretrainedModel.CYTO2 - # settings.detectorSettings["CELL_DIAMETER"] = 11.0 - # settings.detectorSettings["USE_GPU"] = True - # settings.detectorSettings["SIMPLIFY_CONTOURS"] = True - - # settings.addAllAnalyzers() - # spotAnalyzerProvider = SpotAnalyzerProvider(1) - # spotMorphologyProvider = SpotMorphologyAnalyzerProvider(1) - - # #settings.initialSpotFilterValue=quality_thresh - - # # Add the filter on mean intensity - # # Here 'true' takes everything ABOVE the mean_int value - if quality_thresh: - filter1 = FeatureFilter("QUALITY", Double(quality_thresh), True) - settings.addSpotFilter(filter1) - if intensity_thresh: - filter2 = FeatureFilter( - "MEAN_INTENSITY_CH" + str(channel_number), Double(intensity_thresh), True - ) - settings.addSpotFilter(filter2) - if circularity_thresh: - filter3 = FeatureFilter("CIRCULARITY", Double(circularity_thresh), True) - settings.addSpotFilter(filter3) - if area_thresh: - filter4 = FeatureFilter("AREA", Double(area_thresh), False) - settings.addSpotFilter(filter4) - - # Configure tracker - settings.trackerFactory = SparseLAPTrackerFactory() - settings.trackerSettings = settings.trackerFactory.getDefaultSettings() - # settings.addTrackAnalyzer(TrackDurationAnalyzer()) - settings.trackerSettings["LINKING_MAX_DISTANCE"] = 15.0 - settings.trackerSettings["GAP_CLOSING_MAX_DISTANCE"] = 15.0 - settings.trackerSettings["MAX_FRAME_GAP"] = 3 - settings.initialSpotFilterValue = -1.0 - trackmate = TrackMate(model, settings) trackmate.computeSpotFeatures(True) trackmate.computeTrackFeatures(True) @@ -345,25 +302,32 @@ def track_filtering( if "[SparseLAPTracker] The spot collection is empty." in str( trackmate.getErrorMessage() ): - return IJ.createImage( + new_imp = IJ.createImage( "Untitled", - "8-bit black", + "16-bit black", implus.getWidth(), implus.getHeight(), implus.getNFrames(), ) + new_imp.setCalibration(cal) + + return new_imp + else: sys.exit(str(trackmate.getErrorMessage())) + return - sm = SelectionModel(model) + SelectionModel(model) exportSpotsAsDots = False exportTracksOnly = False # implus2.close() - result = LabelImgExporter.createLabelImagePlus( + label_imp = LabelImgExporter.createLabelImagePlus( trackmate, exportSpotsAsDots, exportTracksOnly, False ) - result.setDimensions(dims[2], dims[3], dims[4]) + label_imp.setCalibration(cal) + label_imp.setDimensions(dims[2], dims[3], dims[4]) implus.setDimensions(dims[2], dims[3], dims[4]) - return result + + return label_imp From 81593a60b81e3184b794c7ff1a76a329c9650c07 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 4 Jul 2023 15:56:45 +0200 Subject: [PATCH 045/678] Add misc.sanitize_image_title() --- src/imcflibs/imagej/misc.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 53ed01ae..baf8ff99 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -2,6 +2,7 @@ import sys import time +import os from ij import IJ # pylint: disable-msg=import-error @@ -222,3 +223,21 @@ def setup_clean_ij_environment(rm=None, rt=None): # pylint: disable-msg=unused- prefs.fix_ij_options() return + + +def sanitize_image_title(imp): + """Remove special chars and various suffixes from an open ImagePlus. + + Parameters + ---------- + imp : ImagePlus + The ImagePlus to be renamed. + """ + image_title = os.path.basename(imp.getTitle()) # FIXME: Kai, why this? + image_title = image_title.replace(".czi", "") + image_title = image_title.replace(" ", "_") + image_title = image_title.replace("_-_", "") + image_title = image_title.replace("__", "_") + image_title = image_title.replace("#", "Series") + + imp.setTitle(image_title) From ea2f606775fa917681e7c7375fc8fff1fcb339cf Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Fri, 8 Sep 2023 12:02:48 +0200 Subject: [PATCH 046/678] WIP for BDV module following the latest changes to BigStitcher --- src/imcflibs/imagej/bdv.py | 488 ++++++++++++++++++++++++++++++++++++- 1 file changed, 480 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index e9a6f306..6df378bd 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -7,15 +7,18 @@ # pylint: disable-msg=bad-continuation # Some function names just need to be longer than 30 chars: # pylint: disable-msg=invalid-name +import os from ij import IJ # pylint: disable-msg=import-error +from .. import pathtools + def run_define_dataset_autoloader( project_filename, - czi_path, - dataset_save_path, + file_path, bf_series_type, + dataset_save_path=None, timepoints_per_partition=1, resave="Re-save as multiresolution HDF5", subsampling_factors=None, @@ -26,9 +29,10 @@ def run_define_dataset_autoloader( Parameters ---------- project_filename : str - Name of the project (finishes with .xml) - czi_path : str - path to the first czi + Name of the project + file_path : str + path to the file, can be the first czi or a regex to match all files + with an extension dataset_save_path : str output path for the .xml bf_series_type : str @@ -47,6 +51,16 @@ def run_define_dataset_autoloader( "[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]" """ + file_info = pathtools.parse_path(file_path) + result_folder = pathtools.join2(file_info["path"], project_filename) + + project_filename = project_filename.replace(" ", "_") + + if not os.path.exists(result_folder): + os.makedirs(result_folder) + + if not dataset_save_path: + dataset_save_path = pathtools.join2(result_folder, project_filename) if subsampling_factors: subsampling_factors = "subsampling_factors=" + subsampling_factors + " " else: @@ -55,14 +69,16 @@ def run_define_dataset_autoloader( hdf5_chunk_sizes = "hdf5_chunk_sizes=" + hdf5_chunk_sizes + " " else: hdf5_chunk_sizes = " " + IJ.run( "Define Multi-View Dataset", "define_dataset=[Automatic Loader (Bioformats based)] " + "project_filename=[" + project_filename + + ".xml" + "] " + "path=[" - + czi_path + + file_path + "] " + "exclude=10 " + "bioformats_series_are?=" @@ -73,7 +89,7 @@ def run_define_dataset_autoloader( + resave + "] " + "dataset_save_path=[" - + dataset_save_path + + result_folder + "] " + "check_stack_sizes " + "apply_angle_rotation " @@ -88,7 +104,59 @@ def run_define_dataset_autoloader( + dataset_save_path + "]", ) - return + + +def run_define_dataset_manualoader( + project_filename, + file_path, + series_treatment, +): + """Run the Define Multi-View Dataset command using the "Manual Loader" option + + Parameters + ---------- + project_filename : str + Name of the project + file_path : str + Path to the file + series_treatment : str + How to treat the series, possible choices are only "Tiles" or "Angles" + """ + + xml_filename = project_filename + ".xml" + + parent_dir = os.path.dirname(file_path) + filename = os.path.basename(file_path) + temp = os.path.join(parent_dir, filename + "_temp") + h5_path = os.path.join(temp, project_filename) + + IJ.run( + "Define dataset ...", + "define_dataset=[Automatic Loader (Bioformats based)] " + + "project_filename=[" + + xml_filename + + "] " + + "path=[" + + file_path + + "] " + + "bioformats_series_are?=" + + series_treatment + + " " + + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " + + "how_to_load_images=[Re-save as multiresolution HDF5] " + + "dataset_save_path=[" + + temp + + "] " + + "check_stack_sizes " + + "subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,1}, {8,8,2}, {16,16,4} }] " + + "hdf5_chunk_sizes=[{ {32,32,4}, {32,16,8}, {16,16,16}, {32,16,8}, {32,32,4} }] " + + "timepoints_per_partition=1 " + + "setups_per_partition=0 " + + "use_deflate_compression " + + "export_path=[" + + h5_path + + "]", + ) def run_resave_as_h5( @@ -181,6 +249,254 @@ def run_resave_as_h5( return +def run_phase_correlation_pairwise_shifts_calculation( + project_path, + input_dict, + treat_timepoints="group", + treat_channels="group", + treat_illuminations="group", + treat_angles="[treat individually]", + treat_tiles="group", +): + """Run the Pairwise shifts calculation using Phase Correlation + + Parameters + ---------- + project_path : str + Path to the XML + input_dict : dict + Dictionary containing all the required information for angle, channel, + illuminations and timepoints + treat_timepoints : str, optional + How to deal with the timepoints, by default "group" + treat_channels : str, optional + How to deal with the channels, by default "group" + treat_illuminations : str, optional + How to deal with the illuminations, by default "group" + treat_angles : str, optional + How to deal with the angles, by default "[treat individually]" + treat_tiles : str, optional + How to deal with the tiles, by default "group" + """ + options_dict = parse_options(input_dict) + + use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" + use_channel = "channels=[Average Channels]" if treat_channels == "group" else "" + use_illumination = ( + "illuminations=[Average Illuminations]" + if treat_illuminations == "group" + else "" + ) + use_timepoint = ( + "timepoints=[Average Timepoints]" if treat_timepoints == "group" else "" + ) + use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" + + IJ.run( + "Calculate pairwise shifts ...", + "select=[" + + project_path + + "] " + + "process_angle=" + + options_dict["angle_text"] + + "process_channel=" + + options_dict["channel_text"] + + "process_illumination=" + + options_dict["illumination_text"] + + "process_tile=" + + options_dict["tile_text"] + + "process_timepoint=" + + options_dict["timepoint_text"] + + options_dict["timepoint_select"] + + options_dict["angle_select"] + + options_dict["channel_select"] + + options_dict["illumination_select"] + + options_dict["tile_select"] + + options_dict["timepoint_select"] + + " " + + "method=[Phase Correlation] " + + "show_expert_grouping_options " + + "show_expert_algorithm_parameters " + + use_angle + + " " + + use_channel + + " " + + use_illumination + + " " + + use_timepoint + + " " + + use_tile + + " " + + "how_to_treat_angles=" + + treat_angles + + " " + + "how_to_treat_channels=" + + treat_channels + + " " + + "how_to_treat_illuminations=" + + treat_illuminations + + " " + + "how_to_treat_tiles=" + + treat_tiles + + " " + + "how_to_treat_timepoints=" + + treat_timepoints + + " " + + "subpixel_accuracy", + ) + + +def run_filter_pairwise_shifts( + project_path, + min_r=0.7, + max_r=1, + max_shift_x=0, + max_shift_y=0, + max_shift_z=0, + max_displacement=0, +): + """Filter the pairwise shifts based on different thresholds + + Parameters + ---------- + project_path : str + Path of the XML on which to apply the filters + min_r : float, optional + Minimal quality of the link to keep, by default 0.7 + max_r : float, optional + Maximal quality of the link to keep, by default 1 + max_shift_x : int, optional + Maximal shift in X to keep, by default 0 + max_shift_y : int, optional + Maximal shift in Y to keep, by default 0 + max_shift_z : int, optional + Maximal shift in Z to keep, by default 0 + max_displacement : int, optional + Maximal displacement to keep, by default 0 + """ + IJ.run( + "Filter pairwise shifts ...", + "select=[" + + project_path + + "] " + + "filter_by_link_quality " + + "min_r=" + + str(min_r) + + " " + + "max_r=" + + str(max_r) + + " " + + "max_shift_in_x=" + + str(max_shift_x) + + " " + + "max_shift_in_y=" + + str(max_shift_y) + + " " + + "max_shift_in_z=" + + str(max_shift_z) + + " " + + "max_displacement=" + + str(max_displacement), + ) + + +def run_optimize_apply_shifts( + project_path, + input_dict, + treat_timepoints="group", + treat_channels="group", + treat_illuminations="group", + treat_angles="[treat individually]", + treat_tiles="group", +): + """Optimize the shifts and apply it to the dataset + + Parameters + ---------- + project_path : str + Path of the XML on which to optimize and apply the shifts + input_dict : dict + Dictionary containing all the required informations for angles, + channels, illuminations, tiles and timepoints + treat_timepoints : str, optional + How to treat the timepoints, by default "group" + treat_channels : str, optional + How to treat the channels, by default "group" + treat_illuminations : str, optional + How to treat the illuminations, by default "group" + treat_angles : str, optional + How to treat the angles, by default "[treat individually]" + treat_tiles : str, optional + How to treat the tiles, by default "group" + """ + options_dict = parse_options(input_dict) + + use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" + use_channel = "channels=[Average Channels]" if treat_channels == "group" else "" + use_illumination = ( + "illuminations=[Average Illuminations]" + if treat_illuminations == "group" + else "" + ) + use_timepoint = ( + "timepoints=[Average Timepoints]" if treat_timepoints == "group" else "" + ) + use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" + + IJ.run( + "Optimize globally and apply shifts ...", + "select=[" + + project_path + + "] " + + "process_angle=" + + options_dict["angle_text"] + + "process_channel=" + + options_dict["channel_text"] + + "process_illumination=" + + options_dict["illumination_text"] + + "process_tile=" + + options_dict["tile_text"] + + "process_timepoint=" + + options_dict["timepoint_text"] + + options_dict["timepoint_select"] + + options_dict["angle_select"] + + options_dict["channel_select"] + + options_dict["illumination_select"] + + options_dict["tile_select"] + + options_dict["timepoint_select"] + + " " + + "relative=2.500 " + + "absolute=3.500 " + + "global_optimization_strategy=[Two-Round using Metadata to align unconnected " + + "Tiles and iterative dropping of bad links] " + + "show_expert_grouping_options " + + use_angle + + " " + + use_channel + + " " + + use_illumination + + " " + + use_timepoint + + " " + + use_tile + + " " + + "how_to_treat_angles=" + + treat_angles + + " " + + "how_to_treat_channels=" + + treat_channels + + " " + + "how_to_treat_illuminations=" + + treat_illuminations + + " " + + "how_to_treat_tiles=" + + treat_tiles + + " " + + "how_to_treat_timepoints=" + + treat_timepoints, + ) + + def run_detect_interest_points( project_path, process_timepoint="All Timepoints", @@ -445,3 +761,159 @@ def run_fusion( + "]", ) return + + +def run_2steps_fusion( + project_path, + input_dict, + result_path=None, + downsampling=1, + interpolation="[Linear Interpolation]", + pixel_type="[16-bit unsigned integer]", + export="HDF5", +): + file_info = pathtools.parse_path(project_path) + if not result_path: + result_path = pathtools.join2(file_info["path"], file_info["basename"]) + if not os.path.exists(result_path): + os.makedirs(result_path) + + pathtools.join2(result_path, file_info["basename"] + ".h5") + pathtools.join2(result_path, file_info["basename"] + ".xml") + + h5_fused_path_temp = pathtools.join2( + result_path, file_info["basename"] + "_temp.h5" + ) + xml_fused_path_temp = pathtools.join2( + result_path, file_info["basename"] + "_temp.xml" + ) + + options_dict = parse_options(input_dict) + + IJ.run( + "Fuse dataset...", + "select=[" + + project_path + + "] " + + "process_angle=" + + options_dict["angle_text"] + + "process_channel=" + + options_dict["channel_text"] + + "process_illumination=" + + options_dict["illumination_text"] + + "process_tile=" + + options_dict["tile_text"] + + "process_timepoint=" + + options_dict["timepoint_text"] + + "bouding_box=[All Views] " + + "downsampling=" + + str(downsampling) + + " " + + "interpolation=" + + interpolation + + " " + + "pixel_type=" + + pixel_type + + " " + + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " + + "blend preserve_original produce=[Each timepoint & channel] " + + "fused_image=[ZARR/N5/HDF5 export using N5-API] " + + "define_input=[Auto-load from input data (values shown below)] " + + "export=" + + export + + " " + + "create " + + "create_0 " + + "hdf5_file=[" + + h5_fused_path_temp + + "] " + + "xml_output_file=[" + + xml_fused_path_temp + + "] " + + "show_advanced_block_size_options " + + "block_size_x=128 " + + "block_size_y=128 " + + "block_size_z=64 " + + "block_size_factor_x=1 " + + "block_size_factor_y=1 " + + "block_size_factor_z=1", + ) + + # IJ.run( + # "As HDF5 ...", + # "select=[" + # + h5_fused_path_temp + # + "] " + # + "resave_angle=[All angles] " + # + "resave_channel=[All channels] " + # + "resave_illumination=[All illuminations] " + # + "resave_tile=[All tiles] " + # + "resave_timepoint=[All Timepoints] " + # + "timepoints_per_partition=1 " + # + "setups_per_partition=0 " + # + "use_deflate_compression " + # + "export_path=[" + # + xml_fused_path + # + "]", + # ) + + +def parse_options(input_dict): + output_dict = {} + + if input_dict["process_channel"] or "process_channel" in input_dict: + output_dict["channel_text"], output_dict["channel_select"] = ( + "[Single channel (Select from List)] ", + "processing_channel=[channel " + + str(input_dict["process_channel"] - 1) + + "] ", + ) + else: + output_dict["channel_text"], output_dict["channel_select"] = ( + "[All channels] ", + "", + ) + + if input_dict["process_illumination"] or "process_illumination" in input_dict: + output_dict["illumination_text"], output_dict["illumination_select"] = ( + "[Single illumination (Select from List)] ", + "processing_illumination=[illumination " + + str(input_dict["process_illumination"] - 1) + + "] ", + ) + else: + output_dict["illumination_text"], output_dict["illumination_select"] = ( + "[All illuminations] ", + "", + ) + + if input_dict["process_tile"] or "process_tile" in input_dict: + output_dict["tile_text"], output_dict["tile_select"] = ( + "[Single tile (Select from List)] ", + "processing_tile=[tile " + str(input_dict["process_tile"] - 1) + "] ", + ) + else: + output_dict["tile_text"], output_dict["tile_select"] = ("[All tiles] ", "") + + if input_dict["process_timepoint"] or "process_timepoint" in input_dict: + output_dict["timepoint_text"], output_dict["timepoint_select"] = ( + "[Single timepoint (Select from List)] ", + "processing_timepoint=[timepoint " + + str(input_dict["process_timepoint"] - 1) + + "] ", + ) + else: + output_dict["timepoint_text"], output_dict["timepoint_select"] = ( + "[All Timepoints] ", + "", + ) + + if input_dict["process_angle"] or "process_angle" in input_dict: + output_dict["angle_text"], output_dict["angle_select"] = ( + "[Single angle (Select from List)] ", + "processing_angle=[angle " + str(input_dict["process_angle"] - 1) + "] ", + ) + else: + output_dict["angle_text"], output_dict["angle_select"] = ("[All angles] ", "") + + return output_dict From 33ae247e4657d7ae758c31dd5dbbf025e37c918a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Fri, 8 Sep 2023 17:37:58 +0200 Subject: [PATCH 047/678] Working version for Haohao/Francesca still need to clean and further test --- src/imcflibs/imagej/bdv.py | 102 +++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 44 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 6df378bd..d1580c91 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -108,8 +108,10 @@ def run_define_dataset_autoloader( def run_define_dataset_manualoader( project_filename, - file_path, - series_treatment, + source_directory, + image_file_pattern, + dataset_organisation, + file_definition, ): """Run the Define Multi-View Dataset command using the "Manual Loader" option @@ -117,47 +119,57 @@ def run_define_dataset_manualoader( ---------- project_filename : str Name of the project - file_path : str - Path to the file - series_treatment : str - How to treat the series, possible choices are only "Tiles" or "Angles" + source_directory : str + Path to the folder containing the file(s) + image_file_pattern : str + Pattern corresponding to the names of your files separating the + different dimensions + dataset_organisation : str + Organisation of the dataset and the dimensions to process + file_definition : dict + Dictionary containing all the info about the file repartitions """ xml_filename = project_filename + ".xml" - parent_dir = os.path.dirname(file_path) - filename = os.path.basename(file_path) - temp = os.path.join(parent_dir, filename + "_temp") - h5_path = os.path.join(temp, project_filename) + temp = os.path.join(source_directory, project_filename + "_temp") + os.path.join(temp, project_filename) IJ.run( "Define dataset ...", - "define_dataset=[Automatic Loader (Bioformats based)] " + "define_dataset=[Manual Loader (Bioformats based)] " + "project_filename=[" + xml_filename + "] " - + "path=[" - + file_path - + "] " - + "bioformats_series_are?=" - + series_treatment + + "multiple_timepoints=" + + file_definition["multiple_timepoints"] + " " - + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " - + "how_to_load_images=[Re-save as multiresolution HDF5] " - + "dataset_save_path=[" - + temp - + "] " - + "check_stack_sizes " - + "subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,1}, {8,8,2}, {16,16,4} }] " - + "hdf5_chunk_sizes=[{ {32,32,4}, {32,16,8}, {16,16,16}, {32,16,8}, {32,32,4} }] " - + "timepoints_per_partition=1 " - + "setups_per_partition=0 " - + "use_deflate_compression " - + "export_path=[" - + h5_path - + "]", + + "multiple_channels=" + + file_definition["multiple_channels"] + + " " + + "multiple_illumination_directions=" + + file_definition["multiple_illuminations"] + + " " + + "multiple_angles=" + + file_definition["multiple_angles"] + + " " + + "multiple_tiles=" + + file_definition["multiple_tiles"] + + " " + + "image_file_directory=" + + source_directory + + " " + + "image_file_pattern=" + + image_file_pattern + + dataset_organisation + + " " + + "calibration_type=[Same voxel-size for all views] " + + "calibration_definition=[Load voxel-size(s) from file(s)] " + + "imglib2_data_container=[ArrayImg (faster)]", ) + return + def run_resave_as_h5( source_xml_file, @@ -280,6 +292,8 @@ def run_phase_correlation_pairwise_shifts_calculation( """ options_dict = parse_options(input_dict) + print(options_dict) + use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" use_channel = "channels=[Average Channels]" if treat_channels == "group" else "" use_illumination = ( @@ -778,20 +792,20 @@ def run_2steps_fusion( if not os.path.exists(result_path): os.makedirs(result_path) - pathtools.join2(result_path, file_info["basename"] + ".h5") - pathtools.join2(result_path, file_info["basename"] + ".xml") + pathtools.join2(result_path, file_info["basename"] + ".h5") + pathtools.join2(result_path, file_info["basename"] + ".xml") - h5_fused_path_temp = pathtools.join2( - result_path, file_info["basename"] + "_temp.h5" - ) - xml_fused_path_temp = pathtools.join2( - result_path, file_info["basename"] + "_temp.xml" - ) + h5_fused_path_temp = pathtools.join2( + result_path, file_info["basename"] + "_fused.h5" + ) + xml_fused_path_temp = pathtools.join2( + result_path, file_info["basename"] + "_fused.xml" + ) options_dict = parse_options(input_dict) IJ.run( - "Fuse dataset...", + "Fuse dataset ...", "select=[" + project_path + "] " @@ -861,7 +875,7 @@ def run_2steps_fusion( def parse_options(input_dict): output_dict = {} - if input_dict["process_channel"] or "process_channel" in input_dict: + if "process_channel" in input_dict: output_dict["channel_text"], output_dict["channel_select"] = ( "[Single channel (Select from List)] ", "processing_channel=[channel " @@ -874,7 +888,7 @@ def parse_options(input_dict): "", ) - if input_dict["process_illumination"] or "process_illumination" in input_dict: + if "process_illumination" in input_dict: output_dict["illumination_text"], output_dict["illumination_select"] = ( "[Single illumination (Select from List)] ", "processing_illumination=[illumination " @@ -887,7 +901,7 @@ def parse_options(input_dict): "", ) - if input_dict["process_tile"] or "process_tile" in input_dict: + if "process_tile" in input_dict: output_dict["tile_text"], output_dict["tile_select"] = ( "[Single tile (Select from List)] ", "processing_tile=[tile " + str(input_dict["process_tile"] - 1) + "] ", @@ -895,7 +909,7 @@ def parse_options(input_dict): else: output_dict["tile_text"], output_dict["tile_select"] = ("[All tiles] ", "") - if input_dict["process_timepoint"] or "process_timepoint" in input_dict: + if "process_timepoint" in input_dict: output_dict["timepoint_text"], output_dict["timepoint_select"] = ( "[Single timepoint (Select from List)] ", "processing_timepoint=[timepoint " @@ -908,7 +922,7 @@ def parse_options(input_dict): "", ) - if input_dict["process_angle"] or "process_angle" in input_dict: + if "process_angle" in input_dict: output_dict["angle_text"], output_dict["angle_select"] = ( "[Single angle (Select from List)] ", "processing_angle=[angle " + str(input_dict["process_angle"] - 1) + "] ", From 7be844efb658f20551d6cc19bb71bee70af85d01 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 12 Sep 2023 16:08:29 +0200 Subject: [PATCH 048/678] Add duplicate transformation method both for tiles and channels --- src/imcflibs/imagej/bdv.py | 63 +++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index d1580c91..dbabc481 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -8,6 +8,7 @@ # Some function names just need to be longer than 30 chars: # pylint: disable-msg=invalid-name import os +import sys from ij import IJ # pylint: disable-msg=import-error @@ -674,7 +675,13 @@ def run_interest_points_registration( return -def run_duplicate_channel_transformations(project_path, channel_source): +def run_duplicate_transformations( + project_path, + transformation_type="channel", + channel_source=None, + tile_source=None, + transformation_to_use="[Replace all transformations]", +): """Duplicate the transformation parameters to the other channels. If registration has been generated using a single channel,this can be used @@ -686,22 +693,64 @@ def run_duplicate_channel_transformations(project_path, channel_source): Path to the .xml project channel_source : str Specify the channel name + """ + + tile_apply = "" + tile_process = "" + + chnl_apply = "" + chnl_process = "" + + if transformation_type == "channel": + apply = "[One channel to other channels]" + target = "[All Channels]" + if channel_source: + 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) + "] " + else: + tile_apply = "apply_to_tile=[All tiles] " + elif transformation_type == "tile": + apply = "[One tile to other tiles]" + target = "[All Tiles]" + if tile_source: + source = str(tile_source) + if channel_source: + chnl_apply = "apply_to_channel=[Single channel (Select from List)] " + chnl_process = ( + "processing_channel=[channel " + str(channel_source - 1) + "] " + ) + else: + chnl_apply = "[All Channels] " + else: + sys.exit("Issue with transformation duplication") + IJ.run( "Duplicate Transformations", - "apply=[One channel to other channels] " + "apply=" + + apply + + " " + "select=[" + project_path + "] " + "apply_to_angle=[All angles] " + "apply_to_illumination=[All illuminations] " - + "apply_to_tile=[All tiles] " + + tile_apply + + tile_process + + chnl_apply + + chnl_process + "apply_to_timepoint=[All Timepoints] " + "source=" - + channel_source + + source + + " " + + "target=" + + target + " " - + "target=[All Channels] " - + "duplicate_which_transformations=[Replace all transformations]", + + "duplicate_which_transformations=" + + transformation_to_use + + " ", ) return @@ -777,7 +826,7 @@ def run_fusion( return -def run_2steps_fusion( +def run_fusion2( project_path, input_dict, result_path=None, From 5f82b0f860b88b2d8cdf74c91b4f9736bada8740 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 14 Sep 2023 11:22:39 +0200 Subject: [PATCH 049/678] Add Kai's backup method and distribute fusion based on output format Thanks @schlda00 for the reminder that fusion to TIFF is also possible ^^' --- src/imcflibs/imagej/bdv.py | 200 +++++++++++++++++++++++++++++++++---- 1 file changed, 178 insertions(+), 22 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index dbabc481..90c42835 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -15,6 +15,30 @@ from .. import pathtools +def backup_xml_files(source_directory, subfolder_name): + """Copy all .xml (and .xml~) files to a subfolder inside + a folder called "xml-backup" in the source dir. + Will create a the folders if they don't exists. + Uses shutil.copy2 which will overwrite existing files. + + Parameters + ---------- + source_directory : str + full path to the directory containing the xml files + subfolder_name : str + name of the subfolder. Will be created if it does not exists. + """ + + xml_backup_directory = source_directory + "/xml-backup" + create_directory(xml_backup_directory) + backup_subfolder = xml_backup_directory + "/%s" % (subfolder_name) + create_directory(backup_subfolder) + all_xml_files = list_all_filenames(source_directory, ".xml*") + os.chdir(source_directory) + for xml_file in all_xml_files: + shutil.copy2(xml_file, backup_subfolder) + + def run_define_dataset_autoloader( project_filename, file_path, @@ -360,6 +384,9 @@ def run_phase_correlation_pairwise_shifts_calculation( + "subpixel_accuracy", ) + backup_xml_files(project_path, "phase_correlation_shift_calculation") + return + def run_filter_pairwise_shifts( project_path, @@ -414,6 +441,9 @@ def run_filter_pairwise_shifts( + str(max_displacement), ) + backup_xml_files(project_path, "filter_pairwise_shifts") + return + def run_optimize_apply_shifts( project_path, @@ -511,6 +541,9 @@ def run_optimize_apply_shifts( + treat_timepoints, ) + backup_xml_files(project_path, "optimize_and_apply_shifts") + return + def run_detect_interest_points( project_path, @@ -752,6 +785,8 @@ def run_duplicate_transformations( + transformation_to_use + " ", ) + + backup_xml_files(project_path, "duplicate_transformation_" + transformation_type) return @@ -826,7 +861,7 @@ def run_fusion( return -def run_fusion2( +def fusion_main( project_path, input_dict, result_path=None, @@ -835,6 +870,147 @@ def run_fusion2( pixel_type="[16-bit unsigned integer]", export="HDF5", ): + """Wrapper method to call the right fusion method. + + Depending on the export type, inputs are different and therefore will + distribute inputs differently. + + Parameters + ---------- + project_path : str + Path of the XML on which to do the fusion. + input_dict : dict + Dictionary containing all the required informations for angles, + channels, illuminations, tiles and timepoints. + result_path : str, optional + Path to store the resulting fused image, by default None. + downsampling : int, optional + Downsampling value to use during fusion, by default 1. + interpolation : str, optional + Interpolation to use during fusion, by default "[Linear Interpolation]". + pixel_type : str, optional + Pixel type to use during fusion, by default "[16-bit unsigned integer]". + export : str, optional + Format of the output fused image, by default "HDF5". + """ + if export == "HDF5": + run_fusion_h5( + project_path, + input_dict, + result_path, + downsampling, + interpolation, + pixel_type, + ) + elif export == "Tiff": + run_fusion_tiff( + project_path, + input_dict, + result_path, + downsampling, + interpolation, + pixel_type, + ) + + +def run_fusion_tiff( + project_path, + input_dict, + result_path, + downsampling, + interpolation, + pixel_type, +): + """Wrapper to BigStitcher > Batch Processing > Fuse Dataset. + Fuse a dataset to Tiff. + + Parameters + ---------- + project_path : str + Path to the XML file. + input_dict : dict + Dictionary containing all the required informations for angles, + channels, illuminations, tiles and timepoints. + result_path : str + Path to store the resulting fused image. + downsampling : int + Downsampling value. + interpolation : str + Type of interpolation to do during fusion. + pixel_type : str + Type of pixel to use for the fusion. + """ + file_info = pathtools.parse_path(project_path) + if not result_path: + result_path = pathtools.join2(file_info["path"], file_info["basename"]) + if not os.path.exists(result_path): + os.makedirs(result_path) + + options_dict = parse_options(input_dict) + + IJ.run( + "Fuse dataset ...", + "select=[" + + project_path + + "] " + + "process_angle=" + + options_dict["angle_text"] + + "process_channel=" + + options_dict["channel_text"] + + "process_illumination=" + + options_dict["illumination_text"] + + "process_tile=" + + options_dict["tile_text"] + + "process_timepoint=" + + options_dict["timepoint_text"] + + "bouding_box=[All Views] " + + "downsampling=" + + str(downsampling) + + " " + + "interpolation=" + + interpolation + + " " + + "pixel_type=" + + pixel_type + + " " + + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " + + "blend preserve_original produce=[Each timepoint & channel] " + + "output_file_directory=[" + + result_path + + "] " + + "filename_addition=[]", + ) + + +def run_fusion_h5( + project_path, + input_dict, + result_path, + downsampling, + interpolation, + pixel_type, +): + """Wrapper to BigStitcher > Batch Processing > Fuse Dataset. + Fuse a dataset to a BDV compatible H5/XML using N5-API. + Create_0 = include pyramids. + Uses NON-default block sizes for H5 on purpose to improve speed. + + Parameters + ---------- + project_path : str + Path to the XML file. + input_dict : dict + Dictionary containing all the required informations for angles, + channels, illuminations, tiles and timepoints. + result_path : str + Path to store the resulting fused image. + downsampling : int + Downsampling value. + interpolation : str + Type of interpolation to do during fusion. + pixel_type : str + Type of pixel to use for the fusion. + """ file_info = pathtools.parse_path(project_path) if not result_path: result_path = pathtools.join2(file_info["path"], file_info["basename"]) @@ -882,9 +1058,7 @@ def run_fusion2( + "blend preserve_original produce=[Each timepoint & channel] " + "fused_image=[ZARR/N5/HDF5 export using N5-API] " + "define_input=[Auto-load from input data (values shown below)] " - + "export=" - + export - + " " + + "export=HDF5 " + "create " + "create_0 " + "hdf5_file=[" @@ -902,24 +1076,6 @@ def run_fusion2( + "block_size_factor_z=1", ) - # IJ.run( - # "As HDF5 ...", - # "select=[" - # + h5_fused_path_temp - # + "] " - # + "resave_angle=[All angles] " - # + "resave_channel=[All channels] " - # + "resave_illumination=[All illuminations] " - # + "resave_tile=[All tiles] " - # + "resave_timepoint=[All Timepoints] " - # + "timepoints_per_partition=1 " - # + "setups_per_partition=0 " - # + "use_deflate_compression " - # + "export_path=[" - # + xml_fused_path - # + "]", - # ) - def parse_options(input_dict): output_dict = {} From 7743d31e9232b58c247b6441523ce7bdfae1051e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 14 Sep 2023 13:56:08 +0200 Subject: [PATCH 050/678] Add method from Kai, use makedirs to allow intermediate folder creation --- src/imcflibs/pathtools.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index e169f4bc..37839e6d 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -325,6 +325,18 @@ def folder_size(source): return total_size +def create_directory(new_path): + """create a new directory if it does not already exist + + Parameters + ---------- + new_path : str + Path to the new directory + """ + if not os.path.exists(new_path): + os.makedirs(new_path) + + # pylint: disable-msg=C0103 # we use the variable name 'exists' in its common spelling (lowercase), so # removing this workaround will be straightforward at a later point From 077572a78a3f48c5949e0d0cbeb6ab64016407a1 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 14 Sep 2023 14:03:17 +0200 Subject: [PATCH 051/678] Call available method and fix missing import --- src/imcflibs/imagej/bdv.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 90c42835..7067eacd 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -9,6 +9,7 @@ # pylint: disable-msg=invalid-name import os import sys +import shutil from ij import IJ # pylint: disable-msg=import-error @@ -29,11 +30,11 @@ def backup_xml_files(source_directory, subfolder_name): name of the subfolder. Will be created if it does not exists. """ - xml_backup_directory = source_directory + "/xml-backup" - create_directory(xml_backup_directory) + xml_backup_directory = os.path.join(source_directory, "xml-backup") + pathtools.create_directory(xml_backup_directory) backup_subfolder = xml_backup_directory + "/%s" % (subfolder_name) - create_directory(backup_subfolder) - all_xml_files = list_all_filenames(source_directory, ".xml*") + pathtools.create_directory(backup_subfolder) + all_xml_files = pathtools.listdir_matching(source_directory, ".xml*") os.chdir(source_directory) for xml_file in all_xml_files: shutil.copy2(xml_file, backup_subfolder) From b84b10568bdb73cd8dd05f95f9fc4562183e6771 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 14 Sep 2023 14:16:28 +0200 Subject: [PATCH 052/678] Add all input as options that can be printed if in debug mode --- src/imcflibs/imagej/bdv.py | 383 +++++++++++++++---------------------- 1 file changed, 149 insertions(+), 234 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7067eacd..6eaee59b 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -15,6 +15,8 @@ from .. import pathtools +from .log import LOG as log + def backup_xml_files(source_directory, subfolder_name): """Copy all .xml (and .xml~) files to a subfolder inside @@ -96,8 +98,7 @@ def run_define_dataset_autoloader( else: hdf5_chunk_sizes = " " - IJ.run( - "Define Multi-View Dataset", + options = ( "define_dataset=[Automatic Loader (Bioformats based)] " + "project_filename=[" + project_filename @@ -131,6 +132,10 @@ def run_define_dataset_autoloader( + "]", ) + log.debug(options) + IJ.run("Define Multi-View Dataset", options) + return + def run_define_dataset_manualoader( project_filename, @@ -161,8 +166,7 @@ def run_define_dataset_manualoader( temp = os.path.join(source_directory, project_filename + "_temp") os.path.join(temp, project_filename) - IJ.run( - "Define dataset ...", + options = ( "define_dataset=[Manual Loader (Bioformats based)] " + "project_filename=[" + xml_filename @@ -194,6 +198,9 @@ def run_define_dataset_manualoader( + "imglib2_data_container=[ArrayImg (faster)]", ) + log.debug(options) + IJ.run("Define dataset ...", options) + return @@ -262,8 +269,7 @@ def run_resave_as_h5( else: hdf5_chunk_sizes = " " - IJ.run( - "As HDF5", + options = ( "select=" + str(source_xml_file) + " " @@ -284,6 +290,9 @@ def run_resave_as_h5( + output_h5_file_path, ) + log.debug(options) + IJ.run("As HDF5", options) + return @@ -332,8 +341,7 @@ def run_phase_correlation_pairwise_shifts_calculation( ) use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" - IJ.run( - "Calculate pairwise shifts ...", + options = ( "select=[" + project_path + "] " @@ -385,6 +393,9 @@ def run_phase_correlation_pairwise_shifts_calculation( + "subpixel_accuracy", ) + log.debug(options) + IJ.run("Calculate pairwise shifts ...", options) + backup_xml_files(project_path, "phase_correlation_shift_calculation") return @@ -417,8 +428,8 @@ def run_filter_pairwise_shifts( max_displacement : int, optional Maximal displacement to keep, by default 0 """ - IJ.run( - "Filter pairwise shifts ...", + + options = ( "select=[" + project_path + "] " @@ -442,6 +453,9 @@ def run_filter_pairwise_shifts( + str(max_displacement), ) + log.debug(options) + IJ.run("Filter pairwise shifts ...", options) + backup_xml_files(project_path, "filter_pairwise_shifts") return @@ -489,8 +503,7 @@ def run_optimize_apply_shifts( ) use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" - IJ.run( - "Optimize globally and apply shifts ...", + options = ( "select=[" + project_path + "] " @@ -542,6 +555,9 @@ def run_optimize_apply_shifts( + treat_timepoints, ) + log.debug(options) + IJ.run("Optimize globally and apply shifts ...", options) + backup_xml_files(project_path, "optimize_and_apply_shifts") return @@ -594,8 +610,7 @@ def run_detect_interest_points( + "] " ) - IJ.run( - "Detect Interest Points for Registration", + options = ( "select=[" + project_path + "] " @@ -629,6 +644,9 @@ def run_detect_interest_points( + "type_of_detections_to_use=Brightest " + "compute_on=[CPU (Java)]", ) + + log.debug(options) + IJ.run("Detect Interest Points for Registration", options) return @@ -671,9 +689,7 @@ def run_interest_points_registration( else: rigid_timepoints_arg = " " - # register using interest points - IJ.run( - "Register Dataset based on Interest Points", + options = ( "select=[" + project_path + "] " @@ -706,6 +722,10 @@ def run_interest_points_registration( + "interestpoint_grouping=[Group interest points (simply combine all in one virtual view)] " + "interest=5", ) + + log.debug(options) + # register using interest points + IJ.run("Register Dataset based on Interest Points", options) return @@ -761,8 +781,7 @@ def run_duplicate_transformations( else: sys.exit("Issue with transformation duplication") - IJ.run( - "Duplicate Transformations", + options = ( "apply=" + apply + " " @@ -787,82 +806,86 @@ def run_duplicate_transformations( + " ", ) + log.debug(options) + IJ.run("Duplicate Transformations", options) + backup_xml_files(project_path, "duplicate_transformation_" + transformation_type) return -def run_fusion( - temp_path, - fused_xml_path, - process_timepoint="All Timepoints", - downsampling=1, - ram_handling="Virtual", - save_format="Save as new XML Project (HDF5)", - additional_option="", -): - """run the image fusion command +# def run_fusion( +# temp_path, +# fused_xml_path, +# process_timepoint="All Timepoints", +# downsampling=1, +# ram_handling="Virtual", +# save_format="Save as new XML Project (HDF5)", +# additional_option="", +# ): +# """run the image fusion command + +# Parameters +# ---------- +# temp_path : str +# temporary folder output (scratch) +# fused_xml_path : str +# final xml output path +# process_timepoint : str, optional +# Specify which timepoint should be processed, by default "All Timepoints" +# downsampling : int, optional +# Downsampling factor to use during the fusion, by default 1 (no +# downsampling) +# ram_handling : str, optional +# Which type of ram_handling do you require, by default "Virtual". This is +# the conservative (not likely to fail even if only a low amount of RAM is +# available) and slow approach +# save_format : str, optional +# file format of the new image, by default "Save as new XML Project +# (HDF5)", this is matching the conservative and slow approach +# additional_option : str, optional="" +# Any additional options that should be added to the fusion command. Do +# not forget to finish each additional option with a space +# """ +# # Add preserve original data anisotropy tickbox: +# # tiles=> true; angle=>False + +# options = ( +# "select=[" +# + temp_path +# + "] " +# + "process_angle=[All angles] " +# + "process_channel=[All channels] " +# + "process_illumination=[" +# + process_timepoint +# + "] " +# + "process_tile=[All tiles] " +# + "process_timepoint=[All Timepoints] " +# + "bounding_box=[Currently Selected Views] " +# + "downsampling=" +# + str(downsampling) +# + " " +# + "pixel_type=[16-bit unsigned integer] " +# + "interpolation=[Linear Interpolation] " +# + "image=[" +# + ram_handling +# + "] " +# + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " +# + "blend " +# + additional_option +# + "produce=[Each timepoint & channel] " +# + "fused_image=[" +# + save_format +# + "] " +# + "export_path=[" +# + fused_xml_path +# + "]", +# ) + +# IJ.run("Fuse dataset ...", options) +# return - Parameters - ---------- - temp_path : str - temporary folder output (scratch) - fused_xml_path : str - final xml output path - process_timepoint : str, optional - Specify which timepoint should be processed, by default "All Timepoints" - downsampling : int, optional - Downsampling factor to use during the fusion, by default 1 (no - downsampling) - ram_handling : str, optional - Which type of ram_handling do you require, by default "Virtual". This is - the conservative (not likely to fail even if only a low amount of RAM is - available) and slow approach - save_format : str, optional - file format of the new image, by default "Save as new XML Project - (HDF5)", this is matching the conservative and slow approach - additional_option : str, optional="" - Any additional options that should be added to the fusion command. Do - not forget to finish each additional option with a space - """ - # Add preserve original data anisotropy tickbox: - # tiles=> true; angle=>False - IJ.run( - "Fuse dataset ...", - "select=[" - + temp_path - + "] " - + "process_angle=[All angles] " - + "process_channel=[All channels] " - + "process_illumination=[" - + process_timepoint - + "] " - + "process_tile=[All tiles] " - + "process_timepoint=[All Timepoints] " - + "bounding_box=[Currently Selected Views] " - + "downsampling=" - + str(downsampling) - + " " - + "pixel_type=[16-bit unsigned integer] " - + "interpolation=[Linear Interpolation] " - + "image=[" - + ram_handling - + "] " - + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " - + "blend " - + additional_option - + "produce=[Each timepoint & channel] " - + "fused_image=[" - + save_format - + "] " - + "export_path=[" - + fused_xml_path - + "]", - ) - return - - -def fusion_main( +def run_fusion( project_path, input_dict, result_path=None, @@ -871,7 +894,7 @@ def fusion_main( pixel_type="[16-bit unsigned integer]", export="HDF5", ): - """Wrapper method to call the right fusion method. + """Wrapper to BigStitcher > Batch Processing > Fuse Dataset. Depending on the export type, inputs are different and therefore will distribute inputs differently. @@ -894,53 +917,7 @@ def fusion_main( export : str, optional Format of the output fused image, by default "HDF5". """ - if export == "HDF5": - run_fusion_h5( - project_path, - input_dict, - result_path, - downsampling, - interpolation, - pixel_type, - ) - elif export == "Tiff": - run_fusion_tiff( - project_path, - input_dict, - result_path, - downsampling, - interpolation, - pixel_type, - ) - - -def run_fusion_tiff( - project_path, - input_dict, - result_path, - downsampling, - interpolation, - pixel_type, -): - """Wrapper to BigStitcher > Batch Processing > Fuse Dataset. - Fuse a dataset to Tiff. - Parameters - ---------- - project_path : str - Path to the XML file. - input_dict : dict - Dictionary containing all the required informations for angles, - channels, illuminations, tiles and timepoints. - result_path : str - Path to store the resulting fused image. - downsampling : int - Downsampling value. - interpolation : str - Type of interpolation to do during fusion. - pixel_type : str - Type of pixel to use for the fusion. - """ file_info = pathtools.parse_path(project_path) if not result_path: result_path = pathtools.join2(file_info["path"], file_info["basename"]) @@ -949,8 +926,7 @@ def run_fusion_tiff( options_dict = parse_options(input_dict) - IJ.run( - "Fuse dataset ...", + options = ( "select=[" + project_path + "] " @@ -976,106 +952,44 @@ def run_fusion_tiff( + " " + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " + "blend preserve_original produce=[Each timepoint & channel] " - + "output_file_directory=[" - + result_path - + "] " - + "filename_addition=[]", ) + if export == "TIFF": + options += ( + "output_file_directory=[" + result_path + "] " + "filename_addition=[]", + ) + elif export == "HDF5": + h5_fused_path = pathtools.join2( + result_path, file_info["basename"] + "_fused.h5" + ) + xml_fused_path = pathtools.join2( + result_path, file_info["basename"] + "_fused.xml" + ) -def run_fusion_h5( - project_path, - input_dict, - result_path, - downsampling, - interpolation, - pixel_type, -): - """Wrapper to BigStitcher > Batch Processing > Fuse Dataset. - Fuse a dataset to a BDV compatible H5/XML using N5-API. - Create_0 = include pyramids. - Uses NON-default block sizes for H5 on purpose to improve speed. - - Parameters - ---------- - project_path : str - Path to the XML file. - input_dict : dict - Dictionary containing all the required informations for angles, - channels, illuminations, tiles and timepoints. - result_path : str - Path to store the resulting fused image. - downsampling : int - Downsampling value. - interpolation : str - Type of interpolation to do during fusion. - pixel_type : str - Type of pixel to use for the fusion. - """ - file_info = pathtools.parse_path(project_path) - if not result_path: - result_path = pathtools.join2(file_info["path"], file_info["basename"]) - if not os.path.exists(result_path): - os.makedirs(result_path) - - pathtools.join2(result_path, file_info["basename"] + ".h5") - pathtools.join2(result_path, file_info["basename"] + ".xml") - - h5_fused_path_temp = pathtools.join2( - result_path, file_info["basename"] + "_fused.h5" - ) - xml_fused_path_temp = pathtools.join2( - result_path, file_info["basename"] + "_fused.xml" - ) - - options_dict = parse_options(input_dict) + options += ( + +"fused_image=[ZARR/N5/HDF5 export using N5-API] " + + "define_input=[Auto-load from input data (values shown below)] " + + "export=HDF5 " + + "create " + + "create_0 " + + "hdf5_file=[" + + h5_fused_path + + "] " + + "xml_output_file=[" + + xml_fused_path + + "] " + + "show_advanced_block_size_options " + + "block_size_x=128 " + + "block_size_y=128 " + + "block_size_z=64 " + + "block_size_factor_x=1 " + + "block_size_factor_y=1 " + + "block_size_factor_z=1", + ) - IJ.run( - "Fuse dataset ...", - "select=[" - + project_path - + "] " - + "process_angle=" - + options_dict["angle_text"] - + "process_channel=" - + options_dict["channel_text"] - + "process_illumination=" - + options_dict["illumination_text"] - + "process_tile=" - + options_dict["tile_text"] - + "process_timepoint=" - + options_dict["timepoint_text"] - + "bouding_box=[All Views] " - + "downsampling=" - + str(downsampling) - + " " - + "interpolation=" - + interpolation - + " " - + "pixel_type=" - + pixel_type - + " " - + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " - + "blend preserve_original produce=[Each timepoint & channel] " - + "fused_image=[ZARR/N5/HDF5 export using N5-API] " - + "define_input=[Auto-load from input data (values shown below)] " - + "export=HDF5 " - + "create " - + "create_0 " - + "hdf5_file=[" - + h5_fused_path_temp - + "] " - + "xml_output_file=[" - + xml_fused_path_temp - + "] " - + "show_advanced_block_size_options " - + "block_size_x=128 " - + "block_size_y=128 " - + "block_size_z=64 " - + "block_size_factor_x=1 " - + "block_size_factor_y=1 " - + "block_size_factor_z=1", - ) + log.debug(options) + IJ.run("Fuse dataset ...", options) + return def parse_options(input_dict): @@ -1136,4 +1050,5 @@ def parse_options(input_dict): else: output_dict["angle_text"], output_dict["angle_select"] = ("[All angles] ", "") + log.debug(output_dict) return output_dict From 2ec29edc975d0d5bfd504b4240964143bb36ee85 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 20 Sep 2023 16:27:16 +0200 Subject: [PATCH 053/678] Add method to flip axes copied from Kai --- src/imcflibs/imagej/bdv.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 6eaee59b..9cb53d76 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -296,6 +296,35 @@ def run_resave_as_h5( return +def run_flip_axes(source_xml_file, x=False, y=True, z=False): + """Wrapper for BigStitcher > Batch Processing > Tools > Flip axes. + For example, nd2 files require a flip along the y axis. + + Parameters + ---------- + h5_resave_xml_path : str + full path to the .xml-file + x : bool, optional + flip images along the x axes, by default False + y : bool, optional + flip mages along the axes, by default True + z : bool, optional + flip images along the z axes, by default False + """ + + file_info = pathtools.parse_path(source_xml_file) + + axes_to_flip = "" + if x is True: + axes_to_flip += " flip_x" + if y is True: + axes_to_flip += " flip_y" + if z is True: + axes_to_flip += " flip_z" + + IJ.run("Flip Axes", "select=" + source_xml_file + axes_to_flip) + + backup_xml_files(file_info["path"], "flip_axes") def run_phase_correlation_pairwise_shifts_calculation( project_path, input_dict, From bf7f6a1211f20807db14d772aa0d5cc6c05de40c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 20 Sep 2023 16:29:01 +0200 Subject: [PATCH 054/678] Fix issues with options and path --- src/imcflibs/imagej/bdv.py | 67 ++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9cb53d76..a7619fd9 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -80,9 +80,9 @@ def run_define_dataset_autoloader( """ file_info = pathtools.parse_path(file_path) - result_folder = pathtools.join2(file_info["path"], project_filename) project_filename = project_filename.replace(" ", "_") + result_folder = pathtools.join2(file_info["path"], project_filename) if not os.path.exists(result_folder): os.makedirs(result_folder) @@ -199,7 +199,7 @@ def run_define_dataset_manualoader( ) log.debug(options) - IJ.run("Define dataset ...", options) + IJ.run("Define dataset ...", str(options)) return @@ -291,7 +291,7 @@ def run_resave_as_h5( ) log.debug(options) - IJ.run("As HDF5", options) + IJ.run("As HDF5", str(options)) return @@ -354,6 +354,9 @@ def run_phase_correlation_pairwise_shifts_calculation( treat_tiles : str, optional How to deal with the tiles, by default "group" """ + + file_info = pathtools.parse_path(project_path) + options_dict = parse_options(input_dict) print(options_dict) @@ -423,9 +426,9 @@ def run_phase_correlation_pairwise_shifts_calculation( ) log.debug(options) - IJ.run("Calculate pairwise shifts ...", options) + IJ.run("Calculate pairwise shifts ...", str(options)) - backup_xml_files(project_path, "phase_correlation_shift_calculation") + backup_xml_files(file_info["path"], "phase_correlation_shift_calculation") return @@ -458,6 +461,8 @@ def run_filter_pairwise_shifts( Maximal displacement to keep, by default 0 """ + file_info = pathtools.parse_path(project_path) + options = ( "select=[" + project_path @@ -479,13 +484,13 @@ def run_filter_pairwise_shifts( + str(max_shift_z) + " " + "max_displacement=" - + str(max_displacement), + + str(max_displacement) ) log.debug(options) - IJ.run("Filter pairwise shifts ...", options) + IJ.run("Filter pairwise shifts ...", str(options)) - backup_xml_files(project_path, "filter_pairwise_shifts") + backup_xml_files(file_info["path"], "filter_pairwise_shifts") return @@ -518,6 +523,9 @@ def run_optimize_apply_shifts( treat_tiles : str, optional How to treat the tiles, by default "group" """ + + file_info = pathtools.parse_path(project_path) + options_dict = parse_options(input_dict) use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" @@ -581,13 +589,13 @@ def run_optimize_apply_shifts( + treat_tiles + " " + "how_to_treat_timepoints=" - + treat_timepoints, + + treat_timepoints ) log.debug(options) - IJ.run("Optimize globally and apply shifts ...", options) + IJ.run("Optimize globally and apply shifts ...", str(options)) - backup_xml_files(project_path, "optimize_and_apply_shifts") + backup_xml_files(file_info["path"], "optimize_and_apply_shifts") return @@ -671,11 +679,11 @@ def run_detect_interest_points( + str(maximum_number) + " " + "type_of_detections_to_use=Brightest " - + "compute_on=[CPU (Java)]", + + "compute_on=[CPU (Java)]" ) log.debug(options) - IJ.run("Detect Interest Points for Registration", options) + IJ.run("Detect Interest Points for Registration", str(options)) return @@ -779,6 +787,8 @@ def run_duplicate_transformations( """ + file_info = pathtools.parse_path(project_path) + tile_apply = "" tile_process = "" @@ -832,13 +842,15 @@ def run_duplicate_transformations( + " " + "duplicate_which_transformations=" + transformation_to_use - + " ", + + " " ) log.debug(options) - IJ.run("Duplicate Transformations", options) + IJ.run("Duplicate Transformations", str(options)) - backup_xml_files(project_path, "duplicate_transformation_" + transformation_type) + backup_xml_files( + file_info["path"], "duplicate_transformation_" + transformation_type + ) return @@ -949,9 +961,9 @@ def run_fusion( file_info = pathtools.parse_path(project_path) if not result_path: - result_path = pathtools.join2(file_info["path"], file_info["basename"]) - if not os.path.exists(result_path): - os.makedirs(result_path) + result_path = file_info["path"] + # if not os.path.exists(result_path): + # os.makedirs(result_path) options_dict = parse_options(input_dict) @@ -984,8 +996,12 @@ def run_fusion( ) if export == "TIFF": - options += ( - "output_file_directory=[" + result_path + "] " + "filename_addition=[]", + options = ( + options + + "output_file_directory=[" + + result_path + + "] " + + "filename_addition=[]" ) elif export == "HDF5": h5_fused_path = pathtools.join2( @@ -995,8 +1011,9 @@ def run_fusion( result_path, file_info["basename"] + "_fused.xml" ) - options += ( - +"fused_image=[ZARR/N5/HDF5 export using N5-API] " + options = ( + options + + "fused_image=[ZARR/N5/HDF5 export using N5-API] " + "define_input=[Auto-load from input data (values shown below)] " + "export=HDF5 " + "create " @@ -1013,11 +1030,11 @@ def run_fusion( + "block_size_z=64 " + "block_size_factor_x=1 " + "block_size_factor_y=1 " - + "block_size_factor_z=1", + + "block_size_factor_z=1" ) log.debug(options) - IJ.run("Fuse dataset ...", options) + IJ.run("Fuse dataset ...", str(options)) return From 9803dec2f554b7dae13b3775275804c2dbc34adc Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 20 Sep 2023 17:02:56 +0200 Subject: [PATCH 055/678] Update the input with latest update --- src/imcflibs/imagej/bdv.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index a7619fd9..62cbfc11 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -92,11 +92,16 @@ def run_define_dataset_autoloader( if subsampling_factors: subsampling_factors = "subsampling_factors=" + subsampling_factors + " " else: - subsampling_factors = " " + subsampling_factors = "manual_mipmap_setup subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,1}, {8,8,2}, {16,16,4} }] " if hdf5_chunk_sizes: hdf5_chunk_sizes = "hdf5_chunk_sizes=" + hdf5_chunk_sizes + " " else: - hdf5_chunk_sizes = " " + hdf5_chunk_sizes = "hdf5_chunk_sizes=[{ {32,32,4}, {32,16,8}, {16,16,16}, {32,16,8}, {32,32,4} }] " + + if bf_series_type == "Angles": + angle_rotation = "apply_angle_rotation " + else: + angle_rotation = "" options = ( "define_dataset=[Automatic Loader (Bioformats based)] " @@ -105,12 +110,12 @@ def run_define_dataset_autoloader( + ".xml" + "] " + "path=[" - + file_path + + 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=[" + resave @@ -119,21 +124,30 @@ def run_define_dataset_autoloader( + result_folder + "] " + "check_stack_sizes " - + "apply_angle_rotation " + + angle_rotation + subsampling_factors + hdf5_chunk_sizes + + "split_hdf5 " + "timepoints_per_partition=" + str(timepoints_per_partition) + " " + "setups_per_partition=0 " + "use_deflate_compression " - + "export_path=[" - + dataset_save_path - + "]", + # + "export_path=[" + # + dataset_save_path + # + "]", ) log.debug(options) - IJ.run("Define Multi-View Dataset", options) + + if bf_series_type == "Tiles": + log.debug("Doing tiled dataset definition") + IJ.run("Define dataset ...", 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") return From 13ce5403d82341c20e16a5a4b8a68920e74a222f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 20 Sep 2023 17:03:29 +0200 Subject: [PATCH 056/678] Add default value for the input_dict --- src/imcflibs/imagej/bdv.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 62cbfc11..4b9770e2 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -339,9 +339,11 @@ def run_flip_axes(source_xml_file, x=False, y=True, z=False): IJ.run("Flip Axes", "select=" + source_xml_file + axes_to_flip) backup_xml_files(file_info["path"], "flip_axes") + + def run_phase_correlation_pairwise_shifts_calculation( project_path, - input_dict, + input_dict={}, treat_timepoints="group", treat_channels="group", treat_illuminations="group", @@ -510,7 +512,7 @@ def run_filter_pairwise_shifts( def run_optimize_apply_shifts( project_path, - input_dict, + input_dict={}, treat_timepoints="group", treat_channels="group", treat_illuminations="group", @@ -771,7 +773,7 @@ def run_interest_points_registration( + "allowed_error_for_ransac=5 " + "ransac_iterations=Normal " + "interestpoint_grouping=[Group interest points (simply combine all in one virtual view)] " - + "interest=5", + + "interest=5" ) log.debug(options) @@ -942,7 +944,7 @@ def run_duplicate_transformations( def run_fusion( project_path, - input_dict, + input_dict={}, result_path=None, downsampling=1, interpolation="[Linear Interpolation]", From a219b0b54d611c9a63a4f11ff50cb1d58d915d5a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 20 Sep 2023 17:04:20 +0200 Subject: [PATCH 057/678] Fix import for log --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 4b9770e2..cfcb12a2 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -15,7 +15,7 @@ from .. import pathtools -from .log import LOG as log +from ..log import LOG as log def backup_xml_files(source_directory, subfolder_name): From 4a60fd169c08d504b731ffeaca62bbabc132b627 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 20 Sep 2023 17:04:56 +0200 Subject: [PATCH 058/678] Add regex as input for the listdir_matching method --- src/imcflibs/imagej/bdv.py | 2 +- src/imcflibs/pathtools.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index cfcb12a2..f9fb4db5 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -36,7 +36,7 @@ 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*") + 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) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 37839e6d..832ae39e 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -3,13 +3,14 @@ import os.path import platform from os import sep +import re from . import strtools from .log import LOG as log def parse_path(path, prefix=""): - r"""Parse a path into its components. + """Parse a path into its components. If the path doesn't end with the pathsep, it is assumed being a file! No tests based on existing files are done, as this is supposed to also work @@ -156,7 +157,7 @@ def jython_fiji_exists(path): return False -def listdir_matching(path, suffix, fullpath=False, sort=False): +def listdir_matching(path, suffix, fullpath=False, sort=False, regex=False): """Get a list of files in a directory matching a given suffix. Parameters @@ -180,12 +181,17 @@ def listdir_matching(path, suffix, fullpath=False, sort=False): """ matching_files = list() for candidate in os.listdir(path): - if candidate.lower().endswith(suffix.lower()): + if not regex and candidate.lower().endswith(suffix.lower()): # log.debug("Found file %s", candidate) if fullpath: matching_files.append(os.path.join(path, candidate)) else: matching_files.append(candidate) + if regex and re.match(suffix.lower(), candidate.lower()): + if fullpath: + matching_files.append(os.path.join(path, candidate)) + else: + matching_files.append(candidate) if sort: matching_files = strtools.sort_alphanumerically(matching_files) From 84c7b6a7c9f1423f0acc658fb62f0cf9aaebc60d Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 20 Sep 2023 17:21:08 +0200 Subject: [PATCH 059/678] Fix input if filtering by max displacement --- src/imcflibs/imagej/bdv.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f9fb4db5..e5cbe499 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -499,8 +499,9 @@ def run_filter_pairwise_shifts( + "max_shift_in_z=" + str(max_shift_z) + " " - + "max_displacement=" - + str(max_displacement) + + "filter_by_total_shift_magnitude " + if max_displacement + else "" + "max_displacement=" + str(max_displacement) ) log.debug(options) From bc89facb49df99003bc47752ea9510be18977bbd Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 20 Sep 2023 18:10:43 +0200 Subject: [PATCH 060/678] Add parent as an output and update docstring --- src/imcflibs/pathtools.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 832ae39e..2d094e45 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -37,6 +37,7 @@ def parse_path(path, prefix=""): combined with the prefix in case one was specified). - `full` : The same as `orig` with separators adjusted to the current platform. + - `parent` : The parent folder of the selected file. - `path` : The same as `full`, up to (including) the last separator. - `dname` : The segment between the last two separators (directory). - `fname` : The segment after the last separator (filename). @@ -60,6 +61,7 @@ def parse_path(path, prefix=""): 'full': '/tmp/foo/file', 'basename': 'file', 'orig': '/tmp/foo/file', + 'parent': '/tmp/', 'path': '/tmp/foo/'} POSIX-style path to a directory: @@ -71,6 +73,7 @@ def parse_path(path, prefix=""): 'full': '/tmp/foo/', 'basename': '', 'orig': '/tmp/foo/', + 'parent': '/tmp/', 'path': '/tmp/foo/'} Windows-style path to a file: @@ -82,6 +85,7 @@ def parse_path(path, prefix=""): 'full': 'C:/Temp/foo/file.ext', 'basename': 'file', 'orig': 'C:\\Temp\\foo\\file.ext', + 'parent': 'C:/Temp/', 'path': 'C:/Temp/foo/'} Special treatment for *OME-TIFF* suffixes: @@ -93,6 +97,7 @@ def parse_path(path, prefix=""): 'fname': 'nice.OME.tIf', 'full': '/path/to/some/nice.OME.tIf', 'orig': '/path/to/some/nice.OME.tIf', + 'parent': '/path/to/', 'path': '/path/to/some/'} """ path = str(path) @@ -105,7 +110,9 @@ def parse_path(path, prefix=""): parsed["orig"] = path path = path.replace("\\", sep) parsed["full"] = path - parsed["path"] = os.path.dirname(path) + sep + folder = os.path.dirname(path) + parsed["path"] = folder + sep + parsed["parent"] = os.path.dirname(folder) parsed["fname"] = os.path.basename(path) parsed["dname"] = os.path.basename(os.path.dirname(parsed["path"])) base, ext = os.path.splitext(parsed["fname"]) From 192d8119d0f8d62de97adc3b81cfa9b700f32ab2 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Sun, 24 Sep 2023 17:14:57 +0200 Subject: [PATCH 061/678] add relative and absolute alignment errors as optional parameters for shift optimization --- src/imcflibs/imagej/bdv.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index e5cbe499..a2e41a24 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -519,6 +519,8 @@ def run_optimize_apply_shifts( treat_illuminations="group", treat_angles="[treat individually]", treat_tiles="group", + relative_error=2.5, + absolute_error=3.5, ): """Optimize the shifts and apply it to the dataset @@ -539,6 +541,10 @@ def run_optimize_apply_shifts( How to treat the angles, by default "[treat individually]" treat_tiles : str, optional How to treat the tiles, by default "group" + relative_error: str, optional + relative alignment error in px, by default 2.5 + absolute_error: str, optional + absolute alignment error in px, by default 3.5 """ file_info = pathtools.parse_path(project_path) @@ -578,8 +584,12 @@ def run_optimize_apply_shifts( + options_dict["tile_select"] + options_dict["timepoint_select"] + " " - + "relative=2.500 " - + "absolute=3.500 " + + "relative=" + + str(relative_error) + + " " + + "absolute=" + + str(absolute_error) + + " " + "global_optimization_strategy=[Two-Round using Metadata to align unconnected " + "Tiles and iterative dropping of bad links] " + "show_expert_grouping_options " From 60c8c59b1c8df0239a2d1eb8ae7db76f6fa7c4b9 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Sun, 24 Sep 2023 17:35:59 +0200 Subject: [PATCH 062/678] Add optional downsampling when calculating pairwise shifts Higher values speed up pairwise shift calculation. The BigStitcher Wiki suggests to choose 2 or 4. --- src/imcflibs/imagej/bdv.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index a2e41a24..89452851 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -349,6 +349,7 @@ def run_phase_correlation_pairwise_shifts_calculation( treat_illuminations="group", treat_angles="[treat individually]", treat_tiles="group", + downsampling_xyz="", ): """Run the Pairwise shifts calculation using Phase Correlation @@ -369,6 +370,10 @@ def run_phase_correlation_pairwise_shifts_calculation( How to deal with the angles, by default "[treat individually]" treat_tiles : str, optional How to deal with the tiles, by default "group" + downsampling_xyz : list of int, optional + specify downsampling in x,y and z, e.g. [4,4,4], by default empty, + meaning BigStitcher chooses + """ file_info = pathtools.parse_path(project_path) @@ -389,6 +394,9 @@ def run_phase_correlation_pairwise_shifts_calculation( ) use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" + 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]) + options = ( "select=[" + project_path @@ -438,6 +446,7 @@ def run_phase_correlation_pairwise_shifts_calculation( + "how_to_treat_timepoints=" + treat_timepoints + " " + + downsampling + "subpixel_accuracy", ) From c5aa24ae640c2d6cdca04b6320732dd4154073f0 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Sun, 24 Sep 2023 20:26:10 +0200 Subject: [PATCH 063/678] Bugfix: option to filter links explicitly by max shift or displacement --- src/imcflibs/imagej/bdv.py | 39 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 89452851..5ffc2f70 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -461,10 +461,8 @@ def run_filter_pairwise_shifts( project_path, min_r=0.7, max_r=1, - max_shift_x=0, - max_shift_y=0, - max_shift_z=0, - max_displacement=0, + max_shift_xyz="", + max_displacement="", ): """Filter the pairwise shifts based on different thresholds @@ -476,18 +474,22 @@ def run_filter_pairwise_shifts( Minimal quality of the link to keep, by default 0.7 max_r : float, optional Maximal quality of the link to keep, by default 1 - max_shift_x : int, optional - Maximal shift in X to keep, by default 0 - max_shift_y : int, optional - Maximal shift in Y to keep, by default 0 - max_shift_z : int, optional - Maximal shift in Z to keep, by default 0 + max_shift_xyz : list of int, optional + Maximal shift in X, Y and Z in px to keep, e.g. [10,10,10], by default empty, + meaning this option is skipped max_displacement : int, optional - Maximal displacement to keep, by default 0 + Maximal displacement to keep, by default empty, + meaning this option is skipped """ file_info = pathtools.parse_path(project_path) + if max_shift_xyz != "": + filter_by_max_shift = " filter_by_shift_in_each_dimension max_shift_in_x=%s max_shift_in_y=%s max_shift_in_z=%s" % (max_shift_xyz[0], max_shift_xyz[1], max_shift_xyz[2]) + + if max_displacement != "": + filter_by_max_displacement = " filter_by_total_shift_magnitude max_displacement=%s" % (max_displacement) + options = ( "select=[" + project_path @@ -498,19 +500,8 @@ def run_filter_pairwise_shifts( + " " + "max_r=" + str(max_r) - + " " - + "max_shift_in_x=" - + str(max_shift_x) - + " " - + "max_shift_in_y=" - + str(max_shift_y) - + " " - + "max_shift_in_z=" - + str(max_shift_z) - + " " - + "filter_by_total_shift_magnitude " - if max_displacement - else "" + "max_displacement=" + str(max_displacement) + + filter_by_max_shift + + filter_by_max_displacement ) log.debug(options) From 0111e8e30bca883f18b4469296613a032bdc9bfb Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Sun, 24 Sep 2023 20:42:44 +0200 Subject: [PATCH 064/678] fix typo in fusion function --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 5ffc2f70..0eeec099 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1008,7 +1008,7 @@ def run_fusion( + options_dict["tile_text"] + "process_timepoint=" + options_dict["timepoint_text"] - + "bouding_box=[All Views] " + + "bounding_box=[All Views] " + "downsampling=" + str(downsampling) + " " From b17bb163c85c35dc55f4e3af6889efb0f6255faf Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Mon, 25 Sep 2023 10:50:31 +0200 Subject: [PATCH 065/678] string formatting --- src/imcflibs/imagej/bdv.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 0eeec099..940160ce 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -395,7 +395,12 @@ def run_phase_correlation_pairwise_shifts_calculation( use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" 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] + ) options = ( "select=[" @@ -485,10 +490,19 @@ def run_filter_pairwise_shifts( file_info = pathtools.parse_path(project_path) if max_shift_xyz != "": - filter_by_max_shift = " filter_by_shift_in_each_dimension max_shift_in_x=%s max_shift_in_y=%s max_shift_in_z=%s" % (max_shift_xyz[0], max_shift_xyz[1], max_shift_xyz[2]) + filter_by_max_shift = ( + " filter_by_shift_in_each_dimension" + " max_shift_in_x=%s max_shift_in_y=%s max_shift_in_z=%s") % ( + max_shift_xyz[0], + max_shift_xyz[1], + max_shift_xyz[2] + ) if max_displacement != "": - filter_by_max_displacement = " filter_by_total_shift_magnitude max_displacement=%s" % (max_displacement) + filter_by_max_displacement = ( + " filter_by_total_shift_magnitude max_displacement=%s") % ( + max_displacement + ) options = ( "select=[" @@ -529,7 +543,7 @@ def run_optimize_apply_shifts( project_path : str Path of the XML on which to optimize and apply the shifts input_dict : dict - Dictionary containing all the required informations for angles, + Dictionary containing all the required information for angles, channels, illuminations, tiles and timepoints treat_timepoints : str, optional How to treat the timepoints, by default "group" From 4581191fbaf647f3553ac2a4b7e540ad0ab7575c Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Mon, 25 Sep 2023 10:55:22 +0200 Subject: [PATCH 066/678] Bugfix: assign variable in al cases if or else when filtering pairwise shifts --- src/imcflibs/imagej/bdv.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 940160ce..f37a1bb6 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -497,12 +497,16 @@ def run_filter_pairwise_shifts( max_shift_xyz[1], max_shift_xyz[2] ) + else: + filter_by_max_shift = "" if max_displacement != "": filter_by_max_displacement = ( " filter_by_total_shift_magnitude max_displacement=%s") % ( max_displacement ) + else: + filter_by_max_displacement = "" options = ( "select=[" @@ -555,9 +559,9 @@ def run_optimize_apply_shifts( How to treat the angles, by default "[treat individually]" treat_tiles : str, optional How to treat the tiles, by default "group" - relative_error: str, optional + relative_error: float, optional relative alignment error in px, by default 2.5 - absolute_error: str, optional + absolute_error: float, optional absolute alignment error in px, by default 3.5 """ From dd7c88c44be1be045f7cbb62a9d2c133de90b6ee Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Mon, 25 Sep 2023 17:03:32 +0200 Subject: [PATCH 067/678] Bugfix: assign variable in all cases during pairwise shift calculation --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f37a1bb6..099c616c 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -401,6 +401,8 @@ def run_phase_correlation_pairwise_shifts_calculation( downsampling_xyz[1], downsampling_xyz[2] ) + else: + downsampling = "" options = ( "select=[" From 4eb3066505b47c0e89a722f37cf641f6feb395b1 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Tue, 26 Sep 2023 09:04:24 +0200 Subject: [PATCH 068/678] Clarify docstrings that project_filname is without .xml extension --- 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 099c616c..ea2b3003 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -57,7 +57,7 @@ def run_define_dataset_autoloader( Parameters ---------- project_filename : str - Name of the project + Name of the project without .xml extension file_path : str path to the file, can be the first czi or a regex to match all files with an extension @@ -163,7 +163,7 @@ def run_define_dataset_manualoader( Parameters ---------- project_filename : str - Name of the project + Name of the project without .xml extension source_directory : str Path to the folder containing the file(s) image_file_pattern : str From 7d35c367933e4a3a98e48f98fd51ab2394918827 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Tue, 26 Sep 2023 11:03:13 +0200 Subject: [PATCH 069/678] Bugfix: remove comma at the end of options strings it may get included into the options string --- src/imcflibs/imagej/bdv.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index ea2b3003..3285412d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -209,7 +209,7 @@ def run_define_dataset_manualoader( + " " + "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(options) @@ -238,7 +238,7 @@ def run_resave_as_h5( source_xml_file : File or str XML input file. output_h5_file_path : str - Export path for the output file. + Export path for the output file including .xml extension timepoints : str, optional Which timepoints should be exported, by default "All Timepoints". timepoints_per_partition : int, optional @@ -301,7 +301,7 @@ def run_resave_as_h5( + use_deflate_compression_arg + split_hdf5 + "export_path=" - + output_h5_file_path, + + output_h5_file_path ) log.debug(options) @@ -454,7 +454,7 @@ def run_phase_correlation_pairwise_shifts_calculation( + treat_timepoints + " " + downsampling - + "subpixel_accuracy", + + "subpixel_accuracy" ) log.debug(options) From ef2ab694ffddb368bec06beb80ccf3d911bb0c71 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Wed, 27 Sep 2023 09:51:27 +0200 Subject: [PATCH 070/678] Bugfix: add missing options when fusing to TIFF --- src/imcflibs/imagej/bdv.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 3285412d..c7c14ca3 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1039,16 +1039,20 @@ def run_fusion( + pixel_type + " " + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " - + "blend preserve_original produce=[Each timepoint & channel] " + + "blend " + + "preserve_original " + + "produce=[Each timepoint & channel] " ) if export == "TIFF": options = ( options + + "fused_image=[Save as (compressed) TIFF stacks] " + + "define_input=[Auto-load from input data (values shown below)] " + "output_file_directory=[" + result_path - + "] " - + "filename_addition=[]" + + "/.] " + + "filename_addition=[" + file_info["basename"] + "]" ) elif export == "HDF5": h5_fused_path = pathtools.join2( From cf83b0a56f682e7fa4147d67d904231088e984f3 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Thu, 28 Sep 2023 10:18:35 +0200 Subject: [PATCH 071/678] Bugfix: fix regular expression to backup all xml files --- src/imcflibs/imagej/bdv.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index c7c14ca3..70b2dce2 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -21,7 +21,6 @@ def backup_xml_files(source_directory, subfolder_name): """Copy all .xml (and .xml~) files to a subfolder inside a folder called "xml-backup" in the source dir. - Will create a the folders if they don't exists. Uses shutil.copy2 which will overwrite existing files. Parameters @@ -36,7 +35,7 @@ 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) From 2308d7f591b1a9670b4d038ac07d2a56c20045a6 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Thu, 28 Sep 2023 10:23:40 +0200 Subject: [PATCH 072/678] Add regex option to docstring --- src/imcflibs/pathtools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 2d094e45..73c61427 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -180,6 +180,9 @@ def listdir_matching(path, suffix, fullpath=False, sort=False, regex=False): sort : bool, optional If set to True, the returned list will be sorted using `imcflibs.strtools.sort_alphanumerically()`. + regex : bool, optional + If set to True, uses the suffix-string as regular expression to match + filenames. By default False. Returns ------- From eabcc44c90d1691f5b230c19ffb5cd5a0399a962 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Thu, 28 Sep 2023 10:57:46 +0200 Subject: [PATCH 073/678] Bugfix: fix option string in pairwise shift correlation --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 70b2dce2..47c2fe84 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -395,7 +395,7 @@ def run_phase_correlation_pairwise_shifts_calculation( if downsampling_xyz != "": downsampling = ( - "downsample_in_x=%s" + "downsample_in_y=%s downsample_in_z=%s ") % ( + "downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s ") % ( downsampling_xyz[0], downsampling_xyz[1], downsampling_xyz[2] From a27740f6f1a7db3f06a8c90376e00efe2370e30e Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Fri, 29 Sep 2023 09:44:45 +0200 Subject: [PATCH 074/678] Bugfix: assign variables before use also update docstring --- src/imcflibs/imagej/bdv.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 47c2fe84..d71ede29 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -828,13 +828,22 @@ def run_duplicate_transformations( ---------- project_path : str Path to the .xml project - channel_source : str - Specify the channel name - + transformation_type : str, optional + select mode, e.g. "channel" or "tiles" + channel_source : int, optional + number of the reference channel, starts at 1, by default None + tile source : int, optional + the reference tile, by default None + transformation_to_use : str, optional + select which transformations to duplicate. + Alternative option: "[Add last transformation only]" """ file_info = pathtools.parse_path(project_path) + apply = "" + source = "" + target = "" tile_apply = "" tile_process = "" From a2257dd687789386c77dbfc02e09e553749aa613 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Fri, 29 Sep 2023 09:45:18 +0200 Subject: [PATCH 075/678] Bugfix: fix options string in when duplicating transformations --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index d71ede29..8f069f52 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -871,7 +871,7 @@ def run_duplicate_transformations( "processing_channel=[channel " + str(channel_source - 1) + "] " ) else: - chnl_apply = "[All Channels] " + chnl_apply = "apply_to_channel=[All Channels] " else: sys.exit("Issue with transformation duplication") From 88314f178edae3b079937000e7de5d3be9cf99d6 Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Fri, 29 Sep 2023 10:33:15 +0200 Subject: [PATCH 076/678] Bugfix: fix typo in options string when duplicating transformations --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 8f069f52..10a12c9e 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -871,7 +871,7 @@ def run_duplicate_transformations( "processing_channel=[channel " + str(channel_source - 1) + "] " ) else: - chnl_apply = "apply_to_channel=[All Channels] " + chnl_apply = "apply_to_channel=[All channels] " else: sys.exit("Issue with transformation duplication") From a1897cb3d4ebf12360903cc9d1486b5ce675d60d Mon Sep 17 00:00:00 2001 From: Kai Schleicher Date: Thu, 12 Oct 2023 12:21:08 +0200 Subject: [PATCH 077/678] use exact user input for illumination, tile, timepoint and angle. only channels always start at 0, but user input starts at 1. --- src/imcflibs/imagej/bdv.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 10a12c9e..cca161bd 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1117,7 +1117,7 @@ def parse_options(input_dict): output_dict["illumination_text"], output_dict["illumination_select"] = ( "[Single illumination (Select from List)] ", "processing_illumination=[illumination " - + str(input_dict["process_illumination"] - 1) + + str(input_dict["process_illumination"]) + "] ", ) else: @@ -1129,7 +1129,7 @@ def parse_options(input_dict): if "process_tile" in input_dict: output_dict["tile_text"], output_dict["tile_select"] = ( "[Single tile (Select from List)] ", - "processing_tile=[tile " + str(input_dict["process_tile"] - 1) + "] ", + "processing_tile=[tile " + str(input_dict["process_tile"]) + "] ", ) else: output_dict["tile_text"], output_dict["tile_select"] = ("[All tiles] ", "") @@ -1138,7 +1138,7 @@ def parse_options(input_dict): output_dict["timepoint_text"], output_dict["timepoint_select"] = ( "[Single timepoint (Select from List)] ", "processing_timepoint=[timepoint " - + str(input_dict["process_timepoint"] - 1) + + str(input_dict["process_timepoint"]) + "] ", ) else: @@ -1150,7 +1150,7 @@ def parse_options(input_dict): if "process_angle" in input_dict: output_dict["angle_text"], output_dict["angle_select"] = ( "[Single angle (Select from List)] ", - "processing_angle=[angle " + str(input_dict["process_angle"] - 1) + "] ", + "processing_angle=[angle " + str(input_dict["process_angle"]) + "] ", ) else: output_dict["angle_text"], output_dict["angle_select"] = ("[All angles] ", "") From 7d945a18ac577c378ebbd829ed96de9b6afdeddb Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 09:34:57 +0200 Subject: [PATCH 078/678] Fix "Returns" sections in docstrings --- src/imcflibs/imagej/bioformats.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 037c8ff7..ef97ba37 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -85,7 +85,7 @@ def import_image( Returns ------- - ij.ImagePlus[] + list(ij.ImagePlus) A list of ImagePlus objects resulting from the import. """ options = ImporterOptions() @@ -195,7 +195,7 @@ def export_using_orig_name(imp, path, orig_name, tag, suffix, overwrite=False): Returns ------- - out_file : str + str The full name of the exported file. """ out_file = gen_name_from_orig(path, orig_name, tag, suffix) @@ -236,7 +236,7 @@ def write_bf_memoryfile(path_to_file): Parameters ---------- - path_to_file : string + string The full path to the image file. """ reader = Memoizer(ImageReader()) From 20ee5d70d904eeb7e50e8921ea7e4f77c1323719 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 10:07:17 +0200 Subject: [PATCH 079/678] Explain `basename()` call --- src/imcflibs/imagej/misc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index baf8ff99..a3360e39 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -233,7 +233,9 @@ def sanitize_image_title(imp): imp : ImagePlus The ImagePlus to be renamed. """ - image_title = os.path.basename(imp.getTitle()) # FIXME: Kai, why this? + # sometimes (unclear when) the title contains the full path, therefore we + # simply call `os.path.basename()` on it to remove all up to the last "/": + image_title = os.path.basename(imp.getTitle()) image_title = image_title.replace(".czi", "") image_title = image_title.replace(" ", "_") image_title = image_title.replace("_-_", "") From cf9395a074217e6d5e6da84bef717cb7ea0c3e55 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 10:07:26 +0200 Subject: [PATCH 080/678] Remove pointless statement --- src/imcflibs/imagej/misc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index a3360e39..6cbf29ad 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -222,8 +222,6 @@ def setup_clean_ij_environment(rm=None, rt=None): # pylint: disable-msg=unused- prefs.fix_ij_options() - return - def sanitize_image_title(imp): """Remove special chars and various suffixes from an open ImagePlus. From 318264f6b46b324db343578f2097f6335fd11a74 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 10:12:55 +0200 Subject: [PATCH 081/678] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf1c0100..c2e3e162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ +## 1.5.0 + +### Added + +* `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and + various suffixes from an ImagePlus. +* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image + to get a label image (2D/3D). +* New `imcflibs.imagej.objects3d` submodule, providing: + * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn + an Objects3DPopulation into an ImagePlus (2D/3D). + * `imcflibs.imagej.objects3d.imgplus_to_population3d` to get the + Objects3DPopulation from an ImagePlus (2D/3D). + ## 1.4.0 ### Added From c5ffe364a66e60febf8ec7e562494a2072201a02 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 10:54:29 +0200 Subject: [PATCH 082/678] Docstring conventions for `backup_xml_files()` --- src/imcflibs/imagej/bdv.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index cca161bd..9ea510f5 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -19,18 +19,20 @@ def backup_xml_files(source_directory, subfolder_name): - """Copy all .xml (and .xml~) files to a subfolder inside - a folder called "xml-backup" in the source dir. - Uses shutil.copy2 which will overwrite existing files. + """Create a backup of BDV-XML files inside a subfolder of `xml-backup`. + + Copies all `.xml` and `.xml~` files to a subfolder with the given name inside a + folder called `xml-backup` in the source directory. Uses the `shutil.copy2()` + command, which will overwrite existing files. Parameters ---------- source_directory : str - full path to the directory containing the xml files + Full path to the directory containing the xml files. subfolder_name : str - name of the subfolder. Will be created if it does not exists. + The name of the subfolder that will be used inside `xml-backup`. Will be + created if necessary. """ - xml_backup_directory = os.path.join(source_directory, "xml-backup") pathtools.create_directory(xml_backup_directory) backup_subfolder = xml_backup_directory + "/%s" % (subfolder_name) @@ -394,11 +396,10 @@ def run_phase_correlation_pairwise_shifts_calculation( use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" if downsampling_xyz != "": - downsampling = ( - "downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s ") % ( + downsampling = ("downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s ") % ( downsampling_xyz[0], downsampling_xyz[1], - downsampling_xyz[2] + downsampling_xyz[2], ) else: downsampling = "" @@ -493,19 +494,15 @@ def run_filter_pairwise_shifts( if max_shift_xyz != "": filter_by_max_shift = ( " filter_by_shift_in_each_dimension" - " max_shift_in_x=%s max_shift_in_y=%s max_shift_in_z=%s") % ( - max_shift_xyz[0], - max_shift_xyz[1], - max_shift_xyz[2] - ) + " max_shift_in_x=%s max_shift_in_y=%s max_shift_in_z=%s" + ) % (max_shift_xyz[0], max_shift_xyz[1], max_shift_xyz[2]) else: filter_by_max_shift = "" if max_displacement != "": filter_by_max_displacement = ( - " filter_by_total_shift_magnitude max_displacement=%s") % ( - max_displacement - ) + " filter_by_total_shift_magnitude max_displacement=%s" + ) % (max_displacement) else: filter_by_max_displacement = "" @@ -1060,7 +1057,9 @@ def run_fusion( + "output_file_directory=[" + result_path + "/.] " - + "filename_addition=[" + file_info["basename"] + "]" + + "filename_addition=[" + + file_info["basename"] + + "]" ) elif export == "HDF5": h5_fused_path = pathtools.join2( From 263f824730219bb79701e395f7b2518d28ad774a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 15:21:29 +0200 Subject: [PATCH 083/678] Add FIXME --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9ea510f5..4ca3004e 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -79,6 +79,8 @@ def run_define_dataset_autoloader( Allow specifying hdf5_chunk_sizes factors explicitly, for example "[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]" """ + # FIXME: the docstring is actually not corrct, in the sense that the function will + # switch to `Define dataset ...` in case the `bf_series_type` is `Tiles` file_info = pathtools.parse_path(file_path) From 236d6070f71242ec7c880feaae8eb02d3f8e44eb Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Fri, 13 Oct 2023 13:20:56 +0200 Subject: [PATCH 084/678] Remove commented-out block --- src/imcflibs/imagej/bdv.py | 72 -------------------------------------- 1 file changed, 72 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 4ca3004e..c871fc0f 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -908,78 +908,6 @@ def run_duplicate_transformations( return -# def run_fusion( -# temp_path, -# fused_xml_path, -# process_timepoint="All Timepoints", -# downsampling=1, -# ram_handling="Virtual", -# save_format="Save as new XML Project (HDF5)", -# additional_option="", -# ): -# """run the image fusion command - -# Parameters -# ---------- -# temp_path : str -# temporary folder output (scratch) -# fused_xml_path : str -# final xml output path -# process_timepoint : str, optional -# Specify which timepoint should be processed, by default "All Timepoints" -# downsampling : int, optional -# Downsampling factor to use during the fusion, by default 1 (no -# downsampling) -# ram_handling : str, optional -# Which type of ram_handling do you require, by default "Virtual". This is -# the conservative (not likely to fail even if only a low amount of RAM is -# available) and slow approach -# save_format : str, optional -# file format of the new image, by default "Save as new XML Project -# (HDF5)", this is matching the conservative and slow approach -# additional_option : str, optional="" -# Any additional options that should be added to the fusion command. Do -# not forget to finish each additional option with a space -# """ -# # Add preserve original data anisotropy tickbox: -# # tiles=> true; angle=>False - -# options = ( -# "select=[" -# + temp_path -# + "] " -# + "process_angle=[All angles] " -# + "process_channel=[All channels] " -# + "process_illumination=[" -# + process_timepoint -# + "] " -# + "process_tile=[All tiles] " -# + "process_timepoint=[All Timepoints] " -# + "bounding_box=[Currently Selected Views] " -# + "downsampling=" -# + str(downsampling) -# + " " -# + "pixel_type=[16-bit unsigned integer] " -# + "interpolation=[Linear Interpolation] " -# + "image=[" -# + ram_handling -# + "] " -# + "interest_points_for_non_rigid=[-= Disable Non-Rigid =-] " -# + "blend " -# + additional_option -# + "produce=[Each timepoint & channel] " -# + "fused_image=[" -# + save_format -# + "] " -# + "export_path=[" -# + fused_xml_path -# + "]", -# ) - -# IJ.run("Fuse dataset ...", options) -# return - - def run_fusion( project_path, input_dict={}, From e744e352d5aedb573dcbd98f0c5fddbd1241be27 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Fri, 13 Oct 2023 14:38:14 +0200 Subject: [PATCH 085/678] Clarify what the option is setting --- src/imcflibs/imagej/bdv.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index c871fc0f..2ee89847 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -410,16 +410,16 @@ def run_phase_correlation_pairwise_shifts_calculation( "select=[" + project_path + "] " - + "process_angle=" - + options_dict["angle_text"] + + +"process_angle=" + + options_dict["angle_processing_option"] + "process_channel=" - + options_dict["channel_text"] + + options_dict["channel_processing_option"] + "process_illumination=" - + options_dict["illumination_text"] + + options_dict["illumination_processing_option"] + "process_tile=" - + options_dict["tile_text"] + + options_dict["tile_processing_option"] + "process_timepoint=" - + options_dict["timepoint_text"] + + options_dict["timepoint_processing_option"] + options_dict["timepoint_select"] + options_dict["angle_select"] + options_dict["channel_select"] @@ -586,15 +586,15 @@ def run_optimize_apply_shifts( + project_path + "] " + "process_angle=" - + options_dict["angle_text"] + + options_dict["angle_processing_option"] + "process_channel=" - + options_dict["channel_text"] + + options_dict["channel_processing_option"] + "process_illumination=" - + options_dict["illumination_text"] + + options_dict["illumination_processing_option"] + "process_tile=" - + options_dict["tile_text"] + + options_dict["tile_processing_option"] + "process_timepoint=" - + options_dict["timepoint_text"] + + options_dict["timepoint_processing_option"] + options_dict["timepoint_select"] + options_dict["angle_select"] + options_dict["channel_select"] @@ -954,15 +954,15 @@ def run_fusion( + project_path + "] " + "process_angle=" - + options_dict["angle_text"] + + options_dict["angle_processing_option"] + "process_channel=" - + options_dict["channel_text"] + + options_dict["channel_processing_option"] + "process_illumination=" - + options_dict["illumination_text"] + + options_dict["illumination_processing_option"] + "process_tile=" - + options_dict["tile_text"] + + options_dict["tile_processing_option"] + "process_timepoint=" - + options_dict["timepoint_text"] + + options_dict["timepoint_processing_option"] + "bounding_box=[All Views] " + "downsampling=" + str(downsampling) From 3206856c735659e6957f9db807aa2ba3fbb4f2e4 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Fri, 13 Oct 2023 14:47:58 +0200 Subject: [PATCH 086/678] Increase readability --- src/imcflibs/imagej/bdv.py | 67 +++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 2ee89847..5037a63b 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1029,60 +1029,59 @@ def run_fusion( def parse_options(input_dict): output_dict = {} + # options to select views in a dataset for processing + + output_dict["channel_processing_option"] = "[All channels] " + output_dict["channel_select"] = "" if "process_channel" in input_dict: - output_dict["channel_text"], output_dict["channel_select"] = ( - "[Single channel (Select from List)] ", + output_dict[ + "channel_processing_option" + ] = "[Single channel (Select from List)] " + output_dict["channel_select"] = ( "processing_channel=[channel " + str(input_dict["process_channel"] - 1) - + "] ", - ) - else: - output_dict["channel_text"], output_dict["channel_select"] = ( - "[All channels] ", - "", + + "] " ) + output_dict["illumination_processing_option"] = "[All illuminations] " + output_dict["illumination_select"] = "" if "process_illumination" in input_dict: - output_dict["illumination_text"], output_dict["illumination_select"] = ( - "[Single illumination (Select from List)] ", + output_dict[ + "illumination_processing_option" + ] = "[Single illumination (Select from List)] " + output_dict["illumination_select"] = ( "processing_illumination=[illumination " + str(input_dict["process_illumination"]) - + "] ", - ) - else: - output_dict["illumination_text"], output_dict["illumination_select"] = ( - "[All illuminations] ", - "", + + "] " ) + output_dict["tile_processing_option"] = "[All tiles] " + output_dict["tile_select"] = "" if "process_tile" in input_dict: - output_dict["tile_text"], output_dict["tile_select"] = ( - "[Single tile (Select from List)] ", - "processing_tile=[tile " + str(input_dict["process_tile"]) + "] ", + output_dict["tile_processing_option"] = "[Single tile (Select from List)] " + output_dict["tile_select"] = ( + "processing_tile=[tile " + str(input_dict["process_tile"]) + "] " ) - else: - output_dict["tile_text"], output_dict["tile_select"] = ("[All tiles] ", "") + output_dict["timepoint_processing_option"] = "[All Timepoints] " + output_dict["timepoint_select"] = "" if "process_timepoint" in input_dict: - output_dict["timepoint_text"], output_dict["timepoint_select"] = ( - "[Single timepoint (Select from List)] ", + output_dict[ + "timepoint_processing_option" + ] = "[Single timepoint (Select from List)] " + output_dict["timepoint_select"] = ( "processing_timepoint=[timepoint " + str(input_dict["process_timepoint"]) - + "] ", - ) - else: - output_dict["timepoint_text"], output_dict["timepoint_select"] = ( - "[All Timepoints] ", - "", + + "] " ) + output_dict["angle_processing_option"] = "[All angles] " + output_dict["angle_select"] = "" if "process_angle" in input_dict: - output_dict["angle_text"], output_dict["angle_select"] = ( - "[Single angle (Select from List)] ", - "processing_angle=[angle " + str(input_dict["process_angle"]) + "] ", + output_dict["angle_processing_option"] = "[Single angle (Select from List)] " + output_dict["angle_select"] = ( + "processing_angle=[angle " + str(input_dict["process_angle"]) + "] " ) - else: - output_dict["angle_text"], output_dict["angle_select"] = ("[All angles] ", "") log.debug(output_dict) return output_dict From b5b9a6b48985b5523c37e5f27acba74567b784fb Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Fri, 13 Oct 2023 15:11:22 +0200 Subject: [PATCH 087/678] Option to use a reference channel/tile for registration --- src/imcflibs/imagej/bdv.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 5037a63b..53059239 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -383,10 +383,8 @@ def run_phase_correlation_pairwise_shifts_calculation( options_dict = parse_options(input_dict) - print(options_dict) - use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" - use_channel = "channels=[Average Channels]" if treat_channels == "group" else "" + use_channel = options_dict["use_channel"] if treat_channels == "group" else "" use_illumination = ( "illuminations=[Average Illuminations]" if treat_illuminations == "group" @@ -395,7 +393,7 @@ def run_phase_correlation_pairwise_shifts_calculation( use_timepoint = ( "timepoints=[Average Timepoints]" if treat_timepoints == "group" else "" ) - use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" + use_tile = options_dict["use_tiles"] if treat_tiles == "group" else "" if downsampling_xyz != "": downsampling = ("downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s ") % ( @@ -570,7 +568,7 @@ def run_optimize_apply_shifts( options_dict = parse_options(input_dict) use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" - use_channel = "channels=[Average Channels]" if treat_channels == "group" else "" + use_channel = options_dict["use_channel"] if treat_channels == "group" else "" use_illumination = ( "illuminations=[Average Illuminations]" if treat_illuminations == "group" @@ -579,7 +577,7 @@ def run_optimize_apply_shifts( use_timepoint = ( "timepoints=[Average Timepoints]" if treat_timepoints == "group" else "" ) - use_tile = "tiles=[Average Tiles]" if treat_tiles == "group" else "" + use_tile = options_dict["use_tiles"] if treat_tiles == "group" else "" options = ( "select=[" @@ -1029,6 +1027,20 @@ def run_fusion( def parse_options(input_dict): output_dict = {} + # options to select reference views for grouped views + + output_dict["use_channel"] = "channels=[Average Channels]" + if "reference_channel" in input_dict: + output_dict["use_channel"] = ( + "channels=[use Channel " + str(input_dict["reference_channel"] - 1) + "] " + ) + + output_dict["use_tiles"] = "tiles=[Average Tiles]" + if "reference_tile" in input_dict: + output_dict["use_tiles"] = ( + "tiles=[use Tile " + str(input_dict["reference_tile"]) + "] " + ) + # options to select views in a dataset for processing output_dict["channel_processing_option"] = "[All channels] " From 2edee619a2ef3906d7741f4fac0a038e2576b4db Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 15:30:48 +0200 Subject: [PATCH 088/678] Docstring improvements / clarifications --- src/imcflibs/imagej/bdv.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 53059239..786cb3e9 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -53,31 +53,31 @@ def run_define_dataset_autoloader( subsampling_factors=None, hdf5_chunk_sizes=None, ): - """Run the Define Multi-View Dataset command using the "Auto-Loader" option. + """Run "Define Multi-View Dataset" using the "Auto-Loader" option. Parameters ---------- project_filename : str - Name of the project without .xml extension + Name of the project (without an `.xml` extension). file_path : str - path to the file, can be the first czi or a regex to match all files - with an extension + Path to the file, can be the first `.czi` or a regex to match all files + with an extension. dataset_save_path : str - output path for the .xml + Output path for the `.xml`. bf_series_type : str One of "Angles" or "Tiles", specifying how Bio-Formats interprets the series. timepoints_per_partition : int, optional - split the output by timepoints. Use 0 for no split, by default 1 + Split the output by timepoints. Use 0 for no split, by default 1. resave : str, optional - Allows this function to either re-save the images or simply create a merged xml. - Use "Load raw data" to avoid re-saving, by default "Re-save as multiresolution - HDF5" will resave the input data. + Allow the function to either re-save the images or simply create a + merged xml. Use "Load raw data" to avoid re-saving, by default "Re-save + as multiresolution HDF5" will resave the input data. subsampling_factors: str, optional - Allow specifying subsampling factors explicitly, for example: - "[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }]" + Specify subsampling factors explicitly, for example: + `[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }]`. hdf5_chunk_sizes: str, optional - Allow specifying hdf5_chunk_sizes factors explicitly, for example - "[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]" + Specify hdf5_chunk_sizes factors explicitly, for example + `[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]` """ # FIXME: the docstring is actually not corrct, in the sense that the function will # switch to `Define dataset ...` in case the `bf_series_type` is `Tiles` @@ -161,21 +161,21 @@ def run_define_dataset_manualoader( dataset_organisation, file_definition, ): - """Run the Define Multi-View Dataset command using the "Manual Loader" option + """Run "Define Multi-View Dataset" using the "Manual Loader" option. Parameters ---------- project_filename : str - Name of the project without .xml extension + Name of the project (without an `.xml` extension). source_directory : str - Path to the folder containing the file(s) + Path to the folder containing the file(s). image_file_pattern : str Pattern corresponding to the names of your files separating the - different dimensions + different dimensions. dataset_organisation : str - Organisation of the dataset and the dimensions to process + Organisation of the dataset and the dimensions to process. file_definition : dict - Dictionary containing all the info about the file repartitions + Dictionary containing the details about the file repartitions. """ xml_filename = project_filename + ".xml" From 46ecc6b1ebd902afe7b7308dc32a69d6f744e1b5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 15:31:04 +0200 Subject: [PATCH 089/678] Add FIXMEs --- src/imcflibs/imagej/bdv.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 786cb3e9..1aae3430 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -82,6 +82,8 @@ def run_define_dataset_autoloader( # FIXME: the docstring is actually not corrct, in the sense that the function will # switch to `Define dataset ...` in case the `bf_series_type` is `Tiles` + # FIXME: improve the timepoints_per_partition parameter description! + file_info = pathtools.parse_path(file_path) project_filename = project_filename.replace(" ", "_") @@ -178,6 +180,9 @@ def run_define_dataset_manualoader( Dictionary containing the details about the file repartitions. """ + # FIXME: explain image_file_pattern, dataset_organisation and + # file_definition with more details / examples + xml_filename = project_filename + ".xml" temp = os.path.join(source_directory, project_filename + "_temp") @@ -379,6 +384,9 @@ def run_phase_correlation_pairwise_shifts_calculation( """ + # FIXME: input_dict is not a good parameter name, plus the parse_options() + # function needs to be refactored and documented first! + file_info = pathtools.parse_path(project_path) options_dict = parse_options(input_dict) @@ -563,6 +571,9 @@ def run_optimize_apply_shifts( absolute alignment error in px, by default 3.5 """ + # FIXME: input_dict is not a good parameter name, plus the parse_options() + # function needs to be refactored and documented first! + file_info = pathtools.parse_path(project_path) options_dict = parse_options(input_dict) @@ -835,6 +846,7 @@ def run_duplicate_transformations( select which transformations to duplicate. Alternative option: "[Add last transformation only]" """ + # FIXME: transformation_to_use requires explanations of possible values! file_info = pathtools.parse_path(project_path) @@ -847,6 +859,11 @@ def run_duplicate_transformations( chnl_apply = "" chnl_process = "" + # FIXME: invalid parameter combinations! + # Calling the function with transformation_type="channel" and + # channel_source=None (the default) will lead to an invalid combination! + # Same for transformation_type="tile" / tile_source=None, in both cases the + # resulting call will contain the sequence ` source= `. if transformation_type == "channel": apply = "[One channel to other channels]" target = "[All Channels]" From 68a3040cc493b5682f79947558ceb418e4b8e31b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 15:31:13 +0200 Subject: [PATCH 090/678] Improve readability --- src/imcflibs/imagej/bdv.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 1aae3430..96f6e81a 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -97,11 +97,28 @@ def run_define_dataset_autoloader( if subsampling_factors: subsampling_factors = "subsampling_factors=" + subsampling_factors + " " else: - subsampling_factors = "manual_mipmap_setup subsampling_factors=[{ {1,1,1}, {2,2,1}, {4,4,1}, {8,8,2}, {16,16,4} }] " + subsampling_factors = ( + "manual_mipmap_setup " + "subsampling_factors=[{ " + "{1,1,1}, " + "{2,2,1}, " + "{4,4,1}, " + "{8,8,2}, " + "{16,16,4} " + "}] " + ) if hdf5_chunk_sizes: hdf5_chunk_sizes = "hdf5_chunk_sizes=" + hdf5_chunk_sizes + " " else: - hdf5_chunk_sizes = "hdf5_chunk_sizes=[{ {32,32,4}, {32,16,8}, {16,16,16}, {32,16,8}, {32,32,4} }] " + hdf5_chunk_sizes = ( + "hdf5_chunk_sizes=[{ " + "{32,32,4}, " + "{32,16,8}, " + "{16,16,16}, " + "{32,16,8}, " + "{32,32,4} " + "}] " + ) if bf_series_type == "Angles": angle_rotation = "apply_angle_rotation " From 38c7d1cc517fa81696b26c163e62aa790019bf88 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 15:31:32 +0200 Subject: [PATCH 091/678] Remove unnecessary brackets --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 96f6e81a..7cbc86d5 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -421,7 +421,7 @@ def run_phase_correlation_pairwise_shifts_calculation( use_tile = options_dict["use_tiles"] if treat_tiles == "group" else "" if downsampling_xyz != "": - downsampling = ("downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s ") % ( + downsampling = "downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s " % ( downsampling_xyz[0], downsampling_xyz[1], downsampling_xyz[2], From 387c945f5f7be58083886878455cbff58096b7d4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 15:37:32 +0200 Subject: [PATCH 092/678] Docstring format fixes --- src/imcflibs/imagej/bdv.py | 184 +++++++++++++++++++------------------ 1 file changed, 96 insertions(+), 88 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7cbc86d5..c98f3db7 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -67,17 +67,17 @@ def run_define_dataset_autoloader( bf_series_type : str One of "Angles" or "Tiles", specifying how Bio-Formats interprets the series. timepoints_per_partition : int, optional - Split the output by timepoints. Use 0 for no split, by default 1. + Split the output by timepoints. Use `0` for no split, by default `1`. resave : str, optional Allow the function to either re-save the images or simply create a - merged xml. Use "Load raw data" to avoid re-saving, by default "Re-save - as multiresolution HDF5" will resave the input data. - subsampling_factors: str, optional + merged xml. Use `Load raw data` to avoid re-saving, by default `Re-save + as multiresolution HDF5` which will resave the input data. + subsampling_factors : str, optional Specify subsampling factors explicitly, for example: `[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }]`. - hdf5_chunk_sizes: str, optional + hdf5_chunk_sizes : str, optional Specify hdf5_chunk_sizes factors explicitly, for example - `[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]` + `[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]`. """ # FIXME: the docstring is actually not corrct, in the sense that the function will # switch to `Define dataset ...` in case the `bf_series_type` is `Tiles` @@ -263,19 +263,19 @@ def run_resave_as_h5( source_xml_file : File or str XML input file. output_h5_file_path : str - Export path for the output file including .xml extension + Export path for the output file including the `.xml `extension. timepoints : str, optional - Which timepoints should be exported, by default "All Timepoints". + The timepoints that should be exported, by default `All Timepoints`. timepoints_per_partition : int, optional - How many timepoints per partition should be exported, by default 1. + How many timepoints to export per partition, by default `1`. use_deflate_compression : bool, optional - Run deflate compression, by default True. - subsampling_factors: str, optional - Allow specifying subsampling factors explicitly, for example: - "[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }]" - hdf5_chunk_sizes: str, optional - Allow specifying hdf5_chunk_sizes factors explicitly, for example - "[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]" + Run deflate compression, by default `True`. + subsampling_factors : str, optional + Specify subsampling factors explicitly, for example: + `[{ {1,1,1}, {2,2,1}, {4,4,2}, {8,8,4} }]`. + hdf5_chunk_sizes : str, optional + Specify hdf5_chunk_sizes factors explicitly, for example + `[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]`. """ # save all timepoints or a single one: if timepoints == "All Timepoints": @@ -336,19 +336,22 @@ def run_resave_as_h5( def run_flip_axes(source_xml_file, x=False, y=True, z=False): - """Wrapper for BigStitcher > Batch Processing > Tools > Flip axes. - For example, nd2 files require a flip along the y axis. + """Call BigStitcher's "Flip Axes" command. + + Wrapper for `BigStitcher > Batch Processing > Tools > Flip Axes`. This is + required for some formats, for example Nikon `.nd2` files need a flip along + the Y-axis. Parameters ---------- - h5_resave_xml_path : str - full path to the .xml-file + source_xml_file : str + Full path to the `.xml` file. x : bool, optional - flip images along the x axes, by default False + Flip images along the X-axis, by default `False`. y : bool, optional - flip mages along the axes, by default True + Flip mages along the Y-axis, by default `True`. z : bool, optional - flip images along the z axes, by default False + Flip images along the Z-axis, by default `False`. """ file_info = pathtools.parse_path(source_xml_file) @@ -376,29 +379,28 @@ def run_phase_correlation_pairwise_shifts_calculation( treat_tiles="group", downsampling_xyz="", ): - """Run the Pairwise shifts calculation using Phase Correlation + """Calculate pairwise shifts using Phase Correlation. Parameters ---------- project_path : str - Path to the XML + Full path to the `.xml` file. input_dict : dict - Dictionary containing all the required information for angle, channel, - illuminations and timepoints + Options dict containing the required information for angle, channel, + illuminations and timepoints. treat_timepoints : str, optional - How to deal with the timepoints, by default "group" + How to deal with the timepoints, by default `group`. treat_channels : str, optional - How to deal with the channels, by default "group" + How to deal with the channels, by default `group`. treat_illuminations : str, optional - How to deal with the illuminations, by default "group" + How to deal with the illuminations, by default `group`. treat_angles : str, optional - How to deal with the angles, by default "[treat individually]" + How to deal with the angles, by default `[treat individually]`. treat_tiles : str, optional - How to deal with the tiles, by default "group" + How to deal with the tiles, by default `group`. downsampling_xyz : list of int, optional - specify downsampling in x,y and z, e.g. [4,4,4], by default empty, - meaning BigStitcher chooses - + Downsampling factors in X, Y and Z, for example `[4,4,4]`. By default + empty which will result in BigStitcher choosing the factors. """ # FIXME: input_dict is not a good parameter name, plus the parse_options() @@ -496,22 +498,22 @@ def run_filter_pairwise_shifts( max_shift_xyz="", max_displacement="", ): - """Filter the pairwise shifts based on different thresholds + """Filter the pairwise shifts based on different thresholds. Parameters ---------- project_path : str - Path of the XML on which to apply the filters + Path to the `.xml` on which to apply the filters. min_r : float, optional - Minimal quality of the link to keep, by default 0.7 + Minimal quality of the link to keep, by default `0.7`. max_r : float, optional - Maximal quality of the link to keep, by default 1 - max_shift_xyz : list of int, optional - Maximal shift in X, Y and Z in px to keep, e.g. [10,10,10], by default empty, - meaning this option is skipped + Maximal quality of the link to keep, by default `1`. + max_shift_xyz : list(int), optional + Maximal shift in X, Y and Z (in pixels) to keep, e.g. `[10,10,10]`. By + default empty, meaning no filtering based on the shifts will be applied. max_displacement : int, optional - Maximal displacement to keep, by default empty, - meaning this option is skipped + Maximal displacement to keep. By default empty, meaning no filtering + based on the displacement will be applied. """ file_info = pathtools.parse_path(project_path) @@ -563,29 +565,29 @@ def run_optimize_apply_shifts( relative_error=2.5, absolute_error=3.5, ): - """Optimize the shifts and apply it to the dataset + """Optimize the shifts and apply them to the dataset. Parameters ---------- project_path : str - Path of the XML on which to optimize and apply the shifts + Path to the `.xml` on which to optimize and apply the shifts. input_dict : dict Dictionary containing all the required information for angles, - channels, illuminations, tiles and timepoints + channels, illuminations, tiles and timepoints. treat_timepoints : str, optional - How to treat the timepoints, by default "group" + How to treat the timepoints, by default `group`. treat_channels : str, optional - How to treat the channels, by default "group" + How to treat the channels, by default `group`. treat_illuminations : str, optional - How to treat the illuminations, by default "group" + How to treat the illuminations, by default `group`. treat_angles : str, optional - How to treat the angles, by default "[treat individually]" + How to treat the angles, by default `[treat individually]`. treat_tiles : str, optional - How to treat the tiles, by default "group" - relative_error: float, optional - relative alignment error in px, by default 2.5 - absolute_error: float, optional - absolute alignment error in px, by default 3.5 + How to treat the tiles, by default `group`. + relative_error : float, optional + Relative alignment error (in px) to accept, by default `2.5`. + absolute_error : float, optional + Absolute alignment error (in px) to accept, by default `3.5`. """ # FIXME: input_dict is not a good parameter name, plus the parse_options() @@ -683,17 +685,17 @@ def run_detect_interest_points( Parameters ---------- project_path : str - Path to the .xml project + Path to the `.xml` project. process_timepoint : str, optional - Specify which timepoint should be processed, by default "All Timepoints" + Timepoint to be processed, by default `All Timepoints`. process_channel : str, optional - Specify which channel should be processed, by default "All channels" + Channel to be processed, by default `All channels`. sigma : float, optional - Minimum sigma for interest points detection, by default 1.8 + Minimum sigma for interest points detection, by default `1.8`. threshold : float, optional - Threshold value for the interest point detection, by default 0.008 + Threshold value for the interest point detection, by default `0.008`. maximum_number : int, optional - Maximum number of interest points to use, by default 3000. + Maximum number of interest points to use, by default `3000`. """ # If not process all channels at once, then adapt the option @@ -764,22 +766,24 @@ def run_interest_points_registration( process_channel="All channels", rigid_timepoints=False, ): - """Run the registration command. + """Run the "Register Dataset based on Interest Points" command. Parameters ---------- project_path : str - Path to the .xml project + Path to the `.xml` project. process_timepoint : str, optional - Specify which timepoint should be processed, by default "All Timepoints" + Timepoint to be processed, by default `All Timepoints`. process_channel : str, optional - Specify which channels should be processed. By default, all channels are - processed together, however this behavior could be undesirable if only - one channel is adequate (beads or nuclei). In that case provide the - channel name instead. by default "All channels" + 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`. rigid_timepoints : bool, optional - If spatial registration has already been run, set this boolean to True - to consider each timepoint as rigid unit, by default False + If set to `True` each timepoint will be considered as a rigid unit + (useful e.g. if spatial registration has already been performed before). + By default `False`. """ # If not process all channels at once, then adapt the option @@ -844,24 +848,25 @@ def run_duplicate_transformations( tile_source=None, transformation_to_use="[Replace all transformations]", ): - """Duplicate the transformation parameters to the other channels. + """Duplicate / propagate transformation parameters to other channels. - If registration has been generated using a single channel,this can be used - to propagate it to the others. + Propagate the transformation parameters generated by a previously performed + registration of a single channel to the other channels. Parameters ---------- project_path : str - Path to the .xml project + Path to the `.xml` project. transformation_type : str, optional - select mode, e.g. "channel" or "tiles" + Transformation mode, one of `channel` (to propagate from one channel to + all others) and `tiles` (to propagate from one tile to all others). channel_source : int, optional - number of the reference channel, starts at 1, by default None - tile source : int, optional - the reference tile, by default None + Reference channel nummber (starting at 1), by default None. + tile_source : int, optional + Reference tile, by default None. transformation_to_use : str, optional - select which transformations to duplicate. - Alternative option: "[Add last transformation only]" + One of `[Replace all transformations]` (default) and `[Add last + transformation only]` to specify which transformations to propagate. """ # FIXME: transformation_to_use requires explanations of possible values! @@ -949,7 +954,9 @@ def run_fusion( pixel_type="[16-bit unsigned integer]", export="HDF5", ): - """Wrapper to BigStitcher > Batch Processing > Fuse Dataset. + """Call BigStitcher's "Fuse Dataset" command. + + Wrapper to `BigStitcher > Batch Processing > Fuse Dataset`. Depending on the export type, inputs are different and therefore will distribute inputs differently. @@ -957,20 +964,21 @@ def run_fusion( Parameters ---------- project_path : str - Path of the XML on which to do the fusion. + Path to the `.xml` on which to run the fusion. input_dict : dict Dictionary containing all the required informations for angles, channels, illuminations, tiles and timepoints. result_path : str, optional - Path to store the resulting fused image, by default None. + Path to store the resulting fused image, by default `None` which will + store the result in the same folder as the input project. downsampling : int, optional - Downsampling value to use during fusion, by default 1. + Downsampling value to use during fusion, by default `1`. interpolation : str, optional - Interpolation to use during fusion, by default "[Linear Interpolation]". + Interpolation to use during fusion, by default `[Linear Interpolation]`. pixel_type : str, optional - Pixel type to use during fusion, by default "[16-bit unsigned integer]". + Pixel type to use during fusion, by default `[16-bit unsigned integer]`. export : str, optional - Format of the output fused image, by default "HDF5". + Format of the output fused image, by default `HDF5`. """ file_info = pathtools.parse_path(project_path) From 229cafa06251c77f1c4ac5f9e5a5cb3d30eee2bc Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 22:03:29 +0200 Subject: [PATCH 093/678] Drop pointless return statement --- src/imcflibs/imagej/bdv.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index c98f3db7..9e7b5833 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -240,8 +240,6 @@ def run_define_dataset_manualoader( log.debug(options) IJ.run("Define dataset ...", str(options)) - return - def run_resave_as_h5( source_xml_file, From efcca18cf1b27682710ba41d8e037356125131e2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 23:09:48 +0200 Subject: [PATCH 094/678] Rename functions, mostly dropping the "run_" prefix --- src/imcflibs/imagej/bdv.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9e7b5833..da5c61b3 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -43,7 +43,7 @@ def backup_xml_files(source_directory, subfolder_name): shutil.copy2(xml_file, backup_subfolder) -def run_define_dataset_autoloader( +def define_dataset_auto( project_filename, file_path, bf_series_type, @@ -173,7 +173,7 @@ def run_define_dataset_autoloader( return -def run_define_dataset_manualoader( +def define_dataset_manual( project_filename, source_directory, image_file_pattern, @@ -241,7 +241,7 @@ def run_define_dataset_manualoader( IJ.run("Define dataset ...", str(options)) -def run_resave_as_h5( +def resave_as_h5( source_xml_file, output_h5_file_path, timepoints="All Timepoints", @@ -253,8 +253,7 @@ def run_resave_as_h5( """Resave the xml dataset in a new format (either all or single timepoints). Useful if it hasn't been done during dataset definition (see - `run_define_dataset_autoloader()`). Allows e.g. parallelization of HDF-5 - re-saving. + `define_dataset_auto()`). Allows e.g. parallelization of HDF-5 re-saving. Parameters ---------- @@ -333,7 +332,7 @@ def run_resave_as_h5( return -def run_flip_axes(source_xml_file, x=False, y=True, z=False): +def flip_axes(source_xml_file, x=False, y=True, z=False): """Call BigStitcher's "Flip Axes" command. Wrapper for `BigStitcher > Batch Processing > Tools > Flip Axes`. This is @@ -367,7 +366,7 @@ def run_flip_axes(source_xml_file, x=False, y=True, z=False): backup_xml_files(file_info["path"], "flip_axes") -def run_phase_correlation_pairwise_shifts_calculation( +def phase_correlation_pairwise_shifts_calculation( project_path, input_dict={}, treat_timepoints="group", @@ -489,7 +488,7 @@ def run_phase_correlation_pairwise_shifts_calculation( return -def run_filter_pairwise_shifts( +def filter_pairwise_shifts( project_path, min_r=0.7, max_r=1, @@ -552,7 +551,7 @@ def run_filter_pairwise_shifts( return -def run_optimize_apply_shifts( +def optimize_and_apply_shifts( project_path, input_dict={}, treat_timepoints="group", @@ -670,7 +669,7 @@ def run_optimize_apply_shifts( return -def run_detect_interest_points( +def detect_interest_points( project_path, process_timepoint="All Timepoints", process_channel="All channels", @@ -758,7 +757,7 @@ def run_detect_interest_points( return -def run_interest_points_registration( +def interest_points_registration( project_path, process_timepoint="All Timepoints", process_channel="All channels", @@ -839,7 +838,7 @@ def run_interest_points_registration( return -def run_duplicate_transformations( +def duplicate_transformations( project_path, transformation_type="channel", channel_source=None, @@ -943,7 +942,7 @@ def run_duplicate_transformations( return -def run_fusion( +def fuse_dataset( project_path, input_dict={}, result_path=None, From e992c0886b1e5af5005c395fc0b8d1dda8705e02 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 23:27:30 +0200 Subject: [PATCH 095/678] Add newline at end of file --- src/imcflibs/imagej/_loci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index e3f6bb0c..b675e95e 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -28,4 +28,4 @@ from loci.plugins.in import ImporterOptions # pdoc: skip from loci.formats.in import ZeissCZIReader, DefaultMetadataOptions, MetadataLevel, DynamicMetadataOptions, MetadataOptions # pdoc: skip -from loci.formats import ImageReader, Memoizer \ No newline at end of file +from loci.formats import ImageReader, Memoizer From 4edd38bacb699cf3958fb8716bec1f35ab46e974 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 25 Oct 2023 23:35:27 +0200 Subject: [PATCH 096/678] Add imcflibs.imagej.bdv functions to CHANGELOG --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2e3e162..c0428848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,30 @@ an Objects3DPopulation into an ImagePlus (2D/3D). * `imcflibs.imagej.objects3d.imgplus_to_population3d` to get the Objects3DPopulation from an ImagePlus (2D/3D). +* New `imcflibs.imagej.bdv` submodule, providing BigDataViewer related + functions: + * `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files + * `imcflibs.imagej.bdv.define_dataset_auto` to run "Define Multi-View Dataset" + using the "Auto-Loader" option. + * `imcflibs.imagej.bdv.define_dataset_manual` to run "Define Multi-View + Dataset" using the "Manual Loader" option. + * `imcflibs.imagej.bdv.resave_as_h5` to resave the xml dataset in a new format + (either all or single timepoints). + * `imcflibs.imagej.bdv.flip_axes` tocall BigStitcher's "Flip Axes" command. + * `imcflibs.imagej.bdv.phase_correlation_pairwise_shifts_calculation` to + calculate pairwise shifts using Phase Correlation. + * `imcflibs.imagej.bdv.filter_pairwise_shifts` for filtering pairwise shifts + based on different thresholds. + * `imcflibs.imagej.bdv.optimize_and_apply_shifts` to optimize shifts and apply + them to a dataset. + * `imcflibs.imagej.bdv.detect_interest_points` for running the "Detect + Interest Points" command for registration. + * `imcflibs.imagej.bdv.interest_points_registration` to run the "Register + Dataset based on Interest Points" command. + * `imcflibs.imagej.bdv.duplicate_transformations` for duplicating / + propagating transformation parameters to other channels. + * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Dataset" + command. ## 1.4.0 From c366df73565aace4bdc8e1ae2c7c3edbb8ca1836 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 26 Oct 2023 08:30:29 +0200 Subject: [PATCH 097/678] A few more docstring changes --- src/imcflibs/imagej/bdv.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index da5c61b3..b6f75152 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -382,9 +382,9 @@ def phase_correlation_pairwise_shifts_calculation( ---------- project_path : str Full path to the `.xml` file. - input_dict : dict + input_dict : dict, optional Options dict containing the required information for angle, channel, - illuminations and timepoints. + illuminations and timepoints. By default an empty dict. treat_timepoints : str, optional How to deal with the timepoints, by default `group`. treat_channels : str, optional @@ -568,9 +568,9 @@ def optimize_and_apply_shifts( ---------- project_path : str Path to the `.xml` on which to optimize and apply the shifts. - input_dict : dict + input_dict : dict, optional Dictionary containing all the required information for angles, - channels, illuminations, tiles and timepoints. + channels, illuminations, tiles and timepoints. By default an empty dict. treat_timepoints : str, optional How to treat the timepoints, by default `group`. treat_channels : str, optional @@ -962,9 +962,9 @@ def fuse_dataset( ---------- project_path : str Path to the `.xml` on which to run the fusion. - input_dict : dict + input_dict : dict, optional Dictionary containing all the required informations for angles, - channels, illuminations, tiles and timepoints. + channels, illuminations, tiles and timepoints. By default an empty dict. result_path : str, optional Path to store the resulting fused image, by default `None` which will store the result in the same folder as the input project. From a5276fbdd86687dc03ee42068ff5cfaaa387698a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 26 Oct 2023 08:30:47 +0200 Subject: [PATCH 098/678] Drop pointless return statements --- src/imcflibs/imagej/bdv.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b6f75152..9ee30178 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -666,7 +666,6 @@ def optimize_and_apply_shifts( IJ.run("Optimize globally and apply shifts ...", str(options)) backup_xml_files(file_info["path"], "optimize_and_apply_shifts") - return def detect_interest_points( @@ -1060,7 +1059,6 @@ def fuse_dataset( log.debug(options) IJ.run("Fuse dataset ...", str(options)) - return def parse_options(input_dict): From 13a90d57a0c167af9dcae66178b52166c8135d56 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 26 Oct 2023 08:34:07 +0200 Subject: [PATCH 099/678] Bump to 1.5.0-SNAPSHOT --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a404225a..470a4eac 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 @@ -11,7 +12,7 @@ ch.unibas.biozentrum.imcf python-imcflibs - 1.4.1-SNAPSHOT + 1.5.0-SNAPSHOT python-imcflibs From 033c544b79ada1f99f5a9f411719ef8c1011a861 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 26 Oct 2023 08:40:59 +0200 Subject: [PATCH 100/678] Start refactoring parse_options for a ProcessingOptions class --- src/imcflibs/imagej/bdv.py | 195 +++++++++++++++++++++++-------------- 1 file changed, 123 insertions(+), 72 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9ee30178..49274513 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1061,76 +1061,127 @@ def fuse_dataset( IJ.run("Fuse dataset ...", str(options)) -def parse_options(input_dict): - output_dict = {} - - # options to select reference views for grouped views - - output_dict["use_channel"] = "channels=[Average Channels]" - if "reference_channel" in input_dict: - output_dict["use_channel"] = ( - "channels=[use Channel " + str(input_dict["reference_channel"] - 1) + "] " - ) - - output_dict["use_tiles"] = "tiles=[Average Tiles]" - if "reference_tile" in input_dict: - output_dict["use_tiles"] = ( - "tiles=[use Tile " + str(input_dict["reference_tile"]) + "] " - ) - - # options to select views in a dataset for processing - - output_dict["channel_processing_option"] = "[All channels] " - output_dict["channel_select"] = "" - if "process_channel" in input_dict: - output_dict[ - "channel_processing_option" - ] = "[Single channel (Select from List)] " - output_dict["channel_select"] = ( - "processing_channel=[channel " - + str(input_dict["process_channel"] - 1) - + "] " - ) - - output_dict["illumination_processing_option"] = "[All illuminations] " - output_dict["illumination_select"] = "" - if "process_illumination" in input_dict: - output_dict[ - "illumination_processing_option" - ] = "[Single illumination (Select from List)] " - output_dict["illumination_select"] = ( - "processing_illumination=[illumination " - + str(input_dict["process_illumination"]) - + "] " - ) - - output_dict["tile_processing_option"] = "[All tiles] " - output_dict["tile_select"] = "" - if "process_tile" in input_dict: - output_dict["tile_processing_option"] = "[Single tile (Select from List)] " - output_dict["tile_select"] = ( - "processing_tile=[tile " + str(input_dict["process_tile"]) + "] " +class ProcessingOptions(object): + def __init__(self): + self._use_channel = "channels=[Average Channels]" + self._use_tiles = "tiles=[Average Tiles]" + self._channel_processing_option = "[All channels] " + self._channel_select = "" + self._illumination_processing_option = "[All illuminations] " + self._illumination_select = "" + self._tile_processing_option = "[All tiles] " + self._tile_select = "" + self._timepoint_processing_option = "[All Timepoints] " + self._timepoint_select = "" + self._angle_processing_option = "[All angles] " + self._angle_select = "" + + @property + def use_channel(self): + return self._use_channel + + @property + def use_tiles(self): + return self._use_tiles + + @property + def channel_processing_option(self): + return self._channel_processing_option + + @property + def channel_select(self): + return self._channel_select + + @property + def illumination_processing_option(self): + return self._illumination_processing_option + + @property + def illumination_select(self): + return self._illumination_select + + @property + def tile_processing_option(self): + return self._tile_processing_option + + @property + def tile_select(self): + return self._tile_select + + @property + def timepoint_processing_option(self): + return self._timepoint_processing_option + + @property + def timepoint_select(self): + return self._timepoint_select + + @property + def angle_processing_option(self): + return self._angle_processing_option + + @property + def angle_select(self): + return self._angle_select + + @use_channel.setter + def use_channel(self, value): + channel = int(value) - 1 + self._use_channel = "channels=[use Channel %s] " % channel + + @use_tiles.setter + def use_tiles(self, value): + self._use_tiles = "tiles=[use Tile %s] " % value + + @channel_processing_option.setter + def channel_processing_option(self, value): + self._channel_processing_option = value + + @channel_select.setter + def channel_select(self, value): + # NOTE: also requires `channel_processing_option` to be adjusted + self.channel_processing_option = "[Single channel (Select from List)] " + channel = int(value) - 1 + self._channel_select = "processing_channel=[channel %s] " % channel + + @illumination_processing_option.setter + def illumination_processing_option(self, value): + self._illumination_processing_option = value + + @illumination_select.setter + def illumination_select(self, value): + # NOTE: also requires `illumination_processing_option` to be adjusted + self.illumination_processing_option = ( + "[Single illumination (Select from List)] " ) - - output_dict["timepoint_processing_option"] = "[All Timepoints] " - output_dict["timepoint_select"] = "" - if "process_timepoint" in input_dict: - output_dict[ - "timepoint_processing_option" - ] = "[Single timepoint (Select from List)] " - output_dict["timepoint_select"] = ( - "processing_timepoint=[timepoint " - + str(input_dict["process_timepoint"]) - + "] " - ) - - output_dict["angle_processing_option"] = "[All angles] " - output_dict["angle_select"] = "" - if "process_angle" in input_dict: - output_dict["angle_processing_option"] = "[Single angle (Select from List)] " - output_dict["angle_select"] = ( - "processing_angle=[angle " + str(input_dict["process_angle"]) + "] " - ) - - log.debug(output_dict) - return output_dict + self._illumination_select = "processing_illumination=[illumination %s] " % value + + @tile_processing_option.setter + def tile_processing_option(self, value): + self._tile_processing_option = value + + @tile_select.setter + def tile_select(self, value): + # NOTE: also requires `tile_processing_option` to be adjusted + self.tile_processing_option = "[Single tile (Select from List)] " + self._tile_select = "processing_tile=[tile %s] " % value + + @timepoint_processing_option.setter + def timepoint_processing_option(self, value): + self._timepoint_processing_option = value + + @timepoint_select.setter + def timepoint_select(self, value): + # NOTE: also requires `timepoint_processing_option` to be adjusted + self.timepoint_processing_option = "[Single timepoint (Select from List)] " + self._timepoint_select = "processing_timepoint=[timepoint %s] " % value + + @angle_processing_option.setter + def angle_processing_option(self, value): + self._angle_processing_option = value + + @angle_select.setter + def angle_select(self, value): + # NOTE: also requires `angle_processing_option` to be adjusted + self.angle_processing_option = "[Single angle (Select from List)] " + self._angle_select = "processing_angle=[angle ] " % value From 0822b624a92d88051099b613c18291281fa5be49 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 31 Oct 2023 16:48:31 +0100 Subject: [PATCH 101/678] Refactor fuse_dataset() to use ProcessingOptions --- src/imcflibs/imagej/bdv.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 49274513..d2021f58 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -943,7 +943,7 @@ def duplicate_transformations( def fuse_dataset( project_path, - input_dict={}, + processing_opts=None, result_path=None, downsampling=1, interpolation="[Linear Interpolation]", @@ -961,9 +961,10 @@ def fuse_dataset( ---------- project_path : str Path to the `.xml` on which to run the fusion. - input_dict : dict, optional - Dictionary containing all the required informations for angles, - channels, illuminations, tiles and timepoints. By default an empty dict. + processing_opts : imcflibs.imagej.bdv.ProcessingOptions, optional + The `ProcessingOptinos` object defining parameters for the run. Will + fall back to the defaults defined in the corresponding class if the + parameter is `None` or skipped. result_path : str, optional Path to store the resulting fused image, by default `None` which will store the result in the same folder as the input project. @@ -977,28 +978,29 @@ def fuse_dataset( Format of the output fused image, by default `HDF5`. """ + 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) - options_dict = parse_options(input_dict) - options = ( "select=[" + project_path + "] " + "process_angle=" - + options_dict["angle_processing_option"] + + processing_opts.angle_processing_option + "process_channel=" - + options_dict["channel_processing_option"] + + processing_opts.channel_processing_option + "process_illumination=" - + options_dict["illumination_processing_option"] + + processing_opts.illumination_processing_option + "process_tile=" - + options_dict["tile_processing_option"] + + processing_opts.tile_processing_option + "process_timepoint=" - + options_dict["timepoint_processing_option"] + + processing_opts.timepoint_processing_option + "bounding_box=[All Views] " + "downsampling=" + str(downsampling) From 4d84c7dedf235fa2f33fb72badd4941f47c45f61 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 31 Oct 2023 16:48:53 +0100 Subject: [PATCH 102/678] Refactor optimize_and_apply_shifts() to use ProcessingOptions --- src/imcflibs/imagej/bdv.py | 54 ++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index d2021f58..b24ab454 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -553,7 +553,7 @@ def filter_pairwise_shifts( def optimize_and_apply_shifts( project_path, - input_dict={}, + processing_opts=None, treat_timepoints="group", treat_channels="group", treat_illuminations="group", @@ -568,9 +568,10 @@ def optimize_and_apply_shifts( ---------- project_path : str Path to the `.xml` on which to optimize and apply the shifts. - input_dict : dict, optional - Dictionary containing all the required information for angles, - channels, illuminations, tiles and timepoints. By default an empty dict. + processing_opts : imcflibs.imagej.bdv.ProcessingOptions, optional + The `ProcessingOptinos` object defining parameters for the run. Will + fall back to the defaults defined in the corresponding class if the + parameter is `None` or skipped. treat_timepoints : str, optional How to treat the timepoints, by default `group`. treat_channels : str, optional @@ -587,45 +588,40 @@ def optimize_and_apply_shifts( Absolute alignment error (in px) to accept, by default `3.5`. """ - # FIXME: input_dict is not a good parameter name, plus the parse_options() - # function needs to be refactored and documented first! + if processing_opts is None: + processing_opts = ProcessingOptions() file_info = pathtools.parse_path(project_path) - options_dict = parse_options(input_dict) - use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" - use_channel = options_dict["use_channel"] if treat_channels == "group" else "" - use_illumination = ( - "illuminations=[Average Illuminations]" - if treat_illuminations == "group" - else "" - ) - use_timepoint = ( - "timepoints=[Average Timepoints]" if treat_timepoints == "group" else "" - ) - use_tile = options_dict["use_tiles"] if treat_tiles == "group" else "" + use_channel = processing_opts.use_channel if treat_channels == "group" else "" + use_illumination = "" + if treat_illuminations == "group": + use_illumination = "illuminations=[Average Illuminations]" + use_timepoint = "" + if treat_timepoints == "group": + use_timepoint = "timepoints=[Average Timepoints]" + use_tile = processing_opts.use_tiles if treat_tiles == "group" else "" options = ( "select=[" + project_path + "] " + "process_angle=" - + options_dict["angle_processing_option"] + + processing_opts.angle_processing_option + "process_channel=" - + options_dict["channel_processing_option"] + + processing_opts.channel_processing_option + "process_illumination=" - + options_dict["illumination_processing_option"] + + processing_opts.illumination_processing_option + "process_tile=" - + options_dict["tile_processing_option"] + + processing_opts.tile_processing_option + "process_timepoint=" - + options_dict["timepoint_processing_option"] - + options_dict["timepoint_select"] - + options_dict["angle_select"] - + options_dict["channel_select"] - + options_dict["illumination_select"] - + options_dict["tile_select"] - + options_dict["timepoint_select"] + + processing_opts.timepoint_processing_option + + processing_opts.timepoint_select + + processing_opts.angle_select + + processing_opts.channel_select + + processing_opts.illumination_select + + processing_opts.tile_select + " " + "relative=" + str(relative_error) From a998a5c89e93954932b50457a101f1c6af99e89c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 31 Oct 2023 16:49:31 +0100 Subject: [PATCH 103/678] Add WARNING note --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b24ab454..0d4a07f8 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -622,7 +622,7 @@ def optimize_and_apply_shifts( + processing_opts.channel_select + processing_opts.illumination_select + processing_opts.tile_select - + " " + + " " # WARNING: original code had another "timepoint_select" option here! + "relative=" + str(relative_error) + " " From 34aa5d29043549f2c17ce6cb47ea6f9c5a6a7ea4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 2 Nov 2023 18:47:49 +0100 Subject: [PATCH 104/678] Refactor phase_correlation_pairwise_shifts_calculation() For using a ProcessingOptions object. --- src/imcflibs/imagej/bdv.py | 43 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 0d4a07f8..23d0e750 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -368,7 +368,7 @@ def flip_axes(source_xml_file, x=False, y=True, z=False): def phase_correlation_pairwise_shifts_calculation( project_path, - input_dict={}, + processing_opts=None, treat_timepoints="group", treat_channels="group", treat_illuminations="group", @@ -382,9 +382,10 @@ def phase_correlation_pairwise_shifts_calculation( ---------- project_path : str Full path to the `.xml` file. - input_dict : dict, optional - Options dict containing the required information for angle, channel, - illuminations and timepoints. By default an empty dict. + processing_opts : imcflibs.imagej.bdv.ProcessingOptions, optional + The `ProcessingOptinos` object defining parameters for the run. Will + fall back to the defaults defined in the corresponding class if the + parameter is `None` or skipped. treat_timepoints : str, optional How to deal with the timepoints, by default `group`. treat_channels : str, optional @@ -400,15 +401,13 @@ def phase_correlation_pairwise_shifts_calculation( empty which will result in BigStitcher choosing the factors. """ - # FIXME: input_dict is not a good parameter name, plus the parse_options() - # function needs to be refactored and documented first! + if processing_opts is None: + processing_opts = ProcessingOptions() file_info = pathtools.parse_path(project_path) - options_dict = parse_options(input_dict) - use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" - use_channel = options_dict["use_channel"] if treat_channels == "group" else "" + use_channel = processing_opts.use_channel if treat_channels == "group" else "" use_illumination = ( "illuminations=[Average Illuminations]" if treat_illuminations == "group" @@ -417,7 +416,7 @@ def phase_correlation_pairwise_shifts_calculation( use_timepoint = ( "timepoints=[Average Timepoints]" if treat_timepoints == "group" else "" ) - use_tile = options_dict["use_tiles"] if treat_tiles == "group" else "" + use_tile = processing_opts.use_tiles if treat_tiles == "group" else "" if downsampling_xyz != "": downsampling = "downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s " % ( @@ -432,22 +431,22 @@ def phase_correlation_pairwise_shifts_calculation( "select=[" + project_path + "] " - + +"process_angle=" - + options_dict["angle_processing_option"] + + "process_angle=" + + processing_opts.angle_processing_option + "process_channel=" - + options_dict["channel_processing_option"] + + processing_opts.channel_processing_option + "process_illumination=" - + options_dict["illumination_processing_option"] + + processing_opts.illumination_processing_option + "process_tile=" - + options_dict["tile_processing_option"] + + processing_opts.tile_processing_option + "process_timepoint=" - + options_dict["timepoint_processing_option"] - + options_dict["timepoint_select"] - + options_dict["angle_select"] - + options_dict["channel_select"] - + options_dict["illumination_select"] - + options_dict["tile_select"] - + options_dict["timepoint_select"] + + processing_opts.timepoint_processing_option + + processing_opts.timepoint_select + + processing_opts.angle_select + + processing_opts.channel_select + + processing_opts.illumination_select + + processing_opts.tile_select + # + options_dict["timepoint_select"] # FIXME: is this duplication intended?? + " " + "method=[Phase Correlation] " + "show_expert_grouping_options " From 2e34b47a45f149921c629b6fbcdf4890840b4aed Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 2 Nov 2023 18:56:44 +0100 Subject: [PATCH 105/678] Move class definition before functions --- src/imcflibs/imagej/bdv.py | 252 ++++++++++++++++++------------------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 23d0e750..55be2ae4 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -18,6 +18,132 @@ from ..log import LOG as log +class ProcessingOptions(object): + def __init__(self): + self._use_channel = "channels=[Average Channels]" + self._use_tiles = "tiles=[Average Tiles]" + self._channel_processing_option = "[All channels] " + self._channel_select = "" + self._illumination_processing_option = "[All illuminations] " + self._illumination_select = "" + self._tile_processing_option = "[All tiles] " + self._tile_select = "" + self._timepoint_processing_option = "[All Timepoints] " + self._timepoint_select = "" + self._angle_processing_option = "[All angles] " + self._angle_select = "" + + @property + def use_channel(self): + return self._use_channel + + @property + def use_tiles(self): + return self._use_tiles + + @property + def channel_processing_option(self): + return self._channel_processing_option + + @property + def channel_select(self): + return self._channel_select + + @property + def illumination_processing_option(self): + return self._illumination_processing_option + + @property + def illumination_select(self): + return self._illumination_select + + @property + def tile_processing_option(self): + return self._tile_processing_option + + @property + def tile_select(self): + return self._tile_select + + @property + def timepoint_processing_option(self): + return self._timepoint_processing_option + + @property + def timepoint_select(self): + return self._timepoint_select + + @property + def angle_processing_option(self): + return self._angle_processing_option + + @property + def angle_select(self): + return self._angle_select + + @use_channel.setter + def use_channel(self, value): + channel = int(value) - 1 + self._use_channel = "channels=[use Channel %s] " % channel + + @use_tiles.setter + def use_tiles(self, value): + self._use_tiles = "tiles=[use Tile %s] " % value + + @channel_processing_option.setter + def channel_processing_option(self, value): + self._channel_processing_option = value + + @channel_select.setter + def channel_select(self, value): + # NOTE: also requires `channel_processing_option` to be adjusted + self.channel_processing_option = "[Single channel (Select from List)] " + channel = int(value) - 1 + self._channel_select = "processing_channel=[channel %s] " % channel + + @illumination_processing_option.setter + def illumination_processing_option(self, value): + self._illumination_processing_option = value + + @illumination_select.setter + def illumination_select(self, value): + # NOTE: also requires `illumination_processing_option` to be adjusted + self.illumination_processing_option = ( + "[Single illumination (Select from List)] " + ) + self._illumination_select = "processing_illumination=[illumination %s] " % value + + @tile_processing_option.setter + def tile_processing_option(self, value): + self._tile_processing_option = value + + @tile_select.setter + def tile_select(self, value): + # NOTE: also requires `tile_processing_option` to be adjusted + self.tile_processing_option = "[Single tile (Select from List)] " + self._tile_select = "processing_tile=[tile %s] " % value + + @timepoint_processing_option.setter + def timepoint_processing_option(self, value): + self._timepoint_processing_option = value + + @timepoint_select.setter + def timepoint_select(self, value): + # NOTE: also requires `timepoint_processing_option` to be adjusted + self.timepoint_processing_option = "[Single timepoint (Select from List)] " + self._timepoint_select = "processing_timepoint=[timepoint %s] " % value + + @angle_processing_option.setter + def angle_processing_option(self, value): + self._angle_processing_option = value + + @angle_select.setter + def angle_select(self, value): + # NOTE: also requires `angle_processing_option` to be adjusted + self.angle_processing_option = "[Single angle (Select from List)] " + self._angle_select = "processing_angle=[angle ] " % value + + def backup_xml_files(source_directory, subfolder_name): """Create a backup of BDV-XML files inside a subfolder of `xml-backup`. @@ -1056,129 +1182,3 @@ def fuse_dataset( log.debug(options) IJ.run("Fuse dataset ...", str(options)) - - -class ProcessingOptions(object): - def __init__(self): - self._use_channel = "channels=[Average Channels]" - self._use_tiles = "tiles=[Average Tiles]" - self._channel_processing_option = "[All channels] " - self._channel_select = "" - self._illumination_processing_option = "[All illuminations] " - self._illumination_select = "" - self._tile_processing_option = "[All tiles] " - self._tile_select = "" - self._timepoint_processing_option = "[All Timepoints] " - self._timepoint_select = "" - self._angle_processing_option = "[All angles] " - self._angle_select = "" - - @property - def use_channel(self): - return self._use_channel - - @property - def use_tiles(self): - return self._use_tiles - - @property - def channel_processing_option(self): - return self._channel_processing_option - - @property - def channel_select(self): - return self._channel_select - - @property - def illumination_processing_option(self): - return self._illumination_processing_option - - @property - def illumination_select(self): - return self._illumination_select - - @property - def tile_processing_option(self): - return self._tile_processing_option - - @property - def tile_select(self): - return self._tile_select - - @property - def timepoint_processing_option(self): - return self._timepoint_processing_option - - @property - def timepoint_select(self): - return self._timepoint_select - - @property - def angle_processing_option(self): - return self._angle_processing_option - - @property - def angle_select(self): - return self._angle_select - - @use_channel.setter - def use_channel(self, value): - channel = int(value) - 1 - self._use_channel = "channels=[use Channel %s] " % channel - - @use_tiles.setter - def use_tiles(self, value): - self._use_tiles = "tiles=[use Tile %s] " % value - - @channel_processing_option.setter - def channel_processing_option(self, value): - self._channel_processing_option = value - - @channel_select.setter - def channel_select(self, value): - # NOTE: also requires `channel_processing_option` to be adjusted - self.channel_processing_option = "[Single channel (Select from List)] " - channel = int(value) - 1 - self._channel_select = "processing_channel=[channel %s] " % channel - - @illumination_processing_option.setter - def illumination_processing_option(self, value): - self._illumination_processing_option = value - - @illumination_select.setter - def illumination_select(self, value): - # NOTE: also requires `illumination_processing_option` to be adjusted - self.illumination_processing_option = ( - "[Single illumination (Select from List)] " - ) - self._illumination_select = "processing_illumination=[illumination %s] " % value - - @tile_processing_option.setter - def tile_processing_option(self, value): - self._tile_processing_option = value - - @tile_select.setter - def tile_select(self, value): - # NOTE: also requires `tile_processing_option` to be adjusted - self.tile_processing_option = "[Single tile (Select from List)] " - self._tile_select = "processing_tile=[tile %s] " % value - - @timepoint_processing_option.setter - def timepoint_processing_option(self, value): - self._timepoint_processing_option = value - - @timepoint_select.setter - def timepoint_select(self, value): - # NOTE: also requires `timepoint_processing_option` to be adjusted - self.timepoint_processing_option = "[Single timepoint (Select from List)] " - self._timepoint_select = "processing_timepoint=[timepoint %s] " % value - - @angle_processing_option.setter - def angle_processing_option(self, value): - self._angle_processing_option = value - - @angle_select.setter - def angle_select(self, value): - # NOTE: also requires `angle_processing_option` to be adjusted - self.angle_processing_option = "[Single angle (Select from List)] " - self._angle_select = "processing_angle=[angle ] " % value From 5f417def370c40a2b36735f8c71594dfe3472182 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 2 Nov 2023 22:45:21 +0100 Subject: [PATCH 106/678] Avoid nested if-else-pseudo-oneliners --- src/imcflibs/imagej/bdv.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 55be2ae4..7fbbef6f 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -534,14 +534,12 @@ def phase_correlation_pairwise_shifts_calculation( use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" use_channel = processing_opts.use_channel if treat_channels == "group" else "" - use_illumination = ( - "illuminations=[Average Illuminations]" - if treat_illuminations == "group" - else "" - ) - use_timepoint = ( - "timepoints=[Average Timepoints]" if treat_timepoints == "group" else "" - ) + use_illumination = "" + if treat_illuminations == "group": + use_illumination = "illuminations=[Average Illuminations]" + use_timepoint = "" + if treat_timepoints == "group": + use_timepoint = "timepoints=[Average Timepoints]" use_tile = processing_opts.use_tiles if treat_tiles == "group" else "" if downsampling_xyz != "": From 92bea6595ef22661bbcfb5f98bcdfee0fa7a3171 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 2 Nov 2023 22:48:57 +0100 Subject: [PATCH 107/678] Fix missing placeholder in string formatting --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7fbbef6f..7c5f54f1 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -141,7 +141,7 @@ def angle_processing_option(self, value): def angle_select(self, value): # NOTE: also requires `angle_processing_option` to be adjusted self.angle_processing_option = "[Single angle (Select from List)] " - self._angle_select = "processing_angle=[angle ] " % value + self._angle_select = "processing_angle=[angle %s] " % value def backup_xml_files(source_directory, subfolder_name): From 9a859f1c5636abb524641c7c35d4eb4d93e8184f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 2 Nov 2023 22:52:38 +0100 Subject: [PATCH 108/678] Add ProcessingOptions docstring --- src/imcflibs/imagej/bdv.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7c5f54f1..4b8ced83 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -19,6 +19,25 @@ class ProcessingOptions(object): + + """Helper to store processing options and generate parameter strings. + + Attributes + ---------- + use_channel : str + use_tiles : str + channel_processing_option : str + channel_select : str + illumination_processing_option : str + illumination_select : str + tile_processing_option : str + tile_select : str + timepoint_processing_option : str + timepoint_select : str + angle_processing_option : str + angle_select : str + """ + def __init__(self): self._use_channel = "channels=[Average Channels]" self._use_tiles = "tiles=[Average Tiles]" From febe2f34ae4ec0131357e51e81ccb47a258b0a24 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 00:08:36 +0100 Subject: [PATCH 109/678] Add formatting methods --- src/imcflibs/imagej/bdv.py | 89 +++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 4b8ced83..c65c09a8 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -52,6 +52,50 @@ def __init__(self): self._angle_processing_option = "[All angles] " self._angle_select = "" + def format_acitt_options(self): + """Format Angle / Channel / Illumination / Tile / Timepoint options. + + Build a string providing the `process_angle`, `process_channel`, + `process_illumination`, `process_tile` and `process_timepoint` options + that can be used in a BDV-related `IJ.run` call. + + Returns + ------- + str + """ + parameters = [ + "process_angle=" + self._angle_processing_option, + "process_channel=" + self._channel_processing_option, + "process_illumination=" + self._illumination_processing_option, + "process_tile=" + self._tile_processing_option, + "process_timepoint=" + self._timepoint_processing_option, + ] + parameter_string = " ".join(parameters) + " " + log.debug("Formatted ACITT options: <%s>", parameter_string) + return parameter_string + + def format_acitt_selectors(self): + """Format Angle / Channel / Illumination / Tile / Timepoint selectors. + + Build a string providing the `angle_select`, `channel_select`, + `illumination_select`, `tile_select` and `timepoint_select` options + that can be used in a BDV-related `IJ.run` call. + + Returns + ------- + str + """ + parameters = [ + "angle_select=" + self._angle_select, + "channel_select=" + self._channel_select, + "illumination_select=" + self._illumination_select, + "tile_select=" + self._tile_select, + "timepoint_select=" + self._timepoint_select, + ] + parameter_string = " ".join(parameters) + " " + log.debug("Formatted ACITT selectors: <%s>", parameter_string) + return parameter_string + @property def use_channel(self): return self._use_channel @@ -574,21 +618,8 @@ def phase_correlation_pairwise_shifts_calculation( "select=[" + project_path + "] " - + "process_angle=" - + processing_opts.angle_processing_option - + "process_channel=" - + processing_opts.channel_processing_option - + "process_illumination=" - + processing_opts.illumination_processing_option - + "process_tile=" - + processing_opts.tile_processing_option - + "process_timepoint=" - + processing_opts.timepoint_processing_option - + processing_opts.timepoint_select - + processing_opts.angle_select - + processing_opts.channel_select - + processing_opts.illumination_select - + processing_opts.tile_select + + processing_opts.format_acitt_options() + + processing_opts.format_acitt_selectors() # + options_dict["timepoint_select"] # FIXME: is this duplication intended?? + " " + "method=[Phase Correlation] " @@ -749,21 +780,8 @@ def optimize_and_apply_shifts( "select=[" + project_path + "] " - + "process_angle=" - + processing_opts.angle_processing_option - + "process_channel=" - + processing_opts.channel_processing_option - + "process_illumination=" - + processing_opts.illumination_processing_option - + "process_tile=" - + processing_opts.tile_processing_option - + "process_timepoint=" - + processing_opts.timepoint_processing_option - + processing_opts.timepoint_select - + processing_opts.angle_select - + processing_opts.channel_select - + processing_opts.illumination_select - + processing_opts.tile_select + + processing_opts.format_acitt_options() + + processing_opts.format_acitt_selectors() + " " # WARNING: original code had another "timepoint_select" option here! + "relative=" + str(relative_error) @@ -1129,16 +1147,7 @@ def fuse_dataset( "select=[" + project_path + "] " - + "process_angle=" - + processing_opts.angle_processing_option - + "process_channel=" - + processing_opts.channel_processing_option - + "process_illumination=" - + processing_opts.illumination_processing_option - + "process_tile=" - + processing_opts.tile_processing_option - + "process_timepoint=" - + processing_opts.timepoint_processing_option + + processing_opts.format_acitt_options() + "bounding_box=[All Views] " + "downsampling=" + str(downsampling) From 9b0b3e9d645a963bf5b9958a6af20adee3f3ce47 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 00:08:46 +0100 Subject: [PATCH 110/678] Add docstrings --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index c65c09a8..96185a66 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -98,10 +98,12 @@ def format_acitt_selectors(self): @property def use_channel(self): + """The channels parameter (default: `channels=[Average Channels]`).""" return self._use_channel @property def use_tiles(self): + """The tiles parameter (default: `tiles=[Average Tiles]`).""" return self._use_tiles @property From 0aa1a6b7c0075590f92a0d0187247f0c7f6808d9 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Fri, 3 Nov 2023 14:39:13 +0100 Subject: [PATCH 111/678] add omero file to integrate omero connection to fiji --- src/imcflibs/imagej/omero.py | 122 +++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/imcflibs/imagej/omero.py diff --git a/src/imcflibs/imagej/omero.py b/src/imcflibs/imagej/omero.py new file mode 100644 index 00000000..7db7efec --- /dev/null +++ b/src/imcflibs/imagej/omero.py @@ -0,0 +1,122 @@ +""" +Functions related to connecting OMERO to Fiji for connecting and fetching images directly. + +Contains functions to parse URL, connect to OMERO and fetch image. + +""" +# ImageJ Import +from ij import IJ, Prefs +# Omero Dependencies +from omero.gateway import Gateway +from omero.gateway import LoginCredentials +from omero.log import SimpleLogger + +from loci.plugins import LociExporter + +from loci.plugins. in import ImporterOptions +from loci.plugins.out import Exporter + + + +# โ”€โ”€โ”€ FUNCTIONS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + +def parse_url(omero_str): + """Parse an OMERO URL with one or multiple images selected + + Parameters + ---------- + omero_str : str + String which is either the direct image link gotten from OMERO or + image IDs separated by commas + + Returns + ------- + str[] + List of all the images IDs parsed from the string + """ + if omero_str.startswith("https"): + image_ids = omero_str.split("image-") + image_ids.pop(0) + image_ids = [s.split("%")[0].replace("|", "") for s in image_ids] + else: + image_ids = omero_str.split(",") + return image_ids + + +def connect(host, port, username, password): + """Connect to OMERO using the credentials entered. + + Parameters + ---------- + host : str + The address of the OMERO server. + port : int + The port number for the OMERO server. + username : str + The username for authentication. + password : str + The password for authentication. + + Returns + ------- + omero.gateway.Gateway + A Gateway object representing the connection to the OMERO server. + + """ + + # Omero Connect with credentials and simpleLogger + cred = LoginCredentials() + cred.getServer().setHostname(host) + cred.getServer().setPort(port) + cred.getUser().setUsername(username.strip()) + cred.getUser().setPassword(password.strip()) + simple_logger = SimpleLogger() + gateway = Gateway(simple_logger) + gateway.connect(cred) + return gateway + + +def fetch_image(host, username, password, image_id, group_id=-1): + """Open an image as an ImagePlus object from an OMERO server + + Parameters + ---------- + host : str + Address of your OMERO server. + username : str + Username to use in OMERO. + password : str + Password for authentication. + image_id: int + ID of the image to open. + group_id : double + OMERO group ID, set to -1, signified the user's default group. + """ + + stackview = "viewhyperstack=true stackorder=XYCZT " + dataset_org = "groupfiles=false swapdimensions=false openallseries=false concatenate=false stitchtiles=false" + color_opt = "colormode=Default autoscale=true" + metadata_view = ( + "showmetadata=false showomexml=false showrois=true setroismode=roimanager" + ) + memory_manage = "virtual=false specifyranges=false setcrop=false" + split = " splitchannels=false splitfocalplanes=false splittimepoints=false" + other = "windowless=true" + options = ( + "location=[OMERO] open=[omero:server=%s\nuser=%s\npass=%s\ngroupID=%s\niid=%s] %s %s %s %s %s %s %s " + % ( + host, + username, + password, + group_id, + image_id, + stackview, + dataset_org, + color_opt, + metadata_view, + memory_manage, + split, + other, + ) + ) + IJ.runPlugIn("loci.plugins.LociImporter", options) \ No newline at end of file From e2fe6f1b6ac1cd7e150b4596774f486ac95ece39 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 15:34:39 +0100 Subject: [PATCH 112/678] Clean up unused imports --- src/imcflibs/imagej/omero.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/omero.py b/src/imcflibs/imagej/omero.py index 7db7efec..d8d05fc0 100644 --- a/src/imcflibs/imagej/omero.py +++ b/src/imcflibs/imagej/omero.py @@ -6,16 +6,12 @@ """ # ImageJ Import from ij import IJ, Prefs + # Omero Dependencies from omero.gateway import Gateway from omero.gateway import LoginCredentials from omero.log import SimpleLogger -from loci.plugins import LociExporter - -from loci.plugins. in import ImporterOptions -from loci.plugins.out import Exporter - # โ”€โ”€โ”€ FUNCTIONS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ From 409ef8127fd946c54a3c2d38f87eea0b238d266a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 15:35:12 +0100 Subject: [PATCH 113/678] Remove marker comment (it's an encoding bitch...) --- src/imcflibs/imagej/omero.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/imcflibs/imagej/omero.py b/src/imcflibs/imagej/omero.py index d8d05fc0..e1b409b5 100644 --- a/src/imcflibs/imagej/omero.py +++ b/src/imcflibs/imagej/omero.py @@ -13,9 +13,6 @@ from omero.log import SimpleLogger - -# โ”€โ”€โ”€ FUNCTIONS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - def parse_url(omero_str): """Parse an OMERO URL with one or multiple images selected From 9b39a33368fc2663ad57d78bfbb50b3f5d12f7e8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 15:35:19 +0100 Subject: [PATCH 114/678] Automatic formatting --- src/imcflibs/imagej/omero.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/imcflibs/imagej/omero.py b/src/imcflibs/imagej/omero.py index e1b409b5..4c269a52 100644 --- a/src/imcflibs/imagej/omero.py +++ b/src/imcflibs/imagej/omero.py @@ -96,20 +96,20 @@ def fetch_image(host, username, password, image_id, group_id=-1): split = " splitchannels=false splitfocalplanes=false splittimepoints=false" other = "windowless=true" options = ( - "location=[OMERO] open=[omero:server=%s\nuser=%s\npass=%s\ngroupID=%s\niid=%s] %s %s %s %s %s %s %s " - % ( - host, - username, - password, - group_id, - image_id, - stackview, - dataset_org, - color_opt, - metadata_view, - memory_manage, - split, - other, - ) + "location=[OMERO] open=[omero:server=%s\nuser=%s\npass=%s\ngroupID=%s\niid=%s] %s %s %s %s %s %s %s " + % ( + host, + username, + password, + group_id, + image_id, + stackview, + dataset_org, + color_opt, + metadata_view, + memory_manage, + split, + other, + ) ) - IJ.runPlugIn("loci.plugins.LociImporter", options) \ No newline at end of file + IJ.runPlugIn("loci.plugins.LociImporter", options) From d156e6fd751f6641ff5466dd843fddd990c3cbd2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 15:36:00 +0100 Subject: [PATCH 115/678] Rename 'omero' to 'omerotools' to avoid namespace clashes --- src/imcflibs/imagej/{omero.py => omerotools.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/imcflibs/imagej/{omero.py => omerotools.py} (100%) diff --git a/src/imcflibs/imagej/omero.py b/src/imcflibs/imagej/omerotools.py similarity index 100% rename from src/imcflibs/imagej/omero.py rename to src/imcflibs/imagej/omerotools.py From 4d3cb6121288be94c2146b4df7c2efdeb4ac3a0b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 16:16:32 +0100 Subject: [PATCH 116/678] Rename formatting functions --- src/imcflibs/imagej/bdv.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 96185a66..000f3de1 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -52,7 +52,7 @@ def __init__(self): self._angle_processing_option = "[All angles] " self._angle_select = "" - def format_acitt_options(self): + def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. Build a string providing the `process_angle`, `process_channel`, @@ -74,7 +74,7 @@ def format_acitt_options(self): log.debug("Formatted ACITT options: <%s>", parameter_string) return parameter_string - def format_acitt_selectors(self): + def fmt_acitt_selectors(self): """Format Angle / Channel / Illumination / Tile / Timepoint selectors. Build a string providing the `angle_select`, `channel_select`, @@ -620,8 +620,8 @@ def phase_correlation_pairwise_shifts_calculation( "select=[" + project_path + "] " - + processing_opts.format_acitt_options() - + processing_opts.format_acitt_selectors() + + processing_opts.fmt_acitt_options() + + processing_opts.fmt_acitt_selectors() # + options_dict["timepoint_select"] # FIXME: is this duplication intended?? + " " + "method=[Phase Correlation] " @@ -782,8 +782,8 @@ def optimize_and_apply_shifts( "select=[" + project_path + "] " - + processing_opts.format_acitt_options() - + processing_opts.format_acitt_selectors() + + processing_opts.fmt_acitt_options() + + processing_opts.fmt_acitt_selectors() + " " # WARNING: original code had another "timepoint_select" option here! + "relative=" + str(relative_error) @@ -1149,7 +1149,7 @@ def fuse_dataset( "select=[" + project_path + "] " - + processing_opts.format_acitt_options() + + processing_opts.fmt_acitt_options() + "bounding_box=[All Views] " + "downsampling=" + str(downsampling) From 446b1c7dc99e8ec3aec551b5936babe04eae939d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 17:41:31 +0100 Subject: [PATCH 117/678] Reword module docstring to follow conventions --- src/imcflibs/imagej/omerotools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 4c269a52..a5bb9103 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -1,9 +1,9 @@ -""" -Functions related to connecting OMERO to Fiji for connecting and fetching images directly. - -Contains functions to parse URL, connect to OMERO and fetch image. +"""Functions allowing to interact with an OMERO server. +Contains helpers to parse URLs and / or OMERO image IDs, connect to OMERO and +fetch images from the server. """ + # ImageJ Import from ij import IJ, Prefs From 2842488b394fc0c49d8f16a6b4f60e99028e50a3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 17:41:46 +0100 Subject: [PATCH 118/678] Clean up unused import --- src/imcflibs/imagej/omerotools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index a5bb9103..e30e17cb 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -5,7 +5,7 @@ """ # ImageJ Import -from ij import IJ, Prefs +from ij import IJ # Omero Dependencies from omero.gateway import Gateway From 9aa74e6d0763126df75cecf2ba5458d6b56c874d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 17:44:27 +0100 Subject: [PATCH 119/678] Rename function and parameter Since it's not just parsing URLs this should be reflected in the naming. --- src/imcflibs/imagej/omerotools.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index e30e17cb..661ccc12 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -13,12 +13,12 @@ from omero.log import SimpleLogger -def parse_url(omero_str): +def parse_image_ids(input_string): """Parse an OMERO URL with one or multiple images selected Parameters ---------- - omero_str : str + input_string : str String which is either the direct image link gotten from OMERO or image IDs separated by commas @@ -27,12 +27,12 @@ def parse_url(omero_str): str[] List of all the images IDs parsed from the string """ - if omero_str.startswith("https"): - image_ids = omero_str.split("image-") + if input_string.startswith("https"): + image_ids = input_string.split("image-") image_ids.pop(0) image_ids = [s.split("%")[0].replace("|", "") for s in image_ids] else: - image_ids = omero_str.split(",") + image_ids = input_string.split(",") return image_ids From be3062f47c30d4583170c542a4456d9a8e8abdd5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 17:44:48 +0100 Subject: [PATCH 120/678] Reword docstring --- src/imcflibs/imagej/omerotools.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 661ccc12..0b6429d7 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -14,18 +14,19 @@ def parse_image_ids(input_string): - """Parse an OMERO URL with one or multiple images selected + """Parse an OMERO URL or a string with image IDs into a list. Parameters ---------- input_string : str - String which is either the direct image link gotten from OMERO or - image IDs separated by commas + String which is either the direct image link (URL) from OMERO.web + (which may contain multiple images selected) or a sequence of OMERO + image IDs separated by commas. Returns ------- str[] - List of all the images IDs parsed from the string + List of all the image IDs parsed from the input string. """ if input_string.startswith("https"): image_ids = input_string.split("image-") From 44e8a0275daa6062ab76d9ebfd16500590bc8bb4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 17:46:00 +0100 Subject: [PATCH 121/678] Docstring cleanups --- src/imcflibs/imagej/omerotools.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 0b6429d7..2d20e863 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -38,12 +38,12 @@ def parse_image_ids(input_string): def connect(host, port, username, password): - """Connect to OMERO using the credentials entered. + """Connect to OMERO using the credentials provided. Parameters ---------- host : str - The address of the OMERO server. + The address (FQDN or IP) of the OMERO server. port : int The port number for the OMERO server. username : str @@ -55,9 +55,7 @@ def connect(host, port, username, password): ------- omero.gateway.Gateway A Gateway object representing the connection to the OMERO server. - """ - # Omero Connect with credentials and simpleLogger cred = LoginCredentials() cred.getServer().setHostname(host) From 41641c332903b3896e7fbabee7fd4b31f2cf9dc5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 21:22:38 +0100 Subject: [PATCH 122/678] More docstring cleanups --- src/imcflibs/imagej/omerotools.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 2d20e863..390f1ac8 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -69,20 +69,24 @@ def connect(host, port, username, password): def fetch_image(host, username, password, image_id, group_id=-1): - """Open an image as an ImagePlus object from an OMERO server + """Fetch an image from an OMERO server and open it as an ImagePlus. + + NOTE: the function does **NOT** return the ImagePlus (nor its ID) as this + information is not provided by the underlying `loci.plugins.LociImporter` + call - it simply opens it in the running ImageJ instance. Parameters ---------- host : str - Address of your OMERO server. + The address (FQDN or IP) of the OMERO server. username : str - Username to use in OMERO. + The username for authentication. password : str - Password for authentication. + The password for authentication. image_id: int - ID of the image to open. - group_id : double - OMERO group ID, set to -1, signified the user's default group. + ID of the image to fetch. + group_id : int, optional + The OMERO group ID, by default -1 meaning the user's default group. """ stackview = "viewhyperstack=true stackorder=XYCZT " From 323169a8367cf2710a4455dfcc44788420f606ed Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 3 Nov 2023 21:23:06 +0100 Subject: [PATCH 123/678] Make many-parameter-strings more readable --- src/imcflibs/imagej/omerotools.py | 45 +++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 390f1ac8..562d5794 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -88,24 +88,41 @@ def fetch_image(host, username, password, image_id, group_id=-1): group_id : int, optional The OMERO group ID, by default -1 meaning the user's default group. """ - stackview = "viewhyperstack=true stackorder=XYCZT " - dataset_org = "groupfiles=false swapdimensions=false openallseries=false concatenate=false stitchtiles=false" + dataset_org = " ".join( + [ + "groupfiles=false", + "swapdimensions=false", + "openallseries=false", + "concatenate=false", + "stitchtiles=false", + ] + ) color_opt = "colormode=Default autoscale=true" - metadata_view = ( - "showmetadata=false showomexml=false showrois=true setroismode=roimanager" + metadata_view = " ".join( + [ + "showmetadata=false", + "showomexml=false", + "showrois=true", + "setroismode=roimanager", + ] ) memory_manage = "virtual=false specifyranges=false setcrop=false" - split = " splitchannels=false splitfocalplanes=false splittimepoints=false" + split = "splitchannels=false splitfocalplanes=false splittimepoints=false" other = "windowless=true" - options = ( - "location=[OMERO] open=[omero:server=%s\nuser=%s\npass=%s\ngroupID=%s\niid=%s] %s %s %s %s %s %s %s " - % ( - host, - username, - password, - group_id, - image_id, + open_options = "\n".join( + [ + "open=[omero:server=" + host, + "user=" + username, + "pass=" + password, + "groupID=" + group_id, + "iid=" + image_id + "]", + ] + ) + options = " ".join( + [ + "location=[OMERO]", + open_options, stackview, dataset_org, color_opt, @@ -113,6 +130,6 @@ def fetch_image(host, username, password, image_id, group_id=-1): memory_manage, split, other, - ) + ] ) IJ.runPlugIn("loci.plugins.LociImporter", options) From d1b779f736fd8e1d226a60700e8adce879116214 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 16:58:44 +0100 Subject: [PATCH 124/678] Re-order and -group class attributes --- src/imcflibs/imagej/bdv.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 000f3de1..3835775e 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -39,18 +39,23 @@ class ProcessingOptions(object): """ def __init__(self): - self._use_channel = "channels=[Average Channels]" - self._use_tiles = "tiles=[Average Tiles]" + self._angle_processing_option = "[All angles] " + self._angle_select = "" + self._channel_processing_option = "[All channels] " self._channel_select = "" + self._illumination_processing_option = "[All illuminations] " self._illumination_select = "" + self._tile_processing_option = "[All tiles] " self._tile_select = "" + self._timepoint_processing_option = "[All Timepoints] " self._timepoint_select = "" - self._angle_processing_option = "[All angles] " - self._angle_select = "" + + self._use_channel = "" + self._use_tiles = "tiles=[Average Tiles]" def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. From 43be5dfeb81601fbb5460e84c94662f680028dec Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 17:14:48 +0100 Subject: [PATCH 125/678] Complete "use_" attributes section --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 3835775e..b5cddedb 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -54,7 +54,9 @@ def __init__(self): self._timepoint_processing_option = "[All Timepoints] " self._timepoint_select = "" + self._use_angle = "" self._use_channel = "" + self._use_illuminations = "" self._use_tiles = "tiles=[Average Tiles]" def fmt_acitt_options(self): From 93deca581b50ca988b5b68b075db44bb8c1f0654 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 17:15:08 +0100 Subject: [PATCH 126/678] Add "treat_ACITT" attributes --- src/imcflibs/imagej/bdv.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b5cddedb..ec4de2d3 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -59,6 +59,13 @@ def __init__(self): self._use_illuminations = "" self._use_tiles = "tiles=[Average Tiles]" + # 'treat_*' values are: "group", "compare" or "[treat individually]" + self._treat_angles = "[treat individually]" + self._treat_channels = "group" + self._treat_illuminations = "group" + self._treat_tiles = "group" + self._treat_timepoints = "group" + def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. From 9a2a8366b4ce3bc68ca5ae75bd319e683f67e34c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 17:15:31 +0100 Subject: [PATCH 127/678] Add FIXME --- src/imcflibs/imagej/bdv.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index ec4de2d3..6884c708 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -58,6 +58,7 @@ def __init__(self): self._use_channel = "" self._use_illuminations = "" self._use_tiles = "tiles=[Average Tiles]" + # FIXME: why is there no `use_timepoints` attribute? # 'treat_*' values are: "group", "compare" or "[treat individually]" self._treat_angles = "[treat individually]" From cedc294cd35a0fc445b8da19687cedf75aef062d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 17:18:05 +0100 Subject: [PATCH 128/678] Push "fmt_" methods below attribute getters / setters --- src/imcflibs/imagej/bdv.py | 87 +++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 6884c708..f9bd4ce6 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -67,50 +67,6 @@ def __init__(self): self._treat_tiles = "group" self._treat_timepoints = "group" - def fmt_acitt_options(self): - """Format Angle / Channel / Illumination / Tile / Timepoint options. - - Build a string providing the `process_angle`, `process_channel`, - `process_illumination`, `process_tile` and `process_timepoint` options - that can be used in a BDV-related `IJ.run` call. - - Returns - ------- - str - """ - parameters = [ - "process_angle=" + self._angle_processing_option, - "process_channel=" + self._channel_processing_option, - "process_illumination=" + self._illumination_processing_option, - "process_tile=" + self._tile_processing_option, - "process_timepoint=" + self._timepoint_processing_option, - ] - parameter_string = " ".join(parameters) + " " - log.debug("Formatted ACITT options: <%s>", parameter_string) - return parameter_string - - def fmt_acitt_selectors(self): - """Format Angle / Channel / Illumination / Tile / Timepoint selectors. - - Build a string providing the `angle_select`, `channel_select`, - `illumination_select`, `tile_select` and `timepoint_select` options - that can be used in a BDV-related `IJ.run` call. - - Returns - ------- - str - """ - parameters = [ - "angle_select=" + self._angle_select, - "channel_select=" + self._channel_select, - "illumination_select=" + self._illumination_select, - "tile_select=" + self._tile_select, - "timepoint_select=" + self._timepoint_select, - ] - parameter_string = " ".join(parameters) + " " - log.debug("Formatted ACITT selectors: <%s>", parameter_string) - return parameter_string - @property def use_channel(self): """The channels parameter (default: `channels=[Average Channels]`).""" @@ -223,6 +179,49 @@ def angle_select(self, value): self.angle_processing_option = "[Single angle (Select from List)] " self._angle_select = "processing_angle=[angle %s] " % value + def fmt_acitt_options(self): + """Format Angle / Channel / Illumination / Tile / Timepoint options. + + Build a string providing the `process_angle`, `process_channel`, + `process_illumination`, `process_tile` and `process_timepoint` options + that can be used in a BDV-related `IJ.run` call. + + Returns + ------- + str + """ + parameters = [ + "process_angle=" + self._angle_processing_option, + "process_channel=" + self._channel_processing_option, + "process_illumination=" + self._illumination_processing_option, + "process_tile=" + self._tile_processing_option, + "process_timepoint=" + self._timepoint_processing_option, + ] + parameter_string = " ".join(parameters) + " " + log.debug("Formatted ACITT options: <%s>", parameter_string) + return parameter_string + + def fmt_acitt_selectors(self): + """Format Angle / Channel / Illumination / Tile / Timepoint selectors. + + Build a string providing the `angle_select`, `channel_select`, + `illumination_select`, `tile_select` and `timepoint_select` options + that can be used in a BDV-related `IJ.run` call. + + Returns + ------- + str + """ + parameters = [ + "angle_select=" + self._angle_select, + "channel_select=" + self._channel_select, + "illumination_select=" + self._illumination_select, + "tile_select=" + self._tile_select, + "timepoint_select=" + self._timepoint_select, + ] + parameter_string = " ".join(parameters) + " " + log.debug("Formatted ACITT selectors: <%s>", parameter_string) + return parameter_string def backup_xml_files(source_directory, subfolder_name): """Create a backup of BDV-XML files inside a subfolder of `xml-backup`. From 19da410431d921366567498c9cfd11f006cdede9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 17:23:05 +0100 Subject: [PATCH 129/678] Clean up "X_processing_option" getters / setters Those attributes are truly "private" as their valuewill be derived automatically from other values / actions. --- src/imcflibs/imagej/bdv.py | 60 +++++++------------------------------- 1 file changed, 10 insertions(+), 50 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f9bd4ce6..ba6608bb 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -77,42 +77,22 @@ def use_tiles(self): """The tiles parameter (default: `tiles=[Average Tiles]`).""" return self._use_tiles - @property - def channel_processing_option(self): - return self._channel_processing_option - @property def channel_select(self): return self._channel_select - @property - def illumination_processing_option(self): - return self._illumination_processing_option - @property def illumination_select(self): return self._illumination_select - @property - def tile_processing_option(self): - return self._tile_processing_option - @property def tile_select(self): return self._tile_select - @property - def timepoint_processing_option(self): - return self._timepoint_processing_option - @property def timepoint_select(self): return self._timepoint_select - @property - def angle_processing_option(self): - return self._angle_processing_option - @property def angle_select(self): return self._angle_select @@ -126,57 +106,37 @@ def use_channel(self, value): def use_tiles(self, value): self._use_tiles = "tiles=[use Tile %s] " % value - @channel_processing_option.setter - def channel_processing_option(self, value): - self._channel_processing_option = value - @channel_select.setter def channel_select(self, value): - # NOTE: also requires `channel_processing_option` to be adjusted - self.channel_processing_option = "[Single channel (Select from List)] " + # NOTE: also requires `_channel_processing_option` to be adjusted + self._channel_processing_option = "[Single channel (Select from List)] " channel = int(value) - 1 self._channel_select = "processing_channel=[channel %s] " % channel - @illumination_processing_option.setter - def illumination_processing_option(self, value): - self._illumination_processing_option = value - @illumination_select.setter def illumination_select(self, value): - # NOTE: also requires `illumination_processing_option` to be adjusted - self.illumination_processing_option = ( + # NOTE: also requires `_illumination_processing_option` to be adjusted + self._illumination_processing_option = ( "[Single illumination (Select from List)] " ) self._illumination_select = "processing_illumination=[illumination %s] " % value - @tile_processing_option.setter - def tile_processing_option(self, value): - self._tile_processing_option = value - @tile_select.setter def tile_select(self, value): - # NOTE: also requires `tile_processing_option` to be adjusted - self.tile_processing_option = "[Single tile (Select from List)] " + # NOTE: also requires `_tile_processing_option` to be adjusted + self._tile_processing_option = "[Single tile (Select from List)] " self._tile_select = "processing_tile=[tile %s] " % value - @timepoint_processing_option.setter - def timepoint_processing_option(self, value): - self._timepoint_processing_option = value - @timepoint_select.setter def timepoint_select(self, value): - # NOTE: also requires `timepoint_processing_option` to be adjusted - self.timepoint_processing_option = "[Single timepoint (Select from List)] " + # NOTE: also requires `_timepoint_processing_option` to be adjusted + self._timepoint_processing_option = "[Single timepoint (Select from List)] " self._timepoint_select = "processing_timepoint=[timepoint %s] " % value - @angle_processing_option.setter - def angle_processing_option(self, value): - self._angle_processing_option = value - @angle_select.setter def angle_select(self, value): - # NOTE: also requires `angle_processing_option` to be adjusted - self.angle_processing_option = "[Single angle (Select from List)] " + # NOTE: also requires `_angle_processing_option` to be adjusted + self._angle_processing_option = "[Single angle (Select from List)] " self._angle_select = "processing_angle=[angle %s] " % value def fmt_acitt_options(self): From 37e249649c1560d9a7f71c3a1d34d2da20574e66 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 17:34:29 +0100 Subject: [PATCH 130/678] Always have setters right after their getters Otherwise it's REALLY hard to keep track of what's going on. --- src/imcflibs/imagej/bdv.py | 50 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index ba6608bb..55c55a74 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -72,40 +72,24 @@ def use_channel(self): """The channels parameter (default: `channels=[Average Channels]`).""" return self._use_channel - @property - def use_tiles(self): - """The tiles parameter (default: `tiles=[Average Tiles]`).""" - return self._use_tiles - - @property - def channel_select(self): - return self._channel_select - - @property - def illumination_select(self): - return self._illumination_select - - @property - def tile_select(self): - return self._tile_select - - @property - def timepoint_select(self): - return self._timepoint_select - - @property - def angle_select(self): - return self._angle_select - @use_channel.setter def use_channel(self, value): channel = int(value) - 1 self._use_channel = "channels=[use Channel %s] " % channel + @property + def use_tiles(self): + """The tiles parameter (default: `tiles=[Average Tiles]`).""" + return self._use_tiles + @use_tiles.setter def use_tiles(self, value): self._use_tiles = "tiles=[use Tile %s] " % value + @property + def channel_select(self): + return self._channel_select + @channel_select.setter def channel_select(self, value): # NOTE: also requires `_channel_processing_option` to be adjusted @@ -113,6 +97,10 @@ def channel_select(self, value): channel = int(value) - 1 self._channel_select = "processing_channel=[channel %s] " % channel + @property + def illumination_select(self): + return self._illumination_select + @illumination_select.setter def illumination_select(self, value): # NOTE: also requires `_illumination_processing_option` to be adjusted @@ -121,18 +109,30 @@ def illumination_select(self, value): ) self._illumination_select = "processing_illumination=[illumination %s] " % value + @property + def tile_select(self): + return self._tile_select + @tile_select.setter def tile_select(self, value): # NOTE: also requires `_tile_processing_option` to be adjusted self._tile_processing_option = "[Single tile (Select from List)] " self._tile_select = "processing_tile=[tile %s] " % value + @property + def timepoint_select(self): + return self._timepoint_select + @timepoint_select.setter def timepoint_select(self, value): # NOTE: also requires `_timepoint_processing_option` to be adjusted self._timepoint_processing_option = "[Single timepoint (Select from List)] " self._timepoint_select = "processing_timepoint=[timepoint %s] " % value + @property + def angle_select(self): + return self._angle_select + @angle_select.setter def angle_select(self, value): # NOTE: also requires `_angle_processing_option` to be adjusted From e3d821e4973741487032e27d34f4215eb866aaae Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 17:35:44 +0100 Subject: [PATCH 131/678] Add "fmt_how_to_treat()" formatter method --- src/imcflibs/imagej/bdv.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 55c55a74..86172889 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -183,6 +183,25 @@ def fmt_acitt_selectors(self): log.debug("Formatted ACITT selectors: <%s>", parameter_string) return parameter_string + def fmt_how_to_treat(self): + """Format a parameter string with all `how_to_treat_` options. + + Returns + ------- + str + """ + parameters = [ + "how_to_treat_angles=" + self._treat_angles, + "how_to_treat_channels=" + self._treat_channels, + "how_to_treat_illuminations=" + self._treat_illuminations, + "how_to_treat_tiles=" + self._treat_tiles, + "how_to_treat_timepoints=" + self._treat_timepoints, + ] + parameter_string = " ".join(parameters) + " " + log.debug("Formatted 'how_to_treat_' options: <%s>", parameter_string) + return parameter_string + + def backup_xml_files(source_directory, subfolder_name): """Create a backup of BDV-XML files inside a subfolder of `xml-backup`. From 274262041043b46fa2f49a59c1f546530299984a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 18:44:32 +0100 Subject: [PATCH 132/678] Add pylint pragma for Python 2.7 --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 86172889..5ef3a7b4 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -5,6 +5,8 @@ # The pylint on Python 2.7 is too old to play nicely with black: # pylint: disable-msg=bad-continuation +# There are no such things as f-strings in Python 2.7: +# pylint: disable-msg=consider-using-f-string # Some function names just need to be longer than 30 chars: # pylint: disable-msg=invalid-name import os From 5b402335c1cf9178762a0e60ed8959c0078e0c3e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 18:45:07 +0100 Subject: [PATCH 133/678] Be more consistent with attribute naming --- 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 5ef3a7b4..54af5602 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -58,9 +58,9 @@ def __init__(self): self._use_angle = "" self._use_channel = "" - self._use_illuminations = "" - self._use_tiles = "tiles=[Average Tiles]" - # FIXME: why is there no `use_timepoints` attribute? + self._use_illumination = "" + self._use_tile = "tiles=[Average Tiles]" + self._use_timepoint = "" # 'treat_*' values are: "group", "compare" or "[treat individually]" self._treat_angles = "[treat individually]" @@ -82,11 +82,11 @@ def use_channel(self, value): @property def use_tiles(self): """The tiles parameter (default: `tiles=[Average Tiles]`).""" - return self._use_tiles + return self._use_tile @use_tiles.setter def use_tiles(self, value): - self._use_tiles = "tiles=[use Tile %s] " % value + self._use_tile = "tiles=[use Tile %s] " % value @property def channel_select(self): From 151ed027e831a3d7a1b7036548ef30bf2b15f905 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 18:53:36 +0100 Subject: [PATCH 134/678] Clean up old Pylint setting --- .pylintrc | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index a02e3e5b..5b47f064 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,5 +2,3 @@ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_,x,y,z,rm,rt -# don't mess with black autoformatting, just let it do its job: -disable=bad-continuation \ No newline at end of file From 2d7d6313f312bed6b1ebd03ac2fce9f5b885ea38 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 18:53:59 +0100 Subject: [PATCH 135/678] Prevent pylint from complaining about f-strings and lines count --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index 5b47f064..a23403c0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,3 +2,4 @@ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_,x,y,z,rm,rt +disable=consider-using-f-string,too-many-lines \ No newline at end of file From 069c69e97bc87f1e91547a72b20c80c5d5e99155 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 18:54:10 +0100 Subject: [PATCH 136/678] Pragma cleanup --- src/imcflibs/imagej/bdv.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 54af5602..8d3252ae 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -3,12 +3,9 @@ Mostly convenience wrappers with simplified calls and default values. """ -# The pylint on Python 2.7 is too old to play nicely with black: -# pylint: disable-msg=bad-continuation -# There are no such things as f-strings in Python 2.7: -# pylint: disable-msg=consider-using-f-string # Some function names just need to be longer than 30 chars: # pylint: disable-msg=invalid-name + import os import sys import shutil From e0f0ae71ba391b5be134dd1826f8c6d52abbe4a2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 18:55:56 +0100 Subject: [PATCH 137/678] Add fmt_use_acitt() --- src/imcflibs/imagej/bdv.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 8d3252ae..9ce153ff 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -200,6 +200,27 @@ def fmt_how_to_treat(self): log.debug("Formatted 'how_to_treat_' options: <%s>", parameter_string) return parameter_string + def fmt_use_acitt(self): + """Format expert grouping options, e.g. `channels=[use Channel 2]`. + + Generate a parameter string using the configured expert grouping options + for ACITT. Please note that this may be an empty string (`""`). + + Returns + ------- + str + """ + parameters = [ + self._use_angle, + self._use_channel, + self._use_illumination, + self._use_tile, + self._use_timepoint, + ] + parameter_string = " ".join(parameters) + " " + log.debug("Formatted expert grouping 'use' options: <%s>", parameter_string) + return parameter_string + def backup_xml_files(source_directory, subfolder_name): """Create a backup of BDV-XML files inside a subfolder of `xml-backup`. From 4de0ae9b591fd04e53c40486f85177f6d6d985c7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 20:44:12 +0100 Subject: [PATCH 138/678] Explain default angles value --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9ce153ff..7d034804 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -53,6 +53,8 @@ def __init__(self): self._timepoint_processing_option = "[All Timepoints] " self._timepoint_select = "" + # by default `angles` is empty as the "sane" default value for + # "treat_angles" is "[treat individually]" self._use_angle = "" self._use_channel = "" self._use_illumination = "" From aea61b0ad28e728fa261be4c45a4c42580b7fbcc Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 20:44:39 +0100 Subject: [PATCH 139/678] Fix "use" default values --- src/imcflibs/imagej/bdv.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7d034804..99bd3076 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -56,10 +56,11 @@ def __init__(self): # by default `angles` is empty as the "sane" default value for # "treat_angles" is "[treat individually]" self._use_angle = "" - self._use_channel = "" - self._use_illumination = "" + # all other "use" options are set to averaging by default: + self._use_channel = "channels=[Average Channels]" + self._use_illumination = "illuminations=[Average Illuminations]" self._use_tile = "tiles=[Average Tiles]" - self._use_timepoint = "" + self._use_timepoint = "timepoints=[Average Timepoints]" # 'treat_*' values are: "group", "compare" or "[treat individually]" self._treat_angles = "[treat individually]" From 4b2f87235f6378318436ae716a19f4a0014724b7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:08:27 +0100 Subject: [PATCH 140/678] Get rid of pointless properties The respective attributes are not being accessed directly but only used when calling any of the "fmt_" methods, so there is no need to have getters / setters for them. --- src/imcflibs/imagej/bdv.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 99bd3076..f1fb7c52 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -88,22 +88,13 @@ def use_tiles(self): def use_tiles(self, value): self._use_tile = "tiles=[use Tile %s] " % value - @property - def channel_select(self): - return self._channel_select - @channel_select.setter def channel_select(self, value): # NOTE: also requires `_channel_processing_option` to be adjusted self._channel_processing_option = "[Single channel (Select from List)] " channel = int(value) - 1 self._channel_select = "processing_channel=[channel %s] " % channel - @property - def illumination_select(self): - return self._illumination_select - - @illumination_select.setter def illumination_select(self, value): # NOTE: also requires `_illumination_processing_option` to be adjusted self._illumination_processing_option = ( @@ -111,31 +102,16 @@ def illumination_select(self, value): ) self._illumination_select = "processing_illumination=[illumination %s] " % value - @property - def tile_select(self): - return self._tile_select - - @tile_select.setter def tile_select(self, value): # NOTE: also requires `_tile_processing_option` to be adjusted self._tile_processing_option = "[Single tile (Select from List)] " self._tile_select = "processing_tile=[tile %s] " % value - @property - def timepoint_select(self): - return self._timepoint_select - - @timepoint_select.setter def timepoint_select(self, value): # NOTE: also requires `_timepoint_processing_option` to be adjusted self._timepoint_processing_option = "[Single timepoint (Select from List)] " self._timepoint_select = "processing_timepoint=[timepoint %s] " % value - @property - def angle_select(self): - return self._angle_select - - @angle_select.setter def angle_select(self, value): # NOTE: also requires `_angle_processing_option` to be adjusted self._angle_processing_option = "[Single angle (Select from List)] " From f4ad6d3c5ecd6a3d385b88461f15c8e20367a6ff Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:09:16 +0100 Subject: [PATCH 141/678] Have "fmt_use_acitt()" depend on the "treat_*" settings --- 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 f1fb7c52..68ceae5d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -190,11 +190,11 @@ def fmt_use_acitt(self): str """ parameters = [ - self._use_angle, - self._use_channel, - self._use_illumination, - self._use_tile, - self._use_timepoint, + 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_tile if self._treat_tiles == "group" else "", + self._use_timepoint if self._treat_timepoints == "group" else "", ] parameter_string = " ".join(parameters) + " " log.debug("Formatted expert grouping 'use' options: <%s>", parameter_string) From ce27b1737154cd8e2f6c41019f9a568f36f09cd0 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:10:24 +0100 Subject: [PATCH 142/678] Rework "use_*" attributes into "reference_*" methods --- src/imcflibs/imagej/bdv.py | 64 +++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 68ceae5d..827a3829 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -69,25 +69,61 @@ def __init__(self): self._treat_tiles = "group" self._treat_timepoints = "group" - @property - def use_channel(self): - """The channels parameter (default: `channels=[Average Channels]`).""" - return self._use_channel + def reference_angle(self, value): + # FIXME: is the "expert grouping" statement correct? + """Select an angle when using *Expert Grouping Options*. - @use_channel.setter - def use_channel(self, value): - channel = int(value) - 1 + Select the angle(s) to use for the operation, by default empty (`""`). + + NOTE: this value will be used to render `angles=[use Angle VALUE]` + when calling the `fmt_use_acitt()` method. + + Parameters + ---------- + value : str + The tile to use for the grouping. + """ + self._use_angle = "angles=[use Angle %s] " % str(value) + log.debug("New reference angle setting: %s", self._use_angle) + + def reference_channel(self, value): + # FIXME: is the "expert grouping" statement correct? + # FIXME: explain the "-1", why is it done? + """Select reference channel when using *Expert Grouping Options*. + + Select the channel(s) to use for the operation, by default empty (`""`). + + NOTE: this value will be used to render `channels=[use Channel VALUE]` + when calling the `fmt_use_acitt()` method. + + Parameters + ---------- + value : int or int-like + The channel number + 1 to use for the grouping (in other words: the + effectively used value will be the given one minus 1). + """ + channel = int(value) - 1 # will raise a ValueError if cast fails self._use_channel = "channels=[use Channel %s] " % channel + log.debug("New reference channel setting: %s", self._use_channel) - @property - def use_tiles(self): - """The tiles parameter (default: `tiles=[Average Tiles]`).""" - return self._use_tile + def reference_tile(self, value): + # FIXME: is the "expert grouping" statement correct? + # FIXME: what are possible types for the parameter, is it int? + """Select the reference tile when using *Expert Grouping Options*. - @use_tiles.setter - def use_tiles(self, value): - self._use_tile = "tiles=[use Tile %s] " % value + Select the tile(s) to use for the operation, by default the averaging + mode will be used (`tiles=[Average Tiles]`). + NOTE: this value will be used to render `tiles=[use Tile VALUE]` + when calling the `fmt_use_acitt()` method. + + Parameters + ---------- + value : str + The tile to use for the grouping. + """ + self._use_tile = "tiles=[use Tile %s] " % str(value) + log.debug("New reference tile setting: %s", self._use_tile) def channel_select(self, value): # NOTE: also requires `_channel_processing_option` to be adjusted From f7c70b8343dc5e3438a8fa29cf20bd1c5d144703 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:11:43 +0100 Subject: [PATCH 143/678] ACITT order consistency --- src/imcflibs/imagej/bdv.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 827a3829..ff83dd21 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -125,6 +125,11 @@ def reference_tile(self, value): self._use_tile = "tiles=[use Tile %s] " % str(value) log.debug("New reference tile setting: %s", self._use_tile) + def angle_select(self, value): + # NOTE: also requires `_angle_processing_option` to be adjusted + self._angle_processing_option = "[Single angle (Select from List)] " + self._angle_select = "processing_angle=[angle %s] " % value + def channel_select(self, value): # NOTE: also requires `_channel_processing_option` to be adjusted self._channel_processing_option = "[Single channel (Select from List)] " @@ -148,10 +153,6 @@ def timepoint_select(self, value): self._timepoint_processing_option = "[Single timepoint (Select from List)] " self._timepoint_select = "processing_timepoint=[timepoint %s] " % value - def angle_select(self, value): - # NOTE: also requires `_angle_processing_option` to be adjusted - self._angle_processing_option = "[Single angle (Select from List)] " - self._angle_select = "processing_angle=[angle %s] " % value def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. From 0c4981e32f52fe432983bbbde4f2b7ce4c736ecf Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:25:20 +0100 Subject: [PATCH 144/678] Use template strings to avoid redundancies --- src/imcflibs/imagej/bdv.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index ff83dd21..e79a11d4 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -16,6 +16,9 @@ from ..log import LOG as log +# template strings: +SINGLE = "[Single %s (Select from List)] " + class ProcessingOptions(object): @@ -127,30 +130,28 @@ def reference_tile(self, value): def angle_select(self, value): # NOTE: also requires `_angle_processing_option` to be adjusted - self._angle_processing_option = "[Single angle (Select from List)] " + self._angle_processing_option = SINGLE % "angle" self._angle_select = "processing_angle=[angle %s] " % value def channel_select(self, value): # NOTE: also requires `_channel_processing_option` to be adjusted - self._channel_processing_option = "[Single channel (Select from List)] " + self._channel_processing_option = SINGLE % "channel" channel = int(value) - 1 self._channel_select = "processing_channel=[channel %s] " % channel def illumination_select(self, value): # NOTE: also requires `_illumination_processing_option` to be adjusted - self._illumination_processing_option = ( - "[Single illumination (Select from List)] " - ) + self._illumination_processing_option = SINGLE % "illumination" self._illumination_select = "processing_illumination=[illumination %s] " % value def tile_select(self, value): # NOTE: also requires `_tile_processing_option` to be adjusted - self._tile_processing_option = "[Single tile (Select from List)] " + self._tile_processing_option = SINGLE % "tile" self._tile_select = "processing_tile=[tile %s] " % value def timepoint_select(self, value): # NOTE: also requires `_timepoint_processing_option` to be adjusted - self._timepoint_processing_option = "[Single timepoint (Select from List)] " + self._timepoint_processing_option = SINGLE % "timepoint" self._timepoint_select = "processing_timepoint=[timepoint %s] " % value From eaf6efc3862a0bf3ecc0066612489f000451ffd9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:26:34 +0100 Subject: [PATCH 145/678] Rename "select" to "process" methods This resembles more the original code, so transition should be easier. --- src/imcflibs/imagej/bdv.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index e79a11d4..6095f283 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -128,29 +128,24 @@ def reference_tile(self, value): self._use_tile = "tiles=[use Tile %s] " % str(value) log.debug("New reference tile setting: %s", self._use_tile) - def angle_select(self, value): - # NOTE: also requires `_angle_processing_option` to be adjusted + def process_angle(self, value): # def angle_select(self, value): self._angle_processing_option = SINGLE % "angle" self._angle_select = "processing_angle=[angle %s] " % value - def channel_select(self, value): - # NOTE: also requires `_channel_processing_option` to be adjusted + def process_channel(self, value): # def channel_select(self, value): self._channel_processing_option = SINGLE % "channel" channel = int(value) - 1 self._channel_select = "processing_channel=[channel %s] " % channel - def illumination_select(self, value): - # NOTE: also requires `_illumination_processing_option` to be adjusted + def process_illumination(self, value): # def illumination_select(self, value): self._illumination_processing_option = SINGLE % "illumination" self._illumination_select = "processing_illumination=[illumination %s] " % value - def tile_select(self, value): - # NOTE: also requires `_tile_processing_option` to be adjusted + def process_tile(self, value): # def tile_select(self, value): self._tile_processing_option = SINGLE % "tile" self._tile_select = "processing_tile=[tile %s] " % value - def timepoint_select(self, value): - # NOTE: also requires `_timepoint_processing_option` to be adjusted + def process_timepoint(self, value): # def timepoint_select(self, value): self._timepoint_processing_option = SINGLE % "timepoint" self._timepoint_select = "processing_timepoint=[timepoint %s] " % value From ca301113aafc6f07567165d620a608438db0daa8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:39:28 +0100 Subject: [PATCH 146/678] Add remaining "reference_*" methods --- src/imcflibs/imagej/bdv.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 6095f283..482e5ee6 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -109,6 +109,23 @@ def reference_channel(self, value): self._use_channel = "channels=[use Channel %s] " % channel log.debug("New reference channel setting: %s", self._use_channel) + def reference_illumination(self, value): + # FIXME: is the "expert grouping" statement correct? + """Select reference illumination when using *Expert Grouping Options*. + + Select the illumination(s) to use for the operation, by default the averaging + mode will be used (`illuminations=[Average Illuminations]`). + + NOTE: this value will be used to render `illuminations=[use Illumination VALUE]` + when calling the `fmt_use_acitt()` method. + + Parameters + ---------- + value : int or int-like + """ + self._use_illumination = "illuminations=[use Illumination %s] " % value + log.debug("New reference illumination setting: %s", self._use_illumination) + def reference_tile(self, value): # FIXME: is the "expert grouping" statement correct? # FIXME: what are possible types for the parameter, is it int? @@ -128,6 +145,23 @@ def reference_tile(self, value): self._use_tile = "tiles=[use Tile %s] " % str(value) log.debug("New reference tile setting: %s", self._use_tile) + def reference_timepoint(self, value): + # FIXME: is the "expert grouping" statement correct? + """Select reference timepoint when using *Expert Grouping Options*. + + Select the timepoint(s) to use for the operation, by default the averaging mode + will be used (`illuminations=[Average Illuminations]`). + + NOTE: this value will be used to render `timepoints=[use Timepoint VALUE]` when + calling the `fmt_use_acitt()` method. + + Parameters + ---------- + value : int or int-like + """ + self._use_timepoint = "timepoints=[use Timepoint %s] " % value + log.debug("New reference timepoint setting: %s", self._use_timepoint) + def process_angle(self, value): # def angle_select(self, value): self._angle_processing_option = SINGLE % "angle" self._angle_select = "processing_angle=[angle %s] " % value From 5cdca4560f8a55eb51e53dbf02ed513598442ae9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:29:17 +0100 Subject: [PATCH 147/678] Docstring fixes --- src/imcflibs/imagej/bdv.py | 61 ++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 482e5ee6..b90c66a8 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -74,12 +74,12 @@ def __init__(self): def reference_angle(self, value): # FIXME: is the "expert grouping" statement correct? - """Select an angle when using *Expert Grouping Options*. + """Set the reference angle when using *Expert Grouping Options*. Select the angle(s) to use for the operation, by default empty (`""`). - NOTE: this value will be used to render `angles=[use Angle VALUE]` - when calling the `fmt_use_acitt()` method. + NOTE: this value will be used to render `angles=[use Angle VALUE]` when calling + the `fmt_use_acitt()` method. Parameters ---------- @@ -92,12 +92,13 @@ def reference_angle(self, value): def reference_channel(self, value): # FIXME: is the "expert grouping" statement correct? # FIXME: explain the "-1", why is it done? - """Select reference channel when using *Expert Grouping Options*. + """Set the reference channel when using *Expert Grouping Options*. - Select the channel(s) to use for the operation, by default empty (`""`). + Select the channel(s) to use for the operation, by default the averaging mode + will be used (`channels=[Average Channels]`). - NOTE: this value will be used to render `channels=[use Channel VALUE]` - when calling the `fmt_use_acitt()` method. + NOTE: this value will be used to render `channels=[use Channel VALUE]` when + calling the `fmt_use_acitt()` method. Parameters ---------- @@ -111,7 +112,7 @@ def reference_channel(self, value): def reference_illumination(self, value): # FIXME: is the "expert grouping" statement correct? - """Select reference illumination when using *Expert Grouping Options*. + """Set the reference illumination when using *Expert Grouping Options*. Select the illumination(s) to use for the operation, by default the averaging mode will be used (`illuminations=[Average Illuminations]`). @@ -129,13 +130,13 @@ def reference_illumination(self, value): def reference_tile(self, value): # FIXME: is the "expert grouping" statement correct? # FIXME: what are possible types for the parameter, is it int? - """Select the reference tile when using *Expert Grouping Options*. + """Set the reference tile when using *Expert Grouping Options*. - Select the tile(s) to use for the operation, by default the averaging - mode will be used (`tiles=[Average Tiles]`). + Select the tile(s) to use for the operation, by default the averaging mode will + be used (`tiles=[Average Tiles]`). - NOTE: this value will be used to render `tiles=[use Tile VALUE]` - when calling the `fmt_use_acitt()` method. + NOTE: this value will be used to render `tiles=[use Tile VALUE]` when calling + the `fmt_use_acitt()` method. Parameters ---------- @@ -147,10 +148,10 @@ def reference_tile(self, value): def reference_timepoint(self, value): # FIXME: is the "expert grouping" statement correct? - """Select reference timepoint when using *Expert Grouping Options*. + """Set the reference timepoint when using *Expert Grouping Options*. Select the timepoint(s) to use for the operation, by default the averaging mode - will be used (`illuminations=[Average Illuminations]`). + will be used (`timepoints=[Average Timepoints]`). NOTE: this value will be used to render `timepoints=[use Timepoint VALUE]` when calling the `fmt_use_acitt()` method. @@ -163,23 +164,53 @@ def reference_timepoint(self, value): log.debug("New reference timepoint setting: %s", self._use_timepoint) def process_angle(self, value): # def angle_select(self, value): + """Select a single angle to use for processing. + + Parameters + ---------- + value : int or int-like + """ self._angle_processing_option = SINGLE % "angle" self._angle_select = "processing_angle=[angle %s] " % value def process_channel(self, value): # def channel_select(self, value): + """Select a single channel to use for processing. + + Parameters + ---------- + value : int or int-like + """ self._channel_processing_option = SINGLE % "channel" channel = int(value) - 1 self._channel_select = "processing_channel=[channel %s] " % channel def process_illumination(self, value): # def illumination_select(self, value): + """Select a single illumination to use for processing. + + Parameters + ---------- + value : int or int-like + """ self._illumination_processing_option = SINGLE % "illumination" self._illumination_select = "processing_illumination=[illumination %s] " % value def process_tile(self, value): # def tile_select(self, value): + """Select a single tile to use for processing. + + Parameters + ---------- + value : int or int-like + """ self._tile_processing_option = SINGLE % "tile" self._tile_select = "processing_tile=[tile %s] " % value def process_timepoint(self, value): # def timepoint_select(self, value): + """Select a single timepoint to use for processing. + + Parameters + ---------- + value : int or int-like + """ self._timepoint_processing_option = SINGLE % "timepoint" self._timepoint_select = "processing_timepoint=[timepoint %s] " % value From 04e4e17821cc8becbf80cbb4e0ea0d275424e10b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 21:58:10 +0100 Subject: [PATCH 148/678] Add remaining "treat_*" methods --- src/imcflibs/imagej/bdv.py | 60 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b90c66a8..44e4a7a7 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -214,6 +214,66 @@ def process_timepoint(self, value): # def timepoint_select(self, value): self._timepoint_processing_option = SINGLE % "timepoint" self._timepoint_select = "processing_timepoint=[timepoint %s] " % value + def treat_angles(self, value): + """Set the value for the `how_to_treat_angles` option. + + If the value is set to `group` also the `reference_angle` setting will + be adjusted to `angles=[Average Angles]`. + + Parameters + ---------- + value : str + One of `group`, `compare` or `[treat individually]`. + """ + self._treat_angles = value + log.debug("New 'treat_angles' setting: %s", value) + if value == "group": + self._use_angle = "angles=[Average Angles]" + log.debug("New 'use_angle' setting: %s", self._use_angle) + + def treat_channels(self, value): + """Set the value for the `how_to_treat_channels` option. + + Parameters + ---------- + value : str + One of `group`, `compare` or `[treat individually]`. + """ + self._treat_channels = value + log.debug("New 'treat_channels' setting: %s", value) + + def treat_illuminations(self, value): + """Set the value for the `how_to_treat_illuminations` option. + + Parameters + ---------- + value : str + One of `group`, `compare` or `[treat individually]`. + """ + self._treat_illuminations = value + log.debug("New 'treat_illuminations' setting: %s", value) + + def treat_tiles(self, value): + """Set the value for the `how_to_treat_tiles` option. + + Parameters + ---------- + value : str + One of `group`, `compare` or `[treat individually]`. + """ + self._treat_tiles = value + log.debug("New 'treat_tiles' setting: %s", value) + + def treat_timepoints(self, value): + """Set the value for the `how_to_treat_timepoints` option. + + Parameters + ---------- + value : str + One of `group`, `compare` or `[treat individually]`. + """ + self._treat_timepoints = value + log.debug("New 'treat_timepoints' setting: %s", value) def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. From 011a46d0b5c3881e4d17700eb73b1c7d5df9bdd5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 22:39:06 +0100 Subject: [PATCH 149/678] Leave the trailing / separating space to the formatters --- src/imcflibs/imagej/bdv.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 44e4a7a7..bfd5fa9b 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -17,7 +17,7 @@ from ..log import LOG as log # template strings: -SINGLE = "[Single %s (Select from List)] " +SINGLE = "[Single %s (Select from List)]" class ProcessingOptions(object): @@ -41,19 +41,19 @@ class ProcessingOptions(object): """ def __init__(self): - self._angle_processing_option = "[All angles] " + self._angle_processing_option = "[All angles]" self._angle_select = "" - self._channel_processing_option = "[All channels] " + self._channel_processing_option = "[All channels]" self._channel_select = "" - self._illumination_processing_option = "[All illuminations] " + self._illumination_processing_option = "[All illuminations]" self._illumination_select = "" - self._tile_processing_option = "[All tiles] " + self._tile_processing_option = "[All tiles]" self._tile_select = "" - self._timepoint_processing_option = "[All Timepoints] " + self._timepoint_processing_option = "[All Timepoints]" self._timepoint_select = "" # by default `angles` is empty as the "sane" default value for @@ -86,7 +86,7 @@ def reference_angle(self, value): value : str The tile to use for the grouping. """ - self._use_angle = "angles=[use Angle %s] " % str(value) + self._use_angle = "angles=[use Angle %s]" % str(value) log.debug("New reference angle setting: %s", self._use_angle) def reference_channel(self, value): @@ -107,7 +107,7 @@ def reference_channel(self, value): effectively used value will be the given one minus 1). """ channel = int(value) - 1 # will raise a ValueError if cast fails - self._use_channel = "channels=[use Channel %s] " % channel + self._use_channel = "channels=[use Channel %s]" % channel log.debug("New reference channel setting: %s", self._use_channel) def reference_illumination(self, value): @@ -124,7 +124,7 @@ def reference_illumination(self, value): ---------- value : int or int-like """ - self._use_illumination = "illuminations=[use Illumination %s] " % value + self._use_illumination = "illuminations=[use Illumination %s]" % value log.debug("New reference illumination setting: %s", self._use_illumination) def reference_tile(self, value): @@ -143,7 +143,7 @@ def reference_tile(self, value): value : str The tile to use for the grouping. """ - self._use_tile = "tiles=[use Tile %s] " % str(value) + self._use_tile = "tiles=[use Tile %s]" % str(value) log.debug("New reference tile setting: %s", self._use_tile) def reference_timepoint(self, value): @@ -160,7 +160,7 @@ def reference_timepoint(self, value): ---------- value : int or int-like """ - self._use_timepoint = "timepoints=[use Timepoint %s] " % value + self._use_timepoint = "timepoints=[use Timepoint %s]" % value log.debug("New reference timepoint setting: %s", self._use_timepoint) def process_angle(self, value): # def angle_select(self, value): @@ -171,7 +171,7 @@ def process_angle(self, value): # def angle_select(self, value): value : int or int-like """ self._angle_processing_option = SINGLE % "angle" - self._angle_select = "processing_angle=[angle %s] " % value + self._angle_select = "processing_angle=[angle %s]" % value def process_channel(self, value): # def channel_select(self, value): """Select a single channel to use for processing. @@ -182,7 +182,7 @@ def process_channel(self, value): # def channel_select(self, value): """ self._channel_processing_option = SINGLE % "channel" channel = int(value) - 1 - self._channel_select = "processing_channel=[channel %s] " % channel + self._channel_select = "processing_channel=[channel %s]" % channel def process_illumination(self, value): # def illumination_select(self, value): """Select a single illumination to use for processing. @@ -192,7 +192,7 @@ def process_illumination(self, value): # def illumination_select(self, value): value : int or int-like """ self._illumination_processing_option = SINGLE % "illumination" - self._illumination_select = "processing_illumination=[illumination %s] " % value + self._illumination_select = "processing_illumination=[illumination %s]" % value def process_tile(self, value): # def tile_select(self, value): """Select a single tile to use for processing. @@ -202,7 +202,7 @@ def process_tile(self, value): # def tile_select(self, value): value : int or int-like """ self._tile_processing_option = SINGLE % "tile" - self._tile_select = "processing_tile=[tile %s] " % value + self._tile_select = "processing_tile=[tile %s]" % value def process_timepoint(self, value): # def timepoint_select(self, value): """Select a single timepoint to use for processing. @@ -212,7 +212,7 @@ def process_timepoint(self, value): # def timepoint_select(self, value): value : int or int-like """ self._timepoint_processing_option = SINGLE % "timepoint" - self._timepoint_select = "processing_timepoint=[timepoint %s] " % value + self._timepoint_select = "processing_timepoint=[timepoint %s]" % value def treat_angles(self, value): """Set the value for the `how_to_treat_angles` option. From d3bd62492cac5370642076a5481fda899ca92b29 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 22:39:49 +0100 Subject: [PATCH 150/678] Skip selectors that are unset Otherwise they would result in an empty parameter. --- src/imcflibs/imagej/bdv.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index bfd5fa9b..5e40041c 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -309,11 +309,15 @@ def fmt_acitt_selectors(self): str """ parameters = [ - "angle_select=" + self._angle_select, - "channel_select=" + self._channel_select, - "illumination_select=" + self._illumination_select, - "tile_select=" + self._tile_select, - "timepoint_select=" + self._timepoint_select, + "angle_select=" + self._angle_select if self._angle_select else "", + "channel_select=" + self._channel_select if self._channel_select else "", + "illumination_select=" + self._illumination_select + if self._illumination_select + else "", + "tile_select=" + self._tile_select if self._tile_select else "", + "timepoint_select=" + self._timepoint_select + if self._timepoint_select + else "", ] parameter_string = " ".join(parameters) + " " log.debug("Formatted ACITT selectors: <%s>", parameter_string) From ba3b79d9bff71697df04d8cc183bc9bd8061b5b9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 22:41:18 +0100 Subject: [PATCH 151/678] More pylint settings --- .pylintrc | 2 +- src/imcflibs/imagej/bdv.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index a23403c0..340aca6b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,4 +2,4 @@ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_,x,y,z,rm,rt -disable=consider-using-f-string,too-many-lines \ No newline at end of file +disable=consider-using-f-string,too-many-lines,useless-object-inheritance \ No newline at end of file diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 5e40041c..daa73cbe 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -6,6 +6,9 @@ # Some function names just need to be longer than 30 chars: # pylint: disable-msg=invalid-name +# The attribute count is not really our choice: +# pylint: disable-msg=too-many-instance-attributes + import os import sys import shutil From 1243b7950a34b4ccbdc969aac1366942439c3dc2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 22:41:36 +0100 Subject: [PATCH 152/678] Instruct pdoc to ignore the global template string --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index daa73cbe..f79dedab 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -19,8 +19,8 @@ from ..log import LOG as log -# template strings: SINGLE = "[Single %s (Select from List)]" +"""@private template string""" class ProcessingOptions(object): From b906a9943c039d033629313acb553493f326d5a1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 22:48:30 +0100 Subject: [PATCH 153/678] Add defaults to docstrings --- src/imcflibs/imagej/bdv.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f79dedab..f371089a 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -223,6 +223,8 @@ def treat_angles(self, value): If the value is set to `group` also the `reference_angle` setting will be adjusted to `angles=[Average Angles]`. + The default setting is `[treat individually]`. + Parameters ---------- value : str @@ -237,6 +239,8 @@ def treat_angles(self, value): def treat_channels(self, value): """Set the value for the `how_to_treat_channels` option. + The default setting is `group`. + Parameters ---------- value : str @@ -248,6 +252,8 @@ def treat_channels(self, value): def treat_illuminations(self, value): """Set the value for the `how_to_treat_illuminations` option. + The default setting is `group`. + Parameters ---------- value : str @@ -259,6 +265,8 @@ def treat_illuminations(self, value): def treat_tiles(self, value): """Set the value for the `how_to_treat_tiles` option. + The default setting is `group`. + Parameters ---------- value : str @@ -270,6 +278,8 @@ def treat_tiles(self, value): def treat_timepoints(self, value): """Set the value for the `how_to_treat_timepoints` option. + The default setting is `group`. + Parameters ---------- value : str From 6d236ad436087125c434912eaa182d13130cbc61 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 23:14:08 +0100 Subject: [PATCH 154/678] Minor logging and return string handling --- src/imcflibs/imagej/bdv.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f371089a..08a71a32 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -306,9 +306,9 @@ def fmt_acitt_options(self): "process_tile=" + self._tile_processing_option, "process_timepoint=" + self._timepoint_processing_option, ] - parameter_string = " ".join(parameters) + " " - log.debug("Formatted ACITT options: <%s>", parameter_string) - return parameter_string + parameter_string = " ".join(parameters).strip() + log.debug("Formatted 'process_X' options: <%s>", parameter_string) + return parameter_string + " " def fmt_acitt_selectors(self): """Format Angle / Channel / Illumination / Tile / Timepoint selectors. @@ -332,9 +332,9 @@ def fmt_acitt_selectors(self): if self._timepoint_select else "", ] - parameter_string = " ".join(parameters) + " " - log.debug("Formatted ACITT selectors: <%s>", parameter_string) - return parameter_string + parameter_string = " ".join(parameters).strip() + log.debug("Formatted 'processing_X' selectors: <%s>", parameter_string) + return parameter_string + " " def fmt_how_to_treat(self): """Format a parameter string with all `how_to_treat_` options. @@ -350,9 +350,9 @@ def fmt_how_to_treat(self): "how_to_treat_tiles=" + self._treat_tiles, "how_to_treat_timepoints=" + self._treat_timepoints, ] - parameter_string = " ".join(parameters) + " " - log.debug("Formatted 'how_to_treat_' options: <%s>", parameter_string) - return parameter_string + parameter_string = " ".join(parameters).strip() + log.debug("Formatted 'how_to_treat_X' options: <%s>", parameter_string) + return parameter_string + " " def fmt_use_acitt(self): """Format expert grouping options, e.g. `channels=[use Channel 2]`. @@ -371,9 +371,9 @@ def fmt_use_acitt(self): self._use_tile if self._treat_tiles == "group" else "", self._use_timepoint if self._treat_timepoints == "group" else "", ] - parameter_string = " ".join(parameters) + " " + parameter_string = " ".join(parameters).strip() log.debug("Formatted expert grouping 'use' options: <%s>", parameter_string) - return parameter_string + return parameter_string + " " def backup_xml_files(source_directory, subfolder_name): From 13c1bc5294315a0b1cde1e398a024edd14906ab7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 23:14:28 +0100 Subject: [PATCH 155/678] Remove double-prefix with wrong keywords --- src/imcflibs/imagej/bdv.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 08a71a32..8d32ea52 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -322,15 +322,11 @@ def fmt_acitt_selectors(self): str """ parameters = [ - "angle_select=" + self._angle_select if self._angle_select else "", - "channel_select=" + self._channel_select if self._channel_select else "", - "illumination_select=" + self._illumination_select - if self._illumination_select - else "", - "tile_select=" + self._tile_select if self._tile_select else "", - "timepoint_select=" + self._timepoint_select - if self._timepoint_select - else "", + 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._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) From 59fe23e24adf618c1cd9152369bdc5d9cc039abd Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 23:16:18 +0100 Subject: [PATCH 156/678] Section markers --- src/imcflibs/imagej/bdv.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 8d32ea52..c83d27ba 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -75,6 +75,8 @@ def __init__(self): self._treat_tiles = "group" self._treat_timepoints = "group" + ### reference-X methods + def reference_angle(self, value): # FIXME: is the "expert grouping" statement correct? """Set the reference angle when using *Expert Grouping Options*. @@ -166,6 +168,8 @@ def reference_timepoint(self, value): self._use_timepoint = "timepoints=[use Timepoint %s]" % value log.debug("New reference timepoint setting: %s", self._use_timepoint) + ### process-X methods + def process_angle(self, value): # def angle_select(self, value): """Select a single angle to use for processing. @@ -217,6 +221,8 @@ def process_timepoint(self, value): # def timepoint_select(self, value): self._timepoint_processing_option = SINGLE % "timepoint" self._timepoint_select = "processing_timepoint=[timepoint %s]" % value + ### treat-X methods + def treat_angles(self, value): """Set the value for the `how_to_treat_angles` option. @@ -288,6 +294,8 @@ def treat_timepoints(self, value): self._treat_timepoints = value log.debug("New 'treat_timepoints' setting: %s", value) + ### formatter methods + def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. From 8bc5b1cdc84fdee97132f1c2bcadf1222d110126 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Nov 2023 23:18:34 +0100 Subject: [PATCH 157/678] Add an example --- src/imcflibs/imagej/bdv.py | 45 ++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index c83d27ba..6feaede4 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -27,20 +27,37 @@ class ProcessingOptions(object): """Helper to store processing options and generate parameter strings. - Attributes - ---------- - use_channel : str - use_tiles : str - channel_processing_option : str - channel_select : str - illumination_processing_option : str - illumination_select : str - tile_processing_option : str - tile_select : str - timepoint_processing_option : str - timepoint_select : str - angle_processing_option : str - angle_select : str + Example + ------- + NOTE: for readability reasons the output has been split into multiple lines + even though the formatters are returning a single-line string. + + >>> opts = ProcessingOptions() + >>> opts.process_channel(2) + >>> opts.reference_tile(1) + >>> opts.treat_timepoints("compare") + + >>> opts.fmt_acitt_options() + ... process_angle=[All angles] + ... process_channel=[Single channel (Select from List)] + ... process_illumination=[All illuminations] + ... process_tile=[All tiles] + ... process_timepoint=[All Timepoints] + + >>> opts.fmt_acitt_selectors() + ... processing_channel=[channel 1] + + >>> opts.fmt_use_acitt() + ... channels=[Average Channels] + ... illuminations=[Average Illuminations] + ... tiles=[use Tile 1] + + >>> opts.fmt_how_to_treat() + ... how_to_treat_angles=[treat individually] + ... how_to_treat_channels=group + ... how_to_treat_illuminations=group + ... how_to_treat_tiles=group + ... how_to_treat_timepoints=compare """ def __init__(self): From 0e03d97aae421a2fe80cf1dff594df92c26257c3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 10 Nov 2023 12:46:18 +0100 Subject: [PATCH 158/678] Switch to new preprocessing keyword --- src/imcflibs/imagej/_loci.py | 4 ++-- src/imcflibs/imagej/bioformats.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index b675e95e..28da61b7 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -25,7 +25,7 @@ from loci.plugins import BF -from loci.plugins.in import ImporterOptions # pdoc: skip -from loci.formats.in import ZeissCZIReader, DefaultMetadataOptions, MetadataLevel, DynamicMetadataOptions, MetadataOptions # pdoc: skip +from loci.plugins.in import ImporterOptions # mock-preproc: fix-invalid-keyword +from loci.formats.in import ZeissCZIReader, DefaultMetadataOptions, MetadataLevel, DynamicMetadataOptions, MetadataOptions # mock-preproc: fix-invalid-keyword from loci.formats import ImageReader, Memoizer diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index ef97ba37..ca9f5f2b 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -14,7 +14,7 @@ from ij import IJ -from ._loci import ImporterOptions # pdoc: skip +from ._loci import ImporterOptions from ._loci import BF, ImageReader, Memoizer from ..pathtools import gen_name_from_orig From 422ff234fbb1635d49384c15f308f5b4de4eeef5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 10 Nov 2023 15:28:06 +0100 Subject: [PATCH 159/678] Having the pragma once per file is currently enough This allows the imports to be organized in a more readable manner. --- src/imcflibs/imagej/_loci.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index 28da61b7..9043c712 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -23,9 +23,18 @@ ``` """ +# mock-preproc: fix-invalid-keyword + from loci.plugins import BF -from loci.plugins.in import ImporterOptions # mock-preproc: fix-invalid-keyword -from loci.formats.in import ZeissCZIReader, DefaultMetadataOptions, MetadataLevel, DynamicMetadataOptions, MetadataOptions # mock-preproc: fix-invalid-keyword +from loci.plugins.in import ImporterOptions + +from loci.formats.in import ( + ZeissCZIReader, + DefaultMetadataOptions, + MetadataLevel, + DynamicMetadataOptions, + MetadataOptions, +) from loci.formats import ImageReader, Memoizer From d6b5cfc1af2a8f3f9c8d09c653e673cb1039f7b7 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Wed, 29 Nov 2023 11:58:52 +0100 Subject: [PATCH 160/678] fix bug in phase correlation options string --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9ee30178..f0964cca 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -432,7 +432,7 @@ def phase_correlation_pairwise_shifts_calculation( "select=[" + project_path + "] " - + +"process_angle=" + + "process_angle=" + options_dict["angle_processing_option"] + "process_channel=" + options_dict["channel_processing_option"] From 54bdafa4e40eaf62e33f073f350d793b7e502ea4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 6 Feb 2024 17:36:06 +0100 Subject: [PATCH 161/678] Add file with BDV parameter explanations --- src/imcflibs/imagej/bdv_todo.md | 243 ++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 src/imcflibs/imagej/bdv_todo.md diff --git a/src/imcflibs/imagej/bdv_todo.md b/src/imcflibs/imagej/bdv_todo.md new file mode 100644 index 00000000..da877dc4 --- /dev/null +++ b/src/imcflibs/imagej/bdv_todo.md @@ -0,0 +1,243 @@ +# BDV processing-options-class TODO + +/!\ WARNING /!\ + +When the ***auto-loader*** is used, view enumeration in BigStitcher is based on +*metadata*, for example the first channel in a ligthsheet dataset might then be called +*Channel 488* instead of *Channel 1* or *Channel 0*. + +-> It's actually very difficult to forsee this properly, upfront image metadata +inspection would be required to do this! + +In case the ***manual loader*** is used, view enumeration follows the file naming +pattern, e.g. the file name for the first tile and first channel would read +`myimage_tile0_channel1`. + +-> In that case, tile enumeration starts at **zero** while channel enumeration starts at +**one**! + +## Options + +### First options block: "*what to process*" + +The *first options block* is selecting which parts of the h5/xml dataset will be +processed. + +This block is relevant for *most* of our BDV functions: + +- `resave_as_h5` +- `flip_axes` +- `phase_correlation_pairwise_shifts_calculation` +- `filter_pairwise_shifts` +- `optimize_and_apply_shifts` +- `detect_interest_points` +- `interest_points_registration` +- `duplicate_transformations` +- `fuse_dataset` + +Valid choices are: + +- `[All Xs]` +- `[Single X (Select from List)]` +- `[Multiple Xs (Select from List)]` +- `[Range of Xs (Specify by Name)]` + +With `X` being one of `angle`, `channel`, `illumination`, `tile`, `timepoint`. + +### Second options block: details for "*what to process*" + +The *second options block* is **required** for each component having selected anything +other than `All Xs` in the in the first options block. + +- For `Single X (Select from List)` the required option is `processing_X=[X n]`, e.g. + `processing_channel=[channel 1]` for `[Single channel (Select from List)]`. +- For `Multiple Xs (Select from List)` the required options are `X_n X_m`, e.g. + `channel_1 channel_3` for `[Multiple channels (Select from List)]`. +- For `Range of Xs (Specify by Name)` the required option is `process_following_X=n-m`, + e.g. `process_following_channels=1-3` for `[Range of channels (Specify by Name)]`. + +### Third options block: "*how to treat*" + +The *third options block* selects how the components will be processed. + +This options block is relevant for *some* of our BDV functions, for example: + +- `phase_correlation_pairwise_shifts_calculation` +- `optimize_and_apply_shifts` + +Valid choices are: + +- `[treat individually]` +- `group` +- `compare` + +### Fourth options block: details for "*how to treat*" + +The *fourth option block* is ***required*** for each component that has `group` as their +setting in the *third options block*. Exceptions are when e.g. the dataset only has a +single *illumination* (then nothing illumination-related needs to be put into the fourth +block, even if `group` was selected in the third block), or if the first block already +restricted the used data to a single item (for example `Single channel (Select from +List)` was selected in block 1, then nothing channel-related is required in block 4). + +Valid choices are: + +- `Average Ys` +- `Use Y n` + +With `Y` being one of `Angle`, `Channel`, `Illumination`, `Tile`, `Timepoint`. Note the +difference in uppercase / lowercase compared to the `X` from blocks 1 and 2! + +## Examples + +### Example 1 + +Selected options: + +- First block: + - Process angle: all angles + - Process channel: all channels + - Process illumination: all illuminations + - Process tile: all tiles + - Process timepoint: all Timepoints +- Second block: + - (N/A) +- Third block: + - How to treat Timepoints: treat individually + - How to treat Channels: group + - How to treat Illuminations: group + - How to treat Angles: treat individually + - How to treat Tiles: compare +- Fourth block: + - [use Channel 1] + +Resulting macro parameters: + +```text +process_angle=[All angles] +process_channel=[All channels] +process_illumination=[All illuminations] +process_tile=[All tiles] +process_timepoint=[All Timepoints] +method=[Phase Correlation] +show_expert_grouping_options +how_to_treat_timepoints=[treat individually] +how_to_treat_channels=group +how_to_treat_illuminations=group +how_to_treat_angles=[treat individually] +how_to_treat_tiles=compare +channels=[use Channel 1] +``` + +## Example 2 + +Selected options: + +- First block: + - Process angle: All angles + - Process channel: Single channel (Select from List) + - Process illumination: all illuminations + - Process tile: all tiles + - Process timepoint: all Timepoints +- Second block: + - Processing channel: channel 1 +- Third block: + - How to treat Timepoints: treat individually + - How to treat Channels: group + - How to treat Illuminations: group + - How to treat Angles: treat individually + - How to treat Tiles: group +- Fourth block: + - [use Tile 3] + +```text +process_angle=[All angles] +process_channel=[Single channel (Select from List)] +process_illumination=[All illuminations] +process_tile=[All tiles] +process_timepoint=[All Timepoints] +processing_channel=[channel 1] +method=[Phase Correlation] +show_expert_grouping_options +how_to_treat_timepoints=[treat individually] +how_to_treat_channels=group +how_to_treat_illuminations=group +how_to_treat_angles=[treat individually] +how_to_treat_tiles=group +tiles=[use Tile 3] +``` + +## Example 3 + +Selected options: + +- First block: + - Process angle: All angles + - Process channel: Multiple channels (Select from List) + - Process illumination: all illuminations + - Process tile: all tiles + - Process timepoint: all Timepoints +- Second block: + - Processing channel: channel_n channel_m etc +- Third block: + - How to treat Timepoints: treat individually + - How to treat Channels: group + - How to treat Illuminations: group + - How to treat Angles: treat individually + - How to treat Tiles: compare +- Fourth block: + - Average Channels + +```text +process_angle=[All angles] +process_channel=[Multiple channels (Select from List)] +process_illumination=[All illuminations] +process_tile=[All tiles] +process_timepoint=[All Timepoints] +channel_1 +channel_2 +method=[Phase Correlation] +show_expert_grouping_options +how_to_treat_timepoints=[treat individually] +how_to_treat_channels=group +how_to_treat_illuminations=group +how_to_treat_angles=[treat individually] +how_to_treat_tiles=compare +channels=[Average Channels] +``` + +## Example 4 + +- First block: + - Process angle: All angles + - Process channel: Range of channels (Specify by Name) + - Process illumination: all illuminations + - Process tile: all tiles + - Process timepoint: all Timepoints +- Second block: + - Processing following channels: n-m, e.g. 1-3 +- Third block: + - How to treat Timepoints: treat individually + - How to treat Channels: group + - How to treat Illuminations: group + - How to treat Angles: treat individually + - How to treat Tiles: compare +- Fourth block: + - Average Channels + +```text +process_angle=[All angles] +process_channel=[Range of channels (Specify by Name)] +process_illumination=[All illuminations] +process_tile=[All tiles] +process_timepoint=[All Timepoints] +process_following_channels=1-3 +method=[Phase Correlation] +show_expert_grouping_options +how_to_treat_timepoints=[treat individually] +how_to_treat_channels=group +how_to_treat_illuminations=group +how_to_treat_angles=[treat individually] +how_to_treat_tiles=compare +channels=[Average Channels] +``` From 89d73c0d5a29c57f1a2e3812012c533ef8268ae5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 6 Feb 2024 17:41:01 +0100 Subject: [PATCH 162/678] Use fmt_how_to_treat() --- src/imcflibs/imagej/bdv.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 6feaede4..f6f87451 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -785,15 +785,15 @@ def phase_correlation_pairwise_shifts_calculation( file_info = pathtools.parse_path(project_path) - use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" - use_channel = processing_opts.use_channel if treat_channels == "group" else "" + processing_opts.treat_angles(treat_angles) + processing_opts.treat_channels(treat_channels) use_illumination = "" if treat_illuminations == "group": use_illumination = "illuminations=[Average Illuminations]" use_timepoint = "" if treat_timepoints == "group": use_timepoint = "timepoints=[Average Timepoints]" - use_tile = processing_opts.use_tiles if treat_tiles == "group" else "" + use_tile = processing_opts.use_tile if treat_tiles == "group" else "" if downsampling_xyz != "": downsampling = "downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s " % ( @@ -825,21 +825,7 @@ def phase_correlation_pairwise_shifts_calculation( + " " + use_tile + " " - + "how_to_treat_angles=" - + treat_angles - + " " - + "how_to_treat_channels=" - + treat_channels - + " " - + "how_to_treat_illuminations=" - + treat_illuminations - + " " - + "how_to_treat_tiles=" - + treat_tiles - + " " - + "how_to_treat_timepoints=" - + treat_timepoints - + " " + + processing_opts.fmt_how_to_treat() + downsampling + "subpixel_accuracy" ) @@ -956,7 +942,7 @@ def optimize_and_apply_shifts( file_info = pathtools.parse_path(project_path) - use_angle = "angles=[Average Angles]" if treat_angles == "group" else "" + processing_opts.treat_angles(treat_angles) use_channel = processing_opts.use_channel if treat_channels == "group" else "" use_illumination = "" if treat_illuminations == "group": @@ -964,7 +950,7 @@ def optimize_and_apply_shifts( use_timepoint = "" if treat_timepoints == "group": use_timepoint = "timepoints=[Average Timepoints]" - use_tile = processing_opts.use_tiles if treat_tiles == "group" else "" + use_tile = processing_opts.use_tile if treat_tiles == "group" else "" options = ( "select=[" From 388465e677d7d960ec907b525041f3e4f1451e1e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 13 Feb 2024 14:22:56 +0100 Subject: [PATCH 163/678] Add definition class for manual definition --- src/imcflibs/imagej/bdv.py | 143 +++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f6f87451..af25122f 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -397,6 +397,149 @@ def fmt_use_acitt(self): return parameter_string + " " +SINGLE_FILE = "[NO (one %s)]" +MULTI_SINGLE_FILE = "[YES (all %ss in one file)]" +MULTI_MULTI_FILE = "[YES (one file per %s)]" + + +class DefinitionOptions(object): + + """Helper to store definition options and generate parameters strings. + + Example + ------- + NOTE: for readability reasons the output has been split into multiple lines + even though the formatters are returning a single-line string. + + >>> opts = DefinitionOptions() + >>> opts.set_angle_definition("single") + >>> opts.set_channel_definition("multi_single") + + >>> opts.fmt_acitt_options() + ... multiple_angles=[NO (one angle)] + ... multiple_channels=[YES (all channels in one file)] + ... multiple_illuminations=[NO (one illumination direction)] + ... multiple_tiles=[YES (all tiles in one file)] + ... multiple_timepoints=[NO (one time-point)] + """ + + def __init__(self): + self._angle_definition = SINGLE_FILE % "angle" + self._channel_definition = MULTI_SINGLE_FILE % "channel" + self._illumination_definition = SINGLE_FILE % "illumination" + self._tile_definition = MULTI_SINGLE_FILE % "tile" + self._timepoint_definition = SINGLE_FILE % "time-point" + + def check_definition_option(self, value): + """Check if the value is a valid definition option. + + Parameters + ---------- + value : str + Entered value by the user. + + Returns + ------- + dict(str, str): dictionary containing the correct string definition. + """ + if value not in [ + "single", + "multi_single", + "multi_multi", + ]: + raise ValueError("Value must be one of single, multi_multi or multi_single") + + return { + "single": SINGLE_FILE, + "multi_single": MULTI_SINGLE_FILE, + "multi_multi": MULTI_MULTI_FILE, + } + + def set_angle_definition(self, value): + """Set the value for the angle definition + + Parameters + ---------- + value : str + One of `single`, `multi_single` or `multi_multi`. + """ + choices = self.check_definition_option(value) + self._angle_definition = choices[value] % "angle" + log.debug("New 'angle_definition' setting: %s", self._angle_definition) + + def set_channel_definition(self, value): + """Set the value for the channel definition + + Parameters + ---------- + value : str + One of `single`, `multi_single` or `multi_multi`. + """ + choices = self.check_definition_option(value) + self._channel_definition = choices[value] % "channel" + log.debug("New 'channel_definition' setting: %s", self._channel_definition) + + def set_illumination_definition(self, value): + """Set the value for the illumination definition + + Parameters + ---------- + value : str + One of `single`, `multi_single` or `multi_multi`. + """ + choices = self.check_definition_option(value) + self._illumination_definition = choices[value] % "illumination direction" + log.debug( + "New 'illumination_definition' setting: %s", self._illumination_definition + ) + + def set_tile_definition(self, value): + """Set the value for the tile_definition + + Parameters + ---------- + value : str + One of `single`, `multi_single` or `multi_multi`. + """ + choices = self.check_definition_option(value) + self._tile_definition = choices[value] % "tile" + log.debug("New 'tile_definition' setting: %s", self._tile_definition) + + def set_timepoint_definition(self, value): + """Set the value for the time_point_definition + + Parameters + ---------- + value : str + One of `single`, `multi_single` or `multi_multi`. + """ + choices = self.check_definition_option(value) + self._timepoint_definition = choices[value] % "time-point" + log.debug("New 'timepoint_definition' setting: %s", self._timepoint_definition) + + 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 + that can be used in a BDV-related `IJ.run` call. + + Returns + ------- + str + """ + parameters = [ + "multiple_angles=" + self._angle_definition, + "multiple_channels=" + self._channel_definition, + "multiple_illuminations=" + 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) + return parameter_string + " " + + def backup_xml_files(source_directory, subfolder_name): """Create a backup of BDV-XML files inside a subfolder of `xml-backup`. From 1b0f4683c2acf8c9507e5b5aa7ca44776bc3a0d0 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 13 Feb 2024 14:23:17 +0100 Subject: [PATCH 164/678] Add the resave option --- src/imcflibs/imagej/bdv.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index af25122f..5bef401b 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -313,7 +313,7 @@ def treat_timepoints(self, value): ### formatter methods - def fmt_acitt_options(self): + def fmt_acitt_options(self, input="process"): """Format Angle / Channel / Illumination / Tile / Timepoint options. Build a string providing the `process_angle`, `process_channel`, @@ -324,12 +324,15 @@ def fmt_acitt_options(self): ------- str """ + input_type = ['process','resave'] + if input not in input_type: + raise ValueError("Invalue input type. Expected one of: %s" % input_type) parameters = [ - "process_angle=" + self._angle_processing_option, - "process_channel=" + self._channel_processing_option, - "process_illumination=" + self._illumination_processing_option, - "process_tile=" + self._tile_processing_option, - "process_timepoint=" + self._timepoint_processing_option, + input + "_angle=" + self._angle_processing_option, + input + "_channel" + self._channel_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) From 506aea3ed23d9fe6bf26fe041b59fa57a4004b2a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 13 Feb 2024 14:25:25 +0100 Subject: [PATCH 165/678] Fixes and improvements to doc and FIXMEs --- src/imcflibs/imagej/bdv.py | 236 ++++++++----------------------------- 1 file changed, 46 insertions(+), 190 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 5bef401b..7f30d717 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -9,14 +9,13 @@ # The attribute count is not really our choice: # pylint: disable-msg=too-many-instance-attributes -import os import sys +import os import shutil -from ij import IJ # pylint: disable-msg=import-error +from ij import IJ from .. import pathtools - from ..log import LOG as log SINGLE = "[Single %s (Select from List)]" @@ -95,7 +94,6 @@ def __init__(self): ### reference-X methods def reference_angle(self, value): - # FIXME: is the "expert grouping" statement correct? """Set the reference angle when using *Expert Grouping Options*. Select the angle(s) to use for the operation, by default empty (`""`). @@ -112,8 +110,6 @@ def reference_angle(self, value): log.debug("New reference angle setting: %s", self._use_angle) def reference_channel(self, value): - # FIXME: is the "expert grouping" statement correct? - # FIXME: explain the "-1", why is it done? """Set the reference channel when using *Expert Grouping Options*. Select the channel(s) to use for the operation, by default the averaging mode @@ -133,7 +129,6 @@ def reference_channel(self, value): log.debug("New reference channel setting: %s", self._use_channel) def reference_illumination(self, value): - # FIXME: is the "expert grouping" statement correct? """Set the reference illumination when using *Expert Grouping Options*. Select the illumination(s) to use for the operation, by default the averaging @@ -150,8 +145,6 @@ def reference_illumination(self, value): log.debug("New reference illumination setting: %s", self._use_illumination) def reference_tile(self, value): - # FIXME: is the "expert grouping" statement correct? - # FIXME: what are possible types for the parameter, is it int? """Set the reference tile when using *Expert Grouping Options*. Select the tile(s) to use for the operation, by default the averaging mode will @@ -162,14 +155,13 @@ def reference_tile(self, value): Parameters ---------- - value : str - The tile to use for the grouping. + value : int + The tile number to use for the grouping. """ self._use_tile = "tiles=[use Tile %s]" % str(value) log.debug("New reference tile setting: %s", self._use_tile) def reference_timepoint(self, value): - # FIXME: is the "expert grouping" statement correct? """Set the reference timepoint when using *Expert Grouping Options*. Select the timepoint(s) to use for the operation, by default the averaging mode @@ -578,7 +570,10 @@ def define_dataset_auto( subsampling_factors=None, hdf5_chunk_sizes=None, ): - """Run "Define Multi-View Dataset" using the "Auto-Loader" option. + """Will run the corresponding "Define Dataset" using the "Auto-Loader" + option. + If the series is tiles, will run "Define Dataset...", otherwise will run + "Define Multi-View Dataset...". Parameters ---------- @@ -592,7 +587,9 @@ def define_dataset_auto( bf_series_type : str One of "Angles" or "Tiles", specifying how Bio-Formats interprets the series. timepoints_per_partition : int, optional - Split the output by timepoints. Use `0` for no split, by default `1`. + Split the output dataset by timepoints. Use `0` for no split, resulting + in a single HDF5 file containing all timepoints. By default `1`, + resulting in a HDF5 per timepoints. resave : str, optional Allow the function to either re-save the images or simply create a merged xml. Use `Load raw data` to avoid re-saving, by default `Re-save @@ -604,10 +601,6 @@ def define_dataset_auto( Specify hdf5_chunk_sizes factors explicitly, for example `[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]`. """ - # FIXME: the docstring is actually not corrct, in the sense that the function will - # switch to `Define dataset ...` in case the `bf_series_type` is `Tiles` - - # FIXME: improve the timepoints_per_partition parameter description! file_info = pathtools.parse_path(file_path) @@ -695,7 +688,6 @@ def define_dataset_auto( IJ.run("Define Multi-View Dataset", str(options)) else: raise ValueError("Wrong answer for series type") - return def define_dataset_manual( @@ -703,7 +695,7 @@ def define_dataset_manual( source_directory, image_file_pattern, dataset_organisation, - file_definition, + definition_opts=None, ): """Run "Define Multi-View Dataset" using the "Manual Loader" option. @@ -714,19 +706,21 @@ def define_dataset_manual( source_directory : str Path to the folder containing the file(s). image_file_pattern : str - Pattern corresponding to the names of your files separating the + Regular expression corresponding to the names of your files and how to read the different dimensions. dataset_organisation : str Organisation of the dataset and the dimensions to process. - file_definition : dict + Allows for defining the range of interest of the different dimensions. + Looks like "timepoints_=%s-%s channels_=0-%s tiles_=%s-%s" + definition_opts : dict Dictionary containing the details about the file repartitions. """ - # FIXME: explain image_file_pattern, dataset_organisation and - # file_definition with more details / examples - xml_filename = project_filename + ".xml" + if definition_opts is None: + definition_opts = DefinitionOptions() + temp = os.path.join(source_directory, project_filename + "_temp") os.path.join(temp, project_filename) @@ -735,20 +729,7 @@ def define_dataset_manual( + "project_filename=[" + xml_filename + "] " - + "multiple_timepoints=" - + file_definition["multiple_timepoints"] - + " " - + "multiple_channels=" - + file_definition["multiple_channels"] - + " " - + "multiple_illumination_directions=" - + file_definition["multiple_illuminations"] - + " " - + "multiple_angles=" - + file_definition["multiple_angles"] - + " " - + "multiple_tiles=" - + file_definition["multiple_tiles"] + + definition_opts.fmt_acitt_options() + " " + "image_file_directory=" + source_directory @@ -762,14 +743,14 @@ def define_dataset_manual( + "imglib2_data_container=[ArrayImg (faster)]" ) - log.debug(options) + log.debug("Manual dataset defintion options: <%s>", options) IJ.run("Define dataset ...", str(options)) def resave_as_h5( source_xml_file, output_h5_file_path, - timepoints="All Timepoints", + processing_opts=None, timepoints_per_partition=1, use_deflate_compression=True, subsampling_factors=None, @@ -799,16 +780,9 @@ def resave_as_h5( Specify hdf5_chunk_sizes factors explicitly, for example `[{ {32,16,8}, {16,16,16}, {16,16,16}, {16,16,16} }]`. """ - # save all timepoints or a single one: - if timepoints == "All Timepoints": - timepoints = "resave_timepoint=[All Timepoints] " - else: - timepoints = ( - "resave_timepoint=[Single Timepoint (Select from List)] " - + "processing_timepoint=[Timepoint " - + str(timepoints) - + "] " - ) + + if not processing_opts: + processing_opts = ProcessingOptions() if use_deflate_compression: use_deflate_compression_arg = "use_deflate_compression " @@ -834,11 +808,8 @@ def resave_as_h5( "select=" + str(source_xml_file) + " " - + "resave_angle=[All angles] " - + "resave_channel=[All channels] " - + "resave_illumination=[All illuminations] " - + "resave_tile=[All tiles] " - + timepoints + + processing_opts.fmt_acitt_options("resave") + + processing_opts.fmt_acitt_selectors() + subsampling_factors + hdf5_chunk_sizes + "timepoints_per_partition=" @@ -851,11 +822,9 @@ def resave_as_h5( + output_h5_file_path ) - log.debug(options) + log.debug("Resave as HDF5 options: <%s>", options) IJ.run("As HDF5", str(options)) - return - def flip_axes(source_xml_file, x=False, y=True, z=False): """Call BigStitcher's "Flip Axes" command. @@ -894,11 +863,6 @@ def flip_axes(source_xml_file, x=False, y=True, z=False): def phase_correlation_pairwise_shifts_calculation( project_path, processing_opts=None, - treat_timepoints="group", - treat_channels="group", - treat_illuminations="group", - treat_angles="[treat individually]", - treat_tiles="group", downsampling_xyz="", ): """Calculate pairwise shifts using Phase Correlation. @@ -911,36 +875,16 @@ def phase_correlation_pairwise_shifts_calculation( The `ProcessingOptinos` object defining parameters for the run. Will fall back to the defaults defined in the corresponding class if the parameter is `None` or skipped. - treat_timepoints : str, optional - How to deal with the timepoints, by default `group`. - treat_channels : str, optional - How to deal with the channels, by default `group`. - treat_illuminations : str, optional - How to deal with the illuminations, by default `group`. - treat_angles : str, optional - How to deal with the angles, by default `[treat individually]`. - treat_tiles : str, optional - How to deal with the tiles, by default `group`. downsampling_xyz : list of int, optional Downsampling factors in X, Y and Z, for example `[4,4,4]`. By default empty which will result in BigStitcher choosing the factors. """ - if processing_opts is None: + if not processing_opts: processing_opts = ProcessingOptions() file_info = pathtools.parse_path(project_path) - processing_opts.treat_angles(treat_angles) - processing_opts.treat_channels(treat_channels) - use_illumination = "" - if treat_illuminations == "group": - use_illumination = "illuminations=[Average Illuminations]" - use_timepoint = "" - if treat_timepoints == "group": - use_timepoint = "timepoints=[Average Timepoints]" - use_tile = processing_opts.use_tile if treat_tiles == "group" else "" - if downsampling_xyz != "": downsampling = "downsample_in_x=%s downsample_in_y=%s downsample_in_z=%s " % ( downsampling_xyz[0], @@ -956,31 +900,20 @@ def phase_correlation_pairwise_shifts_calculation( + "] " + processing_opts.fmt_acitt_options() + processing_opts.fmt_acitt_selectors() - # + options_dict["timepoint_select"] # FIXME: is this duplication intended?? + " " + "method=[Phase Correlation] " + "show_expert_grouping_options " + "show_expert_algorithm_parameters " - + use_angle - + " " - + use_channel - + " " - + use_illumination - + " " - + use_timepoint - + " " - + use_tile - + " " + + processing_opts.fmt_use_acitt() + processing_opts.fmt_how_to_treat() + downsampling + "subpixel_accuracy" ) - log.debug(options) + 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") - return def filter_pairwise_shifts( @@ -1039,21 +972,15 @@ def filter_pairwise_shifts( + filter_by_max_displacement ) - log.debug(options) + log.debug("Filter pairwise options: <%s>", options) IJ.run("Filter pairwise shifts ...", str(options)) backup_xml_files(file_info["path"], "filter_pairwise_shifts") - return def optimize_and_apply_shifts( project_path, processing_opts=None, - treat_timepoints="group", - treat_channels="group", - treat_illuminations="group", - treat_angles="[treat individually]", - treat_tiles="group", relative_error=2.5, absolute_error=3.5, ): @@ -1067,44 +994,24 @@ def optimize_and_apply_shifts( The `ProcessingOptinos` object defining parameters for the run. Will fall back to the defaults defined in the corresponding class if the parameter is `None` or skipped. - treat_timepoints : str, optional - How to treat the timepoints, by default `group`. - treat_channels : str, optional - How to treat the channels, by default `group`. - treat_illuminations : str, optional - How to treat the illuminations, by default `group`. - treat_angles : str, optional - How to treat the angles, by default `[treat individually]`. - treat_tiles : str, optional - How to treat the tiles, by default `group`. relative_error : float, optional Relative alignment error (in px) to accept, by default `2.5`. absolute_error : float, optional Absolute alignment error (in px) to accept, by default `3.5`. """ - if processing_opts is None: + if not processing_opts: processing_opts = ProcessingOptions() file_info = pathtools.parse_path(project_path) - processing_opts.treat_angles(treat_angles) - use_channel = processing_opts.use_channel if treat_channels == "group" else "" - use_illumination = "" - if treat_illuminations == "group": - use_illumination = "illuminations=[Average Illuminations]" - use_timepoint = "" - if treat_timepoints == "group": - use_timepoint = "timepoints=[Average Timepoints]" - use_tile = processing_opts.use_tile if treat_tiles == "group" else "" - options = ( "select=[" + project_path + "] " + processing_opts.fmt_acitt_options() + processing_opts.fmt_acitt_selectors() - + " " # WARNING: original code had another "timepoint_select" option here! + + " " + "relative=" + str(relative_error) + " " @@ -1114,33 +1021,11 @@ def optimize_and_apply_shifts( + "global_optimization_strategy=[Two-Round using Metadata to align unconnected " + "Tiles and iterative dropping of bad links] " + "show_expert_grouping_options " - + use_angle - + " " - + use_channel - + " " - + use_illumination - + " " - + use_timepoint - + " " - + use_tile - + " " - + "how_to_treat_angles=" - + treat_angles - + " " - + "how_to_treat_channels=" - + treat_channels - + " " - + "how_to_treat_illuminations=" - + treat_illuminations - + " " - + "how_to_treat_tiles=" - + treat_tiles - + " " - + "how_to_treat_timepoints=" - + treat_timepoints + + processing_opts.fmt_use_acitt() + + processing_opts.fmt_how_to_treat() ) - log.debug(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") @@ -1148,8 +1033,7 @@ def optimize_and_apply_shifts( def detect_interest_points( project_path, - process_timepoint="All Timepoints", - process_channel="All channels", + processing_opts=None, sigma=1.8, threshold=0.008, maximum_number=3000, @@ -1172,40 +1056,14 @@ def detect_interest_points( Maximum number of interest points to use, by default `3000`. """ - # If not process all channels at once, then adapt the option - if process_channel == "All channels": - process_channel_arg = "[" + process_channel + "] " - else: - process_channel_arg = ( - "[Single channel (Select from List)] " - + "processing_channel=[channel " - + process_channel - + "] " - ) - # FIXME look into the actual call! @sebastien - # save all timepoints or a single one: - if process_timepoint == "All Timepoints": - process_timepoint = "resave_timepoint=[All Timepoints] " - else: - process_timepoint = ( - "resave_timepoint=[Single Timepoint (Select from List)] " - + "processing_timepoint=[Timepoint " - + str(process_timepoint) - + "] " - ) + if not processing_opts: + processing_opts = ProcessingOptions() options = ( "select=[" + project_path + "] " - + "process_angle=[All angles] " - + "process_channel=" - + process_channel_arg - + "process_illumination=[All illuminations] " - + "process_tile=[All tiles] " - + "process_timepoint=[" - + process_timepoint - + "] " + + processing_opts.fmt_acitt_options() + "type_of_interest_point_detection=Difference-of-Gaussian " + "label_interest_points=beads " + "limit_amount_of_detections " @@ -1213,8 +1071,8 @@ def detect_interest_points( + "group_illuminations " + "subpixel_localization=[3-dimensional quadratic fit] " + "interest_point_specification=[Advanced ...] " - + "downsample_xy=[Match Z Resolution (less downsampling)] " - + "downsample_z=1x " + + "downsample_xy=8x " + + "downsample_z=2x " + "sigma=" + str(sigma) + " " @@ -1229,9 +1087,8 @@ def detect_interest_points( + "compute_on=[CPU (Java)]" ) - log.debug(options) + log.debug("Interest points detection options: <%s>", options) IJ.run("Detect Interest Points for Registration", str(options)) - return def interest_points_registration( @@ -1410,13 +1267,12 @@ def duplicate_transformations( + " " ) - log.debug(options) + log.debug("Transformation duplication options: <%s>", options) IJ.run("Duplicate Transformations", str(options)) backup_xml_files( file_info["path"], "duplicate_transformation_" + transformation_type ) - return def fuse_dataset( @@ -1528,5 +1384,5 @@ def fuse_dataset( + "block_size_factor_z=1" ) - log.debug(options) + log.debug("Dataset fusion options: <%s>", options) IJ.run("Fuse dataset ...", str(options)) From c8e1992e0829dbc00660545bdec8330a537e32bd Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 13 Feb 2024 14:27:09 +0100 Subject: [PATCH 166/678] Convert to classes Also now using Kai's latest settings which worked well for Haohao --- src/imcflibs/imagej/bdv.py | 45 ++++++++++++++------------------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7f30d717..9ef8de59 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1093,8 +1093,7 @@ def detect_interest_points( def interest_points_registration( project_path, - process_timepoint="All Timepoints", - process_channel="All channels", + processing_opts = None, rigid_timepoints=False, ): """Run the "Register Dataset based on Interest Points" command. @@ -1117,15 +1116,8 @@ def interest_points_registration( By default `False`. """ - # If not process all channels at once, then adapt the option - if process_channel == "All channels": - process_channel_arg = "[All channels] " - else: - process_channel_arg = ( - "[Single channel (Select from List)] processing_channel=[channel " - + process_channel - + "] " - ) + if not processing_opts: + processing_opts = ProcessingOptions() if rigid_timepoints: rigid_timepoints_arg = "consider_each_timepoint_as_rigid_unit " @@ -1136,47 +1128,42 @@ def interest_points_registration( "select=[" + project_path + "] " - + "process_angle=[All angles] " - + "process_channel=" - + process_channel_arg - + "process_illumination=[All illuminations] " - + "process_tile=[All tiles] " - + "process_timepoint=[" - + process_timepoint - + "] " + + processing_opts.fmt_acitt_options() + "registration_algorithm=[Precise descriptor-based (translation invariant)] " - + "registration_in_between_views=[Compare all views against each other] " + + "registration_over_time=[Match against one reference timepoint (no global optimization)] " + + "registration_in_between_views=[Only compare overlapping views (according to current transformations)] " + "interest_points=beads " + "group_tiles " + "group_illuminations " + "group_channels " + + "reference=1 " + rigid_timepoints_arg - + "fix_views=[Fix first view] " - + "map_back_views=[Do not map back (use this if views are fixed)] " + # + "fix_views=[Fix first view] " + # + "map_back_views=[Do not map back (use this if views are fixed)] " + "transformation=Affine " + "regularize_model " - + "model_to_regularize_with=Rigid " + + "model_to_regularize_with=Affine " + "lamba=0.10 " + "number_of_neighbors=3 " - + "redundancy=2 " - + "significance=1 " + + "redundancy=1 " + + "significance=3 " + "allowed_error_for_ransac=5 " + "ransac_iterations=Normal " + + "global_optimization_strategy=[Two-Round: Handle unconnected tiles, remove wrong links RELAXED (5.0x / 7.0px)] " + "interestpoint_grouping=[Group interest points (simply combine all in one virtual view)] " + "interest=5" ) - log.debug(options) + log.debug("Interest points registration options: <%s>", options) # register using interest points IJ.run("Register Dataset based on Interest Points", options) - return def duplicate_transformations( project_path, transformation_type="channel", - channel_source=None, - tile_source=None, + channel_source=1, + tile_source=1, transformation_to_use="[Replace all transformations]", ): """Duplicate / propagate transformation parameters to other channels. From df192595e082bb005281d5a8c18b5a5bb82bf8f4 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 13 Feb 2024 14:27:28 +0100 Subject: [PATCH 167/678] Use default value for tile and channel source --- src/imcflibs/imagej/bdv.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9ef8de59..f6627a05 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1186,7 +1186,6 @@ def duplicate_transformations( One of `[Replace all transformations]` (default) and `[Add last transformation only]` to specify which transformations to propagate. """ - # FIXME: transformation_to_use requires explanations of possible values! file_info = pathtools.parse_path(project_path) @@ -1199,16 +1198,10 @@ def duplicate_transformations( chnl_apply = "" chnl_process = "" - # FIXME: invalid parameter combinations! - # Calling the function with transformation_type="channel" and - # channel_source=None (the default) will lead to an invalid combination! - # Same for transformation_type="tile" / tile_source=None, in both cases the - # resulting call will contain the sequence ` source= `. if transformation_type == "channel": apply = "[One channel to other channels]" target = "[All Channels]" - if channel_source: - source = str(channel_source - 1) + 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) + "] " @@ -1217,8 +1210,7 @@ def duplicate_transformations( elif transformation_type == "tile": apply = "[One tile to other tiles]" target = "[All Tiles]" - if tile_source: - source = str(tile_source) + source = str(tile_source) if channel_source: chnl_apply = "apply_to_channel=[Single channel (Select from List)] " chnl_process = ( From 6b2c082d5e6064b4dbe19a787793c9080d75864b Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 14 Feb 2024 10:15:01 +0100 Subject: [PATCH 168/678] Add methods to upload image and KV to OMERO --- src/imcflibs/imagej/omerotools.py | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 562d5794..66ca5787 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -7,11 +7,32 @@ # ImageJ Import from ij import IJ +# Bioformats imports +from loci.formats import FormatTools, ImageTools +from loci.common import DataTools +from loci.plugins import LociExporter +from loci.plugins.in import ImporterOptions +from loci.plugins.out import Exporter + # Omero Dependencies from omero.gateway import Gateway from omero.gateway import LoginCredentials +from omero.gateway import SecurityContext +from omero.gateway.facility import BrowseFacility +from omero.gateway.facility import DataManagerFacility +from omero.gateway.model import DatasetData, MapAnnotationData from omero.log import SimpleLogger +from omero.sys import ParametersI +from ome.formats.importer import ImportConfig +from ome.formats.importer import OMEROWrapper +from ome.formats.importer import ImportLibrary +from ome.formats.importer import ImportCandidates +from ome.formats.importer.cli import ErrorHandler +from ome.formats.importer.cli import LoggingImportMonitor +import loci.common +from loci.formats.in import DefaultMetadataOptions +from loci.formats.in import MetadataLevel def parse_image_ids(input_string): """Parse an OMERO URL or a string with image IDs into a list. @@ -133,3 +154,111 @@ def fetch_image(host, username, password, image_id, group_id=-1): ] ) IJ.runPlugIn("loci.plugins.LociImporter", options) + + +def upload_image(path, gateway, dataset_id): + """Upload the image back to OMERO + + Parameters + ---------- + path : str + Path of the file to upload back to OMERO + gateway : omero.gateway.Gateway + Gateway to the OMERO server + + Returns + ------- + list[int] + List of IDs of the imported images + """ + + user = gateway.getLoggedInUser() + ctx = SecurityContext(user.getGroupId()) + sessionKey = gateway.getSessionId(user) + + config = ImportConfig() + + config.email.set("") + config.sendFiles.set("true") + config.sendReport.set("false") + config.contOnError.set("false") + config.debug.set("false") + config.hostname.set(HOST) + config.sessionKey.set(sessionKey) + dataset = find_dataset(gateway, dataset_id) + + loci.common.DebugTools.enableLogging("DEBUG") + + store = config.createStore() + reader = OMEROWrapper(config) + + library = ImportLibrary(store, reader) + errorHandler = ErrorHandler(config) + + library.addObserver(LoggingImportMonitor()) + str2d = java.lang.reflect.Array.newInstance(java.lang.String, [1]) + str2d[0] = path + + candidates = ImportCandidates(reader, str2d, errorHandler) + + reader.setMetadataOptions(DefaultMetadataOptions(MetadataLevel.ALL)) + + container_list = candidates.getContainers() + num_done = 0 + ids_list = [] + for i in range(len(container_list)): + container = container_list[i] + container.setTarget(dataset) + pixels = library.importImage(container, i, num_done, len(container_list)) + ids_list.append(pixels[0].getImage().getId().getValue()) + num_done += 1 + + return ids_list + +def upload_kv(gateway, dict, header, image_id): + """Add annotation to OMERO object + + Parameters + ---------- + gateway : omero.gateway.Gateway + Gateway to the OMERO server + dict : dict + Dictionary with the annotation to add + header : str + Name for the annotation header + image_id : int + Image ID on the OMERO server + """ + browse = gateway.getFacility(BrowseFacility) + user = gateway.getLoggedInUser() + ctx = SecurityContext(user.getGroupId()) + image = browse.getImage(ctx, long(image_id)) + + data = MapAnnotationData() + data.setContent(dict) + data.setDescription(header) + data.setNameSpace(MapAnnotationData.NS_CLIENT_CREATED) + + fac = gateway.getFacility(DataManagerFacility) + fac.attachAnnotation(ctx, data, image) + +def find_dataset(gateway, dataset_id): + """Returns the dataset object associated with the given dataset ID + + Parameters + ---------- + gateway : omero.gateway.Gateway + Gateway to the OMERO server + dataset_id : int + Image ID of the dataset + + Returns + ------- + omero.model.Dataset + Dataset object corresponding to the ID + + """ + browse = gateway.getFacility(BrowseFacility) + user = gateway.getLoggedInUser() + ctx = SecurityContext(user.getGroupId()) + return browse.findIObject(ctx, "omero.model.Dataset", dataset_id) \ No newline at end of file From 651d263335bf3e63f26fcb725a009d73c645847c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 15 Feb 2024 14:33:21 +0100 Subject: [PATCH 169/678] Fix default value for the tile files --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f6627a05..d65e3423 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -422,7 +422,7 @@ def __init__(self): self._angle_definition = SINGLE_FILE % "angle" self._channel_definition = MULTI_SINGLE_FILE % "channel" self._illumination_definition = SINGLE_FILE % "illumination" - self._tile_definition = MULTI_SINGLE_FILE % "tile" + self._tile_definition = MULTI_MULTI_FILE % "tile" self._timepoint_definition = SINGLE_FILE % "time-point" def check_definition_option(self, value): From d40a433ab4b2d04e2ce02c4379cecdf9a8d77315 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 19 Feb 2024 09:52:33 +0100 Subject: [PATCH 170/678] Fix call --- src/imcflibs/imagej/bdv.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index d65e3423..4e1b7edb 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -316,15 +316,15 @@ def fmt_acitt_options(self, input="process"): ------- str """ - input_type = ['process','resave'] + input_type = ["process", "resave"] if input not in 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 + "_tile" + self._tile_processing_option, - input + "_timepoint" + self._timepoint_processing_option, + input + "_channel=" + self._channel_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) @@ -1093,7 +1093,7 @@ def detect_interest_points( def interest_points_registration( project_path, - processing_opts = None, + processing_opts=None, rigid_timepoints=False, ): """Run the "Register Dataset based on Interest Points" command. @@ -1162,8 +1162,8 @@ def interest_points_registration( def duplicate_transformations( project_path, transformation_type="channel", - channel_source=1, - tile_source=1, + channel_source=None, + tile_source=None, transformation_to_use="[Replace all transformations]", ): """Duplicate / propagate transformation parameters to other channels. From 6d2efdc9ae944d8e9c4f4b1863cadb89e14f9b1b Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 20 Feb 2024 11:10:29 +0100 Subject: [PATCH 171/678] Add support for URLs containing entire datasets, not just image links --- src/imcflibs/imagej/omerotools.py | 70 ++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 66ca5787..8638d9d1 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -34,28 +34,63 @@ from loci.formats.in import DefaultMetadataOptions from loci.formats.in import MetadataLevel -def parse_image_ids(input_string): - """Parse an OMERO URL or a string with image IDs into a list. +def parse_image_ids(omero_str, gateway=None, ctx=None): + """Parse an OMERO URL with one or multiple images selected, or a link to one or more datasets where all images in + the datasets will be projected. Gateway and ctx are optional - only necessary if parsing a dataset link; if only images, + can be ignored. Parameters ---------- - input_string : str - String which is either the direct image link (URL) from OMERO.web - (which may contain multiple images selected) or a sequence of OMERO - image IDs separated by commas. + omero_str : str + String which is either the dataset link from OMERO, the image link from OMERO or image IDs separated by commas + gateway : omero.gateway.Gateway, optional + An instance of OMERO gateway to use for retrieving dataset children + ctx: omero.gateway.SecurityContext, optional + A security context object that hosts information about the correct connector Returns ------- - str[] - List of all the image IDs parsed from the input string. + images: str[] + List of all the images IDs parsed from the string """ - if input_string.startswith("https"): - image_ids = input_string.split("image-") + dataset_ids = java.util.ArrayList() # Has to be an ArrayList due to how the wrapper works, cannot be a Python list + image_ids = [] + project_string = "_" + projection_type + "_project" + + if "dataset-" in omero_str: + if "|" in omero_str: # If multiple datasets, split by "|" + parts = omero_str.split("|") + for part in parts: + if "dataset-" in part: + dataset_id = java.lang.Long(part.split("dataset-")[1].split("/")[0]) + dataset_ids.add(dataset_id) + else: # not multiple, single dataset + dataset_id = java.lang.Long(omero_str.split("dataset-")[1].split("/")[0]) + dataset_ids.add(dataset_id) # Add dataset ID to the ArrayList + + if gateway: + browse = gateway.getFacility(BrowseFacility) + datasets = browse.getDatasets(ctx, dataset_ids) + + for dataset in datasets: + images = dataset.getImages() + for im in images: + if project_string not in im.getName(): + image_ids.append(str(im.getId())) + return image_ids + else: + raise ValueError("An OMERO gateway instance is required to retrieve dataset children.") + + elif "image-" in omero_str: + image_ids = omero_str.split("image-") image_ids.pop(0) - image_ids = [s.split("%")[0].replace("|", "") for s in image_ids] + image_ids = [s.split('%')[0].replace("|", "") for s in image_ids] + return image_ids + else: - image_ids = input_string.split(",") - return image_ids + image_ids = omero_str.split(",") + return image_ids + def connect(host, port, username, password): @@ -74,8 +109,10 @@ def connect(host, port, username, password): Returns ------- - omero.gateway.Gateway + gateway: omero.gateway.Gateway A Gateway object representing the connection to the OMERO server. + ctx: omero.gateway.SecurityContext + object that hosts information required to access correct connector. """ # Omero Connect with credentials and simpleLogger cred = LoginCredentials() @@ -86,7 +123,10 @@ def connect(host, port, username, password): simple_logger = SimpleLogger() gateway = Gateway(simple_logger) gateway.connect(cred) - return gateway + # Get user for SecurityContext object + user = gateway.getLoggedInUser() + ctx = SecurityContext(user.getGroupId()) + return gateway, ctx def fetch_image(host, username, password, image_id, group_id=-1): From 03c46dfff02685d34f4f9c030479f4476f986401 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Tue, 20 Feb 2024 11:15:59 +0100 Subject: [PATCH 172/678] fix options string for interest point detection and registration --- src/imcflibs/imagej/bdv.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 4e1b7edb..b915af40 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -23,7 +23,6 @@ class ProcessingOptions(object): - """Helper to store processing options and generate parameter strings. Example @@ -398,7 +397,6 @@ def fmt_use_acitt(self): class DefinitionOptions(object): - """Helper to store definition options and generate parameters strings. Example @@ -1068,7 +1066,6 @@ def detect_interest_points( + "label_interest_points=beads " + "limit_amount_of_detections " + "group_tiles " - + "group_illuminations " + "subpixel_localization=[3-dimensional quadratic fit] " + "interest_point_specification=[Advanced ...] " + "downsample_xy=8x " @@ -1132,14 +1129,13 @@ def interest_points_registration( + "registration_algorithm=[Precise descriptor-based (translation invariant)] " + "registration_over_time=[Match against one reference timepoint (no global optimization)] " + "registration_in_between_views=[Only compare overlapping views (according to current transformations)] " + + "interest_point_inclusion=[Compare all interest point of overlapping views] " + "interest_points=beads " + "group_tiles " + "group_illuminations " + "group_channels " + "reference=1 " + rigid_timepoints_arg - # + "fix_views=[Fix first view] " - # + "map_back_views=[Do not map back (use this if views are fixed)] " + "transformation=Affine " + "regularize_model " + "model_to_regularize_with=Affine " From 7dafeea93c6e64405d32750d210e2bbed1c61613 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Tue, 20 Feb 2024 13:42:59 +0100 Subject: [PATCH 173/678] add missing fmt_acitt_selectors --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b915af40..113892a5 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -1062,6 +1062,7 @@ def detect_interest_points( + project_path + "] " + processing_opts.fmt_acitt_options() + + processing_opts.fmt_acitt_selectors() + "type_of_interest_point_detection=Difference-of-Gaussian " + "label_interest_points=beads " + "limit_amount_of_detections " @@ -1126,6 +1127,7 @@ def interest_points_registration( + project_path + "] " + processing_opts.fmt_acitt_options() + + processing_opts.fmt_acitt_selectors() + "registration_algorithm=[Precise descriptor-based (translation invariant)] " + "registration_over_time=[Match against one reference timepoint (no global optimization)] " + "registration_in_between_views=[Only compare overlapping views (according to current transformations)] " From 73932c6ae5add9c0fc8282600b617a5a9b49340a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 13 Nov 2023 15:26:29 +0100 Subject: [PATCH 174/678] Add local ("non-PyPI") dependency packages --- local-packages/imcf-fiji-mocks/README.md | 1 + local-packages/micrometa/README.md | 1 + local-packages/sjlogging/README.md | 1 + 3 files changed, 3 insertions(+) create mode 100644 local-packages/imcf-fiji-mocks/README.md create mode 100644 local-packages/micrometa/README.md create mode 100644 local-packages/sjlogging/README.md diff --git a/local-packages/imcf-fiji-mocks/README.md b/local-packages/imcf-fiji-mocks/README.md new file mode 100644 index 00000000..193b05cf --- /dev/null +++ b/local-packages/imcf-fiji-mocks/README.md @@ -0,0 +1 @@ +https://github.com/imcf/imcf-fiji-mocks diff --git a/local-packages/micrometa/README.md b/local-packages/micrometa/README.md new file mode 100644 index 00000000..eb5e9f95 --- /dev/null +++ b/local-packages/micrometa/README.md @@ -0,0 +1 @@ +https://github.com/imcf/python-micrometa diff --git a/local-packages/sjlogging/README.md b/local-packages/sjlogging/README.md new file mode 100644 index 00000000..6953abad --- /dev/null +++ b/local-packages/sjlogging/README.md @@ -0,0 +1 @@ +https://github.com/imcf/jython-scijava-logging From 647befd793cf293b2a4af2f67583874d37251938 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 13 Nov 2023 15:26:51 +0100 Subject: [PATCH 175/678] Preliminary poetry project setup --- pyproject.toml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..65b368d0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +authors = [ + "Niko Ehrenfeuchter ", + "Laurent Guerard ", + "Kai Schleicher ", + "Sรฉbastien Herbert ", +] +description = "Mostly ImageJ/Fiji-related Python helper functions." +license = "GPL-3.0-or-later" +name = "imcflibs" +readme = "README.md" +version = "1.5.0.a0" + +[tool.poetry.dependencies] +olefile = "==0.46" +python = "^3.10" + +[tool.poetry.group.dev.dependencies] +ipython = "^8.17.2" +pytest = "^7.4.3" + +[build-system] +build-backend = "poetry.core.masonry.api" +requires = ["poetry-core"] From ba0a7c753a9ab08c32ebe31fe209afb426b44e89 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 13 Nov 2023 15:27:47 +0100 Subject: [PATCH 176/678] Add local dependencies --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 65b368d0..502d3e30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,8 +12,11 @@ readme = "README.md" version = "1.5.0.a0" [tool.poetry.dependencies] +imcf-fiji-mocks = {path = "./local-packages/imcf-fiji-mocks/dist/imcf_fiji_mocks-0.1.0-py3-none-any.whl"} +micrometa = {path = "./local-packages/micrometa/dist/micrometa-15.2.2-py3-none-any.whl"} olefile = "==0.46" python = "^3.10" +sjlogging = {path = "./local-packages/sjlogging/dist/sjlogging-0.1.0-py3-none-any.whl"} [tool.poetry.group.dev.dependencies] ipython = "^8.17.2" From 9de9f5882e071869a938f3c40d18405a2dd39191 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 13 Nov 2023 15:53:20 +0100 Subject: [PATCH 177/678] Move non-pytest stuff into separate subdir --- conftest.py | 1 + .../bioformats/import_image/test_01.py | 0 .../bioformats/write_bf_memoryfile/test_01.py | 0 3 files changed, 1 insertion(+) create mode 100644 conftest.py rename tests/{imagej => interactive-imagej}/bioformats/import_image/test_01.py (100%) rename tests/{imagej => interactive-imagej}/bioformats/write_bf_memoryfile/test_01.py (100%) diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..e8b929e2 --- /dev/null +++ b/conftest.py @@ -0,0 +1 @@ +collect_ignore = ["tests/interactive-imagej"] diff --git a/tests/imagej/bioformats/import_image/test_01.py b/tests/interactive-imagej/bioformats/import_image/test_01.py similarity index 100% rename from tests/imagej/bioformats/import_image/test_01.py rename to tests/interactive-imagej/bioformats/import_image/test_01.py diff --git a/tests/imagej/bioformats/write_bf_memoryfile/test_01.py b/tests/interactive-imagej/bioformats/write_bf_memoryfile/test_01.py similarity index 100% rename from tests/imagej/bioformats/write_bf_memoryfile/test_01.py rename to tests/interactive-imagej/bioformats/write_bf_memoryfile/test_01.py From bba5cdfc112e8a5df2491175a169c152ff52710b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 13 Nov 2023 16:10:40 +0100 Subject: [PATCH 178/678] Whitespace and quotes cleanup --- tests/test_iotools.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/test_iotools.py b/tests/test_iotools.py index 12084766..1b8b680f 100644 --- a/tests/test_iotools.py +++ b/tests/test_iotools.py @@ -15,43 +15,43 @@ def test_filehandle(tmpdir): - tmpfile = tmpdir.join('testfile') + tmpfile = tmpdir.join("testfile") tmpname = str(tmpfile) - tmphandle = open(str(tmpfile), 'w') + tmphandle = open(str(tmpfile), "w") print(type(tmphandle)) assert type(tmpname) is str assert type(tmphandle) is file assert type(filehandle(tmpname)) is file - assert type(filehandle(tmphandle, 'w')) is file + assert type(filehandle(tmphandle, "w")) is file def test_readtxt(tmpdir): content = [ - u'lorem\n', - u'ipsum\n', - u'and some more\n', - u'dummy text\n', + "lorem\n", + "ipsum\n", + "and some more\n", + "dummy text\n", ] fh = tmpdir.mkdir("readtxt").join("content.txt") - fh.write(u''.join(content)) + fh.write("".join(content)) print(fh.basename) print(fh.dirname) - with zipfile.ZipFile(join(fh.dirname, 'archive.zip'), 'w') as zf: - zf.write(str(fh), arcname='content.txt') + with zipfile.ZipFile(join(fh.dirname, "archive.zip"), "w") as zf: + zf.write(str(fh), arcname="content.txt") print("wrote [%s] into [%s]" % (str(fh), zf.filename)) - + print(content) fromfile = readtxt(str(fh)) print(fromfile) assert fromfile == content fromfile_flat = readtxt(str(fh), flat=True) - assert fromfile_flat == ''.join(content) + assert fromfile_flat == "".join(content) - print(join(fh.dirname, 'archive.zip')) - fromzip = readtxt('content.txt', join(fh.dirname, 'archive.zip')) + print(join(fh.dirname, "archive.zip")) + fromzip = readtxt("content.txt", join(fh.dirname, "archive.zip")) print(fromzip) assert fromzip == content - fromzip_flat = readtxt('content.txt', join(fh.dirname, 'archive.zip'), flat=True) - assert fromzip_flat == ''.join(content) + fromzip_flat = readtxt("content.txt", join(fh.dirname, "archive.zip"), flat=True) + assert fromzip_flat == "".join(content) From 1d9f59534bb60ae1f7c8358e6bed4957f96a7b30 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 20 Feb 2024 16:26:16 +0100 Subject: [PATCH 179/678] Relax Python version to build "py2.py3" wheels --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 502d3e30..42036090 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,8 +15,8 @@ version = "1.5.0.a0" imcf-fiji-mocks = {path = "./local-packages/imcf-fiji-mocks/dist/imcf_fiji_mocks-0.1.0-py3-none-any.whl"} micrometa = {path = "./local-packages/micrometa/dist/micrometa-15.2.2-py3-none-any.whl"} olefile = "==0.46" -python = "^3.10" sjlogging = {path = "./local-packages/sjlogging/dist/sjlogging-0.1.0-py3-none-any.whl"} +python = ">=2.7" [tool.poetry.group.dev.dependencies] ipython = "^8.17.2" From 388b8a74eca1a9e080f44de2dde5a537f39ce226 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 20 Feb 2024 17:17:22 +0100 Subject: [PATCH 180/678] Move away from local dependencies --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 42036090..52a36433 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,11 +12,11 @@ readme = "README.md" version = "1.5.0.a0" [tool.poetry.dependencies] -imcf-fiji-mocks = {path = "./local-packages/imcf-fiji-mocks/dist/imcf_fiji_mocks-0.1.0-py3-none-any.whl"} -micrometa = {path = "./local-packages/micrometa/dist/micrometa-15.2.2-py3-none-any.whl"} olefile = "==0.46" -sjlogging = {path = "./local-packages/sjlogging/dist/sjlogging-0.1.0-py3-none-any.whl"} +imcf-fiji-mocks = "^0.1.1" +micrometa = "^15.2.2" python = ">=2.7" +sjlogging = "^0.1.0" [tool.poetry.group.dev.dependencies] ipython = "^8.17.2" From c582f68e0ac357e8858eef26dcca11b0f562bbe0 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 20 Feb 2024 17:18:03 +0100 Subject: [PATCH 181/678] Upgrade pytest --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 52a36433..47875fce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ sjlogging = "^0.1.0" [tool.poetry.group.dev.dependencies] ipython = "^8.17.2" -pytest = "^7.4.3" +pytest = "^8.0.1" [build-system] build-backend = "poetry.core.masonry.api" From 01cd1f16f5640fee0edc3f7f7785f1a4a5dad4df Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 20 Feb 2024 17:18:16 +0100 Subject: [PATCH 182/678] Add Rohan to the contributors list --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 47875fce..4b6b8da2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ authors = [ "Laurent Guerard ", "Kai Schleicher ", "Sรฉbastien Herbert ", + "Rohan Girish ", ] description = "Mostly ImageJ/Fiji-related Python helper functions." license = "GPL-3.0-or-later" From e1f54eca637123977b868e984bb3a01988f32e75 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 20 Feb 2024 17:43:37 +0100 Subject: [PATCH 183/678] Fix optional channel issue, add dictionary for pretrained models and edit docstrings --- src/imcflibs/imagej/trackmate.py | 52 ++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 6ff6a0d6..f673fa4e 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -5,17 +5,17 @@ from fiji.plugin.trackmate import Logger, Model, SelectionModel, Settings, TrackMate from fiji.plugin.trackmate.action import LabelImgExporter -from fiji.plugin.trackmate.cellpose import ( - CellposeDetectorFactory, - LogDetectorFactory, - StarDistDetectorFactory, -) +from fiji.plugin.trackmate.detection import LogDetectorFactory +from fiji.plugin.trackmate.cellpose import CellposeDetectorFactory +from fiji.plugin.trackmate.stardist import StarDistDetectorFactory +from fiji.plugin.trackmate.cellpose.CellposeSettings import PretrainedModel + from fiji.plugin.trackmate.features import FeatureFilter from fiji.plugin.trackmate.tracking import LAPUtils from fiji.plugin.trackmate.tracking.jaqaman import SparseLAPTrackerFactory from java.lang import Double -from . import pathtools +from .. import pathtools def cellpose_detector( @@ -23,8 +23,8 @@ def cellpose_detector( cellpose_env_path, model_to_use, obj_diameter, - target_chnl, - optional_chnl=None, + target_channel, + optional_channel=0, use_gpu=True, simplify_contours=True, ): @@ -41,10 +41,10 @@ def cellpose_detector( obj_diameter : float Diameter of the objects to detect in the image. This will be calibrated to the unit used in the image. - target_chnl : int + target_channel : int Index of the channel to use for segmentation. - optional_chnl : int, optional - Index of the secondary channel to use for segmentation, by default None. + optional_channel : int, optional + Index of the secondary channel to use for segmentation, by default 0. use_gpu : bool, optional Boolean for GPU usage, by default True. simplify_contours : bool, optional @@ -55,19 +55,33 @@ def cellpose_detector( fiji.plugin.trackmate.Settings Dictionary containing all the settings to use for TrackMate. """ + + # Example Usage: settings = cellpose_detector(imp, "S:\cellpose_env", "NUCLEI", 23.0, 1, 0) settings = Settings(imageplus) settings.detectorFactory = CellposeDetectorFactory() - settings.detectorSettings["TARGET_CHANNEL"] = target_chnl - if optional_chnl: - settings.detectorSettings["OPTIONAL_CHANNEL_2"] = optional_chnl + settings.detectorSettings["TARGET_CHANNEL"] = target_channel + settings.detectorSettings["OPTIONAL_CHANNEL_2"] = optional_channel # Set optional channel to 0, will be + # overwritten if needed + settings.detectorSettings["CELLPOSE_PYTHON_FILEPATH"] = pathtools.join2( cellpose_env_path, "python.exe" ) settings.detectorSettings["CELLPOSE_MODEL_FILEPATH"] = os.path.join( os.environ["USERPROFILE"], ".cellpose", "models" ) - settings.detectorSettings["CELLPOSE_MODEL"] = model_to_use + input_to_model = { + "nuclei": PretrainedModel.NUCLEI, + "cyto": PretrainedModel.CYTO, + "cyto2": PretrainedModel.CYTO2 + } + if model_to_use.lower() in input_to_model: + selected_model = input_to_model[model_to_use.lower()] + else: + print("Selected Model Does Not Exist") + return + + settings.detectorSettings["CELLPOSE_MODEL"] = selected_model settings.detectorSettings["CELL_DIAMETER"] = obj_diameter settings.detectorSettings["USE_GPU"] = use_gpu settings.detectorSettings["SIMPLIFY_CONTOURS"] = simplify_contours @@ -101,7 +115,7 @@ def stardist_detector(imageplus, target_chnl): def log_detector( imageplus, radius, - target_chnl, + target_channel, quality_threshold=0.0, median_filtering=True, subpix_localization=True, @@ -114,7 +128,7 @@ def log_detector( Image on which to do the segmentation. radius : float Radius of the objects to detect. - target_chnl : int + target_channel : int Index of the channel on which to do the segmentation. quality_threshold : int, optional Threshold to use for excluding the spots by quality, by default 0. @@ -133,7 +147,7 @@ def log_detector( settings.detectorFactory = LogDetectorFactory() settings.detectorSettings["RADIUS"] = Double(radius) - settings.detectorSettings["TARGET_CHANNEL"] = target_chnl + settings.detectorSettings["TARGET_CHANNEL"] = target_channel settings.detectorSettings["THRESHOLD"] = Double(quality_threshold) settings.detectorSettings["DO_MEDIAN_FILTERING"] = median_filtering settings.detectorSettings["DO_SUBPIXEL_LOCALIZATION"] = subpix_localization @@ -330,4 +344,4 @@ def run_tm( label_imp.setDimensions(dims[2], dims[3], dims[4]) implus.setDimensions(dims[2], dims[3], dims[4]) - return label_imp + return label_imp \ No newline at end of file From 3f836c8784dc10d7394ed4b7d8e83af518d1d535 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 21 Feb 2024 15:37:37 +0100 Subject: [PATCH 184/678] Auto-sort --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4b6b8da2..51bc46fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,9 +13,9 @@ readme = "README.md" version = "1.5.0.a0" [tool.poetry.dependencies] -olefile = "==0.46" imcf-fiji-mocks = "^0.1.1" micrometa = "^15.2.2" +olefile = "==0.46" python = ">=2.7" sjlogging = "^0.1.0" From 25d26bcab9d0f4737c04956a2200f7a555f813f8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 21 Feb 2024 15:43:37 +0100 Subject: [PATCH 185/678] Use proper Poetry 'sjlogging' package dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 51bc46fe..ed31b0f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ imcf-fiji-mocks = "^0.1.1" micrometa = "^15.2.2" olefile = "==0.46" python = ">=2.7" -sjlogging = "^0.1.0" +sjlogging = ">=0.5.2" [tool.poetry.group.dev.dependencies] ipython = "^8.17.2" From 6ef0939278d33ff49c6eaeedecae4e78ca787ce1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 21 Feb 2024 15:44:59 +0100 Subject: [PATCH 186/678] Drop 'olefile' dependency, it's already pulled in by 'micrometa' --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ed31b0f9..79bee630 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ version = "1.5.0.a0" [tool.poetry.dependencies] imcf-fiji-mocks = "^0.1.1" micrometa = "^15.2.2" -olefile = "==0.46" python = ">=2.7" sjlogging = ">=0.5.2" From fda047416882d5e76e184901a2508e752aebee9a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 21 Feb 2024 17:41:29 +0100 Subject: [PATCH 187/678] Automatic markdown formatting --- TESTING.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/TESTING.md b/TESTING.md index b515e21d..7769b2d4 100644 --- a/TESTING.md +++ b/TESTING.md @@ -4,7 +4,7 @@ Unfortunately there is nothing like `pytest` available for the parts that are running exclusively in a ImageJ2 / Fiji context. So in order to provide at least some basic, semi-interactive tests the following conventions are being used: -* Each ***function*** in any of the `imcflibs.imagej` submodules should have its +* Each _**function**_ in any of the `imcflibs.imagej` submodules should have its own directory underneath `/tests/imagej/`, using their fully qualified name as the path (only skipping the `imcflibs.` prefix). For example test scripts for `imcflibs.imagej.bioformats.import_image()` will be placed in the @@ -12,13 +12,13 @@ some basic, semi-interactive tests the following conventions are being used: * The scripts inside those directories are intended to be run interactively / manually in a (freshly started) Fiji instance. Yes, really. Any other suggestions are highly welcome! -* To facilitate this, a collection of *test images* (and possibly other input +* To facilitate this, a collection of _test images_ (and possibly other input data) should be cloned to the local file system. Currently this `sample_data` - repository is *NOT* publicly available due to legal โš– uncertainties. A repo + repository is _NOT_ publicly available due to legal โš– uncertainties. A repo containing test data ๐Ÿ—ž that can be published should be assembled over time though! -* Any *interactive* test script should start with a header similar to the one - described below. Paths to input data *inside* the test scripts **has** to be +* Any _interactive_ test script should start with a header similar to the one + described below. Paths to input data _inside_ the test scripts **has** to be relative to the location of the `sample_data` repository mentioned above. This will allow for a fairly okayish testing workflow like this: * Make your changes in VS Code, then trigger a build by pressing `Shift` + @@ -27,12 +27,12 @@ some basic, semi-interactive tests the following conventions are being used: `jars/` folder. * Next, start a fresh instance of the Fiji that received the newly built JAR. * After Fiji has started, simply drag and drop the desired test script onto - the main window. This will open the *Script Editor*, then press `Ctrl` + `R` + the main window. This will open the _Script Editor_, then press `Ctrl` + `R` to launch the script. * Only on the first run on the machine being used you will have to select the base location of the `sample_data` repository. - * All subsequent runs of ***any*** test script using the defined *Script - Parameter* `IMCF_TESTDATA` will remember this selection, so it will be + * All subsequent runs of _**any**_ test script using the defined _Script + Parameter_ `IMCF_TESTDATA` will remember this selection, so it will be sufficient to just confirm the dialog by pressing `Enter`. ## Quick Workflow Summary From aaf30a5d7a33627a1fdb8ad3f67ac79f5efb849b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 21 Feb 2024 17:42:02 +0100 Subject: [PATCH 188/678] Instructions for setting up a (Py 3) pytest environment --- TESTING.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/TESTING.md b/TESTING.md index 7769b2d4..38f42c9a 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,5 +1,34 @@ # Testing ๐Ÿงช in Fiji / ImageJ2 +## Using `pytest` for plain Python code + +Those parts of the package that do not interact / depend on ImageJ objects can +be tested via [`pytest`][pytest] up to a certain level, some (most?) of them +should even work in a Python 3 environment. + +To perform those tests, the packges otherwise provided by ImageJ need to be +mocked using the `imcf-fiji-mocks` package. For seting up a _venv_ use the steps +described here: + +```bash +python3 -m venv venv +source venv/bin/activate +MOCKS_REL="0.1.1" +URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download/v$MOCKS_REL" +pip install --upgrade \ + $URL_PFX/imcf_fiji_mocks-0.1.1-py2.py3-none-any.whl \ + $URL_PFX/micrometa-15.2.2-py2.py3-none-any.whl \ + $URL_PFX/sjlogging-0.5.2-py2.py3-none-any.whl \ + olefile \ + pytest \ + pip + +# now install the 'imcflibs' package in editable mode: +pip install -e . +``` + +## Common testing with ImageJ2 / Fiji + Unfortunately there is nothing like `pytest` available for the parts that are running exclusively in a ImageJ2 / Fiji context. So in order to provide at least some basic, semi-interactive tests the following conventions are being used: @@ -35,7 +64,7 @@ some basic, semi-interactive tests the following conventions are being used: Parameter_ `IMCF_TESTDATA` will remember this selection, so it will be sufficient to just confirm the dialog by pressing `Enter`. -## Quick Workflow Summary +### Quick Workflow Summary First, make sure to have the test data ๐Ÿ”ฌ๐Ÿ”ญaround (or some mocks ๐Ÿชจ๐Ÿชต), then: @@ -48,7 +77,7 @@ First, make sure to have the test data ๐Ÿ”ฌ๐Ÿ”ญaround (or some mocks ๐Ÿชจ๐Ÿชต), 1. Inspect the output ๐Ÿ”Ž๐Ÿ‘€ 1. Repeat ๐Ÿ” -## Test Script Template ๐Ÿ— +### Test Script Template ๐Ÿ— As described above, each test script should use the `IMCF_TESTDATA` parameter to facilitate the manual testing approach. Simply use this template header for @@ -76,3 +105,5 @@ from imcflibs.pathtools import parse_path components = parse_path("systems/lsm700/beads/10x_phmax.czi", IMCF_TESTDATA) assert os.path.exists(components["full"]) ``` + +[pytest]: https://pytest.org From cd1e8bd7c76fc3e080b4138747eb2781fe3b04f1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 22 Feb 2024 16:26:54 +0100 Subject: [PATCH 189/678] Fix DeprecationWarning on invalid escape sequence --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 113892a5..26d366de 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -552,7 +552,7 @@ 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) From c40ac08563d0c678c6f66382b9aa6bc66e8a4218 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Fri, 23 Feb 2024 12:55:50 +0100 Subject: [PATCH 190/678] Change run_tm to run_trackmate, change deprecated method name --- src/imcflibs/imagej/trackmate.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index f673fa4e..38d8671d 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -111,6 +111,7 @@ def stardist_detector(imageplus, target_chnl): return settings +# TODO: check all filters and test def log_detector( imageplus, @@ -249,7 +250,7 @@ def track_filtering( """ settings.trackerFactory = SparseLAPTrackerFactory() - settings.trackerSettings = LAPUtils.getDefaultLAPSettingsMap() + settings.trackerSettings = LAPUtils.getDefaultSegmentSettingsMap() settings.trackerSettings["LINKING_MAX_DISTANCE"] = link_max_dist # must be double settings.trackerSettings[ "GAP_CLOSING_MAX_DISTANCE" @@ -265,7 +266,7 @@ def track_filtering( return settings -def run_tm( +def run_trackmate( implus, settings, crop_roi=None, @@ -276,9 +277,9 @@ def run_tm( Parameters ---------- implus : ij.ImagePlus - ImagePlus on which to run image + ImagePlus image on which to run Trackmate settings : fiji.plugin.trackmate.Settings - Settings to use for TrackMate + Settings to use for TrackMate, see detector methods for different settings crop_roi : ij.gui.Roi, optional ROI to crop on the image, by default None @@ -302,6 +303,7 @@ def run_tm( model.setLogger(Logger.IJTOOLBAR_LOGGER) + trackmate = TrackMate(model, settings) trackmate.computeSpotFeatures(True) trackmate.computeTrackFeatures(True) From 8f5c75f55d4bfbeb3349296f447e1cf2a0ec529d Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Fri, 23 Feb 2024 12:57:07 +0100 Subject: [PATCH 191/678] Add tracker configuration with correct SettingsMap --- src/imcflibs/imagej/trackmate.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 38d8671d..6d2fb089 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -303,6 +303,14 @@ def run_trackmate( model.setLogger(Logger.IJTOOLBAR_LOGGER) + # Configure tracker + settings.trackerFactory = SparseLAPTrackerFactory() + settings.trackerSettings = LAPUtils.getDefaultSegmentSettingsMap() + # settings.addTrackAnalyzer(TrackDurationAnalyzer()) + settings.trackerSettings['LINKING_MAX_DISTANCE'] = 15.0 + settings.trackerSettings['GAP_CLOSING_MAX_DISTANCE'] = 15.0 + settings.trackerSettings['MAX_FRAME_GAP'] = 3 + settings.initialSpotFilterValue = -1. trackmate = TrackMate(model, settings) trackmate.computeSpotFeatures(True) From 823f0bfe46c4c7367167d3525c113f98664c9164 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Fri, 23 Feb 2024 12:59:13 +0100 Subject: [PATCH 192/678] Add tracker configuration with correct SettingsMap, reformat code for readability --- src/imcflibs/imagej/trackmate.py | 71 +++++++++++++++----------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 6d2fb089..3e74191b 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -1,32 +1,30 @@ import os import sys -from ij import IJ - from fiji.plugin.trackmate import Logger, Model, SelectionModel, Settings, TrackMate from fiji.plugin.trackmate.action import LabelImgExporter -from fiji.plugin.trackmate.detection import LogDetectorFactory from fiji.plugin.trackmate.cellpose import CellposeDetectorFactory -from fiji.plugin.trackmate.stardist import StarDistDetectorFactory from fiji.plugin.trackmate.cellpose.CellposeSettings import PretrainedModel - +from fiji.plugin.trackmate.detection import LogDetectorFactory from fiji.plugin.trackmate.features import FeatureFilter +from fiji.plugin.trackmate.stardist import StarDistDetectorFactory from fiji.plugin.trackmate.tracking import LAPUtils from fiji.plugin.trackmate.tracking.jaqaman import SparseLAPTrackerFactory +from ij import IJ from java.lang import Double from .. import pathtools def cellpose_detector( - imageplus, - cellpose_env_path, - model_to_use, - obj_diameter, - target_channel, - optional_channel=0, - use_gpu=True, - simplify_contours=True, + imageplus, + cellpose_env_path, + model_to_use, + obj_diameter, + target_channel, + optional_channel=0, + use_gpu=True, + simplify_contours=True, ): """Create a dictionary with all settings for TrackMate using Cellpose. @@ -61,7 +59,7 @@ def cellpose_detector( settings.detectorFactory = CellposeDetectorFactory() settings.detectorSettings["TARGET_CHANNEL"] = target_channel - settings.detectorSettings["OPTIONAL_CHANNEL_2"] = optional_channel # Set optional channel to 0, will be + settings.detectorSettings["OPTIONAL_CHANNEL_2"] = optional_channel # Set optional channel to 0, will be # overwritten if needed settings.detectorSettings["CELLPOSE_PYTHON_FILEPATH"] = pathtools.join2( @@ -111,15 +109,14 @@ def stardist_detector(imageplus, target_chnl): return settings -# TODO: check all filters and test def log_detector( - imageplus, - radius, - target_channel, - quality_threshold=0.0, - median_filtering=True, - subpix_localization=True, + imageplus, + radius, + target_channel, + quality_threshold=0.0, + median_filtering=True, + subpix_localization=True, ): """Create a dictionary with all settings for TrackMate using the LogDetector. @@ -157,11 +154,11 @@ def log_detector( def spot_filtering( - settings, - quality_thresh=None, - area_thresh=None, - circularity_thresh=None, - intensity_dict_thresh=None, + settings, + quality_thresh=None, + area_thresh=None, + circularity_thresh=None, + intensity_dict_thresh=None, ): """Add spot filtering for different features to the settings dictionary. @@ -219,12 +216,12 @@ def spot_filtering( def track_filtering( - settings, - link_max_dist=0.5, - gap_closing_dist=0.5, - max_frame_gap=2, - track_splitting_max_dist=None, - track_merging_max_distance=None, + settings, + link_max_dist=0.5, + gap_closing_dist=0.5, + max_frame_gap=2, + track_splitting_max_dist=None, + track_merging_max_distance=None, ): """Add track filtering for different features to the settings dictionary. @@ -267,9 +264,9 @@ def track_filtering( def run_trackmate( - implus, - settings, - crop_roi=None, + implus, + settings, + crop_roi=None, ): # sourcery skip: merge-else-if-into-elif, swap-if-else-branches """Function to run TrackMate on open data. Has some specific input @@ -324,9 +321,9 @@ def run_trackmate( ok = trackmate.process() if not ok: if "[SparseLAPTracker] The spot collection is empty." in str( - trackmate.getErrorMessage() + trackmate.getErrorMessage() ): - new_imp = IJ.createImage( + new_imp = IJ.createImage( "Untitled", "16-bit black", implus.getWidth(), From 69045d79f38acd55492e2821378e76f9761f93e9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 26 Feb 2024 16:05:36 +0100 Subject: [PATCH 193/678] Example for running pytest --- TESTING.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/TESTING.md b/TESTING.md index 38f42c9a..98fdc150 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,4 +1,4 @@ -# Testing ๐Ÿงช in Fiji / ImageJ2 +# Testing ๐Ÿงช๐Ÿงซ in Fiji / ImageJ2 ## Using `pytest` for plain Python code @@ -27,6 +27,13 @@ pip install --upgrade \ pip install -e . ``` +Using this _venv_, tests can be triggered just the usual way. To run only +specific tests, use e.g. + +```bash +pytest tests/bdv/test_processingoptions.py +``` + ## Common testing with ImageJ2 / Fiji Unfortunately there is nothing like `pytest` available for the parts that are From 80688a6d4ae01b53c749e1aba2151c52d875eab7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 26 Feb 2024 16:06:23 +0100 Subject: [PATCH 194/678] Template for creating tests for the ProcessingOptions class --- tests/bdv/test_processingoptions.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/bdv/test_processingoptions.py diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py new file mode 100644 index 00000000..d58d190f --- /dev/null +++ b/tests/bdv/test_processingoptions.py @@ -0,0 +1,7 @@ +import pytest + +from imcflibs.imagej.bdv import ProcessingOptions + + +def test_one(): + proc_opts = ProcessingOptions() \ No newline at end of file From 53e24ac4192d3b8984df1b20706b6e9e5abf2086 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 27 Feb 2024 18:46:20 +0100 Subject: [PATCH 195/678] Fully rework the "loci" imports This way we are able to avoid any pragma pre-processing and other funny workarounds while still being able to import the code in C-Python right away, thus having access to all the nice tools like black, pylint, pdoc and even pytest (where it makes sense). --- src/imcflibs/imagej/_loci.py | 66 +++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index 9043c712..c33283d2 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -1,40 +1,42 @@ """Internal wrapper module to import from the (Java) loci package. -This module mostly exists to work around the issue that importing the -`ImporterOptions` class from the Java `loci` package requires syntax to be used -that is considered invalid by any C-Python parser (but is still valid and -working in Jython) and hence will break usage of Black, Pylint, and similar. - -By stashing this into an internal submodule only checks on this specific -(minimalistic) file will fail but remain operational for the other code. - -So why are the other imports in here then? - -This was a conscious decision as it seems to be confusing that *some* parts from -the `loci` package need to be imported from the `imcflibs.imagej._loci` -sub-module while others are imported directly. Instead we're simply importing -proxy-style all loci components through the file here. - -NOTE: the actual import of `ImporterOptions` still requires the `# pdoc: skip` -pragma to work with the documentation generation scripts, e.g. - -``` -from ._loci import ImporterOptions # pdoc: skip -``` +This module exists to work around the issue that importing some of the classes +from the Java `loci` package would require syntax that is considered invalid by +any C-Python parser (but is still valid and working in Jython) and hence will +break usage of Black, Pylint, and similar. + +By aggregating those "special" imports into this (private) submodule we can +properly work around that issue by providing "dummy" objects for C-Python and +importing the actual modules / classes when running within Jython. To avoid the +invalid syntax issue (which would still prevent C-Python-based tools like black +and pdoc to run) those parts are done via `importlib` calls. + +Other loci-related imports (i.e. those without problematic syntax) are placed in +here simply for consistency reasons (to have everything in the same place). """ -# mock-preproc: fix-invalid-keyword - from loci.plugins import BF -from loci.plugins.in import ImporterOptions - -from loci.formats.in import ( - ZeissCZIReader, - DefaultMetadataOptions, - MetadataLevel, - DynamicMetadataOptions, - MetadataOptions, -) +# dummy objects to prevent failing imports in a non-ImageJ / Jython context: +ImporterOptions = None +ZeissCZIReader = None +DefaultMetadataOptions = None +MetadataLevel = None +DynamicMetadataOptions = None + +# perform the actual imports when running under Jython using `importlib` calls: +import platform as _python_platform +if _python_platform.python_implementation() == "Jython": + import importlib + _loci_plugins_in = importlib.import_module("loci.plugins.in") + ImporterOptions = _loci_plugins_in.ImporterOptions + + _loci_formats_in = importlib.import_module("loci.formats.in") + ZeissCZIReader = _loci_formats_in.ZeissCZIReader + DefaultMetadataOptions = _loci_formats_in.DefaultMetadataOptions + MetadataLevel = _loci_formats_in.MetadataLevel + DynamicMetadataOptions = _loci_formats_in.DynamicMetadataOptions + MetadataOptions = _loci_formats_in.MetadataOptions +del _python_platform from loci.formats import ImageReader, Memoizer From b72ebda36d7dc4e126c5232de1ef8a76b75459b9 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 29 Feb 2024 15:30:23 +0100 Subject: [PATCH 196/678] Remove redundancy in track settings --- src/imcflibs/imagej/trackmate.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 3e74191b..9a93c197 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -217,9 +217,9 @@ def spot_filtering( def track_filtering( settings, - link_max_dist=0.5, - gap_closing_dist=0.5, - max_frame_gap=2, + link_max_dist=15, + gap_closing_dist=15, + max_frame_gap=3, track_splitting_max_dist=None, track_merging_max_distance=None, ): @@ -301,12 +301,7 @@ def run_trackmate( model.setLogger(Logger.IJTOOLBAR_LOGGER) # Configure tracker - settings.trackerFactory = SparseLAPTrackerFactory() - settings.trackerSettings = LAPUtils.getDefaultSegmentSettingsMap() # settings.addTrackAnalyzer(TrackDurationAnalyzer()) - settings.trackerSettings['LINKING_MAX_DISTANCE'] = 15.0 - settings.trackerSettings['GAP_CLOSING_MAX_DISTANCE'] = 15.0 - settings.trackerSettings['MAX_FRAME_GAP'] = 3 settings.initialSpotFilterValue = -1. trackmate = TrackMate(model, settings) From 56d72f8436ab67f125164b313958924b7994ec41 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 29 Feb 2024 15:30:37 +0100 Subject: [PATCH 197/678] New image will be of same bit depth as input --- src/imcflibs/imagej/trackmate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 9a93c197..a18ec077 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -320,7 +320,7 @@ def run_trackmate( ): new_imp = IJ.createImage( "Untitled", - "16-bit black", + str(implus.getBitDepth()) + "-bit black", implus.getWidth(), implus.getHeight(), implus.getNFrames(), From c190fb82478169f4ce0859b33772381359502a6d Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 29 Feb 2024 16:28:53 +0100 Subject: [PATCH 198/678] Add method for simple flatfield correction --- src/imcflibs/imagej/shading.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py index 291842c1..b3e40cbd 100644 --- a/src/imcflibs/imagej/shading.py +++ b/src/imcflibs/imagej/shading.py @@ -3,7 +3,9 @@ import os import ij # pylint: disable-msg=import-error - +from ij import IJ +from ij.plugin import ImageCalculator, Concatenator +from ij.process import StackStatistics, ImageProcessor from ..imagej import bioformats # pylint: disable-msg=no-name-in-module from ..imagej import misc, projections from ..log import LOG as log @@ -179,3 +181,29 @@ def process_files(files, outpath, model_file, fmt): if model: model.close() + +def simple_flatfield_correction(imp, sigma=20.0): + """ + Performs a simple flatfield correction to a given ImagePlus stack and returns a 32-bit corrected flatfield image. + Parameters + ---------- + imp : ij.ImagePlus + The input stack to be projected. + sigma: double, default 20.0 + The sigma value for the Gaussian blur, default = 20.0 + Returns + ------- + ij.ImagePlus + The 32-bit image result of the flatfield correction + + """ + flatfield = imp.duplicate() + sigma_str = "sigma=" + str(sigma) + IJ.run(flatfield, "Gaussian Blur...", sigma_str) # Apply a gaussian blur + stats = StackStatistics(flatfield) + IJ.run(flatfield, "32-bit", "") # Make a 32 bit version of the image + IJ.run(flatfield, "Divide...", "value=" + str(stats.max)) # Normalize 32 bit image to the highest value of original + ic = ImageCalculator() + flatfield_corrected = ic.run("Divide create", imp, flatfield) + + return flatfield_corrected \ No newline at end of file From 3218ff3b72ccf43f04956ce731724c74a2fcae27 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 13:44:12 +0100 Subject: [PATCH 199/678] Improve testing instructions for pytest --- TESTING.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/TESTING.md b/TESTING.md index 98fdc150..a9bfea48 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,6 +1,6 @@ # Testing ๐Ÿงช๐Ÿงซ in Fiji / ImageJ2 -## Using `pytest` for plain Python code +## Using `pytest` ๐Ÿ๐Ÿ”ฌ and Python 3 for plain Python code Those parts of the package that do not interact / depend on ImageJ objects can be tested via [`pytest`][pytest] up to a certain level, some (most?) of them @@ -11,8 +11,17 @@ mocked using the `imcf-fiji-mocks` package. For seting up a _venv_ use the steps described here: ```bash -python3 -m venv venv +# check if we're "inside" the repo already, otherwise clone it here: +git remote -v 2>/dev/null | grep -q imcf/python-imcflibs || { + git clone https://github.com/imcf/python-imcflibs/ + cd python-imcflibs + git checkout -b pytest origin/pytest +} +# create and activate a new venv: +test -d "venv" || python3 -m venv venv source venv/bin/activate + +# install dependencies / requirements: MOCKS_REL="0.1.1" URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download/v$MOCKS_REL" pip install --upgrade \ @@ -21,6 +30,7 @@ pip install --upgrade \ $URL_PFX/sjlogging-0.5.2-py2.py3-none-any.whl \ olefile \ pytest \ + pytest-cov \ pip # now install the 'imcflibs' package in editable mode: From c13e3ea0d98b90cd767f7f58305289c3085d3e11 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 14:14:04 +0100 Subject: [PATCH 200/678] Make filehandle() py2/py3 capable --- src/imcflibs/iotools.py | 14 ++++++++++++-- tests/test_iotools.py | 23 +++++++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/iotools.py b/src/imcflibs/iotools.py index c5da0cd2..c7a634a1 100644 --- a/src/imcflibs/iotools.py +++ b/src/imcflibs/iotools.py @@ -1,5 +1,7 @@ """I/O related functions.""" +import io + import glob import zipfile @@ -10,6 +12,14 @@ from .strtools import flatten +try: + # Python 2: "file" is built-in + file_types = file, io.IOBase +except NameError: + # Python 3: "file" fully replaced with IOBase + file_types = (io.IOBase,) + + def filehandle(fname, mode="r"): """Make sure a variable is either a filehandle or create one from it. @@ -44,13 +54,13 @@ def filehandle(fname, mode="r"): """ log.debug(type(fname)) - if type(fname).__name__ == "str": + if isinstance(fname, str): try: return open(fname, mode) except IOError as err: message = "can't open '%s': %s" raise SystemExit(message % (fname, err)) - elif type(fname).__name__ == "file": + elif isinstance(fname, file_types): if fname.mode != mode: message = "mode mismatch: %s != %s" raise IOError(message % (fname.mode, mode)) diff --git a/tests/test_iotools.py b/tests/test_iotools.py index 1b8b680f..75708fce 100644 --- a/tests/test_iotools.py +++ b/tests/test_iotools.py @@ -6,9 +6,19 @@ from os.path import join +import io + from imcflibs.iotools import filehandle from imcflibs.iotools import readtxt +try: + # Python 2: "file" is built-in + file_types = file, io.IOBase +except NameError: + # Python 3: "file" fully replaced with IOBase + file_types = (io.IOBase,) + + __author__ = "Niko Ehrenfeuchter" __copyright__ = "Niko Ehrenfeuchter" __license__ = "gpl3" @@ -17,12 +27,17 @@ def test_filehandle(tmpdir): tmpfile = tmpdir.join("testfile") tmpname = str(tmpfile) + # print(tmpname) tmphandle = open(str(tmpfile), "w") print(type(tmphandle)) - assert type(tmpname) is str - assert type(tmphandle) is file - assert type(filehandle(tmpname)) is file - assert type(filehandle(tmphandle, "w")) is file + assert isinstance(tmpname, str) + print("tmpname is str-like ๐Ÿฆ‹") + assert isinstance(tmphandle, file_types) + print("tmphandle is file/io-like ๐Ÿฆ‹") + assert isinstance(filehandle(tmpname), file_types) + print("filehandle(tmpname) is file/io-like ๐Ÿฆ‹") + assert isinstance(filehandle(tmphandle, "w"), file_types) + print("filehandle(tmphandle) is file/io-like ๐Ÿฆ‹") def test_readtxt(tmpdir): From d198e3bb663fd52c5406f9d51a9b5bbc3d6d3ab6 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 14:43:18 +0100 Subject: [PATCH 201/678] Add instructions and script for pytest / Python 2 --- TESTING.md | 19 +++++++++++++++++++ scripts/py2-pytest.sh | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 scripts/py2-pytest.sh diff --git a/TESTING.md b/TESTING.md index a9bfea48..06f93e11 100644 --- a/TESTING.md +++ b/TESTING.md @@ -44,6 +44,24 @@ specific tests, use e.g. pytest tests/bdv/test_processingoptions.py ``` +## Using `pytest` ๐Ÿ๐Ÿ”ฌ and Python 2 for plain Python code + +For running [`pytest`][pytest] in a C-Python 2 environment, things are slightly +more complicated than the approach described for Python 3 above as `pip` for +Python 2 cannot install a project in _editable_ mode unless it has a `setup.py` +file (which we don't have and don't want). + +Therefore, a wheel needs to be built (e.g. using [`poetry`][poetry]) and +installed (every time) into the corresponding virtualenv when performing the +tests. Assuming you're having a working _poetry_ setup on your machine, you can +simply use the provided `scripts/py2-pytest.sh` wrapper that will create the +virtualenv, build and install the `imcflibs` wheel and launch `pytest` with the +parameters specified, e.g. + +```bash +bash scripts/py2-pytest.sh -rv --cov --cov-report html +``` + ## Common testing with ImageJ2 / Fiji Unfortunately there is nothing like `pytest` available for the parts that are @@ -124,3 +142,4 @@ assert os.path.exists(components["full"]) ``` [pytest]: https://pytest.org +[poetry]: https://python-poetry.org diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh new file mode 100644 index 00000000..471678d6 --- /dev/null +++ b/scripts/py2-pytest.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -o errexit # exit on any error +set -o nounset # empty variables are not permitted + +# set the version of "imcf-fiji-mocks" to be used: +MOCKS_RELEASE="0.1.1" + +cd "$(dirname "$0")"/.. + +test -d "venv2" || { + python2 -m virtualenv venv2 + source venv2/bin/activate + URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download" + pip install --upgrade \ + $URL_PFX/v$MOCKS_RELEASE/imcf_fiji_mocks-0.1.1-py2.py3-none-any.whl \ + $URL_PFX/v$MOCKS_RELEASE/micrometa-15.2.2-py2.py3-none-any.whl \ + $URL_PFX/v$MOCKS_RELEASE/sjlogging-0.5.2-py2.py3-none-any.whl \ + olefile==0.46 \ + pytest \ + pytest-cov \ + pip + + deactivate +} + +poetry build -vv + +venv2/bin/pip uninstall --yes imcflibs +venv2/bin/pip install dist/*.whl + +# call this script with `--cov --cov-report html` to generate coverage reports: +venv2/bin/pytest "$@" From ad75f854d966310221bdee06371dc36f4417e2ba Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 14:43:25 +0100 Subject: [PATCH 202/678] Add .coveragerc --- .coveragerc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..3fdcd375 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,5 @@ +[run] +omit = + # omit anything in a venv / venv2 directory + ./venv/* + ./venv2/* From eb3b155196716d85af1261656f2bacfdeac552a2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 15:30:08 +0100 Subject: [PATCH 203/678] Make readtxt() py3 capable --- src/imcflibs/iotools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/imcflibs/iotools.py b/src/imcflibs/iotools.py index c7a634a1..a47d179e 100644 --- a/src/imcflibs/iotools.py +++ b/src/imcflibs/iotools.py @@ -111,6 +111,10 @@ def readtxt(fname, path="", flat=False): else: fin = open(join(path, fname), "r") txt = fin.readlines() # returns file as a list, one entry per line + try: + txt = [x.decode("utf-8") for x in txt] + except AttributeError: + pass # in Python2 decoding isn't necessary if flat: txt = flatten(txt) fin.close() From ad233b18f09aabeb8cb1f772bf0e445a5fa6bce3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 15:33:02 +0100 Subject: [PATCH 204/678] Make filename() py3 capable --- src/imcflibs/strtools.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index f59143e2..6005d51e 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -1,7 +1,14 @@ """String related helper functions.""" +import io import re +try: + # Python 2: "file" is built-in + file_types = file, io.IOBase +except NameError: + # Python 3: "file" fully replaced with IOBase + file_types = (io.IOBase,) # this is taken from numpy's iotools: def _is_string_like(obj): @@ -62,7 +69,7 @@ def filename(name): # likely we are not running under Jython pass - if isinstance(name, file): + if isinstance(name, file_types): return name.name elif _is_string_like(name): return name From 9aab09d5210c6b9e59f371257745e5d38aec1a27 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 15:42:03 +0100 Subject: [PATCH 205/678] Some coverage exclude pragmas --- src/imcflibs/strtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index 6005d51e..2a11e3d7 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -3,10 +3,10 @@ import io import re -try: +try: # pragma: no cover # Python 2: "file" is built-in file_types = file, io.IOBase -except NameError: +except NameError: # pragma: no cover # Python 3: "file" fully replaced with IOBase file_types = (io.IOBase,) From 23169777a2ad3c60cf2e40e5e6871ffab123af98 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 15:43:33 +0100 Subject: [PATCH 206/678] Move "BDV" module TODO --- src/imcflibs/imagej/bdv_todo.md => TODO-BDV.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/imcflibs/imagej/bdv_todo.md => TODO-BDV.md (100%) diff --git a/src/imcflibs/imagej/bdv_todo.md b/TODO-BDV.md similarity index 100% rename from src/imcflibs/imagej/bdv_todo.md rename to TODO-BDV.md From a852aa5a79e2e37186e832b92432b096b79e50a9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 15:54:29 +0100 Subject: [PATCH 207/678] Black formatting --- tests/test_pathtools.py | 48 ++++++++++++++++++++--------------------- tests/test_strtools.py | 14 ++++++------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index fb81e811..b83b8390 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -12,36 +12,36 @@ def test_parse_path(): - path = '/tmp/foo/' + path = "/tmp/foo/" path_to_dir = parse_path(path) - path_to_file = parse_path(path + 'file.ext') - path_to_file_noext = parse_path(path + 'file') + path_to_file = parse_path(path + "file.ext") + path_to_file_noext = parse_path(path + "file") - assert path_to_file['orig'] == path + 'file.ext' - assert path_to_file['path'] == path - assert path_to_file['dname'] == 'foo' - assert path_to_file['fname'] == 'file.ext' - assert path_to_file['ext'] == '.ext' + assert path_to_file["orig"] == path + "file.ext" + assert path_to_file["path"] == path + assert path_to_file["dname"] == "foo" + assert path_to_file["fname"] == "file.ext" + assert path_to_file["ext"] == ".ext" - assert path_to_file_noext['ext'] == '' - assert path_to_file_noext['fname'] == 'file' - assert path_to_file_noext['dname'] == 'foo' - assert path_to_file_noext['path'] == path + assert path_to_file_noext["ext"] == "" + assert path_to_file_noext["fname"] == "file" + assert path_to_file_noext["dname"] == "foo" + assert path_to_file_noext["path"] == path - assert path_to_dir['path'] == path - assert path_to_dir['fname'] == '' - assert path_to_dir['dname'] == 'foo' - assert path_to_dir['ext'] == '' + assert path_to_dir["path"] == path + assert path_to_dir["fname"] == "" + assert path_to_dir["dname"] == "foo" + assert path_to_dir["ext"] == "" def test_parse_path_windows(): - path = r'C:\foo\bar' + path = r"C:\foo\bar" parsed = parse_path(path) - assert parsed['orig'] == path - assert parsed['full'] == r'C:/foo/bar' - assert parsed['fname'] == 'bar' - assert parsed['dname'] == 'foo' + assert parsed["orig"] == path + assert parsed["full"] == r"C:/foo/bar" + assert parsed["fname"] == "bar" + assert parsed["dname"] == "foo" def test_jython_fiji_exists(tmpdir): @@ -49,6 +49,6 @@ def test_jython_fiji_exists(tmpdir): def test_image_basename(): - assert image_basename('/path/to/image_file_01.png') == 'image_file_01' - assert image_basename('more-complex-stack.ome.tif') == 'more-complex-stack' - assert image_basename('/tmp/FoObAr.OMe.tIf') == 'FoObAr' + assert image_basename("/path/to/image_file_01.png") == "image_file_01" + assert image_basename("more-complex-stack.ome.tif") == "more-complex-stack" + assert image_basename("/tmp/FoObAr.OMe.tIf") == "FoObAr" diff --git a/tests/test_strtools.py b/tests/test_strtools.py index 823b5615..9c61c1b8 100644 --- a/tests/test_strtools.py +++ b/tests/test_strtools.py @@ -15,24 +15,24 @@ def test__is_string_like(): - assert _is_string_like('foo') == True + assert _is_string_like("foo") == True assert _is_string_like(12345) == False def test_filename_from_string(): - assert filename('test_file_name') == 'test_file_name' + assert filename("test_file_name") == "test_file_name" def test_filename_from_handle(tmpdir): path = str(tmpdir) - fhandle = tmpdir.join('foo.txt') - assert filename(fhandle) == os.path.join(path, 'foo.txt') + fhandle = tmpdir.join("foo.txt") + assert filename(fhandle) == os.path.join(path, "foo.txt") def test_flatten(): - assert flatten(('foo', 'bar')) == 'foobar' + assert flatten(("foo", "bar")) == "foobar" def test_strip_prefix(): - assert strip_prefix('foobar', 'foo') == 'bar' - assert strip_prefix('foobar', 'bar') == 'foobar' \ No newline at end of file + assert strip_prefix("foobar", "foo") == "bar" + assert strip_prefix("foobar", "bar") == "foobar" From 96bdfb65982ae30cd0f30e72e7fdb90d7b34d754 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 15:55:06 +0100 Subject: [PATCH 208/678] More black formatting --- src/imcflibs/imagej/_loci.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index c33283d2..6d33eba7 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -26,8 +26,10 @@ # perform the actual imports when running under Jython using `importlib` calls: import platform as _python_platform + if _python_platform.python_implementation() == "Jython": import importlib + _loci_plugins_in = importlib.import_module("loci.plugins.in") ImporterOptions = _loci_plugins_in.ImporterOptions From 57daa318aa53195020dae523d6b6b8232a4c237d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 16:09:54 +0100 Subject: [PATCH 209/678] Improve fmt_acitt_selectors() docstring --- src/imcflibs/imagej/bdv.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 26d366de..0dc7ba1d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -333,12 +333,15 @@ def fmt_acitt_selectors(self): """Format Angle / Channel / Illumination / Tile / Timepoint selectors. Build a string providing the `angle_select`, `channel_select`, - `illumination_select`, `tile_select` and `timepoint_select` options - that can be used in a BDV-related `IJ.run` call. + `illumination_select`, `tile_select` and `timepoint_select` options that + can be used in a BDV-related `IJ.run` call. In case no selectors have + been chosen, nothing but a single space will be returned. Returns ------- str + The formatted selector string. Will be a single white-space in case + no selectors have been configured for the object. """ parameters = [ self._angle_select if self._angle_select else "", From 1d8f7b6b268f95ccc777c97cf1559f1b730e2c89 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 16:10:58 +0100 Subject: [PATCH 210/678] Add test_processingoptions.py::test_defaults --- tests/bdv/test_processingoptions.py | 32 +++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index d58d190f..5f21d433 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -3,5 +3,33 @@ from imcflibs.imagej.bdv import ProcessingOptions -def test_one(): - proc_opts = ProcessingOptions() \ No newline at end of file +def test_defaults(): + """Test the default options by calling all fomatters on a "raw" object.""" + acitt_options = ( + "process_angle=[All angles] " + "process_channel=[All channels] " + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " + "process_timepoint=[All Timepoints] " + ) + acitt_selectors = " " + use_acitt = ( + "channels=[Average Channels] " + "illuminations=[Average Illuminations] " + "tiles=[Average Tiles] " + "timepoints=[Average Timepoints] " + ) + how_to_treat = ( + "how_to_treat_angles=[treat individually] " + "how_to_treat_channels=group " + "how_to_treat_illuminations=group " + "how_to_treat_tiles=group " + "how_to_treat_timepoints=group " + ) + + proc_opts = ProcessingOptions() + + assert proc_opts.fmt_acitt_options() == acitt_options + assert proc_opts.fmt_acitt_selectors() == acitt_selectors + assert proc_opts.fmt_use_acitt() == use_acitt + assert proc_opts.fmt_how_to_treat() == how_to_treat From b9e6e685a271bc4c0001dd63432b05306480d104 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 16:14:14 +0100 Subject: [PATCH 211/678] Stick to "acitt" order --- TODO-BDV.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO-BDV.md b/TODO-BDV.md index da877dc4..da374ed4 100644 --- a/TODO-BDV.md +++ b/TODO-BDV.md @@ -103,11 +103,11 @@ Selected options: - Second block: - (N/A) - Third block: - - How to treat Timepoints: treat individually + - How to treat Angles: treat individually - How to treat Channels: group - How to treat Illuminations: group - - How to treat Angles: treat individually - How to treat Tiles: compare + - How to treat Timepoints: treat individually - Fourth block: - [use Channel 1] @@ -121,11 +121,11 @@ process_tile=[All tiles] process_timepoint=[All Timepoints] method=[Phase Correlation] show_expert_grouping_options -how_to_treat_timepoints=[treat individually] +how_to_treat_angles=[treat individually] how_to_treat_channels=group how_to_treat_illuminations=group -how_to_treat_angles=[treat individually] how_to_treat_tiles=compare +how_to_treat_timepoints=[treat individually] channels=[use Channel 1] ``` From 171768dc4f6409b1f3e8581afd5aed75c96e5059 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 16:16:37 +0100 Subject: [PATCH 212/678] Use same order as in object methods --- tests/bdv/test_processingoptions.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index 5f21d433..7b740e2b 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -13,12 +13,6 @@ def test_defaults(): "process_timepoint=[All Timepoints] " ) acitt_selectors = " " - use_acitt = ( - "channels=[Average Channels] " - "illuminations=[Average Illuminations] " - "tiles=[Average Tiles] " - "timepoints=[Average Timepoints] " - ) how_to_treat = ( "how_to_treat_angles=[treat individually] " "how_to_treat_channels=group " @@ -26,10 +20,17 @@ def test_defaults(): "how_to_treat_tiles=group " "how_to_treat_timepoints=group " ) + use_acitt = ( + "channels=[Average Channels] " + "illuminations=[Average Illuminations] " + "tiles=[Average Tiles] " + "timepoints=[Average Timepoints] " + ) proc_opts = ProcessingOptions() assert proc_opts.fmt_acitt_options() == acitt_options assert proc_opts.fmt_acitt_selectors() == acitt_selectors - assert proc_opts.fmt_use_acitt() == use_acitt assert proc_opts.fmt_how_to_treat() == how_to_treat + assert proc_opts.fmt_use_acitt() == use_acitt + From 0d007b924015f7c253fdda2b9ed4432262cef0d7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 4 Mar 2024 16:26:10 +0100 Subject: [PATCH 213/678] Add test for TODO-example 1 --- tests/bdv/test_processingoptions.py | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index 7b740e2b..68faf20d 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -34,3 +34,39 @@ def test_defaults(): assert proc_opts.fmt_how_to_treat() == how_to_treat assert proc_opts.fmt_use_acitt() == use_acitt + +def test__treat_tc_ti__ref_c2(): + """Test an example setting how to treat components using a reference channel.""" + # refers to "Example 1" from the BDV TODO list + # FIXME: what are the actual inputs and the correct output string?? + acitt_options = ( + "process_angle=[All angles] " + "process_channel=[All channels] " + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " + "process_timepoint=[All Timepoints] " + ) + acitt_selectors = " " + how_to_treat = ( + "how_to_treat_angles=[treat individually] " + "how_to_treat_channels=group " + "how_to_treat_illuminations=group " + "how_to_treat_tiles=compare " + "how_to_treat_timepoints=[treat individually] " + ) + use_acitt = ( + "channels=[use Channel 1] " + "illuminations=[Average Illuminations] " + "tiles=[Average Tiles] " + "timepoints=[Average Timepoints] " + ) + + proc_opts = ProcessingOptions() + proc_opts.treat_tiles("compare") + proc_opts.treat_timepoints("[treat individually]") + proc_opts.reference_channel(2) + + assert proc_opts.fmt_acitt_options() == acitt_options + assert proc_opts.fmt_acitt_selectors() == acitt_selectors + assert proc_opts.fmt_use_acitt() == use_acitt + assert proc_opts.fmt_how_to_treat() == how_to_treat From 4bf9b1861d5741031158d9cf4d43057dc101684b Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 5 Mar 2024 11:41:14 +0100 Subject: [PATCH 214/678] Use correct channel number to be consistent --- src/imcflibs/imagej/bdv.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 0dc7ba1d..57360341 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -120,11 +120,10 @@ def reference_channel(self, value): Parameters ---------- value : int or int-like - The channel number + 1 to use for the grouping (in other words: the - effectively used value will be the given one minus 1). + The channel number to use for the grouping. """ - channel = int(value) - 1 # will raise a ValueError if cast fails - self._use_channel = "channels=[use Channel %s]" % channel + # 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) def reference_illumination(self, value): From 1ffd8c9042fa1550b8e5042a2455da9f9d0d3701 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 5 Mar 2024 11:51:00 +0100 Subject: [PATCH 215/678] Reflect merged 'pytest' branch --- TESTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TESTING.md b/TESTING.md index 06f93e11..4aa7a254 100644 --- a/TESTING.md +++ b/TESTING.md @@ -15,7 +15,7 @@ described here: git remote -v 2>/dev/null | grep -q imcf/python-imcflibs || { git clone https://github.com/imcf/python-imcflibs/ cd python-imcflibs - git checkout -b pytest origin/pytest + git checkout -b processing-options-class origin/processing-options-class } # create and activate a new venv: test -d "venv" || python3 -m venv venv From 0864c401b2ec244c459b07d0a727c6e54cb1397b Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 5 Mar 2024 12:14:58 +0100 Subject: [PATCH 216/678] Change channel number Also add average text as we can't be as smart as BigStitcher --- tests/bdv/test_processingoptions.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index 68faf20d..0e975295 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -35,7 +35,7 @@ def test_defaults(): assert proc_opts.fmt_use_acitt() == use_acitt -def test__treat_tc_ti__ref_c2(): +def test__treat_tc_ti__ref_c1(): """Test an example setting how to treat components using a reference channel.""" # refers to "Example 1" from the BDV TODO list # FIXME: what are the actual inputs and the correct output string?? @@ -57,14 +57,17 @@ def test__treat_tc_ti__ref_c2(): use_acitt = ( "channels=[use Channel 1] " "illuminations=[Average Illuminations] " - "tiles=[Average Tiles] " - "timepoints=[Average Timepoints] " ) proc_opts = ProcessingOptions() proc_opts.treat_tiles("compare") proc_opts.treat_timepoints("[treat individually]") - proc_opts.reference_channel(2) + proc_opts.reference_channel(1) + + assert proc_opts.fmt_acitt_options() == acitt_options + assert proc_opts.fmt_acitt_selectors() == acitt_selectors + assert proc_opts.fmt_use_acitt() == use_acitt + assert proc_opts.fmt_how_to_treat() == how_to_treat assert proc_opts.fmt_acitt_options() == acitt_options assert proc_opts.fmt_acitt_selectors() == acitt_selectors From 1fed4ff2acaf740efc1548ee0e30e8a4726ad520 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 5 Mar 2024 12:15:10 +0100 Subject: [PATCH 217/678] Use correct channel number --- 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 57360341..2154b5ff 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -195,8 +195,8 @@ def process_channel(self, value): # def channel_select(self, value): value : int or int-like """ self._channel_processing_option = SINGLE % "channel" - channel = int(value) - 1 - self._channel_select = "processing_channel=[channel %s]" % channel + # channel = int(value) - 1 + self._channel_select = "processing_channel=[channel %s]" % int(value) def process_illumination(self, value): # def illumination_select(self, value): """Select a single illumination to use for processing. From b6ffe4d6b3a2b1b9d40cb7d58ba2dfc53632d6c1 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 5 Mar 2024 15:08:53 +0100 Subject: [PATCH 218/678] Add markdown file detailing a testing script for the shading/flatfield correction method. --- tests/imagej/shading-test.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/imagej/shading-test.md diff --git a/tests/imagej/shading-test.md b/tests/imagej/shading-test.md new file mode 100644 index 00000000..9371be38 --- /dev/null +++ b/tests/imagej/shading-test.md @@ -0,0 +1,18 @@ +### ---------------------- + + The following code block is a `python` script to be used in a Fiji with the shading branch's .jar already pasted into ./jars in the Fiji installation + + Recommended is to import an image you wish to test on (Shaded-blobs.png e.g) and then drag this script into Fiji and run it. + If a resulting image pops up (while using flatfield method), everything works finely. +### ---------------------- + +```python +from imcflibs.imagej import shading +# import imcflibs.imagej +import ij +from ij import IJ + +imp = IJ.getImage() +imcf_shading = shading.simple_flatfield_correction(imp) +# Or any other method in class shading +imcf_shading.show() \ No newline at end of file From 8e8d959a54026924b8d7d87cff015206d32b7524 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 5 Mar 2024 15:52:40 +0100 Subject: [PATCH 219/678] Fix imports due to some files being moved by trackmate --- src/imcflibs/imagej/trackmate.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index a18ec077..5182f2bf 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -1,16 +1,17 @@ import os import sys +from ij import IJ + from fiji.plugin.trackmate import Logger, Model, SelectionModel, Settings, TrackMate from fiji.plugin.trackmate.action import LabelImgExporter +from fiji.plugin.trackmate.detection import LogDetectorFactory from fiji.plugin.trackmate.cellpose import CellposeDetectorFactory +from fiji.plugin.trackmate.stardist import StarDistDetectorFactory from fiji.plugin.trackmate.cellpose.CellposeSettings import PretrainedModel -from fiji.plugin.trackmate.detection import LogDetectorFactory + from fiji.plugin.trackmate.features import FeatureFilter -from fiji.plugin.trackmate.stardist import StarDistDetectorFactory -from fiji.plugin.trackmate.tracking import LAPUtils -from fiji.plugin.trackmate.tracking.jaqaman import SparseLAPTrackerFactory -from ij import IJ +from fiji.plugin.trackmate.tracking.jaqaman import LAPUtils, SparseLAPTrackerFactory from java.lang import Double from .. import pathtools From 9247abe00b2bd9c611d2143fad81da142990af09 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 5 Mar 2024 16:17:18 +0100 Subject: [PATCH 220/678] Change filtering parameter to double from integer --- src/imcflibs/imagej/trackmate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 5182f2bf..a58c0a3b 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -218,8 +218,8 @@ def spot_filtering( def track_filtering( settings, - link_max_dist=15, - gap_closing_dist=15, + link_max_dist=15.0, + gap_closing_dist=15.0, max_frame_gap=3, track_splitting_max_dist=None, track_merging_max_distance=None, From 93b4ddb906d11f6b33b0832f83bec0e6debb5b10 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 14 Mar 2024 14:55:06 +0100 Subject: [PATCH 221/678] Add method to segment a 3D binary stack that returns a labelled stack as Imageplus --- src/imcflibs/imagej/objects3d.py | 44 +++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index cc0b8300..41aca296 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -1,6 +1,6 @@ from ij import IJ from mcib3d.geom import Objects3DPopulation -from mcib3d.image3d import ImageHandler +from mcib3d.image3d import ImageHandler, ImageLabeller def population3d_to_imgplus(imp, population): @@ -54,3 +54,45 @@ def imgplus_to_population3d(imp): """ img = ImageHandler.wrap(imp) return Objects3DPopulation(img) + + +def segment_3D_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): + """Segment a 3D binary image to get a labelled stack + + Parameters + ---------- + imp : ImagePlus + Binary 3D stack + title : str, optional + Title of the new image + min_thresh : int, optional + Threshold to do segmentation, also allows for label filtering by default 1 + min_vol : int, optional + Volume (voxels) under which to filter objects, by default None + max_vol : int, optional + Volume above which to filter objects, by default None + + Returns + ------- + ImagePlus + Segmented 3D labelled ImagePlus + """ + cal = imp.getCalibration() + img = ImageHandler.wrap(imp) + img = img.threshold(min_thresh, False, False) + + labeler = ImageLabeller() + if min_vol: + labeler.setMinSizeCalibrated(min_vol, img) + if max_vol: + labeler.setMaxSizeCalibrated(max_vol, img) # Issue with typo an + # TODO: this method might not work on older Fiji deployments, needs testing on newer versions + # (deprecated .setMaxsize, it's .setMaxSizeCalibrated) + # TODO Keep it in mind for next Fiji deployment + # labeler.setMaxsize(max_vol) + + seg = labeler.getLabels(img) + seg.setScale(cal.pixelWidth, cal.pixelDepth, cal.getUnits()) + seg.setTitle(title) + + return seg.getImagePlus() \ No newline at end of file From b57fbda14885af1cb88d489a9417a8aa66672a80 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 14 Mar 2024 17:21:45 +0100 Subject: [PATCH 222/678] Add method to subtract one image from another --- src/imcflibs/imagej/misc.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 6cbf29ad..6a28cd9c 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -8,6 +8,8 @@ from . import prefs from ..log import LOG as log +from ij.plugin import ImageCalculator, Concatenator +from ij.process import StackStatistics, ImageProcessor def show_status(msg): @@ -241,3 +243,24 @@ def sanitize_image_title(imp): image_title = image_title.replace("#", "Series") imp.setTitle(image_title) + + +def subtract_images(imp1, imp2): + """Subtract one image from the other (imp1 - imp2) + + Parameters + ---------- + imp1: ImagePlus + The ImagePlus that is to be subtracted from + imp2: ImagePlus + The ImagePlus that is to be subtracted + + Returns + --------- + subtract: ImagePlus + The result of the subtraction + """ + ic = ImageCalculator() + subtracted = ic.run("Subtract create", imp1, imp2) + + return subtracted \ No newline at end of file From b7ff84ce2525f2faeebe861d9e5b413768d718c8 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 14 Mar 2024 17:22:33 +0100 Subject: [PATCH 223/678] Add method to close a list of images that are open --- src/imcflibs/imagej/misc.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 6a28cd9c..ea3d6e42 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -263,4 +263,18 @@ def subtract_images(imp1, imp2): ic = ImageCalculator() subtracted = ic.run("Subtract create", imp1, imp2) - return subtracted \ No newline at end of file + return subtracted + + +def close_images(list_of_imps): + """Closes all open image plus objects given in a list + + Parameters + ---------- + list_of_imps : list + A list of open ImagePlus objects + + """ + for imp in list_of_imps: + imp.changes = False + imp.close() \ No newline at end of file From 4a63e3b6748137429dff1362acf16026f5715bd6 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 14 Mar 2024 17:24:50 +0100 Subject: [PATCH 224/678] Add method thresholding an image With options for channel, method and dark background. --- src/imcflibs/imagej/misc.py | 46 ++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index ea3d6e42..8a5be7e7 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -277,4 +277,48 @@ def close_images(list_of_imps): """ for imp in list_of_imps: imp.changes = False - imp.close() \ No newline at end of file + imp.close() + + +def get_threshold_from_method(imp, channel, method, dark=True): + """Returns the threshold value of an ImagePlus object using the chosen + IJ AutoThreshold method in desired channel. ImagePlus Object needs to be 8 or 16 bit, 32 will throw an error. + + Parameters + ---------- + imp : ImagePlus + The imp from which to get the threshold value + channel : integer + The channel in which to get the threshold + method : string + The AutoThreshold method to use + dark: bool + Sets background to be dark or not + + Returns + ------- + list + the upper and the lower threshold (integer values) + """ + imp.setC(channel) # starts at 1 + max_int_val = 2 ** imp.getBitDepth() - 1 # Get maximum intensity value depending on image bit depth + if imp.getBitDepth == 32: # Raise error if 32 bit image + raise ValueError("Image is 32-bit; thresholding values will be off.") + + ip = imp.getProcessor() + if dark: + ip.setAutoThreshold(method + " dark") + else: + ip.setAutoThreshold(method + "") + + lower_thr = ip.getMinThreshold() + upper_thr = ip.getMaxThreshold() + + if lower_thr == 0: + threshold = upper_thr + elif upper_thr == max_int_val: + threshold = lower_thr + + ip.resetThreshold() + + return threshold \ No newline at end of file From 8c7b79ec1f97cde154ce9f696cb0a2b9c47953d2 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 14 Mar 2024 17:29:37 +0100 Subject: [PATCH 225/678] Change method name for clarity --- src/imcflibs/imagej/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 8a5be7e7..55425b9f 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -280,7 +280,7 @@ def close_images(list_of_imps): imp.close() -def get_threshold_from_method(imp, channel, method, dark=True): +def get_threshold_value_from_method(imp, channel, method, dark=True): """Returns the threshold value of an ImagePlus object using the chosen IJ AutoThreshold method in desired channel. ImagePlus Object needs to be 8 or 16 bit, 32 will throw an error. From ce1edebdd7262327cfa965a47b36b295f8b4d371 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:16:21 +0200 Subject: [PATCH 226/678] Add method to create sparseLAP tracker Uses default settings to create the tracker --- src/imcflibs/imagej/trackmate.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index a58c0a3b..1be92b8c 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -215,6 +215,25 @@ def spot_filtering( return settings +def sparseLAP_tracker(settings): + """ + Create a Sparse LAP Tracker with default settings. Necessary for trackmate to run + Parameters + ---------- + settings : fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + + Returns + ------- + fiji.plugin.trackmate.Settings + Dictionary containing all the settings to use for TrackMate. + """ + + settings.trackerFactory = SparseLAPTrackerFactory() + settings.trackerSettings = settings.trackerFactory.getDefaultSettings() + + return settings + def track_filtering( settings, From 47830a1a87125c7d58f924b14b9a8abc48b1794d Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:17:31 +0200 Subject: [PATCH 227/678] Create sparseLAP tracker if it doesn't exist --- src/imcflibs/imagej/trackmate.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 1be92b8c..bab4e9d0 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -328,6 +328,10 @@ def run_trackmate( trackmate.computeSpotFeatures(True) trackmate.computeTrackFeatures(True) + if not settings.trackerFactory: + # Create a Sparse LAP Tracker if no Tracker has been created + settings = sparseLAP_tracker(settings) + ok = trackmate.checkInput() if not ok: sys.exit(str(trackmate.getErrorMessage())) From 607d0bbcc2bff31822b319625b497d6f8779cdf0 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:19:08 +0200 Subject: [PATCH 228/678] Comment sparseLAP tracker creation --- src/imcflibs/imagej/trackmate.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index bab4e9d0..8865a554 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -266,8 +266,7 @@ def track_filtering( Dictionary containing all the settings to use for TrackMate. """ - settings.trackerFactory = SparseLAPTrackerFactory() - settings.trackerSettings = LAPUtils.getDefaultSegmentSettingsMap() + # settings.trackerSettings = LAPUtils.getDefaultSegmentSettingsMap() # Not necessary anymore settings.trackerSettings["LINKING_MAX_DISTANCE"] = link_max_dist # must be double settings.trackerSettings[ "GAP_CLOSING_MAX_DISTANCE" From a89d756b2697673f551f15e6dc0a3ecd8ef374a3 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:21:13 +0200 Subject: [PATCH 229/678] Set max merging distance to method parameter --- src/imcflibs/imagej/trackmate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 8865a554..a91f858d 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -277,7 +277,7 @@ def track_filtering( settings.trackerSettings["SPLITTING_MAX_DISTANCE"] = track_splitting_max_dist if track_merging_max_distance: settings.trackerSettings["ALLOW_TRACK_MERGING"] = True - settings.trackerSettings["MERGING_MAX_DISTANCE"] = True + settings.trackerSettings["MERGING_MAX_DISTANCE"] = track_merging_max_distance return settings From a25d9b6e43fc592c548c2716b8bb443e68b3248e Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:22:03 +0200 Subject: [PATCH 230/678] Fix circularity threshold docstring for clarity --- src/imcflibs/imagej/trackmate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index a91f858d..4d7fcae3 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -175,8 +175,8 @@ def spot_filtering( Threshold to use for area filtering of the spots, by default None. If the threshold is positive, will exclude everything below the value. If the threshold is negative, will exclude everything above the value. - circularity_thresh : float, optional - Threshold to use for circularity filtering of the spots, by default None. + circularity_thresh : float, optional - + Threshold to use for circularity (needs to be between 0 and 1) filtering of the spots, by default None. If the threshold is positive, will exclude everything below the value. If the threshold is negative, will exclude everything above the value. intensity_dict_thresh : dict, optional From 8a291d78655a5e6abb32d4c571f456374703a912 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:23:44 +0200 Subject: [PATCH 231/678] Add missing value to filter spot --- src/imcflibs/imagej/trackmate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 4d7fcae3..1d8b904a 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -196,7 +196,7 @@ def spot_filtering( # Here 'true' takes everything ABOVE the mean_int value if quality_thresh: - filter_spot = FeatureFilter("QUALITY", Double(abs(quality_thresh))) + filter_spot = FeatureFilter("QUALITY", Double(abs(quality_thresh)), quality_thresh >= 0) settings.addSpotFilter(filter_spot) if area_thresh: filter_spot = FeatureFilter("AREA", Double(abs(area_thresh)), area_thresh >= 0) From 0e82d2b65d071c91ff75c07f729f26c0a43ffd11 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:24:14 +0200 Subject: [PATCH 232/678] Check if detector is not LogDetector and add comment for clarity --- src/imcflibs/imagej/trackmate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 1d8b904a..dbf2b65f 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -198,10 +198,10 @@ def spot_filtering( if quality_thresh: filter_spot = FeatureFilter("QUALITY", Double(abs(quality_thresh)), quality_thresh >= 0) settings.addSpotFilter(filter_spot) - if area_thresh: + if area_thresh and settings.detectorFactory is not LogDetectorFactory: # area_thresh and detection is not log then go into this filter_spot = FeatureFilter("AREA", Double(abs(area_thresh)), area_thresh >= 0) settings.addSpotFilter(filter_spot) - if circularity_thresh: + if circularity_thresh: # has to be between 0 and 1 filter_spot = FeatureFilter( "CIRCULARITY", Double(abs(circularity_thresh)), circularity_thresh >= 0 ) From 86abf30366010f8a59f9f6b1bc47d7544312bf49 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:26:52 +0200 Subject: [PATCH 233/678] Add comment to make clear of parameter redundancy for LoG detector --- src/imcflibs/imagej/trackmate.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index dbf2b65f..260880d4 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -172,11 +172,13 @@ def spot_filtering( If the threshold is positive, will exclude everything below the value. If the threshold is negative, will exclude everything above the value. area_thresh : float, optional - Threshold to use for area filtering of the spots, by default None. + Threshold to use for area filtering of the spots, keep None with LoG Detector - + by default also None. If the threshold is positive, will exclude everything below the value. If the threshold is negative, will exclude everything above the value. - circularity_thresh : float, optional - - Threshold to use for circularity (needs to be between 0 and 1) filtering of the spots, by default None. + circularity_thresh : float, optional + Threshold to use for circularity thresholding (needs to be between 0 and 1, keep None with LoG Detector) + - by default None. If the threshold is positive, will exclude everything below the value. If the threshold is negative, will exclude everything above the value. intensity_dict_thresh : dict, optional From 712133048b6cdd1716b315b27bdb44ce4421eaef Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 10 Jul 2024 15:27:37 +0200 Subject: [PATCH 234/678] Remove check for LogDetectorFactory, redundant --- src/imcflibs/imagej/trackmate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 260880d4..b8e9fc1a 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -200,10 +200,10 @@ def spot_filtering( if quality_thresh: filter_spot = FeatureFilter("QUALITY", Double(abs(quality_thresh)), quality_thresh >= 0) settings.addSpotFilter(filter_spot) - if area_thresh and settings.detectorFactory is not LogDetectorFactory: # area_thresh and detection is not log then go into this + if area_thresh: # Keep none for log detector filter_spot = FeatureFilter("AREA", Double(abs(area_thresh)), area_thresh >= 0) settings.addSpotFilter(filter_spot) - if circularity_thresh: # has to be between 0 and 1 + if circularity_thresh: # has to be between 0 and 1, keep none for log detector filter_spot = FeatureFilter( "CIRCULARITY", Double(abs(circularity_thresh)), circularity_thresh >= 0 ) From 689bc838dd1b9893ac95133e3dc4eafb556c9e30 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 7 Mar 2024 17:00:40 +0100 Subject: [PATCH 235/678] Add testing markdown file to test trackmate functions --- tests/imagej/test_trackmate.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/imagej/test_trackmate.md diff --git a/tests/imagej/test_trackmate.md b/tests/imagej/test_trackmate.md new file mode 100644 index 00000000..4561e790 --- /dev/null +++ b/tests/imagej/test_trackmate.md @@ -0,0 +1,27 @@ +This is a testing file for the trackmate branch and for the trackmate python class. + +The following Fiji script needs a `.jar` of the trackmate branch to be installed into Fiji already. + +You can open the a blobs image (`CTRL+SHIFT+B`) and then run the following script: + +```python +from imcflibs.imagej import trackmate +from ij import IJ + +imp = IJ.getImage() +# Detector +settings = trackmate.log_detector(imp, 5, 1, 0) +# settings = trackmate.cellpose_detector(imp, "S:\cellpose_env", "NUCLEI", 23.0, 1, 0) # WORKS, tested +# settings = trackmate.stardist_detector(imp, 1) # WORKS, tested + +# Manual tracker addition, run_trackmate does this otherwise +# settings = trackmate.sparseLAP_tracker(settings) + +# Spot and track filtering +# settings = trackmate.spot_filtering(settings, None, 1.0, None, None) +# settings = trackmate.track_filtering(settings, 15.0, 15.0, 3, 1, 1) + +res_img = trackmate.run_trackmate(imp, settings) +res_img.show() + +``` \ No newline at end of file From 51dcd21e3cf03662f9a575d16e15fb6d39f3a753 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 13:19:06 +0200 Subject: [PATCH 236/678] Add test for example 2 Example 2 is using a reference channel and a reference tile. --- tests/bdv/test_processingoptions.py | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index 0e975295..c6631297 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -69,6 +69,36 @@ def test__treat_tc_ti__ref_c1(): assert proc_opts.fmt_use_acitt() == use_acitt assert proc_opts.fmt_how_to_treat() == how_to_treat +def test__process_c1_treat_tg_ti_use_t3(): + """Test an example setting using a reference channel and a reference tile.""" + + acitt_options = ( + "process_angle=[All angles] " + "process_channel=[Single channel (Select from List)] " + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " + "process_timepoint=[All Timepoints] " + ) + + acitt_selectors = "processing_channel=[channel 1] " + how_to_treat = ( + "how_to_treat_angles=[treat individually] " + "how_to_treat_channels=group " + "how_to_treat_illuminations=group " + "how_to_treat_tiles=group " + "how_to_treat_timepoints=[treat individually] " + ) + use_acitt = ( + "channels=[Average Channels] " + "illuminations=[Average Illuminations] " + "tiles=[use Tile 3] " + ) + + proc_opts = ProcessingOptions() + proc_opts.process_channel(1) + proc_opts.treat_timepoints("[treat individually]") + proc_opts.reference_tile(3) + assert proc_opts.fmt_acitt_options() == acitt_options assert proc_opts.fmt_acitt_selectors() == acitt_selectors assert proc_opts.fmt_use_acitt() == use_acitt From 6710ad985f45a1b79d605b4d8bdf117d69c89eef Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 5 Mar 2024 16:01:19 +0100 Subject: [PATCH 237/678] Add tests for DefinitionOptions --- tests/bdv/test_definitionoptions.py | 78 +++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/bdv/test_definitionoptions.py diff --git a/tests/bdv/test_definitionoptions.py b/tests/bdv/test_definitionoptions.py new file mode 100644 index 00000000..d3674f65 --- /dev/null +++ b/tests/bdv/test_definitionoptions.py @@ -0,0 +1,78 @@ +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_tiles=[YES (one file per tile)] " + "multiple_timepoints=[NO (one time-point)] " + ) + + def_opts = DefinitionOptions() + + assert def_opts.fmt_acitt_options() == acitt_options + +def test__definition_option(): + """Test an example with wrong setting for definition option.""" + + test_value = "Multiple" + + 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" + +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_tiles=[YES (one file per tile)] " + "multiple_timepoints=[YES (one file per time-point)] " + ) + + def_opts = DefinitionOptions() + def_opts.set_timepoint_definition("multi_multi") + + 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_tiles=[YES (one file per tile)] " + "multiple_timepoints=[YES (all time-points in one file)] " + ) + + def_opts = DefinitionOptions() + def_opts.set_channel_definition("multi_multi") + def_opts.set_timepoint_definition("multi_single") + + 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""" + + acitt_options = ( + "multiple_angles=[YES (one file per angle)] " + "multiple_channels=[YES (all channels in one file)] " + "multiple_illuminations=[NO (one illumination direction)] " + "multiple_tiles=[NO (one tile)] " + "multiple_timepoints=[NO (one time-point)] " + ) + + def_opts = DefinitionOptions() + def_opts.set_angle_definition("multi_multi") + def_opts.set_tile_definition("single") + + assert def_opts.fmt_acitt_options() == acitt_options From 502009a39337bd8a7a61228c51fc2e02d14b67f3 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 5 Mar 2024 16:01:40 +0100 Subject: [PATCH 238/678] Fix typo --- tests/bdv/test_processingoptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index c6631297..56a8f83c 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -5,6 +5,7 @@ def test_defaults(): """Test the default options by calling all fomatters on a "raw" object.""" + """Test the default options by calling all formatters on a "raw" object.""" acitt_options = ( "process_angle=[All angles] " "process_channel=[All channels] " From 04b341ad917b6d567045b51aa931a7c75bc04854 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 5 Mar 2024 16:12:07 +0100 Subject: [PATCH 239/678] Fix input --- src/imcflibs/imagej/bdv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 2154b5ff..8003e086 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -196,7 +196,7 @@ def process_channel(self, value): # def channel_select(self, value): """ self._channel_processing_option = SINGLE % "channel" # channel = int(value) - 1 - self._channel_select = "processing_channel=[channel %s]" % int(value) + self._channel_select = "processing_channel=[channel %s]" % value def process_illumination(self, value): # def illumination_select(self, value): """Select a single illumination to use for processing. @@ -421,7 +421,7 @@ 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" + self._illumination_definition = SINGLE_FILE % "illumination direction" self._tile_definition = MULTI_MULTI_FILE % "tile" self._timepoint_definition = SINGLE_FILE % "time-point" @@ -661,7 +661,7 @@ def define_dataset_auto( + resave + "] " + "dataset_save_path=[" - + result_folder + + dataset_save_path + "] " + "check_stack_sizes " + angle_rotation From 391920b95b38122b88bd2420ecc1138aedaefd31 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 14:04:25 +0200 Subject: [PATCH 240/678] Add global variables for new selection modes --- src/imcflibs/imagej/bdv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 8003e086..7f749ac6 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -19,6 +19,8 @@ from ..log import LOG as log SINGLE = "[Single %s (Select from List)]" +MULTIPLE = "[Multiple %ss (Select from List)] " +RANGE = "[Range of %ss (Specify by Name)]" """@private template string""" From 45278341cef6a4a07382c4f9807bebf51b7a6111 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 14:35:44 +0200 Subject: [PATCH 241/678] Add functions to get the chosen processing mode and settings --- src/imcflibs/imagej/bdv.py | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 7f749ac6..b537086d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -537,6 +537,86 @@ def fmt_acitt_options(self): return parameter_string + " " +def check_processing_input(value, range_end): + """Sanitize and clarifies the acitt input selection. + + Check if the input is valid by checking the type and returning the expected output. + + Parameters + ---------- + value : str, int, list of int or list of str + 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 + Returns the type of selection: single, multiple or range + """ + if type(value) is not list: + 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") + 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") + elif len(value) != 1: + raise ValueError( + "Invalid input type. Expected a single number for the range start" + ) + else: + return "range" + elif len(value) == 1: + return "single" + else: + return "multiple" + + +def get_processing_settings(dimension, selection, value, range_end): + """Get the variables corresponding to the dimension selection and processing mode. + + Get the processing option and dimension selection string that corresponds + to the selected processing mode. + + Parameters + ---------- + dimension : str + "angle", "channel", "illumination", "tile" or "timepoint" + selection : str + "single", "multiple", or "range" + value : str, int, list of int or list of str + 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 + ------- + list of str + processing options string, dimension selection string + """ + + if selection == "single": + processing_option = SINGLE % dimension + dimension_select = "processing_" + dimension + "=[" + dimension + " %s]" % value + + if selection == "multiple": + processing_option = MULTIPLE % dimension + dimension_list = "" + for dimension_name in value: + dimension_list += dimension + "_%s " % dimension_name + dimension_select = dimension_list + + if selection == "range": + processing_option = RANGE % dimension + dimension_select = ( + "process_following_" + dimension + "s=%s-%s" % value, + range_end, + ) + + return processing_option, dimension_select + + def backup_xml_files(source_directory, subfolder_name): """Create a backup of BDV-XML files inside a subfolder of `xml-backup`. From 001ea40ac28e6e48fcfb833da2dec486eda5aab7 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 7 Aug 2024 15:00:08 +0200 Subject: [PATCH 242/678] Add testing file for the flatfield method Markdown file for testing the shading/flatfield method. Instructions at the top --- tests/imagej/shading-test.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/imagej/shading-test.md diff --git a/tests/imagej/shading-test.md b/tests/imagej/shading-test.md new file mode 100644 index 00000000..9371be38 --- /dev/null +++ b/tests/imagej/shading-test.md @@ -0,0 +1,18 @@ +### ---------------------- + + The following code block is a `python` script to be used in a Fiji with the shading branch's .jar already pasted into ./jars in the Fiji installation + + Recommended is to import an image you wish to test on (Shaded-blobs.png e.g) and then drag this script into Fiji and run it. + If a resulting image pops up (while using flatfield method), everything works finely. +### ---------------------- + +```python +from imcflibs.imagej import shading +# import imcflibs.imagej +import ij +from ij import IJ + +imp = IJ.getImage() +imcf_shading = shading.simple_flatfield_correction(imp) +# Or any other method in class shading +imcf_shading.show() \ No newline at end of file From eee54514c3328a422448d2ce5b0c7bf839db41e1 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 15:00:16 +0200 Subject: [PATCH 243/678] Use functions to set processing options according to user input Modifies the values of the default options to use the user input. Also add the possibility to work with ranges. --- src/imcflibs/imagej/bdv.py | 122 +++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 26 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b537086d..bfa601b2 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -179,56 +179,126 @@ def reference_timepoint(self, value): ### process-X methods - def process_angle(self, value): # def angle_select(self, value): - """Select a single angle to use for processing. + def process_angle(self, value, range_end=None): # def angle_select(self, value): + """Set the processing option for angles. + + Update the angle processing option and selection depending on input. + If the range_end is not None, it is considered as a range. Parameters ---------- - value : int or int-like + value : str, int, list of int or list of str + Contains the list of input dimensions, the first input dimension of a range or a single channel + range_end : int, optional + Contains the end of the range, by default None + """ - self._angle_processing_option = SINGLE % "angle" - self._angle_select = "processing_angle=[angle %s]" % value - def process_channel(self, value): # def channel_select(self, value): - """Select a single channel to use for processing. + selection = check_processing_input(value, range_end) + processing_option, dimension_select = get_processing_settings( + "angle", selection, value, range_end + ) + + self._angle_processing_option = processing_option + self._angle_select = dimension_select + + def process_channel( + self, value, range_end=None + ): # def channel_select(self, value): + """Set the processing option for channels. + + Update the channel processing option and selection depending on input. + If the range_end is not None, it is considered as a range. Parameters ---------- - value : int or int-like + value : str, int, list of int or list of str + Contains the list of input dimensions, the first input dimension of a range or a single channel + range_end : int, optional + Contains the end of the range, by default None + """ - self._channel_processing_option = SINGLE % "channel" - # channel = int(value) - 1 - self._channel_select = "processing_channel=[channel %s]" % value - def process_illumination(self, value): # def illumination_select(self, value): - """Select a single illumination to use for processing. + selection = check_processing_input(value, range_end) + processing_option, dimension_select = get_processing_settings( + "channel", selection, value, range_end + ) + + self._channel_processing_option = processing_option + self._channel_select = dimension_select + + def process_illumination( + self, value, range_end=None + ): # def illumination_select(self, value): + """Set the processing option for illuminations. + + Update the illumination processing option and selection depending on input. + If the range_end is not None, it is considered as a range. Parameters ---------- - value : int or int-like + value : str, int, list of int or list of str + Contains the list of input dimensions, the first input dimension of a range or a single channel + range_end : int, optional + Contains the end of the range, by default None + """ - self._illumination_processing_option = SINGLE % "illumination" - self._illumination_select = "processing_illumination=[illumination %s]" % value - def process_tile(self, value): # def tile_select(self, value): - """Select a single tile to use for processing. + selection = check_processing_input(value, range_end) + processing_option, dimension_select = get_processing_settings( + "illumination", selection, value, range_end + ) + + self._illumination_processing_option = processing_option + self._illumination_select = dimension_select + + def process_tile(self, value, range_end=None): # def tile_select(self, value): + """Set the processing option for tiles. + + Update the tile processing option and selection depending on input. + If the range_end is not None, it is considered as a range. Parameters ---------- - value : int or int-like + value : str, int, list of int or list of str + Contains the list of input dimensions, the first input dimension of a range or a single channel + range_end : int, optional + Contains the end of the range, by default None + """ - self._tile_processing_option = SINGLE % "tile" - self._tile_select = "processing_tile=[tile %s]" % value - def process_timepoint(self, value): # def timepoint_select(self, value): - """Select a single timepoint to use for processing. + selection = check_processing_input(value, range_end) + processing_option, dimension_select = get_processing_settings( + "tile", selection, value, range_end + ) + + self._tile_processing_option = processing_option + self._tile_select = dimension_select + + def process_timepoint( + self, value, range_end=None + ): # def timepoint_select(self, value): + """Set the processing option for timepoints. + + Update the timepoint processing option and selection depending on input. + If the range_end is not None, it is considered as a range. Parameters ---------- - value : int or int-like + value : str, int, list of int or list of str + Contains the list of input dimensions, the first input dimension of a range or a single channel + range_end : int, optional + Contains the end of the range, by default None + """ - self._timepoint_processing_option = SINGLE % "timepoint" - self._timepoint_select = "processing_timepoint=[timepoint %s]" % value + + selection = check_processing_input(value, range_end) + processing_option, dimension_select = get_processing_settings( + "timepoint", selection, value, range_end + ) + + self._timepoint_processing_option = processing_option + self._timepoint_select = dimension_select ### treat-X methods From df6bcbe581313f3aec6221e51c52fa16fff782b1 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Tue, 5 Mar 2024 17:02:33 +0100 Subject: [PATCH 244/678] Bugfix --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index bfa601b2..8ce0f111 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -675,7 +675,7 @@ def get_processing_settings(dimension, selection, value, range_end): dimension_list = "" for dimension_name in value: dimension_list += dimension + "_%s " % dimension_name - dimension_select = dimension_list + dimension_select = dimension_list.rstrip() if selection == "range": processing_option = RANGE % dimension From 51928d06d2aba9fa895972f4cbf50013a6088bc6 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 7 Aug 2024 15:08:34 +0200 Subject: [PATCH 245/678] Format code for readability and clarity --- src/imcflibs/imagej/shading.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py index b3e40cbd..12be6a35 100644 --- a/src/imcflibs/imagej/shading.py +++ b/src/imcflibs/imagej/shading.py @@ -184,7 +184,9 @@ def process_files(files, outpath, model_file, fmt): def simple_flatfield_correction(imp, sigma=20.0): """ - Performs a simple flatfield correction to a given ImagePlus stack and returns a 32-bit corrected flatfield image. + Performs a simple flatfield correction to a given ImagePlus stack + + The function returns a 32-bit corrected flatfield image. Parameters ---------- imp : ij.ImagePlus @@ -202,8 +204,11 @@ def simple_flatfield_correction(imp, sigma=20.0): IJ.run(flatfield, "Gaussian Blur...", sigma_str) # Apply a gaussian blur stats = StackStatistics(flatfield) IJ.run(flatfield, "32-bit", "") # Make a 32 bit version of the image - IJ.run(flatfield, "Divide...", "value=" + str(stats.max)) # Normalize 32 bit image to the highest value of original + IJ.run( + flatfield, + "Divide...", + "value=" + str(stats.max)) # Normalize 32 bit image to the highest value of original ic = ImageCalculator() flatfield_corrected = ic.run("Divide create", imp, flatfield) - return flatfield_corrected \ No newline at end of file + return flatfield_corrected From 13ef2d893106d36776136e9d6e692463179bd112 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 15:09:53 +0200 Subject: [PATCH 246/678] Add test function for example case 3 --- tests/bdv/test_processingoptions_example3.py | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/bdv/test_processingoptions_example3.py diff --git a/tests/bdv/test_processingoptions_example3.py b/tests/bdv/test_processingoptions_example3.py new file mode 100644 index 00000000..44aa95ef --- /dev/null +++ b/tests/bdv/test_processingoptions_example3.py @@ -0,0 +1,34 @@ +import pytest + +from imcflibs.imagej.bdv import ProcessingOptions + + +def test__process_c1c2_treat_tc_ti(): + """Test an example setting using 2 reference channels.""" + + acitt_options = ( + "process_angle=[All angles] " + "process_channel=[Multiple channels (Select from List)] " + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " + "process_timepoint=[All Timepoints] " + ) + + acitt_selectors = "channel_1 channel_2" + how_to_treat = ( + "how_to_treat_angles=[treat individually] " + "how_to_treat_channels=group " + "how_to_treat_illuminations=group " + "how_to_treat_tiles=compare " + "how_to_treat_timepoints=[treat individually] " + ) + use_acitt = "channels=[Average Channels] illuminations=[Average Illuminations] " + + proc_opts = ProcessingOptions() + proc_opts.process_channel([1, 2]) + proc_opts.treat_timepoints("[treat individually]") + + assert proc_opts.fmt_acitt_options() == acitt_options + assert proc_opts.fmt_acitt_selectors() == acitt_selectors + assert proc_opts.fmt_use_acitt() == use_acitt + assert proc_opts.fmt_how_to_treat() == how_to_treat From 6b327c71e6da6156aecd95e54a7c2836ba904148 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Mar 2024 09:56:04 +0100 Subject: [PATCH 247/678] Clean up unnecessary string and add missing ones --- src/imcflibs/imagej/bdv.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 8ce0f111..371c2df0 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -765,29 +765,14 @@ def define_dataset_auto( if not dataset_save_path: dataset_save_path = pathtools.join2(result_folder, project_filename) if subsampling_factors: - subsampling_factors = "subsampling_factors=" + subsampling_factors + " " + subsampling_factors = "manual_mipmap_setup subsampling_factors=" + subsampling_factors + " " else: - subsampling_factors = ( - "manual_mipmap_setup " - "subsampling_factors=[{ " - "{1,1,1}, " - "{2,2,1}, " - "{4,4,1}, " - "{8,8,2}, " - "{16,16,4} " - "}] " - ) + subsampling_factors = "" if hdf5_chunk_sizes: hdf5_chunk_sizes = "hdf5_chunk_sizes=" + hdf5_chunk_sizes + " " else: hdf5_chunk_sizes = ( - "hdf5_chunk_sizes=[{ " - "{32,32,4}, " - "{32,16,8}, " - "{16,16,16}, " - "{32,16,8}, " - "{32,32,4} " - "}] " + "" ) if bf_series_type == "Angles": From 77364bcd4acf7ee89ce76471743bc7ff5aeb46d1 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Mar 2024 09:56:44 +0100 Subject: [PATCH 248/678] Merge remote-tracking branch 'origin/processing-options-class' into processing-options-class --- TESTING.md | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TESTING.md b/TESTING.md index 4aa7a254..3a663c05 100644 --- a/TESTING.md +++ b/TESTING.md @@ -22,10 +22,10 @@ test -d "venv" || python3 -m venv venv source venv/bin/activate # install dependencies / requirements: -MOCKS_REL="0.1.1" +MOCKS_REL="0.2.0" URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download/v$MOCKS_REL" pip install --upgrade \ - $URL_PFX/imcf_fiji_mocks-0.1.1-py2.py3-none-any.whl \ + $URL_PFX/imcf_fiji_mocks-${MOCKS_REL}-py2.py3-none-any.whl \ $URL_PFX/micrometa-15.2.2-py2.py3-none-any.whl \ $URL_PFX/sjlogging-0.5.2-py2.py3-none-any.whl \ olefile \ diff --git a/pyproject.toml b/pyproject.toml index 79bee630..d37ac7cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ readme = "README.md" version = "1.5.0.a0" [tool.poetry.dependencies] -imcf-fiji-mocks = "^0.1.1" +imcf-fiji-mocks = "^0.2.0" micrometa = "^15.2.2" python = ">=2.7" sjlogging = ">=0.5.2" From 5895bbad2d09edf6d2ef22e3a41a46df954db205 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Mar 2024 10:34:52 +0100 Subject: [PATCH 249/678] Change default settings for tiles and timepoints --- 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 371c2df0..9907cb92 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -89,8 +89,8 @@ def __init__(self): self._treat_angles = "[treat individually]" self._treat_channels = "group" self._treat_illuminations = "group" - self._treat_tiles = "group" - self._treat_timepoints = "group" + self._treat_tiles = "compare" + self._treat_timepoints = "[treat individually]" ### reference-X methods From a638f012e73d7ae52c11b57da7c9147189757380 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Mar 2024 10:35:16 +0100 Subject: [PATCH 250/678] Adapt tests to the new default settings --- tests/bdv/test_processingoptions.py | 10 ++++------ tests/bdv/test_processingoptions_example3.py | 4 +--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index 56a8f83c..d29906bb 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -1,4 +1,3 @@ -import pytest from imcflibs.imagej.bdv import ProcessingOptions @@ -18,14 +17,12 @@ def test_defaults(): "how_to_treat_angles=[treat individually] " "how_to_treat_channels=group " "how_to_treat_illuminations=group " - "how_to_treat_tiles=group " - "how_to_treat_timepoints=group " + "how_to_treat_tiles=compare " + "how_to_treat_timepoints=[treat individually] " ) use_acitt = ( "channels=[Average Channels] " "illuminations=[Average Illuminations] " - "tiles=[Average Tiles] " - "timepoints=[Average Timepoints] " ) proc_opts = ProcessingOptions() @@ -97,7 +94,8 @@ def test__process_c1_treat_tg_ti_use_t3(): proc_opts = ProcessingOptions() proc_opts.process_channel(1) - proc_opts.treat_timepoints("[treat individually]") + # proc_opts.treat_timepoints("[treat individually]") + proc_opts.treat_tiles("group") proc_opts.reference_tile(3) assert proc_opts.fmt_acitt_options() == acitt_options diff --git a/tests/bdv/test_processingoptions_example3.py b/tests/bdv/test_processingoptions_example3.py index 44aa95ef..124572b8 100644 --- a/tests/bdv/test_processingoptions_example3.py +++ b/tests/bdv/test_processingoptions_example3.py @@ -1,4 +1,3 @@ -import pytest from imcflibs.imagej.bdv import ProcessingOptions @@ -14,7 +13,7 @@ def test__process_c1c2_treat_tc_ti(): "process_timepoint=[All Timepoints] " ) - acitt_selectors = "channel_1 channel_2" + acitt_selectors = "channel_1 channel_2 " how_to_treat = ( "how_to_treat_angles=[treat individually] " "how_to_treat_channels=group " @@ -26,7 +25,6 @@ def test__process_c1c2_treat_tc_ti(): proc_opts = ProcessingOptions() proc_opts.process_channel([1, 2]) - proc_opts.treat_timepoints("[treat individually]") assert proc_opts.fmt_acitt_options() == acitt_options assert proc_opts.fmt_acitt_selectors() == acitt_selectors From 91792c96be3ef1b85365db0406771bccf66c243c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 15:14:23 +0200 Subject: [PATCH 251/678] Add test for the auto define dataset method for tiles and angles This uses caplog and requires the new MOCK done by @ehrenfeu --- tests/bdv/test_define_dataset_auto.py | 163 ++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 tests/bdv/test_define_dataset_auto.py diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py new file mode 100644 index 00000000..e3a67f47 --- /dev/null +++ b/tests/bdv/test_define_dataset_auto.py @@ -0,0 +1,163 @@ +import logging + +from imcflibs import pathtools +from imcflibs.imagej import bdv + +def set_default_values(project_filename, file_path): + """Set the default values for dataset definitions. + + Parameters + ---------- + project_filename : str + Name of the project + file_path : pathlib.Path + Path to a temporary folder + + Returns + ---------- + str + Start of the options for dataset definitions. + """ + # Additional settings + file_info = pathtools.parse_path(file_path) + + options = ( + "define_dataset=[Automatic Loader (Bioformats based)] " + + "project_filename=[" + + project_filename + + ".xml" + + "] " + + "path=[" + + file_info["path"] + + "] " + + "exclude=10 " + + "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. + + Parameters + ---------- + tmp_path : pytest.fixture + Temporary path for the test. + caplog : pytest.fixture + Log capturing fixture. + """ + + # Set the logging level to capture warnings + caplog.set_level(logging.WARNING) + # Clear the log + caplog.clear() + + # Define the project and file names + project_filename = "proj_name" + file_path = tmp_path + file_info = pathtools.parse_path(file_path) + + # Define the result and dataset save paths + result_folder = pathtools.join2(file_info["path"], project_filename) + dataset_save_path = pathtools.join2(result_folder, project_filename) + + # Default settings + + # Define the type of Bio-Formats series + bf_series_type = "Tiles" + + # Define the ImageJ command + cmd = "Define 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=[" + + "Re-save as multiresolution HDF5" + + "] " + + "dataset_save_path=[" + + dataset_save_path + + "] " + + "check_stack_sizes " + + "split_hdf5 " + + "timepoints_per_partition=1 " + + "setups_per_partition=0 " + + "use_deflate_compression " + ) + + # Construct the final call to ImageJ + 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) + # 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. + + Parameters + ---------- + tmp_path : pytest.fixture + Temporary path for the test. + caplog : pytest.fixture + Log capturing fixture. + """ + + # Set the logging level to capture warnings + caplog.set_level(logging.WARNING) + # Clear the log + caplog.clear() + + # Define the project and file names + project_filename = "proj_name" + file_path = tmp_path + file_info = pathtools.parse_path(file_path) + + # Define the result and dataset save paths + result_folder = pathtools.join2(file_info["path"], project_filename) + dataset_save_path = pathtools.join2(result_folder, project_filename) + + # Default settings + + # Define the type of Bio-Formats series + bf_series_type = "Angles" + + # Define the ImageJ command + 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=[" + + "Re-save as multiresolution HDF5" + + "] " + + "dataset_save_path=[" + + dataset_save_path + + "] " + + "check_stack_sizes " + + "apply_angle_rotation " + + "split_hdf5 " + + "timepoints_per_partition=1 " + + "setups_per_partition=0 " + + "use_deflate_compression " + ) + + # Construct the final call to ImageJ + 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) + # Check if the final call is in the log + assert final_call == caplog.messages[0] From e8a396091ca2bc677e46d843e476562495ba6d1f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 15:22:15 +0200 Subject: [PATCH 252/678] Fix spelling --- tests/bdv/test_processingoptions.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/bdv/test_processingoptions.py b/tests/bdv/test_processingoptions.py index d29906bb..57593241 100644 --- a/tests/bdv/test_processingoptions.py +++ b/tests/bdv/test_processingoptions.py @@ -1,9 +1,7 @@ - from imcflibs.imagej.bdv import ProcessingOptions def test_defaults(): - """Test the default options by calling all fomatters on a "raw" object.""" """Test the default options by calling all formatters on a "raw" object.""" acitt_options = ( "process_angle=[All angles] " @@ -20,10 +18,7 @@ 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() @@ -52,10 +47,7 @@ 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") @@ -67,6 +59,7 @@ def test__treat_tc_ti__ref_c1(): assert proc_opts.fmt_use_acitt() == use_acitt assert proc_opts.fmt_how_to_treat() == how_to_treat + def test__process_c1_treat_tg_ti_use_t3(): """Test an example setting using a reference channel and a reference tile.""" From 5bab3e52f10d686791aeea0936cc0d7eab152794 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Mar 2024 13:42:47 +0100 Subject: [PATCH 253/678] Fix docstring default settings --- src/imcflibs/imagej/bdv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9907cb92..8dadc298 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -9,9 +9,9 @@ # The attribute count is not really our choice: # pylint: disable-msg=too-many-instance-attributes -import sys import os import shutil +import sys from ij import IJ @@ -350,7 +350,7 @@ def treat_illuminations(self, value): def treat_tiles(self, value): """Set the value for the `how_to_treat_tiles` option. - The default setting is `group`. + The default setting is `compare`. Parameters ---------- @@ -363,7 +363,7 @@ def treat_tiles(self, value): def treat_timepoints(self, value): """Set the value for the `how_to_treat_timepoints` option. - The default setting is `group`. + The default setting is `[treat individually]`. Parameters ---------- From 7cf746dcc941c91653e992e8b874df2a8acba519 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Mar 2024 13:43:05 +0100 Subject: [PATCH 254/678] Auto format --- src/imcflibs/imagej/bdv.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 8dadc298..aed9d37b 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -765,15 +765,15 @@ def define_dataset_auto( if not dataset_save_path: dataset_save_path = pathtools.join2(result_folder, project_filename) if subsampling_factors: - subsampling_factors = "manual_mipmap_setup subsampling_factors=" + subsampling_factors + " " + subsampling_factors = ( + "manual_mipmap_setup subsampling_factors=" + subsampling_factors + " " + ) else: subsampling_factors = "" if hdf5_chunk_sizes: hdf5_chunk_sizes = "hdf5_chunk_sizes=" + hdf5_chunk_sizes + " " else: - hdf5_chunk_sizes = ( - "" - ) + hdf5_chunk_sizes = "" if bf_series_type == "Angles": angle_rotation = "apply_angle_rotation " From 59d9e027dcbdc99b79b43e102866960c3b883fb8 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Mar 2024 14:04:03 +0100 Subject: [PATCH 255/678] Add inputs needed for BDV scripts --- src/imcflibs/imagej/bioformats.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index ca9f5f2b..fcee7459 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -14,8 +14,7 @@ from ij import IJ -from ._loci import ImporterOptions -from ._loci import BF, ImageReader, Memoizer +from ._loci import BF, ImageReader, ImporterOptions, DynamicMetadataOptions, Memoizer, ZeissCZIReader from ..pathtools import gen_name_from_orig from ..log import LOG as log From 8a44f41a5eb9db0ef71f83988fa194b0c5adafd1 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 15:24:44 +0200 Subject: [PATCH 256/678] Add test function for example case 4 --- tests/bdv/test_processingoptions_example4.py | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tests/bdv/test_processingoptions_example4.py diff --git a/tests/bdv/test_processingoptions_example4.py b/tests/bdv/test_processingoptions_example4.py new file mode 100644 index 00000000..0eab6db8 --- /dev/null +++ b/tests/bdv/test_processingoptions_example4.py @@ -0,0 +1,30 @@ +from imcflibs.imagej.bdv import ProcessingOptions + + +def test__process_c1c3(): + """Test an example setting to process only 2 channels.""" + + acitt_options = ( + "process_angle=[All angles] " + "process_channel=[Range of channels (Specify by Name)] " + "process_illumination=[All illuminations] " + "process_tile=[All tiles] " + "process_timepoint=[All Timepoints] " + ) + acitt_selectors = "process_following_channels=1-3 " + how_to_treat = ( + "how_to_treat_angles=[treat individually] " + "how_to_treat_channels=group " + "how_to_treat_illuminations=group " + "how_to_treat_tiles=compare " + "how_to_treat_timepoints=[treat individually] " + ) + use_acitt = "channels=[Average Channels] illuminations=[Average Illuminations] " + + proc_opts = ProcessingOptions() + proc_opts.process_channel(1, 3) + + assert proc_opts.fmt_acitt_options() == acitt_options + assert proc_opts.fmt_acitt_selectors() == acitt_selectors + assert proc_opts.fmt_use_acitt() == use_acitt + assert proc_opts.fmt_how_to_treat() == how_to_treat From c4084c25622a20f1a7f30790fcb53c3cb827a193 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 15:25:28 +0200 Subject: [PATCH 257/678] Ruff formatting --- src/imcflibs/imagej/bdv.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index aed9d37b..30305587 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -680,8 +680,13 @@ def get_processing_settings(dimension, selection, value, range_end): if selection == "range": processing_option = RANGE % dimension dimension_select = ( - "process_following_" + dimension + "s=%s-%s" % value, - range_end, + "process_following_" + + dimension + + "s=%s-%s" + % ( + value, + range_end, + ) ) return processing_option, dimension_select From 70e3a75b087fe7ad5b668209a29e37ace85c0a7e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 15:25:51 +0200 Subject: [PATCH 258/678] Fix some weird (autocorrect ?) issues --- 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 5326f298..1f1a9b65 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -90,7 +90,7 @@ def relate_label_images(label_image_ref, label_image_to_relate): Prefs.blackBackground = True IJ.run(imp_dup, "Convert to Mask", "") IJ.run(imp_dup, "Divide...", "value=255") - return ImageCalculator.run(label_image_ref, imp_dup, "Multimage_processorly create") + return ImageCalculator.run(label_image_ref, imp_dup, "Multiply create") def filter_objects(label_image, table, string, min_val, max_val): From 318d628e6854d8faa10e0f8db64f585c713eff43 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 15:29:08 +0200 Subject: [PATCH 259/678] Add explanation to the method --- src/imcflibs/imagej/labelimage.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 5326f298..f0960804 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -70,7 +70,9 @@ def relate_label_images(label_image_ref, label_image_to_relate): โ— NOTE: Won't work with touching labels โ— - FIXME: explain with an example what the function is doing! + Given two label images, this function will create a new label image + with the same labels as the reference image, but with the objects + of the second image. Parameters ---------- From 0fe5a413a63516df72094b1beefd71291ffa42e8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 15:37:06 +0200 Subject: [PATCH 260/678] Minor formatting and line length adjustments --- src/imcflibs/imagej/objects3d.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 41aca296..fbd01b1a 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -57,25 +57,25 @@ def imgplus_to_population3d(imp): def segment_3D_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): - """Segment a 3D binary image to get a labelled stack + """Segment a 3D binary image to get a labelled stack. Parameters ---------- imp : ImagePlus - Binary 3D stack + Binary 3D stack. title : str, optional - Title of the new image + Title of the new image. min_thresh : int, optional - Threshold to do segmentation, also allows for label filtering by default 1 + Threshold to do segmentation, also allows for label filtering, by default 1. min_vol : int, optional - Volume (voxels) under which to filter objects, by default None + Volume (voxels) under which to filter objects, by default None. max_vol : int, optional - Volume above which to filter objects, by default None + Volume above which to filter objects, by default None. Returns ------- ImagePlus - Segmented 3D labelled ImagePlus + Segmented 3D labelled ImagePlus. """ cal = imp.getCalibration() img = ImageHandler.wrap(imp) @@ -85,9 +85,9 @@ def segment_3D_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): if min_vol: labeler.setMinSizeCalibrated(min_vol, img) if max_vol: - labeler.setMaxSizeCalibrated(max_vol, img) # Issue with typo an - # TODO: this method might not work on older Fiji deployments, needs testing on newer versions - # (deprecated .setMaxsize, it's .setMaxSizeCalibrated) + labeler.setMaxSizeCalibrated(max_vol, img) # Issue with typo an + # TODO: this method might not work on older Fiji deployments, needs testing + # on newer versions (deprecated .setMaxsize, it's .setMaxSizeCalibrated) # TODO Keep it in mind for next Fiji deployment # labeler.setMaxsize(max_vol) @@ -95,4 +95,4 @@ def segment_3D_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): seg.setScale(cal.pixelWidth, cal.pixelDepth, cal.getUnits()) seg.setTitle(title) - return seg.getImagePlus() \ No newline at end of file + return seg.getImagePlus() From 70b9a82c3f7ec62fb1327f40e8a2d98638579953 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 15:37:16 +0200 Subject: [PATCH 261/678] Function names should be all lowercase --- src/imcflibs/imagej/objects3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index fbd01b1a..3c3eea65 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -56,7 +56,7 @@ def imgplus_to_population3d(imp): return Objects3DPopulation(img) -def segment_3D_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): +def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): """Segment a 3D binary image to get a labelled stack. Parameters From dedb00606007d0b59e39eddb4535d57d16dc3595 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 15:51:35 +0200 Subject: [PATCH 262/678] Black formatting --- src/imcflibs/imagej/trackmate.py | 75 +++++++++++++++++--------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index b8e9fc1a..d8aea195 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -18,14 +18,14 @@ def cellpose_detector( - imageplus, - cellpose_env_path, - model_to_use, - obj_diameter, - target_channel, - optional_channel=0, - use_gpu=True, - simplify_contours=True, + imageplus, + cellpose_env_path, + model_to_use, + obj_diameter, + target_channel, + optional_channel=0, + use_gpu=True, + simplify_contours=True, ): """Create a dictionary with all settings for TrackMate using Cellpose. @@ -72,7 +72,7 @@ def cellpose_detector( input_to_model = { "nuclei": PretrainedModel.NUCLEI, "cyto": PretrainedModel.CYTO, - "cyto2": PretrainedModel.CYTO2 + "cyto2": PretrainedModel.CYTO2, } if model_to_use.lower() in input_to_model: selected_model = input_to_model[model_to_use.lower()] @@ -112,12 +112,12 @@ def stardist_detector(imageplus, target_chnl): def log_detector( - imageplus, - radius, - target_channel, - quality_threshold=0.0, - median_filtering=True, - subpix_localization=True, + imageplus, + radius, + target_channel, + quality_threshold=0.0, + median_filtering=True, + subpix_localization=True, ): """Create a dictionary with all settings for TrackMate using the LogDetector. @@ -155,11 +155,11 @@ def log_detector( def spot_filtering( - settings, - quality_thresh=None, - area_thresh=None, - circularity_thresh=None, - intensity_dict_thresh=None, + settings, + quality_thresh=None, + area_thresh=None, + circularity_thresh=None, + intensity_dict_thresh=None, ): """Add spot filtering for different features to the settings dictionary. @@ -198,12 +198,16 @@ def spot_filtering( # Here 'true' takes everything ABOVE the mean_int value if quality_thresh: - filter_spot = FeatureFilter("QUALITY", Double(abs(quality_thresh)), quality_thresh >= 0) + filter_spot = FeatureFilter( + "QUALITY", + Double(abs(quality_thresh)), + quality_thresh >= 0, + ) settings.addSpotFilter(filter_spot) - if area_thresh: # Keep none for log detector + if area_thresh: # Keep none for log detector filter_spot = FeatureFilter("AREA", Double(abs(area_thresh)), area_thresh >= 0) settings.addSpotFilter(filter_spot) - if circularity_thresh: # has to be between 0 and 1, keep none for log detector + if circularity_thresh: # has to be between 0 and 1, keep none for log detector filter_spot = FeatureFilter( "CIRCULARITY", Double(abs(circularity_thresh)), circularity_thresh >= 0 ) @@ -217,6 +221,7 @@ def spot_filtering( return settings + def sparseLAP_tracker(settings): """ Create a Sparse LAP Tracker with default settings. Necessary for trackmate to run @@ -238,12 +243,12 @@ def sparseLAP_tracker(settings): def track_filtering( - settings, - link_max_dist=15.0, - gap_closing_dist=15.0, - max_frame_gap=3, - track_splitting_max_dist=None, - track_merging_max_distance=None, + settings, + link_max_dist=15.0, + gap_closing_dist=15.0, + max_frame_gap=3, + track_splitting_max_dist=None, + track_merging_max_distance=None, ): """Add track filtering for different features to the settings dictionary. @@ -285,9 +290,9 @@ def track_filtering( def run_trackmate( - implus, - settings, - crop_roi=None, + implus, + settings, + crop_roi=None, ): # sourcery skip: merge-else-if-into-elif, swap-if-else-branches """Function to run TrackMate on open data. Has some specific input @@ -323,7 +328,7 @@ def run_trackmate( # Configure tracker # settings.addTrackAnalyzer(TrackDurationAnalyzer()) - settings.initialSpotFilterValue = -1. + settings.initialSpotFilterValue = -1.0 trackmate = TrackMate(model, settings) trackmate.computeSpotFeatures(True) @@ -341,7 +346,7 @@ def run_trackmate( ok = trackmate.process() if not ok: if "[SparseLAPTracker] The spot collection is empty." in str( - trackmate.getErrorMessage() + trackmate.getErrorMessage() ): new_imp = IJ.createImage( "Untitled", @@ -371,4 +376,4 @@ def run_trackmate( label_imp.setDimensions(dims[2], dims[3], dims[4]) implus.setDimensions(dims[2], dims[3], dims[4]) - return label_imp \ No newline at end of file + return label_imp From 362f8e1d27fdc471101c70b24869ce684a7ba48e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 15:52:08 +0200 Subject: [PATCH 263/678] Move example from comment to the docstring --- src/imcflibs/imagej/trackmate.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index d8aea195..80905a3d 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -53,9 +53,18 @@ def cellpose_detector( ------- fiji.plugin.trackmate.Settings Dictionary containing all the settings to use for TrackMate. - """ - # Example Usage: settings = cellpose_detector(imp, "S:\cellpose_env", "NUCLEI", 23.0, 1, 0) + Example + ------- + >>> settings = cellpose_detector( + ... imageplus=imp, + ... cellpose_env_path="D:/CondaEnvs/cellpose", + ... model_to_use="NUCLEI", + ... obj_diameter=23.0, + ... target_channel=1, + ... optional_channel=0 + ... ) + """ settings = Settings(imageplus) settings.detectorFactory = CellposeDetectorFactory() From 17654044bfc95457a8cde4207c4ab5915962b0ae Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 15:56:15 +0200 Subject: [PATCH 264/678] Move multi-line comment above statement --- src/imcflibs/imagej/trackmate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 80905a3d..e5d93c59 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -69,8 +69,8 @@ def cellpose_detector( settings.detectorFactory = CellposeDetectorFactory() settings.detectorSettings["TARGET_CHANNEL"] = target_channel - settings.detectorSettings["OPTIONAL_CHANNEL_2"] = optional_channel # Set optional channel to 0, will be - # overwritten if needed + # set optional channel to 0, will be overwritten if needed: + settings.detectorSettings["OPTIONAL_CHANNEL_2"] = optional_channel settings.detectorSettings["CELLPOSE_PYTHON_FILEPATH"] = pathtools.join2( cellpose_env_path, "python.exe" From a3448ff09f4b66a84f05ca910afd72743f8596c9 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 7 Aug 2024 15:56:34 +0200 Subject: [PATCH 265/678] Correct docstring to conform to convention Add period to docstring, change from double to float and remove redundant "the". --- src/imcflibs/imagej/shading.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py index 12be6a35..71470470 100644 --- a/src/imcflibs/imagej/shading.py +++ b/src/imcflibs/imagej/shading.py @@ -184,19 +184,19 @@ def process_files(files, outpath, model_file, fmt): def simple_flatfield_correction(imp, sigma=20.0): """ - Performs a simple flatfield correction to a given ImagePlus stack + Performs a simple flatfield correction to a given ImagePlus stack. The function returns a 32-bit corrected flatfield image. Parameters ---------- imp : ij.ImagePlus The input stack to be projected. - sigma: double, default 20.0 - The sigma value for the Gaussian blur, default = 20.0 + sigma: float, optional + The sigma value for the Gaussian blur, default=20.0 Returns ------- ij.ImagePlus - The 32-bit image result of the flatfield correction + The 32-bit image result of flatfield correction """ flatfield = imp.duplicate() From d2c45907598c4ccd55e1c99ec764d72e5827d3fd Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 16:08:51 +0200 Subject: [PATCH 266/678] Move in-line comments above statements --- src/imcflibs/imagej/trackmate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index e5d93c59..2e8d612e 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -283,10 +283,10 @@ def track_filtering( """ # settings.trackerSettings = LAPUtils.getDefaultSegmentSettingsMap() # Not necessary anymore - settings.trackerSettings["LINKING_MAX_DISTANCE"] = link_max_dist # must be double - settings.trackerSettings[ - "GAP_CLOSING_MAX_DISTANCE" - ] = gap_closing_dist # must be double + + # NOTE: `link_max_dist` and `gap_closing_dist` must be double! + settings.trackerSettings["LINKING_MAX_DISTANCE"] = link_max_dist + settings.trackerSettings["GAP_CLOSING_MAX_DISTANCE"] = gap_closing_dist settings.trackerSettings["MAX_FRAME_GAP"] = max_frame_gap if track_splitting_max_dist: settings.trackerSettings["ALLOW_TRACK_SPLITTING"] = True From 862bbefc8874302e5e1f0da4ba8a6f940665d5d7 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 7 Aug 2024 16:10:00 +0200 Subject: [PATCH 267/678] Remove redundant comments --- src/imcflibs/imagej/shading.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py index 71470470..cd71d929 100644 --- a/src/imcflibs/imagej/shading.py +++ b/src/imcflibs/imagej/shading.py @@ -201,13 +201,16 @@ def simple_flatfield_correction(imp, sigma=20.0): """ flatfield = imp.duplicate() sigma_str = "sigma=" + str(sigma) - IJ.run(flatfield, "Gaussian Blur...", sigma_str) # Apply a gaussian blur + + IJ.run(flatfield, "Gaussian Blur...", sigma_str) stats = StackStatistics(flatfield) - IJ.run(flatfield, "32-bit", "") # Make a 32 bit version of the image + + # Normalize image to the highest value of original (requires 32-bit image) + IJ.run(flatfield, "32-bit", "") IJ.run( flatfield, "Divide...", - "value=" + str(stats.max)) # Normalize 32 bit image to the highest value of original + "value=" + str(stats.max)) ic = ImageCalculator() flatfield_corrected = ic.run("Divide create", imp, flatfield) From 99cad639e37794fc51a27cf7924e1af2bd3d1126 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 16:10:58 +0200 Subject: [PATCH 268/678] Remove unused code --- src/imcflibs/imagej/trackmate.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 2e8d612e..d8ba0567 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -281,9 +281,6 @@ def track_filtering( fiji.plugin.trackmate.Settings Dictionary containing all the settings to use for TrackMate. """ - - # settings.trackerSettings = LAPUtils.getDefaultSegmentSettingsMap() # Not necessary anymore - # NOTE: `link_max_dist` and `gap_closing_dist` must be double! settings.trackerSettings["LINKING_MAX_DISTANCE"] = link_max_dist settings.trackerSettings["GAP_CLOSING_MAX_DISTANCE"] = gap_closing_dist From fcb70cf9a6b9b2ac61348da2363a8b601da3844b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 16:11:20 +0200 Subject: [PATCH 269/678] Remove unreachable "return" statements --- src/imcflibs/imagej/trackmate.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index d8ba0567..fc9b4dfd 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -347,7 +347,6 @@ def run_trackmate( ok = trackmate.checkInput() if not ok: sys.exit(str(trackmate.getErrorMessage())) - return ok = trackmate.process() if not ok: @@ -368,8 +367,6 @@ def run_trackmate( else: sys.exit(str(trackmate.getErrorMessage())) - return - SelectionModel(model) exportSpotsAsDots = False From e8aa40b2784565baaef16051cad49488349dfc7d Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 16:13:10 +0200 Subject: [PATCH 270/678] Improve docstring --- src/imcflibs/imagej/objects3d.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 3c3eea65..e93bd90d 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -61,12 +61,14 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): Parameters ---------- - imp : ImagePlus + imp : ij.ImagePlus Binary 3D stack. title : str, optional Title of the new image. min_thresh : int, optional Threshold to do segmentation, also allows for label filtering, by default 1. + Since the segmentation is happening on a binary stack, values are either 0 or 255 + so using 0 allows to discard only the background. min_vol : int, optional Volume (voxels) under which to filter objects, by default None. max_vol : int, optional @@ -74,7 +76,7 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): Returns ------- - ImagePlus + ij.ImagePlus Segmented 3D labelled ImagePlus. """ cal = imp.getCalibration() From 5994ef35a0346a03accf82f7d732b4ccc2d1462c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 16:13:30 +0200 Subject: [PATCH 271/678] Remove comments as fixed --- src/imcflibs/imagej/objects3d.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index e93bd90d..e7ac1c41 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -87,11 +87,7 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): if min_vol: labeler.setMinSizeCalibrated(min_vol, img) if max_vol: - labeler.setMaxSizeCalibrated(max_vol, img) # Issue with typo an - # TODO: this method might not work on older Fiji deployments, needs testing - # on newer versions (deprecated .setMaxsize, it's .setMaxSizeCalibrated) - # TODO Keep it in mind for next Fiji deployment - # labeler.setMaxsize(max_vol) + labeler.setMaxSizeCalibrated(max_vol, img) seg = labeler.getLabels(img) seg.setScale(cal.pixelWidth, cal.pixelDepth, cal.getUnits()) From 0be62b7199fb3cb4479630d73eb79581c0e45dd5 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 7 Aug 2024 16:13:46 +0200 Subject: [PATCH 272/678] Only sets the title if the input is not None --- src/imcflibs/imagej/objects3d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index e7ac1c41..ff4571b7 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -91,6 +91,7 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): seg = labeler.getLabels(img) seg.setScale(cal.pixelWidth, cal.pixelDepth, cal.getUnits()) - seg.setTitle(title) + if title: + seg.setTitle(title) return seg.getImagePlus() From 8e550dbdee0114765003a351b82c5ca350ccedea Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 16:14:07 +0200 Subject: [PATCH 273/678] Function names should be all lowercase --- src/imcflibs/imagej/trackmate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index fc9b4dfd..d7948be0 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -231,7 +231,7 @@ def spot_filtering( return settings -def sparseLAP_tracker(settings): +def sparse_lap_tracker(settings): """ Create a Sparse LAP Tracker with default settings. Necessary for trackmate to run Parameters From b3ae2000fb35675a86fe6d7d886a91babae43e3f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 16:27:26 +0200 Subject: [PATCH 274/678] Docstring formatting --- src/imcflibs/imagej/trackmate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index d7948be0..5595dfb3 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -232,8 +232,8 @@ def spot_filtering( def sparse_lap_tracker(settings): - """ - Create a Sparse LAP Tracker with default settings. Necessary for trackmate to run + """Create a sparse LAP tracker with default settings. + Parameters ---------- settings : fiji.plugin.trackmate.Settings From 173e9e143a83594236d3318e79400671e3a84b51 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 7 Aug 2024 16:27:36 +0200 Subject: [PATCH 275/678] Docstring formatting --- src/imcflibs/imagej/trackmate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 5595dfb3..4ca6264c 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -301,16 +301,16 @@ def run_trackmate( crop_roi=None, ): # sourcery skip: merge-else-if-into-elif, swap-if-else-branches - """Function to run TrackMate on open data. Has some specific input + """Function to run TrackMate on already opened data. Parameters ---------- implus : ij.ImagePlus - ImagePlus image on which to run Trackmate + ImagePlus image on which to run Trackmate. settings : fiji.plugin.trackmate.Settings - Settings to use for TrackMate, see detector methods for different settings + Settings to use for TrackMate, see detector methods for different settings. crop_roi : ij.gui.Roi, optional - ROI to crop on the image, by default None + ROI to crop on the image, by default None. Returns ------- From 98392d4bc36cf6188a74f3729c2bee2b82df281e Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Fri, 9 Aug 2024 10:37:25 +0200 Subject: [PATCH 276/678] Adjust docstring formatting Removed one blank line and added one before each section heading --- src/imcflibs/imagej/shading.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py index cd71d929..c7ebb698 100644 --- a/src/imcflibs/imagej/shading.py +++ b/src/imcflibs/imagej/shading.py @@ -183,16 +183,17 @@ def process_files(files, outpath, model_file, fmt): model.close() def simple_flatfield_correction(imp, sigma=20.0): - """ - Performs a simple flatfield correction to a given ImagePlus stack. + """Performs a simple flatfield correction to a given ImagePlus stack. The function returns a 32-bit corrected flatfield image. + Parameters ---------- imp : ij.ImagePlus The input stack to be projected. sigma: float, optional The sigma value for the Gaussian blur, default=20.0 + Returns ------- ij.ImagePlus From b29a44cb58ab02a38c92d8d597f85411b4d7953c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 9 Oct 2024 16:36:07 +0200 Subject: [PATCH 277/678] Add missing import --- src/imcflibs/imagej/_loci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index b675e95e..4795a870 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -28,4 +28,4 @@ from loci.plugins.in import ImporterOptions # pdoc: skip from loci.formats.in import ZeissCZIReader, DefaultMetadataOptions, MetadataLevel, DynamicMetadataOptions, MetadataOptions # pdoc: skip -from loci.formats import ImageReader, Memoizer +from loci.formats import ImageReader, Memoizer, MetadataTools From b741b27a04011c3e8cf4651e288f7002b55c1b65 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 22 Oct 2024 11:27:29 +0200 Subject: [PATCH 278/678] Add method to dilate labels in 2D in a stack --- src/imcflibs/imagej/labelimage.py | 51 ++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index f0960804..d607b28c 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -1,6 +1,6 @@ """Functions to work with ImageJ label images.""" -from ij import IJ, ImagePlus, Prefs +from ij import IJ, ImagePlus, Prefs, ImageStack from ij.plugin import Duplicator, ImageCalculator from ij.plugin.filter import ImageProcessor, ThresholdToSelection from ij.process import FloatProcessor, ImageProcessor @@ -182,3 +182,52 @@ def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): seg.setTitle(title) return seg.getImagePlus() + + +def dilate_labels_2d(imp, dilation_radius): + """ + Dilate each label in the given ImagePlus using the specified dilation radius. + + Parameters + ---------- + imp : ij.ImagePlus + Input ImagePlus with the labels to dilate + dilation_radius : int + Number of pixels to dilate each label + + Returns + ------- + ij.ImagePlus + New ImagePlus with the dilated labels + """ + + # Create a list of the dilated labels + dilated_labels_list = [] + + # Iterate over each slice of the input ImagePlus + for i in range(1, imp.getNSlices() + 1): + # Duplicate the current slice + 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() + + # Append the dilated labels to the list + dilated_labels_list.append(dilated_labels_imp) + + # Create a new ImagePlus with the dilated labels + dilated_labels_imp = ImagePlus( + "Dilated labels", ImageStack().create(dilated_labels_list) + ) + + return dilated_labels_imp From f51ae6ccb840a2659029c62ab304da1566ad2d7d Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 22 Oct 2024 11:42:22 +0200 Subject: [PATCH 279/678] Update method with more comments --- src/imcflibs/imagej/labelimage.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index d607b28c..35d9f341 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -144,19 +144,19 @@ def measure_objects_size_shape_2d(label_image): def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): - """Segment a binary image to get a label image (2D/3D). + """ + Segment a binary image to get a label image (2D/3D). - Works on: 2D and 3D binary data. + This function works on both 2D and 3D binary data. Parameters ---------- - imp : ImagePlus + imp : ij.ImagePlus Binary 3D stack or 2D image. title : str Title of the new image. min_thresh : int, optional - Threshold to do segmentation, also allows for label filtering, by - default 1. + Threshold to do segmentation, also allows for label filtering, by default 1. min_vol : float, optional Volume under which to exclude objects, by default None. max_vol : float, optional @@ -164,23 +164,39 @@ def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): Returns ------- - ImagePlus + ij.ImagePlus Segmented labeled ImagePlus. """ + # Get the calibration of the input ImagePlus cal = imp.getCalibration() + + # Wrap the ImagePlus in an ImageHandler img = ImageHandler.wrap(imp) + + # Threshold the image using the specified threshold value img = img.threshold(min_thresh, False, False) + # Create an ImageLabeller instance labeler = ImageLabeller() + + # Set the minimum size for labeling if provided if min_vol: labeler.setMinSize(min_vol) + + # Set the maximum size for labeling if provided if max_vol: labeler.setMaxSize(max_vol) + # Get the labeled image seg = labeler.getLabels(img) + + # Set the scale of the labeled image seg.setScale(cal.pixelWidth, cal.pixelDepth, cal.getUnits()) + + # Set the title of the labeled image seg.setTitle(title) + # Return the segmented labeled ImagePlus return seg.getImagePlus() From dbc4c886acd48a048c604b088ce06259042df549 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 22 Oct 2024 11:43:06 +0200 Subject: [PATCH 280/678] Add method to only get 3D objects in an intensity range --- src/imcflibs/imagej/objects3d.py | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index ff4571b7..75225a14 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -95,3 +95,57 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): seg.setTitle(title) return seg.getImagePlus() + + +def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): + """Return a new population with the objects that have a mean intensity within + the specified range. + + Parameters + ---------- + obj_pop : Objects3DPopulation + Population of 3D objects + imp : ij.ImagePlus + ImagePlus on which the population is based + min_intensity : float + Minimum mean intensity of the objects + max_intensity : float + Maximum mean intensity of the objects + + Returns + ------- + Objects3DPopulation + New population with the objects filtered by intensity + """ + objects_within_intensity = [] + + # Iterate over all objects in the population + for i in range(0, obj_pop.getNbObjects()): + obj = obj_pop.getObject(i) + # Calculate the mean intensity of the object + mean_intensity = obj.getPixMeanValue(ImageHandler.wrap(imp)) + # Check if the object is within the specified intensity range + if mean_intensity >= min_intensity and mean_intensity < max_intensity: + objects_within_intensity.append(obj) + + # Return the new population with the filtered objects + return Objects3DPopulation(objects_within_intensity) + + +def get_objects3Dpop_names(obj_pop): + """Get the names of all the 3D objects in the specified Objects3DPopulation + + Parameters + ---------- + obj_pop : mcib3d.geom.Objects3DPopulation + Population of 3D objects + + Returns + ------- + str[] + List of the names of all the 3D objects in the population + """ + names = [] + for i in range(0, obj_pop.getNbObjects()): + names.append(obj_pop.getObject(i).getName()) + return names From 60405cc29a5b9d2a51b7cfd74e5722c798e3d9e6 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 22 Oct 2024 11:36:06 +0200 Subject: [PATCH 281/678] Update methods to use simple-omero-client Kept the methods to keep compatibility even though they're now easily done with one-liner --- src/imcflibs/imagej/omerotools.py | 337 ++++++++++-------------------- 1 file changed, 115 insertions(+), 222 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 8638d9d1..4c46aff6 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -4,93 +4,85 @@ fetch images from the server. """ -# ImageJ Import -from ij import IJ - -# Bioformats imports -from loci.formats import FormatTools, ImageTools -from loci.common import DataTools -from loci.plugins import LociExporter -from loci.plugins.in import ImporterOptions -from loci.plugins.out import Exporter - -# Omero Dependencies -from omero.gateway import Gateway -from omero.gateway import LoginCredentials -from omero.gateway import SecurityContext -from omero.gateway.facility import BrowseFacility -from omero.gateway.facility import DataManagerFacility -from omero.gateway.model import DatasetData, MapAnnotationData -from omero.log import SimpleLogger -from omero.sys import ParametersI - -from ome.formats.importer import ImportConfig -from ome.formats.importer import OMEROWrapper -from ome.formats.importer import ImportLibrary -from ome.formats.importer import ImportCandidates -from ome.formats.importer.cli import ErrorHandler -from ome.formats.importer.cli import LoggingImportMonitor -import loci.common -from loci.formats.in import DefaultMetadataOptions -from loci.formats.in import MetadataLevel - -def parse_image_ids(omero_str, gateway=None, ctx=None): - """Parse an OMERO URL with one or multiple images selected, or a link to one or more datasets where all images in - the datasets will be projected. Gateway and ctx are optional - only necessary if parsing a dataset link; if only images, - can be ignored. +from java.lang import Long + + +from fr.igred.omero import Client +from fr.igred.omero.annotations import MapAnnotationWrapper + + +def parse_url(client, omero_str): + """Parse an OMERO URL with one or multiple images selected or a dataset + and return the ImageWrapper list Parameters ---------- + client : fr.igred.omero.Client + Client used for login to OMERO omero_str : str - String which is either the dataset link from OMERO, the image link from OMERO or image IDs separated by commas - gateway : omero.gateway.Gateway, optional - An instance of OMERO gateway to use for retrieving dataset children - ctx: omero.gateway.SecurityContext, optional - A security context object that hosts information about the correct connector + String which is either the link gotten from OMERO or image IDs separated by commas Returns ------- - images: str[] - List of all the images IDs parsed from the string + list(fr.igred.omero.repositor.ImageWrapper) + List of ImageWrappers parsed from the string """ - dataset_ids = java.util.ArrayList() # Has to be an ArrayList due to how the wrapper works, cannot be a Python list image_ids = [] - project_string = "_" + projection_type + "_project" + dataset_ids = [] + image_wpr_list = [] + # Check if the url is a dataset if "dataset-" in omero_str: - if "|" in omero_str: # If multiple datasets, split by "|" + # If there are multiple datasets + if "|" in omero_str: parts = omero_str.split("|") for part in parts: if "dataset-" in part: - dataset_id = java.lang.Long(part.split("dataset-")[1].split("/")[0]) - dataset_ids.add(dataset_id) - else: # not multiple, single dataset - dataset_id = java.lang.Long(omero_str.split("dataset-")[1].split("/")[0]) - dataset_ids.add(dataset_id) # Add dataset ID to the ArrayList - - if gateway: - browse = gateway.getFacility(BrowseFacility) - datasets = browse.getDatasets(ctx, dataset_ids) - - for dataset in datasets: - images = dataset.getImages() - for im in images: - if project_string not in im.getName(): - image_ids.append(str(im.getId())) - return image_ids + image_ids.extend( + [ + image + for image in client.getDataset( + Long(part.split("dataset-")[1].split("/")[0]) + ).getImages() + ] + ) + dataset_id = Long(part.split("dataset-")[1].split("/")[0]) + dataset_ids.append(dataset_id) else: - raise ValueError("An OMERO gateway instance is required to retrieve dataset children.") - + image_ids.extend( + [ + image + for image in client.getDataset( + Long(omero_str.split("dataset-")[1].split("/")[0]) + ).getImages() + ] + ) + # If there is only one dataset + dataset_id = Long(omero_str.split("dataset-")[1].split("/")[0]) + dataset_ids.append(dataset_id) + + # Get the images from the dataset + for dataset_id in dataset_ids: + dataset_wpr = client.getDataset(dataset_id) + image_wpr_list.extend(dataset_wpr.getImages()) + + return image_wpr_list + + # Check if the url is an image elif "image-" in omero_str: image_ids = omero_str.split("image-") image_ids.pop(0) - image_ids = [s.split('%')[0].replace("|", "") for s in image_ids] - return image_ids - + image_ids = [s.split("%")[0].replace("|", "") for s in image_ids] else: + image_ids = ( + [s.split("%")[0].replace("|", "") for s in omero_str.split("image-")[1:]] + if "image-" in omero_str + else omero_str.split(",") + ) + # If it is a list of IDs separated by commas image_ids = omero_str.split(",") - return image_ids + return [client.getImage(Long(image_id)) for image_id in image_ids] def connect(host, port, username, password): @@ -109,196 +101,97 @@ def connect(host, port, username, password): Returns ------- - gateway: omero.gateway.Gateway - A Gateway object representing the connection to the OMERO server. - ctx: omero.gateway.SecurityContext - object that hosts information required to access correct connector. + client: fr.igred.omero.Client + A Client object representing the connection to the OMERO server. """ - # Omero Connect with credentials and simpleLogger - cred = LoginCredentials() - cred.getServer().setHostname(host) - cred.getServer().setPort(port) - cred.getUser().setUsername(username.strip()) - cred.getUser().setPassword(password.strip()) - simple_logger = SimpleLogger() - gateway = Gateway(simple_logger) - gateway.connect(cred) - # Get user for SecurityContext object - user = gateway.getLoggedInUser() - ctx = SecurityContext(user.getGroupId()) - return gateway, ctx - - -def fetch_image(host, username, password, image_id, group_id=-1): - """Fetch an image from an OMERO server and open it as an ImagePlus. + # Create a new OMERO client + client = Client() - NOTE: the function does **NOT** return the ImagePlus (nor its ID) as this - information is not provided by the underlying `loci.plugins.LociImporter` - call - it simply opens it in the running ImageJ instance. + # Connect to the OMERO server using provided credentials + client.connect(host, port, username, password) - Parameters - ---------- - host : str - The address (FQDN or IP) of the OMERO server. - username : str - The username for authentication. - password : str - The password for authentication. - image_id: int - ID of the image to fetch. - group_id : int, optional - The OMERO group ID, by default -1 meaning the user's default group. - """ - stackview = "viewhyperstack=true stackorder=XYCZT " - dataset_org = " ".join( - [ - "groupfiles=false", - "swapdimensions=false", - "openallseries=false", - "concatenate=false", - "stitchtiles=false", - ] - ) - color_opt = "colormode=Default autoscale=true" - metadata_view = " ".join( - [ - "showmetadata=false", - "showomexml=false", - "showrois=true", - "setroismode=roimanager", - ] - ) - memory_manage = "virtual=false specifyranges=false setcrop=false" - split = "splitchannels=false splitfocalplanes=false splittimepoints=false" - other = "windowless=true" - open_options = "\n".join( - [ - "open=[omero:server=" + host, - "user=" + username, - "pass=" + password, - "groupID=" + group_id, - "iid=" + image_id + "]", - ] - ) - options = " ".join( - [ - "location=[OMERO]", - open_options, - stackview, - dataset_org, - color_opt, - metadata_view, - memory_manage, - split, - other, - ] - ) - IJ.runPlugIn("loci.plugins.LociImporter", options) - - -def upload_image(path, gateway, dataset_id): - """Upload the image back to OMERO + # Return the connected client + return client + + +def fetch_image(client, image_id): + """Fetch an image from an OMERO server and open it as an ImagePlus. Parameters ---------- - path : str - Path of the file to upload back to OMERO - gateway : omero.gateway.Gateway - Gateway to the OMERO server + client : fr.igred.omero.Client + The OMERO client used to connect to the server + image_id : int + The ID of the image to fetch Returns ------- - list[int] - List of IDs of the imported images + ij.ImagePlus + The fetched image as an ImagePlus. """ - user = gateway.getLoggedInUser() - ctx = SecurityContext(user.getGroupId()) - sessionKey = gateway.getSessionId(user) - - config = ImportConfig() + # Fetch the image from the OMERO server + image_wpr = client.getImage(Long(image_id)) - config.email.set("") - config.sendFiles.set("true") - config.sendReport.set("false") - config.contOnError.set("false") - config.debug.set("false") - config.hostname.set(HOST) - config.sessionKey.set(sessionKey) - dataset = find_dataset(gateway, dataset_id) + # Convert the image to an ImagePlus + return image_wpr.toImagePlus() - loci.common.DebugTools.enableLogging("DEBUG") - store = config.createStore() - reader = OMEROWrapper(config) - - library = ImportLibrary(store, reader) - errorHandler = ErrorHandler(config) - - library.addObserver(LoggingImportMonitor()) - str2d = java.lang.reflect.Array.newInstance(java.lang.String, [1]) - str2d[0] = path - - candidates = ImportCandidates(reader, str2d, errorHandler) +def upload_image_to_omero(user_client, path, dataset_id): + """Upload the image back to OMERO - reader.setMetadataOptions(DefaultMetadataOptions(MetadataLevel.ALL)) + Parameters + ---------- + user_client : fr.igred.omero.Client + Client used for login to OMERO + path : str + Path of the file to upload back to OMERO + dataset_id : Long + ID of the dataset where to upload the file - container_list = candidates.getContainers() - num_done = 0 - ids_list = [] - for i in range(len(container_list)): - container = container_list[i] - container.setTarget(dataset) - pixels = library.importImage(container, i, num_done, len(container_list)) - ids_list.append(pixels[0].getImage().getId().getValue()) - num_done += 1 + Returns + ------- + Long + ID of the uploaded image + """ + return user_client.getDataset(Long(dataset_id)).importImage(user_client, path)[0] - return ids_list -def upload_kv(gateway, dict, header, image_id): +def add_annotation(client, repository_wpr, dict, header): """Add annotation to OMERO object Parameters ---------- - gateway : omero.gateway.Gateway - Gateway to the OMERO server + user_client : fr.igred.omero.Client + Client used for login to OMERO + repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper + Wrapper to the object for the anotation dict : dict Dictionary with the annotation to add header : str Name for the annotation header - image_id : int - Image ID on the OMERO server """ - browse = gateway.getFacility(BrowseFacility) - user = gateway.getLoggedInUser() - ctx = SecurityContext(user.getGroupId()) - image = browse.getImage(ctx, long(image_id)) + # for pair in dict: + # result.add + map_annotation_wpr = MapAnnotationWrapper(dict) + map_annotation_wpr.setNameSpace(header) + repository_wpr.addMapAnnotation(client, map_annotation_wpr) - data = MapAnnotationData() - data.setContent(dict) - data.setDescription(header) - data.setNameSpace(MapAnnotationData.NS_CLIENT_CREATED) - fac = gateway.getFacility(DataManagerFacility) - fac.attachAnnotation(ctx, data, image) - -def find_dataset(gateway, dataset_id): - """Returns the dataset object associated with the given dataset ID +def find_dataset(client, dataset_id): + """Retrieve a dataset from the OMERO server. Parameters ---------- - gateway : omero.gateway.Gateway - Gateway to the OMERO server + client : fr.igred.omero.Client + The OMERO client used to connect to the server. dataset_id : int - Image ID of the dataset + The ID of the dataset to retrieve. Returns ------- - omero.model.Dataset - Dataset object corresponding to the ID - + fr.igred.omero.repositor.DatasetWrapper + The dataset wrapper retrieved from the server. """ - browse = gateway.getFacility(BrowseFacility) - user = gateway.getLoggedInUser() - ctx = SecurityContext(user.getGroupId()) - return browse.findIObject(ctx, "omero.model.Dataset", dataset_id) \ No newline at end of file + # Fetch the dataset from the OMERO server using the provided dataset ID + return client.getDataset(Long(dataset_id)) From 7fbd82c0c83c224ddfbfc4cbd5341fd73bf4c5ba Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 30 Oct 2024 10:49:44 +0100 Subject: [PATCH 282/678] Specify file encoding to cover for utf-8 chars in docstrings Should fix #19 --- src/imcflibs/imagej/labelimage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 35d9f341..23ae621c 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + """Functions to work with ImageJ label images.""" from ij import IJ, ImagePlus, Prefs, ImageStack From 5a9e92ae309286f5c415052dec482480f44b9cc8 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Nov 2024 15:00:13 +0100 Subject: [PATCH 283/678] Fix the Works on convention --- 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 23ae621c..2cd1a082 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -149,7 +149,7 @@ def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): """ Segment a binary image to get a label image (2D/3D). - This function works on both 2D and 3D binary data. + Works on both 2D and 3D binary data. Parameters ---------- From 07d8a81ca4c09431391234a6e898f0b0ab402e8c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Nov 2024 15:17:55 +0100 Subject: [PATCH 284/678] Fix docstring convention and add info --- src/imcflibs/imagej/labelimage.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 2cd1a082..e5f69cc1 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -146,8 +146,7 @@ def measure_objects_size_shape_2d(label_image): def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): - """ - Segment a binary image to get a label image (2D/3D). + """Segment a binary image to get a label image (2D/3D). Works on both 2D and 3D binary data. @@ -203,8 +202,10 @@ def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): def dilate_labels_2d(imp, dilation_radius): - """ - Dilate each label in the given ImagePlus using the specified dilation radius. + """Dilate each label in the given ImagePlus using the specified dilation radius. + + This method will use a 2D dilation to be applied to each slice of the ImagePlus + and return a new stack. Parameters ---------- From 331840b03d2e1f37b1c355dbe722d687104d0c44 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Nov 2024 15:20:41 +0100 Subject: [PATCH 285/678] Remove wrong and duplicate import --- 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 e5f69cc1..9d095746 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -4,7 +4,7 @@ from ij import IJ, ImagePlus, Prefs, ImageStack from ij.plugin import Duplicator, ImageCalculator -from ij.plugin.filter import ImageProcessor, ThresholdToSelection +from ij.plugin.filter import ThresholdToSelection from ij.process import FloatProcessor, ImageProcessor from inra.ijpb.label import LabelImages as li from inra.ijpb.plugins import AnalyzeRegions From d6ce2425c47396f97587ea6e6411cab9d6d97dcf Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Nov 2024 16:30:30 +0100 Subject: [PATCH 286/678] Fix documentation and convention --- src/imcflibs/imagej/bdv.py | 53 +++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 30305587..620dab5d 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -140,6 +140,7 @@ def reference_illumination(self, value): Parameters ---------- 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) @@ -173,13 +174,14 @@ def reference_timepoint(self, value): Parameters ---------- value : int or int-like + 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) ### process-X methods - def process_angle(self, value, range_end=None): # def angle_select(self, value): + def process_angle(self, value, range_end=None): """Set the processing option for angles. Update the angle processing option and selection depending on input. @@ -188,10 +190,13 @@ def process_angle(self, value, range_end=None): # def angle_select(self, value) Parameters ---------- value : str, int, list of int or list of str - Contains the list of input dimensions, the first input dimension of a range or a single channel + The angle(s) to use for processing, either a single value or a list. range_end : int, optional - Contains the end of the range, by default None + Contains the end of the range, by default None. + Notes: + ------ + Previous function name : angle_select(). """ selection = check_processing_input(value, range_end) @@ -202,9 +207,7 @@ def process_angle(self, value, range_end=None): # def angle_select(self, value) self._angle_processing_option = processing_option self._angle_select = dimension_select - def process_channel( - self, value, range_end=None - ): # def channel_select(self, value): + def process_channel(self, value, range_end=None): """Set the processing option for channels. Update the channel processing option and selection depending on input. @@ -213,10 +216,13 @@ def process_channel( Parameters ---------- value : str, int, list of int or list of str - Contains the list of input dimensions, the first input dimension of a range or a single channel + The channel(s) to use for processing, either a single value or a list. range_end : int, optional - Contains the end of the range, by default None + Contains the end of the range, by default None. + Notes: + ------ + Previous function name : channel_select(). """ selection = check_processing_input(value, range_end) @@ -227,9 +233,7 @@ def process_channel( self._channel_processing_option = processing_option self._channel_select = dimension_select - def process_illumination( - self, value, range_end=None - ): # def illumination_select(self, value): + def process_illumination(self, value, range_end=None): """Set the processing option for illuminations. Update the illumination processing option and selection depending on input. @@ -238,10 +242,13 @@ def process_illumination( Parameters ---------- value : str, int, list of int or list of str - Contains the list of input dimensions, the first input dimension of a range or a single channel + The illumination(s) to use for processing, either a single value or a list. range_end : int, optional - Contains the end of the range, by default None + Contains the end of the range, by default None. + Notes: + ------ + Previous function name : illumination_select(). """ selection = check_processing_input(value, range_end) @@ -252,7 +259,7 @@ def process_illumination( self._illumination_processing_option = processing_option self._illumination_select = dimension_select - def process_tile(self, value, range_end=None): # def tile_select(self, value): + def process_tile(self, value, range_end=None): """Set the processing option for tiles. Update the tile processing option and selection depending on input. @@ -261,10 +268,13 @@ def process_tile(self, value, range_end=None): # def tile_select(self, value): Parameters ---------- value : str, int, list of int or list of str - Contains the list of input dimensions, the first input dimension of a range or a single channel + The tile(s) to use for processing, either a single value or a list. range_end : int, optional - Contains the end of the range, by default None + Contains the end of the range, by default None. + Notes: + ------ + Previous function name : tile_select(). """ selection = check_processing_input(value, range_end) @@ -275,9 +285,7 @@ def process_tile(self, value, range_end=None): # def tile_select(self, value): self._tile_processing_option = processing_option self._tile_select = dimension_select - def process_timepoint( - self, value, range_end=None - ): # def timepoint_select(self, value): + def process_timepoint(self, value, range_end=None): """Set the processing option for timepoints. Update the timepoint processing option and selection depending on input. @@ -286,10 +294,13 @@ def process_timepoint( Parameters ---------- value : str, int, list of int or list of str - Contains the list of input dimensions, the first input dimension of a range or a single channel + The timepoint(s) to use for processing, either a single value or a list. range_end : int, optional - Contains the end of the range, by default None + Contains the end of the range, by default None. + Notes: + ------ + Previous function name : timepoint_select(). """ selection = check_processing_input(value, range_end) From 966af1e70aecc3ad16a7b481dddc9202b0850706 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Nov 2024 15:20:41 +0100 Subject: [PATCH 287/678] Remove wrong and duplicate import --- 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 f0960804..62ec3499 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -2,7 +2,7 @@ from ij import IJ, ImagePlus, Prefs from ij.plugin import Duplicator, ImageCalculator -from ij.plugin.filter import ImageProcessor, ThresholdToSelection +from ij.plugin.filter import ThresholdToSelection from ij.process import FloatProcessor, ImageProcessor from inra.ijpb.label import LabelImages as li from inra.ijpb.plugins import AnalyzeRegions From 139a3b5c3aacebd8edf289f6bc5b8f722c076324 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 12 Nov 2024 14:22:17 +0100 Subject: [PATCH 288/678] Fix stuff lost in merging Fixes #21 --- 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 9d095746..d4ef54e0 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -148,7 +148,7 @@ def measure_objects_size_shape_2d(label_image): def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): """Segment a binary image to get a label image (2D/3D). - Works on both 2D and 3D binary data. + Works on: 2D and 3D binary data. Parameters ---------- From 89b5191d82777fd4e60bbe0e9d0b84fbc7c4dcfa Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Nov 2024 14:46:24 +0100 Subject: [PATCH 289/678] Add documentation for the global vars Cherry-picked from #25 Fixes #17 --- src/imcflibs/imagej/bdv.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 620dab5d..a9bdaffc 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -19,9 +19,11 @@ from ..log import LOG as log SINGLE = "[Single %s (Select from List)]" -MULTIPLE = "[Multiple %ss (Select from List)] " +"""Option to use to select only one value for the current dimension""" +MULTIPLE = "[Multiple %ss (Select from List)]" +"""Option to use to select specified multiple values for the current dimension""" RANGE = "[Range of %ss (Specify by Name)]" -"""@private template string""" +"""Option to use to select a range of values for the current dimension""" class ProcessingOptions(object): @@ -477,8 +479,13 @@ def fmt_use_acitt(self): SINGLE_FILE = "[NO (one %s)]" +"""Option to use if the current dimension is singular (like only one angle).""" MULTI_SINGLE_FILE = "[YES (all %ss in one file)]" +"""Option to use if the current dimension is plural (like multiple angles) +contained in a single file.""" MULTI_MULTI_FILE = "[YES (one file per %s)]" +"""Option to use if the current dimension is plural (like multiple angles) +contained in multiple files.""" class DefinitionOptions(object): From bcd0ca1728fd37a828cceb6f5dc22c1e33715cbe Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 6 Nov 2024 14:51:44 +0100 Subject: [PATCH 290/678] Add fix for \n and \t getting interpretated as new line and tabs --- src/imcflibs/pathtools.py | 1 + tests/test_pathtools.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 73c61427..78090e8a 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -101,6 +101,7 @@ def parse_path(path, prefix=""): 'path': '/path/to/some/'} """ path = str(path) + path = path.replace("\n", "\\n").replace("\t", "\\t") if prefix: # remove leading slash, otherwise join() will discard the first path: if path.startswith("/"): diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index fb81e811..b78b3e41 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import pytest from imcflibs.pathtools import parse_path from imcflibs.pathtools import jython_fiji_exists from imcflibs.pathtools import image_basename @@ -44,6 +43,14 @@ def test_parse_path_windows(): assert parsed['dname'] == 'foo' +def test_parse_path_windows_tabs_and_lines(): + path = "C:\new_folder\test" + parsed = parse_path(path) + + assert parsed["full"] == r"C:\new_folder\test" + assert parsed["fname"] == "test" + + def test_jython_fiji_exists(tmpdir): assert jython_fiji_exists(str(tmpdir)) == True From 5433c0c4e9d87a7cb6f82e6369f75027e3c4457d Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 12 Nov 2024 16:03:14 +0100 Subject: [PATCH 291/678] Add period to comment lines --- src/imcflibs/imagej/bdv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index a9bdaffc..87cdcd36 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -19,11 +19,11 @@ from ..log import LOG as log SINGLE = "[Single %s (Select from List)]" -"""Option to use to select only one value for the current dimension""" +"""Option to use to select only one value for the current dimension.""" MULTIPLE = "[Multiple %ss (Select from List)]" -"""Option to use to select specified multiple values for the current dimension""" +"""Option to use to select specified multiple values for the current dimension.""" RANGE = "[Range of %ss (Specify by Name)]" -"""Option to use to select a range of values for the current dimension""" +"""Option to use to select a range of values for the current dimension.""" class ProcessingOptions(object): From 4e6f5b56c0eec042fb5ef141ec2e63bd0ceece47 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 12 Nov 2024 16:03:29 +0100 Subject: [PATCH 292/678] Fix variable for dataset definition --- src/imcflibs/imagej/bdv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 87cdcd36..3d2bb7e4 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -786,7 +786,7 @@ def define_dataset_auto( os.makedirs(result_folder) if not dataset_save_path: - dataset_save_path = pathtools.join2(result_folder, project_filename) + dataset_save_path = result_folder if subsampling_factors: subsampling_factors = ( "manual_mipmap_setup subsampling_factors=" + subsampling_factors + " " From 8034612bcb9eb14ff09478ad299401db36948fc2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 13 Nov 2024 18:18:04 +0100 Subject: [PATCH 293/678] Fully rework the "loci" imports This way we are able to avoid any pragma pre-processing and other funny workarounds while still being able to import the code in C-Python right away, thus having access to all the nice tools like black, pylint, pdoc and even pytest (where it makes sense). --- src/imcflibs/imagej/_loci.py | 59 ++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index 4795a870..59c1803e 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -1,31 +1,44 @@ """Internal wrapper module to import from the (Java) loci package. -This module mostly exists to work around the issue that importing the -`ImporterOptions` class from the Java `loci` package requires syntax to be used -that is considered invalid by any C-Python parser (but is still valid and -working in Jython) and hence will break usage of Black, Pylint, and similar. - -By stashing this into an internal submodule only checks on this specific -(minimalistic) file will fail but remain operational for the other code. - -So why are the other imports in here then? - -This was a conscious decision as it seems to be confusing that *some* parts from -the `loci` package need to be imported from the `imcflibs.imagej._loci` -sub-module while others are imported directly. Instead we're simply importing -proxy-style all loci components through the file here. - -NOTE: the actual import of `ImporterOptions` still requires the `# pdoc: skip` -pragma to work with the documentation generation scripts, e.g. - -``` -from ._loci import ImporterOptions # pdoc: skip -``` +This module exists to work around the issue that importing some of the classes +from the Java `loci` package would require syntax that is considered invalid by +any C-Python parser (but is still valid and working in Jython) and hence will +break usage of Black, Pylint, and similar. + +By aggregating those "special" imports into this (private) submodule we can +properly work around that issue by providing "dummy" objects for C-Python and +importing the actual modules / classes when running within Jython. To avoid the +invalid syntax issue (which would still prevent C-Python-based tools like black +and pdoc to run) those parts are done via `importlib` calls. + +Other loci-related imports (i.e. those without problematic syntax) are placed in +here simply for consistency reasons (to have everything in the same place). """ from loci.plugins import BF -from loci.plugins.in import ImporterOptions # pdoc: skip -from loci.formats.in import ZeissCZIReader, DefaultMetadataOptions, MetadataLevel, DynamicMetadataOptions, MetadataOptions # pdoc: skip +# dummy objects to prevent failing imports in a non-ImageJ / Jython context: +ImporterOptions = None +ZeissCZIReader = None +DefaultMetadataOptions = None +MetadataLevel = None +DynamicMetadataOptions = None + +# perform the actual imports when running under Jython using `importlib` calls: +import platform as _python_platform + +if _python_platform.python_implementation() == "Jython": + import importlib + + _loci_plugins_in = importlib.import_module("loci.plugins.in") + ImporterOptions = _loci_plugins_in.ImporterOptions + + _loci_formats_in = importlib.import_module("loci.formats.in") + ZeissCZIReader = _loci_formats_in.ZeissCZIReader + DefaultMetadataOptions = _loci_formats_in.DefaultMetadataOptions + MetadataLevel = _loci_formats_in.MetadataLevel + DynamicMetadataOptions = _loci_formats_in.DynamicMetadataOptions + MetadataOptions = _loci_formats_in.MetadataOptions +del _python_platform from loci.formats import ImageReader, Memoizer, MetadataTools From 61436bb7cd0f25d454cc2ebb6df4615b9c124620 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 12 Nov 2024 16:52:20 +0100 Subject: [PATCH 294/678] Modify documentation to use "Template string" instead of option Cherry-picked in the context of #31 --- src/imcflibs/imagej/bdv.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 3d2bb7e4..7dfa1325 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -19,11 +19,13 @@ from ..log import LOG as log SINGLE = "[Single %s (Select from List)]" -"""Option to use to select only one value for the current dimension.""" +"""Template string to use to select only one value for the current dimension.""" MULTIPLE = "[Multiple %ss (Select from List)]" -"""Option to use to select specified multiple values for the current dimension.""" +"""Template string to use to select specified multiple values for the current +dimension.""" RANGE = "[Range of %ss (Specify by Name)]" -"""Option to use to select a range of values for the current dimension.""" +"""Template string to use to select a range of values for the current +dimension.""" class ProcessingOptions(object): @@ -479,13 +481,14 @@ def fmt_use_acitt(self): SINGLE_FILE = "[NO (one %s)]" -"""Option to use if the current dimension is singular (like only one angle).""" +"""Template string to use if the current dimension is singular (like only one +angle).""" MULTI_SINGLE_FILE = "[YES (all %ss in one file)]" -"""Option to use if the current dimension is plural (like multiple angles) -contained in a single file.""" +"""Template string to use if the current dimension is plural (like multiple +angles) contained in a single file.""" MULTI_MULTI_FILE = "[YES (one file per %s)]" -"""Option to use if the current dimension is plural (like multiple angles) -contained in multiple files.""" +"""Template string to use if the current dimension is plural (like multiple +angles) contained in multiple files.""" class DefinitionOptions(object): From 32bd9e3c027d821cfc2b684688e454fff2b7524c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 14 Nov 2024 10:37:20 +0100 Subject: [PATCH 295/678] Add a textual warning on new imports in "_loci.py" --- src/imcflibs/imagej/_loci.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index 59c1803e..a5644556 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -15,6 +15,16 @@ here simply for consistency reasons (to have everything in the same place). """ +# +### *** WARNING *** ### *** WARNING *** ### *** WARNING *** ### *** WARNING *** +# +# Whenever an import is added here, make sure to also update the corresponding +# part in `imcf-fiji-mocks`: https://github.com/imcf/imcf-fiji-mocks/ +# +### *** WARNING *** ### *** WARNING *** ### *** WARNING *** ### *** WARNING *** +# + + from loci.plugins import BF # dummy objects to prevent failing imports in a non-ImageJ / Jython context: From d4ccdecdb7c1dac139c34901b2aa4b440c087710 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 14 Nov 2024 12:02:52 +0100 Subject: [PATCH 296/678] Improve pytest script output --- scripts/py2-pytest.sh | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 471678d6..6a77e87f 100644 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -9,6 +9,7 @@ MOCKS_RELEASE="0.1.1" cd "$(dirname "$0")"/.. test -d "venv2" || { + echo "== Creating a Python2 venv..." python2 -m virtualenv venv2 source venv2/bin/activate URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download" @@ -22,12 +23,25 @@ test -d "venv2" || { pip deactivate + echo "== Finished creating a Python2 venv." } +echo "== Running 'poetry build'..." poetry build -vv +echo "== Finished 'poetry build'." +echo +echo "== Installing local version of 'imcflibs' package..." venv2/bin/pip uninstall --yes imcflibs venv2/bin/pip install dist/*.whl +echo "== Finished installing local 'imcflibs'." +echo -# call this script with `--cov --cov-report html` to generate coverage reports: +echo "== Running pytest..." +echo "$@" | grep -q -- "--cov" || { + echo "== NOTE: coverage reports will NOT be generated!" + echo "== Run with '--cov --cov-report html' to generate coverage reports!" +} + +set -x venv2/bin/pytest "$@" From 61788207c2e7df01130a26a0d26ad41848aeec38 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 14 Nov 2024 12:03:48 +0100 Subject: [PATCH 297/678] Use wheels from imcf-fiji-mocks 0.2.0 for testing --- scripts/py2-pytest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 6a77e87f..c30065bf 100644 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -4,7 +4,7 @@ set -o errexit # exit on any error set -o nounset # empty variables are not permitted # set the version of "imcf-fiji-mocks" to be used: -MOCKS_RELEASE="0.1.1" +MOCKS_RELEASE="0.2.0" cd "$(dirname "$0")"/.. From 7477fbd47c8d37bbff97d474b18c0b0b082cd406 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 14 Nov 2024 12:04:09 +0100 Subject: [PATCH 298/678] Use imcf-fiji-mocks from PyPI, add FIXME --- scripts/py2-pytest.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index c30065bf..aacb3979 100644 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -3,7 +3,11 @@ set -o errexit # exit on any error set -o nounset # empty variables are not permitted -# set the version of "imcf-fiji-mocks" to be used: +# FIXME: the MOCKS_RELEASE is now only used for DOWNLOADING the 'micrometa' and +# 'sjlogging' wheels from github, this should be cleaned up and moved to PyPI as +# well as soon as possible! + +# set the version of "imcf-fiji-mocks" to be used for downloading wheels: MOCKS_RELEASE="0.2.0" cd "$(dirname "$0")"/.. @@ -14,9 +18,9 @@ test -d "venv2" || { source venv2/bin/activate URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download" pip install --upgrade \ - $URL_PFX/v$MOCKS_RELEASE/imcf_fiji_mocks-0.1.1-py2.py3-none-any.whl \ $URL_PFX/v$MOCKS_RELEASE/micrometa-15.2.2-py2.py3-none-any.whl \ $URL_PFX/v$MOCKS_RELEASE/sjlogging-0.5.2-py2.py3-none-any.whl \ + imcf-fiji-mocks \ olefile==0.46 \ pytest \ pytest-cov \ From 444a68fa2706261c868578054542ae9d693e8baa Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 14 Nov 2024 15:38:45 +0100 Subject: [PATCH 299/678] Don't run tests on tests/* and conftest.py --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index 3fdcd375..f3df8539 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,5 +1,7 @@ [run] omit = + conftest.py + tests/* # omit anything in a venv / venv2 directory ./venv/* ./venv2/* From ec73039708131cbb1d250a03168520210cf1fd75 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 14 Nov 2024 15:39:10 +0100 Subject: [PATCH 300/678] Skip Jython branches in coverage reports --- .coveragerc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.coveragerc b/.coveragerc index f3df8539..5f82dec4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,3 +5,7 @@ omit = # omit anything in a venv / venv2 directory ./venv/* ./venv2/* + +[report] +exclude_also = + if _python_platform.python_implementation\(\) == \"Jython\": From 7832c952b011a4b5527a0841d74a1940340b22e9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 14:51:59 +0100 Subject: [PATCH 301/678] No need to activate the venv --- scripts/py2-pytest.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index aacb3979..e25dec40 100644 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -15,9 +15,8 @@ cd "$(dirname "$0")"/.. test -d "venv2" || { echo "== Creating a Python2 venv..." python2 -m virtualenv venv2 - source venv2/bin/activate URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download" - pip install --upgrade \ + venv2/bin/pip --no-python-version-warning install --upgrade \ $URL_PFX/v$MOCKS_RELEASE/micrometa-15.2.2-py2.py3-none-any.whl \ $URL_PFX/v$MOCKS_RELEASE/sjlogging-0.5.2-py2.py3-none-any.whl \ imcf-fiji-mocks \ @@ -25,8 +24,6 @@ test -d "venv2" || { pytest \ pytest-cov \ pip - - deactivate echo "== Finished creating a Python2 venv." } From dacd5da410e31d5a6e0968625dcb0440960093e6 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 14:53:36 +0100 Subject: [PATCH 302/678] Use explicit pragmas to exclude from coverage The 'exclude_also' config setting requires coverage >= 7.2 which is not available when testing with Python2 (latest coverage is 5.5 there). --- .coveragerc | 7 +++++-- src/imcflibs/__init__.py | 2 +- src/imcflibs/imagej/_loci.py | 2 +- src/imcflibs/pathtools.py | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.coveragerc b/.coveragerc index 5f82dec4..b6500981 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,5 +7,8 @@ omit = ./venv2/* [report] -exclude_also = - if _python_platform.python_implementation\(\) == \"Jython\": +## NOTE: `exclude_also` is only supported for coverage 7.2 and newer, which +## won't work when testing with Python2 (coverage 5.5 is the latest one +## supporting Python2), hence we cannot use it: +; exclude_also = +; if _python_platform.python_implementation\(\) == \"Jython\": diff --git a/src/imcflibs/__init__.py b/src/imcflibs/__init__.py index e8055314..b5d3fe00 100644 --- a/src/imcflibs/__init__.py +++ b/src/imcflibs/__init__.py @@ -17,6 +17,6 @@ # check if we're running in Jython, then also import the 'imagej' submodule: import platform as _python_platform -if _python_platform.python_implementation() == "Jython": +if _python_platform.python_implementation() == "Jython": # pragma: no cover from . import imagej del _python_platform diff --git a/src/imcflibs/imagej/_loci.py b/src/imcflibs/imagej/_loci.py index a5644556..0c354373 100644 --- a/src/imcflibs/imagej/_loci.py +++ b/src/imcflibs/imagej/_loci.py @@ -37,7 +37,7 @@ # perform the actual imports when running under Jython using `importlib` calls: import platform as _python_platform -if _python_platform.python_implementation() == "Jython": +if _python_platform.python_implementation() == "Jython": # pragma: no cover import importlib _loci_plugins_in = importlib.import_module("loci.plugins.in") diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 73c61427..7053b2ae 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -356,7 +356,7 @@ def create_directory(new_path): # pylint: disable-msg=C0103 # we use the variable name 'exists' in its common spelling (lowercase), so # removing this workaround will be straightforward at a later point -if platform.python_implementation() == "Jython": +if platform.python_implementation() == "Jython": # pragma: no cover # pylint: disable-msg=F0401 # java.lang is only importable within Jython, pylint would complain import java.lang From 357fb6df67d8bad98f217353afe205ac8bfd9259 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 14:55:40 +0100 Subject: [PATCH 303/678] Explicitly specify the source for coverage reports Otherwise files that are not imported at all by any test run will be excluded by coverage, making it report a too high ratio. --- .coveragerc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.coveragerc b/.coveragerc index b6500981..243e8705 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,7 @@ [run] +source = + src/imcflibs + omit = conftest.py tests/* From ae14035b9f37d64631ced8ebf4acd20318ebfca8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 14:56:10 +0100 Subject: [PATCH 304/678] Use 'sjlogging' from PyPI --- scripts/py2-pytest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index e25dec40..1e3b19b1 100644 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -18,7 +18,7 @@ test -d "venv2" || { URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download" venv2/bin/pip --no-python-version-warning install --upgrade \ $URL_PFX/v$MOCKS_RELEASE/micrometa-15.2.2-py2.py3-none-any.whl \ - $URL_PFX/v$MOCKS_RELEASE/sjlogging-0.5.2-py2.py3-none-any.whl \ + sjlogging \ imcf-fiji-mocks \ olefile==0.46 \ pytest \ From a027670fa5016ca1562c82a37867655d8d91d473 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 16:40:45 +0100 Subject: [PATCH 305/678] Use editable install also for Python2 To do this it is necessary to mock a 'setup.py' file so the Python2-pip will install the package in editable mode (and temporarily move the 'pyproject.toml' file out of the way while installing). --- scripts/py2-pytest.sh | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 1e3b19b1..36516aa5 100644 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -25,24 +25,31 @@ test -d "venv2" || { pytest-cov \ pip echo "== Finished creating a Python2 venv." -} - -echo "== Running 'poetry build'..." -poetry build -vv -echo "== Finished 'poetry build'." -echo - -echo "== Installing local version of 'imcflibs' package..." -venv2/bin/pip uninstall --yes imcflibs -venv2/bin/pip install dist/*.whl -echo "== Finished installing local 'imcflibs'." -echo -echo "== Running pytest..." -echo "$@" | grep -q -- "--cov" || { - echo "== NOTE: coverage reports will NOT be generated!" - echo "== Run with '--cov --cov-report html' to generate coverage reports!" + echo "== Installing local version of 'imcflibs' package..." + # NOTE: for being able to use coverage, the package has to be installed in + # editable mode, making it necessary to move `pyproject.toml` out of the way + # and creating a `setup.py` for the actual installation process (will be + # reverted after installing): + echo "== * Mocking 'setup.py'..." + echo "import setuptools +setuptools.setup( + name='imcflibs', + package_dir={'': 'src'}, +) +" > setup.py + echo "== * Temporarily disabling 'pyproject.toml'..." + mv pyproject.toml pyproject_.toml + echo "== * Installing package in editable mode..." + venv2/bin/pip --no-python-version-warning install --editable . + echo "== * Re-enabling 'pyproject.toml'..." + mv pyproject_.toml pyproject.toml + echo "== * Removing 'setup.py'..." + rm setup.py + echo "== Finished installing local 'imcflibs'." + echo } +echo "== Running pytest..." set -x venv2/bin/pytest "$@" From 280c17fbcc48d366597f912f947f3ba9abc9a3b4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 17:46:57 +0100 Subject: [PATCH 306/678] Organize imports --- src/imcflibs/imagej/trackmate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 4ca6264c..aef61ac8 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -1,17 +1,17 @@ import os import sys -from ij import IJ - from fiji.plugin.trackmate import Logger, Model, SelectionModel, Settings, TrackMate from fiji.plugin.trackmate.action import LabelImgExporter -from fiji.plugin.trackmate.detection import LogDetectorFactory from fiji.plugin.trackmate.cellpose import CellposeDetectorFactory -from fiji.plugin.trackmate.stardist import StarDistDetectorFactory from fiji.plugin.trackmate.cellpose.CellposeSettings import PretrainedModel - +from fiji.plugin.trackmate.detection import LogDetectorFactory from fiji.plugin.trackmate.features import FeatureFilter +from fiji.plugin.trackmate.stardist import StarDistDetectorFactory from fiji.plugin.trackmate.tracking.jaqaman import LAPUtils, SparseLAPTrackerFactory + +from ij import IJ + from java.lang import Double from .. import pathtools From e35d3b4f75a169d90fbc6df80be2a5ef20baefea Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 17:49:54 +0100 Subject: [PATCH 307/678] Clean up '.egg-info' folder created by setuptools This doesn't seem to impact if the package can be imported according to some quick tests. --- scripts/py2-pytest.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 36516aa5..73b55f10 100644 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -46,6 +46,8 @@ setuptools.setup( mv pyproject_.toml pyproject.toml echo "== * Removing 'setup.py'..." rm setup.py + echo "== * Cleaning up egg-info..." + rm -r src/imcflibs.egg-info echo "== Finished installing local 'imcflibs'." echo } From d1a64c00aa54e6679b23502f307dd9ab910eed18 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 21:45:30 +0100 Subject: [PATCH 308/678] Adapt to new name of micrometa package --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d37ac7cb..44a087af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,8 +14,8 @@ version = "1.5.0.a0" [tool.poetry.dependencies] imcf-fiji-mocks = "^0.2.0" -micrometa = "^15.2.2" python = ">=2.7" +python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" [tool.poetry.group.dev.dependencies] From 2327b348d5b6f6baa4a849f04c8949eb3948df0e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 21:45:36 +0100 Subject: [PATCH 309/678] Add package metadata --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 44a087af..2e6c47e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ description = "Mostly ImageJ/Fiji-related Python helper functions." license = "GPL-3.0-or-later" name = "imcflibs" readme = "README.md" +repository = "https://github.com/imcf/python-imcflibs" version = "1.5.0.a0" [tool.poetry.dependencies] From b2cca7a2d78c87724887719dc047c506feb32a1e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 21:45:49 +0100 Subject: [PATCH 310/678] Always use relative imports --- src/imcflibs/imagej/stitching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/stitching.py b/src/imcflibs/imagej/stitching.py index a82a66d2..6f7ee7aa 100644 --- a/src/imcflibs/imagej/stitching.py +++ b/src/imcflibs/imagej/stitching.py @@ -7,7 +7,7 @@ import micrometa # pylint: disable-msg=import-error import ij # pylint: disable-msg=import-error -from imcflibs.imagej.misc import show_status, show_progress, error_exit +from .misc import show_status, show_progress, error_exit from ..strtools import flatten from ..log import LOG as log From 312d238211341de23d78b768389f863e302d734d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 21:53:55 +0100 Subject: [PATCH 311/678] Clean up leftovers from local package dependencies --- local-packages/imcf-fiji-mocks/README.md | 1 - local-packages/micrometa/README.md | 1 - local-packages/sjlogging/README.md | 1 - 3 files changed, 3 deletions(-) delete mode 100644 local-packages/imcf-fiji-mocks/README.md delete mode 100644 local-packages/micrometa/README.md delete mode 100644 local-packages/sjlogging/README.md diff --git a/local-packages/imcf-fiji-mocks/README.md b/local-packages/imcf-fiji-mocks/README.md deleted file mode 100644 index 193b05cf..00000000 --- a/local-packages/imcf-fiji-mocks/README.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/imcf/imcf-fiji-mocks diff --git a/local-packages/micrometa/README.md b/local-packages/micrometa/README.md deleted file mode 100644 index eb5e9f95..00000000 --- a/local-packages/micrometa/README.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/imcf/python-micrometa diff --git a/local-packages/sjlogging/README.md b/local-packages/sjlogging/README.md deleted file mode 100644 index 6953abad..00000000 --- a/local-packages/sjlogging/README.md +++ /dev/null @@ -1 +0,0 @@ -https://github.com/imcf/jython-scijava-logging From 7d56e07c874c0955d282a1897d5fc9a194f4876c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 22:26:32 +0100 Subject: [PATCH 312/678] Add package metadata --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2e6c47e9..3c3e9868 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ authors = [ "Rohan Girish ", ] description = "Mostly ImageJ/Fiji-related Python helper functions." +documentation = "https://imcf.one/apidocs/imcflibs/imcflibs.html" license = "GPL-3.0-or-later" name = "imcflibs" readme = "README.md" From cf456065e14424bdf749a79f4f01a4437da898ea Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 22:47:11 +0100 Subject: [PATCH 313/678] Re-organize scripts dir --- scripts/doctest_runner.py | 22 ------- ...uild_and_deploy.sh => mvn-build-deploy.sh} | 0 scripts/run-poetry.sh | 57 +++++++++++++++++++ 3 files changed, 57 insertions(+), 22 deletions(-) delete mode 100644 scripts/doctest_runner.py rename scripts/{build_and_deploy.sh => mvn-build-deploy.sh} (100%) create mode 100755 scripts/run-poetry.sh diff --git a/scripts/doctest_runner.py b/scripts/doctest_runner.py deleted file mode 100644 index a1782e0d..00000000 --- a/scripts/doctest_runner.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python - -"""Doctest runner for the imcflibs package. - -Needs to be run from imcflibs's parent directory. -""" - -if __name__ == "__main__": - import doctest - import sys - - VERB = '-v' in sys.argv - - import imcflibs - import imcflibs.pathtools - import imcflibs.iotools - import imcflibs.strtools - - doctest.testmod(imcflibs, verbose=VERB) - doctest.testmod(imcflibs.pathtools, verbose=VERB) - doctest.testmod(imcflibs.iotools, verbose=VERB) - doctest.testmod(imcflibs.strtools, verbose=VERB) diff --git a/scripts/build_and_deploy.sh b/scripts/mvn-build-deploy.sh similarity index 100% rename from scripts/build_and_deploy.sh rename to scripts/mvn-build-deploy.sh diff --git a/scripts/run-poetry.sh b/scripts/run-poetry.sh new file mode 100755 index 00000000..d166c23d --- /dev/null +++ b/scripts/run-poetry.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -e + +cd "$(dirname "$0")/.." + +STATUS=$(git status --porcelain) + +if [ -n "$STATUS" ]; then + echo "====================================================================" + echo "=============== ERROR: repository unclean, stopping! ===============" + echo "====================================================================" + echo + git status + echo + echo "====================================================================" + echo "=============== ERROR: repository unclean, stopping! ===============" + echo "====================================================================" + exit 1 +fi + +### clean up old poetry artifacts: +rm -rf dist/ + +### parse the version from 'pom.xml': +PACKAGE_VERSION=$(xmlstarlet sel --template -m _:project -v _:version pom.xml) +PACKAGE_NAME=$(xmlstarlet sel --template -m _:project -v _:artifactId pom.xml) +PACKAGE_DIR="src/${PACKAGE_NAME#python-}" # strip 'python-' prefix if present + +echo "Package version from POM: [$PACKAGE_VERSION]" +### make sure to have a valid Python package version: +case $PACKAGE_VERSION in +*-SNAPSHOT*) + PACKAGE_VERSION=${PACKAGE_VERSION/-SNAPSHOT/} + ### calculate the distance to the last release tag: + LAST_TAG=$(git tag --list "${PACKAGE_NAME}-*" | sort | tail -n1) + # echo "Last git tag: '$LAST_TAG'" + COMMITS_SINCE=$(git rev-list "${LAST_TAG}..HEAD" | wc -l) + # echo "Nr of commits since last tag: $COMMITS_SINCE" + HEAD_ID=$(git rev-parse --short HEAD) + # echo "HEAD commit hash: $HEAD_ID" + PACKAGE_VERSION="${PACKAGE_VERSION}.dev${COMMITS_SINCE}+${HEAD_ID}" + ;; +esac + +echo "Using Python package version: [$PACKAGE_VERSION]" + +### put the version into the project file and the package source: +sed -i "s/\${project.version}/${PACKAGE_VERSION}/" pyproject.toml +sed -i "s/\${project.version}/${PACKAGE_VERSION}/" "${PACKAGE_DIR}/__init__.py" + +### now call poetry with the given parameters: +poetry "$@" + +### clean up the moved source tree and restore the previous state: +git restore pyproject.toml +git restore "${PACKAGE_DIR}/__init__.py" From 29011686f8d489523ad08bf7b71410ddcb3dff72 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 22:47:29 +0100 Subject: [PATCH 314/678] Let the poetry wrapper inject the package version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3c3e9868..494ddab9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ license = "GPL-3.0-or-later" name = "imcflibs" readme = "README.md" repository = "https://github.com/imcf/python-imcflibs" -version = "1.5.0.a0" +version = "${project.version}" [tool.poetry.dependencies] imcf-fiji-mocks = "^0.2.0" From d4d5c05af991ba18ad304657a43e9d28cc7a0054 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 23:15:49 +0100 Subject: [PATCH 315/678] Update mocks version dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 494ddab9..a3e86e6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ repository = "https://github.com/imcf/python-imcflibs" version = "${project.version}" [tool.poetry.dependencies] -imcf-fiji-mocks = "^0.2.0" +imcf-fiji-mocks = ">=0.3.0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From edac014f1cd88d0e54978fa1d26a126c19ba8c26 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 09:58:05 +0100 Subject: [PATCH 316/678] Make scripts executable --- scripts/mvn-build-deploy.sh | 0 scripts/py2-pytest.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/mvn-build-deploy.sh mode change 100644 => 100755 scripts/py2-pytest.sh diff --git a/scripts/mvn-build-deploy.sh b/scripts/mvn-build-deploy.sh old mode 100644 new mode 100755 diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh old mode 100644 new mode 100755 From 80a8d643e90dd6f7bca54d4705172ba4cf1a0993 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 19 Nov 2024 23:35:34 +0100 Subject: [PATCH 317/678] Use PyPi packages for building the Python2 venv --- scripts/py2-pytest.sh | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 73b55f10..2e17b9ab 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -3,27 +3,11 @@ set -o errexit # exit on any error set -o nounset # empty variables are not permitted -# FIXME: the MOCKS_RELEASE is now only used for DOWNLOADING the 'micrometa' and -# 'sjlogging' wheels from github, this should be cleaned up and moved to PyPI as -# well as soon as possible! - -# set the version of "imcf-fiji-mocks" to be used for downloading wheels: -MOCKS_RELEASE="0.2.0" - cd "$(dirname "$0")"/.. test -d "venv2" || { echo "== Creating a Python2 venv..." python2 -m virtualenv venv2 - URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download" - venv2/bin/pip --no-python-version-warning install --upgrade \ - $URL_PFX/v$MOCKS_RELEASE/micrometa-15.2.2-py2.py3-none-any.whl \ - sjlogging \ - imcf-fiji-mocks \ - olefile==0.46 \ - pytest \ - pytest-cov \ - pip echo "== Finished creating a Python2 venv." echo "== Installing local version of 'imcflibs' package..." @@ -50,6 +34,14 @@ setuptools.setup( rm -r src/imcflibs.egg-info echo "== Finished installing local 'imcflibs'." echo + venv2/bin/pip --no-python-version-warning install \ + python-micrometa \ + sjlogging \ + "imcf-fiji-mocks>=0.3.0" \ + olefile==0.46 \ + pytest \ + pytest-cov \ + pip } echo "== Running pytest..." From 2b29aeafebae646b16c4908c9e99edf5371db8e4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 12:41:24 +0100 Subject: [PATCH 318/678] Properly set the package version for py2-pytest --- scripts/py2-pytest.sh | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 2e17b9ab..96bda512 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -15,10 +15,31 @@ test -d "venv2" || { # editable mode, making it necessary to move `pyproject.toml` out of the way # and creating a `setup.py` for the actual installation process (will be # reverted after installing): - echo "== * Mocking 'setup.py'..." + ### parse the version from 'pom.xml': + PACKAGE_VERSION=$(xmlstarlet sel --template -m _:project -v _:version pom.xml) + echo "Package version from POM: [$PACKAGE_VERSION]" + ### make sure to have a valid Python package version: + case $PACKAGE_VERSION in + *-SNAPSHOT*) + PACKAGE_VERSION=${PACKAGE_VERSION/-SNAPSHOT/} + ### calculate the distance to the last release tag: + LAST_TAG=$(git tag --list "${PACKAGE_NAME}-*" | sort | tail -n1) + # echo "Last git tag: '$LAST_TAG'" + COMMITS_SINCE=$(git rev-list "${LAST_TAG}..HEAD" | wc -l) + # echo "Nr of commits since last tag: $COMMITS_SINCE" + HEAD_ID=$(git rev-parse --short HEAD) + # echo "HEAD commit hash: $HEAD_ID" + PACKAGE_VERSION="${PACKAGE_VERSION}.dev${COMMITS_SINCE}+${HEAD_ID}" + ;; + esac + + echo "Using Python package version: [$PACKAGE_VERSION]" + + echo "== * Mocking 'setup.py' (package version: $PACKAGE_VERSION)..." echo "import setuptools setuptools.setup( name='imcflibs', + version='$PACKAGE_VERSION', package_dir={'': 'src'}, ) " > setup.py From 66832ad8b8cd36b504ba9a56d1a867ce5ae7fe40 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 12:46:27 +0100 Subject: [PATCH 319/678] Move egg cleanup after pip install --- scripts/py2-pytest.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 96bda512..488bd94b 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -51,10 +51,6 @@ setuptools.setup( mv pyproject_.toml pyproject.toml echo "== * Removing 'setup.py'..." rm setup.py - echo "== * Cleaning up egg-info..." - rm -r src/imcflibs.egg-info - echo "== Finished installing local 'imcflibs'." - echo venv2/bin/pip --no-python-version-warning install \ python-micrometa \ sjlogging \ @@ -63,6 +59,12 @@ setuptools.setup( pytest \ pytest-cov \ pip + echo "== * Cleaning up egg-info..." + # NOTE: this can only be done AFTER the pip-install from above as otherwise + # dependency resolution won't work due to lack of package metadata: + rm -r src/imcflibs.egg-info + echo "== Finished installing local 'imcflibs'." + echo } echo "== Running pytest..." From 0753c31d666bdc9244905d59ff7af31c5bca4bd9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 13:07:28 +0100 Subject: [PATCH 320/678] Always use a temporary venv for py2-pytest --- scripts/py2-pytest.sh | 129 +++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 488bd94b..7a810d9e 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -4,69 +4,82 @@ set -o errexit # exit on any error set -o nounset # empty variables are not permitted cd "$(dirname "$0")"/.. +VENV="$(mktemp --directory --dry-run --tmpdir=. venv2.pytest-XXX)" -test -d "venv2" || { - echo "== Creating a Python2 venv..." - python2 -m virtualenv venv2 - echo "== Finished creating a Python2 venv." - - echo "== Installing local version of 'imcflibs' package..." - # NOTE: for being able to use coverage, the package has to be installed in - # editable mode, making it necessary to move `pyproject.toml` out of the way - # and creating a `setup.py` for the actual installation process (will be - # reverted after installing): - ### parse the version from 'pom.xml': - PACKAGE_VERSION=$(xmlstarlet sel --template -m _:project -v _:version pom.xml) - echo "Package version from POM: [$PACKAGE_VERSION]" - ### make sure to have a valid Python package version: - case $PACKAGE_VERSION in - *-SNAPSHOT*) - PACKAGE_VERSION=${PACKAGE_VERSION/-SNAPSHOT/} - ### calculate the distance to the last release tag: - LAST_TAG=$(git tag --list "${PACKAGE_NAME}-*" | sort | tail -n1) - # echo "Last git tag: '$LAST_TAG'" - COMMITS_SINCE=$(git rev-list "${LAST_TAG}..HEAD" | wc -l) - # echo "Nr of commits since last tag: $COMMITS_SINCE" - HEAD_ID=$(git rev-parse --short HEAD) - # echo "HEAD commit hash: $HEAD_ID" - PACKAGE_VERSION="${PACKAGE_VERSION}.dev${COMMITS_SINCE}+${HEAD_ID}" - ;; - esac +function vpip() { + "$VENV/bin/pip" --no-python-version-warning "$@" +} - echo "Using Python package version: [$PACKAGE_VERSION]" +echo "== Creating a Python2 venv in [$VENV]..." +python2 -m virtualenv "$VENV" +echo "== Finished creating a Python2 venv." - echo "== * Mocking 'setup.py' (package version: $PACKAGE_VERSION)..." - echo "import setuptools +echo "== Installing local version of 'imcflibs' package..." +# NOTE: for being able to use coverage, the package has to be installed in +# editable mode, making it necessary to move `pyproject.toml` out of the way +# and creating a `setup.py` for the actual installation process (will be +# reverted after installing): +### parse the version from 'pom.xml': +PACKAGE_VERSION=$(xmlstarlet sel --template -m _:project -v _:version pom.xml) +echo "Package version from POM: [$PACKAGE_VERSION]" +### make sure to have a valid Python package version: +case $PACKAGE_VERSION in +*-SNAPSHOT*) + PACKAGE_VERSION=${PACKAGE_VERSION/-SNAPSHOT/} + ### calculate the distance to the last release tag: + LAST_TAG=$(git tag --list "${PACKAGE_NAME}-*" | sort | tail -n1) + # echo "Last git tag: '$LAST_TAG'" + COMMITS_SINCE=$(git rev-list "${LAST_TAG}..HEAD" | wc -l) + # echo "Nr of commits since last tag: $COMMITS_SINCE" + HEAD_ID=$(git rev-parse --short HEAD) + # echo "HEAD commit hash: $HEAD_ID" + PACKAGE_VERSION="${PACKAGE_VERSION}.dev${COMMITS_SINCE}+${HEAD_ID}" + ;; +esac +echo "== * Mocking 'setup.py' with package version $PACKAGE_VERSION" +echo "import setuptools setuptools.setup( - name='imcflibs', - version='$PACKAGE_VERSION', - package_dir={'': 'src'}, +name='imcflibs', +version='$PACKAGE_VERSION', +package_dir={'': 'src'}, ) " > setup.py - echo "== * Temporarily disabling 'pyproject.toml'..." - mv pyproject.toml pyproject_.toml - echo "== * Installing package in editable mode..." - venv2/bin/pip --no-python-version-warning install --editable . - echo "== * Re-enabling 'pyproject.toml'..." - mv pyproject_.toml pyproject.toml - echo "== * Removing 'setup.py'..." - rm setup.py - venv2/bin/pip --no-python-version-warning install \ - python-micrometa \ - sjlogging \ - "imcf-fiji-mocks>=0.3.0" \ - olefile==0.46 \ - pytest \ - pytest-cov \ - pip - echo "== * Cleaning up egg-info..." - # NOTE: this can only be done AFTER the pip-install from above as otherwise - # dependency resolution won't work due to lack of package metadata: - rm -r src/imcflibs.egg-info - echo "== Finished installing local 'imcflibs'." - echo -} + +echo "== * Temporarily disabling 'pyproject.toml'..." +mv pyproject.toml pyproject_.toml + +echo "== * Installing package in editable mode..." +vpip install --editable . + +echo "== * Re-enabling 'pyproject.toml'..." +mv pyproject_.toml pyproject.toml + +echo "== * Removing 'setup.py'..." +rm setup.py + +echo "== * Installing dependencies..." +vpip install \ + python-micrometa \ + sjlogging \ + "imcf-fiji-mocks>=0.3.0" \ + olefile==0.46 \ + pytest \ + pytest-cov \ + pip + +echo "== * Cleaning up egg-info..." +# NOTE: this can only be done AFTER the pip-install from above as otherwise +# dependency resolution won't work due to lack of package metadata: +rm -r src/imcflibs.egg-info +echo "== Finished installing local 'imcflibs'." +echo echo "== Running pytest..." -set -x -venv2/bin/pytest "$@" +set -o xtrace +set +o errexit +"$VENV/bin/pytest" "$@" +set +o xtrace + +echo +echo "== Done. Leaving venv around: [$VENV]" +echo From f7df12279bcafa62177aa1093cc6f2f65a2e2535 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 13:11:39 +0100 Subject: [PATCH 321/678] Minor readability improvement --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d38bf9fa..99c39ce2 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # IMCFlibs ๐Ÿ โ˜• ๐Ÿ”ฉ ๐Ÿ”ง ๐Ÿช› -[![Build Status](https://github.com/imcf/python-imcflibs/actions/workflows/build.yml/badge.svg)](https://github.com/imcf/python-imcflibs/actions/workflows/build.yml) -[![DOI](https://zenodo.org/badge/156891364.svg)](https://zenodo.org/badge/latestdoi/156891364) +[![Build Status](https://github.com/imcf/python-imcflibs/actions/workflows/build.yml/badge.svg)][build] +[![DOI](https://zenodo.org/badge/156891364.svg)][doi] This package contains a diverse collection of Python functions dealing with paths, I/O (file handles, ...), strings etc. and tons of [Fiji][fiji] / @@ -55,3 +55,5 @@ correct_and_project(raw_image, out_path, model, "Maximum", ".ics") [imcf_updsite]: https://imagej.net/list-of-update-sites/ [script_split]: https://github.com/imcf/imcf-fiji-scripts/blob/master/src/main/resources/scripts/Plugins/IMCF_Utilities/Convert/Split_TIFFs_By_Channels_And_Slices.py [script_fvstitch]: https://github.com/imcf/imcf-fiji-scripts/blob/master/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/FluoView_OIF_OIB_OIR_Simple_Stitcher.py +[doi]: https://zenodo.org/badge/latestdoi/156891364 +[build]: https://github.com/imcf/python-imcflibs/actions/workflows/build.yml From ea9e56c1cfdae1d36896582ebde8cb646805a71d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 13:20:30 +0100 Subject: [PATCH 322/678] Update README in regards to PyPI --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 99c39ce2..449e4024 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,17 @@ Initially this has been a multi-purpose package where a substantial part had been useful in **CPython** as well. However, since the latest Jython release is still based on Python 2.7 (see the [Jython 3 roadmap][jython3] for more info), *imcflibs* is now basically limited to the **Fiji / ImageJ2 -ecosystem** (which is also the reason why no `pip install`able package is -provided). +ecosystem**. Releases are made through Maven and published to the [SciJava Maven repository][sj_maven]. The easiest way to use the lib is by adding the **`IMCF Uni Basel`** [update site][imcf_updsite] to your ImageJ installation. +The [`pip install`able package][pypi] is probably only useful for two cases: +running `pytest` (where applicable) and rendering [HTML-based API docs][apidocs] +using [`pdoc`][pdoc]. Let us know in case you're having another use case ๐ŸŽช for +it. + Developed and provided by the [Imaging Core Facility (IMCF)][imcf] of the Biozentrum, University of Basel, Switzerland. @@ -57,3 +61,6 @@ correct_and_project(raw_image, out_path, model, "Maximum", ".ics") [script_fvstitch]: https://github.com/imcf/imcf-fiji-scripts/blob/master/src/main/resources/scripts/Plugins/IMCF_Utilities/Stitching_Registration/FluoView_OIF_OIB_OIR_Simple_Stitcher.py [doi]: https://zenodo.org/badge/latestdoi/156891364 [build]: https://github.com/imcf/python-imcflibs/actions/workflows/build.yml +[apidocs]: https://imcf.one/apidocs/imcflibs/imcflibs.html +[pdoc]: https://pdoc.dev/ +[pypi]: https://pypi.org/project/imcflibs/ From c629abc081e0ca6a34ffd4b2dd3f318ea18e8465 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 17:20:25 +0100 Subject: [PATCH 323/678] Add pytest-cov dev dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a3e86e6b..303367ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ sjlogging = ">=0.5.2" [tool.poetry.group.dev.dependencies] ipython = "^8.17.2" pytest = "^8.0.1" +pytest-cov = "^6.0.0" [build-system] build-backend = "poetry.core.masonry.api" From df8f9dcfe26e13d94a203e3314bb0f1ec5795411 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 16:35:47 +0100 Subject: [PATCH 324/678] Add note about running 'poetry install' and such --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 303367ff..fd215f41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,12 @@ readme = "README.md" repository = "https://github.com/imcf/python-imcflibs" version = "${project.version}" +# NOTE: to create an environment using 'poetry install' or similar operations, +# the following modifications are temporarily necessary (can / should be +# reverted after creating the env): +# - either: python = ">=3.9" AND disable ipython in the dev dependencies +# - or: python = ">=3.10" + [tool.poetry.dependencies] imcf-fiji-mocks = ">=0.3.0" python = ">=2.7" From 0d0b3881dec1e9614586c4b2b88c62bea15fd769 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 20 Nov 2024 17:27:17 +0100 Subject: [PATCH 325/678] Make sure 'git restore' is called eventually --- scripts/run-poetry.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/run-poetry.sh b/scripts/run-poetry.sh index d166c23d..7a164512 100755 --- a/scripts/run-poetry.sh +++ b/scripts/run-poetry.sh @@ -49,6 +49,7 @@ echo "Using Python package version: [$PACKAGE_VERSION]" sed -i "s/\${project.version}/${PACKAGE_VERSION}/" pyproject.toml sed -i "s/\${project.version}/${PACKAGE_VERSION}/" "${PACKAGE_DIR}/__init__.py" +set +e # required as otherwise the 'restore' calls below might be skipped ### now call poetry with the given parameters: poetry "$@" From 86ce86044f25498a2e92877fef28572d5f4bb538 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 09:33:52 +0100 Subject: [PATCH 326/678] Add GH action: pytest-poetry --- .github/workflows/pytest-poetry.yml | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/pytest-poetry.yml diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml new file mode 100644 index 00000000..d6ff189c --- /dev/null +++ b/.github/workflows/pytest-poetry.yml @@ -0,0 +1,83 @@ +## action file inspired by https://jacobian.org/til/github-actions-poetry/ + +name: pytest (via poetry) + +on: + push: + branches: + - master + - devel + - gh-action + tags: + - "*-[0-9]+.*" + pull_request: + branches: + - master + +jobs: + pytest-poetry: + # runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + + # If you wanted to use multiple Python versions, you'd have specify a + # matrix in the job and reference the matrix python version here. + - uses: actions/setup-python@v2 + with: + python-version: "3.10" + + # Cache the installation of Poetry itself, e.g. the next step. This + # prevents the workflow from installing Poetry every time, which can be + # slow. Note the use of the Poetry version number in the cache key, and + # the "-0" suffix: this allows you to invalidate the cache manually + # if/when you want to upgrade Poetry, or if something goes wrong (could be + # done mildly cleaner by using an environment variable). + - name: cache poetry install + uses: actions/cache@v2 + with: + path: ~/.local + key: poetry-1.8.2-0 + + # Install Poetry. You could do this manually, or there are several actions + # that do this. `snok/install-poetry` seems to be minimal yet complete, + # and really just calls out to Poetry's default install script, which + # feels correct. I pin the Poetry version here because Poetry does + # occasionally change APIs between versions and I don't want my actions to + # break if it does. + # + # The key configuration value here is `virtualenvs-in-project: true`: this + # creates the venv as a `.venv` in your testing directory, which allows + # the next step to easily cache it. + - uses: snok/install-poetry@v1 + with: + version: 1.8.2 + virtualenvs-create: true + virtualenvs-in-project: true + + # Cache your dependencies (i.e. all the stuff in your `pyproject.toml`). + # Note the cache key: if you're using multiple Python versions, or + # multiple OSes, you'd need to include them in the cache key. I'm not, so + # it can be simple and just depend on the poetry.lock. + - name: cache deps + id: cache-deps + uses: actions/cache@v2 + with: + path: .venv + key: pydeps-${{ hashFiles('**/poetry.lock') }} + + # Install dependencies. `--no-root` means "install all dependencies but + # not the project itself", which is what you want to avoid caching _your_ + # code. The `if` statement ensures this only runs on a cache miss. + - run: poetry install --no-interaction --no-root + if: steps.cache-deps.outputs.cache-hit != 'true' + + # Now install _your_ project. This isn't necessary for many types of + # projects -- particularly things like Django apps don't need this. But + # it's a good idea since it fully-exercises the pyproject.toml and makes + # that if you add things like console-scripts at some point that they'll + # be installed and working. + - run: poetry install --no-interaction + + # And finally run the tests. + - run: poetry run pytest From e46f0694c7dd8d275eaf32373be30da2bf40f1a4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 14:22:02 +0100 Subject: [PATCH 327/678] GH action: use poetry wrapper --- .github/workflows/pytest-poetry.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index d6ff189c..335a8353 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -69,7 +69,7 @@ jobs: # Install dependencies. `--no-root` means "install all dependencies but # not the project itself", which is what you want to avoid caching _your_ # code. The `if` statement ensures this only runs on a cache miss. - - run: poetry install --no-interaction --no-root + - run: scripts/run-poetry.sh install --no-interaction --no-root if: steps.cache-deps.outputs.cache-hit != 'true' # Now install _your_ project. This isn't necessary for many types of @@ -77,7 +77,7 @@ jobs: # it's a good idea since it fully-exercises the pyproject.toml and makes # that if you add things like console-scripts at some point that they'll # be installed and working. - - run: poetry install --no-interaction + - run: scripts/run-poetry.sh install --no-interaction # And finally run the tests. - - run: poetry run pytest + - run: scripts/run-poetry.sh run pytest From 436fcca807382ea7f8d080c6d5099c61132bf522 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 14:31:44 +0100 Subject: [PATCH 328/678] GH action: install xmlstarlet --- .github/workflows/pytest-poetry.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 335a8353..c2a29c1a 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -21,6 +21,12 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Cache APT Packages + uses: awalsh128/cache-apt-pkgs-action@v1.4.2 + with: + packages: xmlstarlet + version: 1.0 + # If you wanted to use multiple Python versions, you'd have specify a # matrix in the job and reference the matrix python version here. - uses: actions/setup-python@v2 From 8271e1481366c19a9deaca156299f9dadfe5a3d8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 14:15:03 +0100 Subject: [PATCH 329/678] Use a valid pyproject.toml by default Also known as: convince poetry all is good. --- pyproject.toml | 2 +- scripts/run-poetry.sh | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fd215f41..0beb2732 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ license = "GPL-3.0-or-later" name = "imcflibs" readme = "README.md" repository = "https://github.com/imcf/python-imcflibs" -version = "${project.version}" +version = "0.0.0" # NOTE: to create an environment using 'poetry install' or similar operations, # the following modifications are temporarily necessary (can / should be diff --git a/scripts/run-poetry.sh b/scripts/run-poetry.sh index 7a164512..73e38e97 100755 --- a/scripts/run-poetry.sh +++ b/scripts/run-poetry.sh @@ -44,11 +44,12 @@ case $PACKAGE_VERSION in esac echo "Using Python package version: [$PACKAGE_VERSION]" - ### put the version into the project file and the package source: -sed -i "s/\${project.version}/${PACKAGE_VERSION}/" pyproject.toml +sed -i "s/\"0.0.0\"/\"${PACKAGE_VERSION}\"/" pyproject.toml sed -i "s/\${project.version}/${PACKAGE_VERSION}/" "${PACKAGE_DIR}/__init__.py" +sed -i 's/^python = ">=2.7"$/python = ">=3.10"/' pyproject.toml + set +e # required as otherwise the 'restore' calls below might be skipped ### now call poetry with the given parameters: poetry "$@" From 7f9e32129aa4be2ff39b8d453c1d4e7d783abe5d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 14:43:49 +0100 Subject: [PATCH 330/678] =?UTF-8?q?Add=20poetry.lock=20=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 506 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 506 insertions(+) create mode 100644 poetry.lock diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..7f972446 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,506 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.7" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"}, + {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"}, + {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"}, + {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"}, + {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"}, + {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"}, + {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"}, + {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"}, + {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"}, + {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"}, + {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"}, + {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"}, + {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"}, + {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"}, + {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"}, + {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"}, + {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"}, + {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"}, + {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"}, + {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"}, + {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"}, + {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"}, + {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"}, + {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"}, + {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"}, + {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"}, + {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"}, + {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"}, + {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"}, + {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"}, + {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"}, + {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"}, + {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"}, + {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"}, + {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"}, + {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"}, + {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"}, + {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "executing" +version = "2.1.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +files = [ + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "imcf-fiji-mocks" +version = "0.3.0" +description = "Mocks collection for Fiji-Python. Zero functional code." +optional = false +python-versions = ">=2.7" +files = [ + {file = "imcf_fiji_mocks-0.3.0-py2.py3-none-any.whl", hash = "sha256:53c26891ee9b71cb0af047e0b95baf8997d244746bb997ab50f24a9062114fdb"}, + {file = "imcf_fiji_mocks-0.3.0.tar.gz", hash = "sha256:acd16358f6ca5a7ded6b7c7a29d7006b2d363ba430eb72981f99997fed40d5a8"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "ipython" +version = "8.29.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "ipython-8.29.0-py3-none-any.whl", hash = "sha256:0188a1bd83267192123ccea7f4a8ed0a78910535dbaa3f37671dca76ebd429c8"}, + {file = "ipython-8.29.0.tar.gz", hash = "sha256:40b60e15b22591450eef73e40a027cf77bd652e757523eebc5bd7c7c498290eb"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5.13.0" +typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} + +[package.extras] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] +kernel = ["ipykernel"] +matplotlib = ["matplotlib"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "jedi" +version = "0.19.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, +] + +[package.dependencies] +parso = ">=0.8.4,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "olefile" +version = "0.46" +description = "Python package to parse, read and write Microsoft OLE2 files (Structured Storage or Compound Document, Microsoft Office)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "olefile-0.46.zip", hash = "sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.48" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, +] + +[package.dependencies] +coverage = {version = ">=7.5", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "python-micrometa" +version = "15.2.2" +description = "Process metadata from various light-microscopy related formats." +optional = false +python-versions = ">=2.7" +files = [ + {file = "python_micrometa-15.2.2-py2.py3-none-any.whl", hash = "sha256:1667dc19b08897c243356c8fda3670bcb5e8ce934fcea58ba6aa432313709a5c"}, + {file = "python_micrometa-15.2.2.tar.gz", hash = "sha256:91a58a6d61d565a4c3d3ac639150fb4bd58473b7c6f9b50845f4cd993f5665d5"}, +] + +[package.dependencies] +imcflibs = ">=1.4,<2.0" +olefile = ">=0.46,<0.47" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sjlogging" +version = "0.5.4" +description = "Jython package for using SciJava's LogService for logging." +optional = false +python-versions = ">=2.7" +files = [ + {file = "sjlogging-0.5.4-py2.py3-none-any.whl", hash = "sha256:e7db0a34ac2788a0404ac02beee232132f3946330cc04d28a37eea9adb3cfd42"}, + {file = "sjlogging-0.5.4.tar.gz", hash = "sha256:5fb1a4e6338088bdbf9d943a867bf1bc6f77031ec48dbb7dd96f09abb0aaaa92"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "tomli" +version = "2.1.0" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.10" +content-hash = "5eab384b395adebc2d3b05d2cceb6beccd1090491891713bd7e97e05d23e6c93" From 8f5b39b880e3fde6cae7c94ffeef2db38cda44bd Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 14:47:53 +0100 Subject: [PATCH 331/678] Return the exit code of the wrapped command --- scripts/py2-pytest.sh | 7 +++++-- scripts/run-poetry.sh | 10 ++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 7a810d9e..8a1c2b77 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -76,10 +76,13 @@ echo echo "== Running pytest..." set -o xtrace -set +o errexit -"$VENV/bin/pytest" "$@" +set +o errexit # otherwise the script stops if pytest exits non-zero +"$VENV/bin/pytest" "$@" # run pytest with the parameters given to the script +RETVAL=$? # remember the actual exit code of pytest for returning it below! set +o xtrace echo echo "== Done. Leaving venv around: [$VENV]" echo + +exit $RETVAL # now return the exit code from running pytest diff --git a/scripts/run-poetry.sh b/scripts/run-poetry.sh index 73e38e97..95b42648 100755 --- a/scripts/run-poetry.sh +++ b/scripts/run-poetry.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -o errexit # exit on any error cd "$(dirname "$0")/.." @@ -50,10 +50,12 @@ sed -i "s/\${project.version}/${PACKAGE_VERSION}/" "${PACKAGE_DIR}/__init__.py" sed -i 's/^python = ">=2.7"$/python = ">=3.10"/' pyproject.toml -set +e # required as otherwise the 'restore' calls below might be skipped -### now call poetry with the given parameters: -poetry "$@" +set +o errexit # otherwise the script stops if poetry exits non-zero +poetry "$@" # run poetry with the parameters given to the script +RETVAL=$? # remember the actual exit code of poetry for returning it below! ### clean up the moved source tree and restore the previous state: git restore pyproject.toml git restore "${PACKAGE_DIR}/__init__.py" + +exit $RETVAL # now return the exit code from running poetry From f8f1c9a6f63faa6ef65ac7cec13e449f3e623d00 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 15:24:30 +0100 Subject: [PATCH 332/678] =?UTF-8?q?Allow=20wrapper=20to=20force-run=20on?= =?UTF-8?q?=20unclean=20=F0=9F=A7=B9=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/run-poetry.sh | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/scripts/run-poetry.sh b/scripts/run-poetry.sh index 95b42648..6f51c2b4 100755 --- a/scripts/run-poetry.sh +++ b/scripts/run-poetry.sh @@ -6,17 +6,24 @@ cd "$(dirname "$0")/.." STATUS=$(git status --porcelain) -if [ -n "$STATUS" ]; then - echo "====================================================================" - echo "=============== ERROR: repository unclean, stopping! ===============" - echo "====================================================================" - echo - git status - echo - echo "====================================================================" - echo "=============== ERROR: repository unclean, stopping! ===============" - echo "====================================================================" - exit 1 +if [ -z "$RUN_ON_UNCLEAN" ]; then + if [ -n "$STATUS" ]; then + echo "================================================================" + echo "============= ERROR: repository unclean, stopping! =============" + echo "================================================================" + echo + git status + echo + echo "================================================================" + echo "============= ERROR: repository unclean, stopping! =============" + echo "================================================================" + echo + echo "To ignore this (you have been warned!), set an environment var:" + echo + echo "> export RUN_ON_UNCLEAN=true" + echo + exit 1 + fi fi ### clean up old poetry artifacts: From 0bc0dafd4b31fec17fb94aff947d81a797285b21 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 15:25:36 +0100 Subject: [PATCH 333/678] Automatic formatting --- 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 e3a67f47..b05d904b 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -3,6 +3,7 @@ from imcflibs import pathtools from imcflibs.imagej import bdv + def set_default_values(project_filename, file_path): """Set the default values for dataset definitions. @@ -18,7 +19,7 @@ def set_default_values(project_filename, file_path): str Start of the options for dataset definitions. """ - # Additional settings + # Additional settings file_info = pathtools.parse_path(file_path) options = ( @@ -37,7 +38,6 @@ def set_default_values(project_filename, file_path): return options - def test_define_dataset_auto_tile(tmp_path, caplog): """ Test automatic dataset definition method for tile series. From 9a68ffa1af409375f3deacdeff3db83ea3d00861 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 15:28:55 +0100 Subject: [PATCH 334/678] Make test consistent with actual code logic Refers to #32 --- tests/bdv/test_define_dataset_auto.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index b05d904b..85f4b641 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -62,7 +62,6 @@ def test_define_dataset_auto_tile(tmp_path, caplog): # Define the result and dataset save paths result_folder = pathtools.join2(file_info["path"], project_filename) - dataset_save_path = pathtools.join2(result_folder, project_filename) # Default settings @@ -82,7 +81,7 @@ def test_define_dataset_auto_tile(tmp_path, caplog): + "Re-save as multiresolution HDF5" + "] " + "dataset_save_path=[" - + dataset_save_path + + result_folder + "] " + "check_stack_sizes " + "split_hdf5 " From 9c4193ba9edca9e343147cd071eba7fb3eae8d04 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 15:33:15 +0100 Subject: [PATCH 335/678] Adjust action triggers --- .github/workflows/pytest-poetry.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index c2a29c1a..88f30116 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -7,9 +7,9 @@ on: branches: - master - devel - - gh-action tags: - - "*-[0-9]+.*" + - "*run-pytest.*" + - "*-[0-9]+.*" pull_request: branches: - master From fd84b94c1c141c8a3d3080999ce0acca444bbfe0 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 16:07:34 +0100 Subject: [PATCH 336/678] More test-vs-codelogic consistency Refers to #32 --- tests/bdv/test_define_dataset_auto.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index 85f4b641..914a8ae9 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -123,7 +123,6 @@ def test_define_dataset_auto_angle(tmp_path, caplog): # Define the result and dataset save paths result_folder = pathtools.join2(file_info["path"], project_filename) - dataset_save_path = pathtools.join2(result_folder, project_filename) # Default settings @@ -143,7 +142,7 @@ def test_define_dataset_auto_angle(tmp_path, caplog): + "Re-save as multiresolution HDF5" + "] " + "dataset_save_path=[" - + dataset_save_path + + result_folder + "] " + "check_stack_sizes " + "apply_angle_rotation " From 1b0de644fb09d73ca1c95704091b6c948ecae1ef Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 16:08:57 +0100 Subject: [PATCH 337/678] Run tests on PRs to 'devel' as well --- .github/workflows/pytest-poetry.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 88f30116..65be9dcc 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -13,6 +13,7 @@ on: pull_request: branches: - master + - devel jobs: pytest-poetry: From e119850614f980584bdbfb3e2b982542c3505d4a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 16:10:58 +0100 Subject: [PATCH 338/678] Don't run tests on pushes to 'devel' --- .github/workflows/pytest-poetry.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 65be9dcc..1c7f1355 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -6,7 +6,6 @@ on: push: branches: - master - - devel tags: - "*run-pytest.*" - "*-[0-9]+.*" From 6d35720a5e534c9059182e935a609c92be8e9608 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 16:22:36 +0100 Subject: [PATCH 339/678] Adjust action trigger --- .github/workflows/pytest-poetry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 1c7f1355..8e803db2 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -7,7 +7,7 @@ on: branches: - master tags: - - "*run-pytest.*" + - run-pytest.* - "*-[0-9]+.*" pull_request: branches: From dae7be025c297962879aa1587d7a54f907ca90fc Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 16:29:08 +0100 Subject: [PATCH 340/678] Relax tag naming pattern --- .github/workflows/pytest-poetry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 8e803db2..e214aa77 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -7,7 +7,7 @@ on: branches: - master tags: - - run-pytest.* + - run-pytest* - "*-[0-9]+.*" pull_request: branches: From 8e094472a1333d5eec109449410c91b6933265da Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 21:55:52 +0100 Subject: [PATCH 341/678] Update actions versions --- .github/workflows/pytest-poetry.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index e214aa77..88e63cec 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -19,7 +19,7 @@ jobs: # runs-on: ubuntu-latest runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Cache APT Packages uses: awalsh128/cache-apt-pkgs-action@v1.4.2 @@ -29,7 +29,7 @@ jobs: # If you wanted to use multiple Python versions, you'd have specify a # matrix in the job and reference the matrix python version here. - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v5.3.0 with: python-version: "3.10" @@ -40,7 +40,7 @@ jobs: # if/when you want to upgrade Poetry, or if something goes wrong (could be # done mildly cleaner by using an environment variable). - name: cache poetry install - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.local key: poetry-1.8.2-0 @@ -67,7 +67,7 @@ jobs: # it can be simple and just depend on the poetry.lock. - name: cache deps id: cache-deps - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: .venv key: pydeps-${{ hashFiles('**/poetry.lock') }} From f4608dd10b633f0d3886c45738f33a04bcf2482b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 21 Nov 2024 22:01:02 +0100 Subject: [PATCH 342/678] Enable explicit specification of a venv path This allows for using cached venvs e.g. with GH actions. --- scripts/py2-pytest.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 8a1c2b77..5cc21cea 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -4,15 +4,26 @@ set -o errexit # exit on any error set -o nounset # empty variables are not permitted cd "$(dirname "$0")"/.. -VENV="$(mktemp --directory --dry-run --tmpdir=. venv2.pytest-XXX)" + +if [ -n "$VENV_PATH" ]; then + VENV="$VENV_PATH" +else + VENV="$(mktemp --directory --dry-run --tmpdir=. venv2.pytest-XXX)" +fi + +if ! [ -d "$VENV" ]; then + echo "== Creating a Python2 venv in [$VENV]..." + python2 -m virtualenv "$VENV" + echo "== Finished creating a Python2 venv." +fi function vpip() { "$VENV/bin/pip" --no-python-version-warning "$@" } -echo "== Creating a Python2 venv in [$VENV]..." -python2 -m virtualenv "$VENV" -echo "== Finished creating a Python2 venv." +echo +echo "===== Using venv at: [$VENV] =====" +echo echo "== Installing local version of 'imcflibs' package..." # NOTE: for being able to use coverage, the package has to be installed in From 1caf284840e5d6c8e522f0f6491ccc1ace448424 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:14:09 +0100 Subject: [PATCH 343/678] Define missing variable --- scripts/py2-pytest.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 5cc21cea..4f4636d3 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -32,6 +32,8 @@ echo "== Installing local version of 'imcflibs' package..." # reverted after installing): ### parse the version from 'pom.xml': PACKAGE_VERSION=$(xmlstarlet sel --template -m _:project -v _:version pom.xml) +PACKAGE_NAME=$(xmlstarlet sel --template -m _:project -v _:artifactId pom.xml) + echo "Package version from POM: [$PACKAGE_VERSION]" ### make sure to have a valid Python package version: case $PACKAGE_VERSION in From 74c795d5ce6bb43d86bb7721fece3e7b2b3c60d9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:14:20 +0100 Subject: [PATCH 344/678] Show Python version from venv --- scripts/py2-pytest.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 4f4636d3..ef385cae 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -23,6 +23,7 @@ function vpip() { echo echo "===== Using venv at: [$VENV] =====" +"$VENV/bin/python" --version echo echo "== Installing local version of 'imcflibs' package..." From 180bff8bc5d86753d20527cf5f87a004be4f31f3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:14:43 +0100 Subject: [PATCH 345/678] Force-copy files for the venv --- scripts/py2-pytest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index ef385cae..46aaf50c 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -13,7 +13,7 @@ fi if ! [ -d "$VENV" ]; then echo "== Creating a Python2 venv in [$VENV]..." - python2 -m virtualenv "$VENV" + python2 -m virtualenv --always-copy "$VENV" echo "== Finished creating a Python2 venv." fi From 60fc39689aa9a27065c08c9700d200fe199be8a7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:15:11 +0100 Subject: [PATCH 346/678] Install 'virtualenv' module in case it's not there yet --- scripts/py2-pytest.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 46aaf50c..68c4e76a 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -12,6 +12,10 @@ else fi if ! [ -d "$VENV" ]; then + pip2 --no-python-version-warning show virtualenv > /dev/null || { + echo "== Installing 'virtualenv' for Python2..." + pip2 --no-python-version-warning install virtualenv + } echo "== Creating a Python2 venv in [$VENV]..." python2 -m virtualenv --always-copy "$VENV" echo "== Finished creating a Python2 venv." From 310843dd643d5548d845fde6bcfb5afa1964eca7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:18:51 +0100 Subject: [PATCH 347/678] Having 'nounset' is only possible *after* 'test -n' stuff --- scripts/py2-pytest.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 68c4e76a..9315ebad 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -1,8 +1,6 @@ #!/bin/bash set -o errexit # exit on any error -set -o nounset # empty variables are not permitted - cd "$(dirname "$0")"/.. if [ -n "$VENV_PATH" ]; then @@ -11,6 +9,10 @@ else VENV="$(mktemp --directory --dry-run --tmpdir=. venv2.pytest-XXX)" fi + +# now we're done checking the environment, so disallow empty variables below: +set -o nounset + if ! [ -d "$VENV" ]; then pip2 --no-python-version-warning show virtualenv > /dev/null || { echo "== Installing 'virtualenv' for Python2..." From 8878e7eb894b0700d6fe075cf73d279df81d187b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:19:05 +0100 Subject: [PATCH 348/678] Explain purpose of VENV_PATH --- scripts/py2-pytest.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 9315ebad..f129f987 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -4,6 +4,8 @@ set -o errexit # exit on any error cd "$(dirname "$0")"/.. if [ -n "$VENV_PATH" ]; then + # can be used for GH actions to cache the venv by giving it a fixed path: + echo "Using venv path from envvar VENV_PATH='$VENV_PATH'." VENV="$VENV_PATH" else VENV="$(mktemp --directory --dry-run --tmpdir=. venv2.pytest-XXX)" From 152f4a617a3361737019ead9a0a72bb488224502 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:19:45 +0100 Subject: [PATCH 349/678] Adjust PATH in case PYENV_ROOT is set --- scripts/py2-pytest.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index f129f987..a864398e 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -11,6 +11,16 @@ else VENV="$(mktemp --directory --dry-run --tmpdir=. venv2.pytest-XXX)" fi +if [ -n "$PYENV_ROOT" ]; then + # in case pyenv was retrieved e.g. from a GH actions cache, it will be + # present in the filesystem but not added to the path (this is done when + # the installer runs, but will not persist to subsequent runs that will get + # their pyenv extracted from the cache), therefore we're force-adding it: + echo "Found envvar PYENV_ROOT='$PYENV_ROOT', adjusting PATH..." + export PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" +fi + +echo "PATH=$PATH" # now we're done checking the environment, so disallow empty variables below: set -o nounset From 4425e5d8189eb8c1d877407020eddc9ceb0fcfe5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:20:05 +0100 Subject: [PATCH 350/678] Call 'pyenv local' in case PY_VERSION is set --- scripts/py2-pytest.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index a864398e..d6566074 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -22,6 +22,11 @@ fi echo "PATH=$PATH" +if [ -n "$PY_VERSION" ]; then + echo "Calling [pyenv local $PY_VERSION]..." + pyenv local "$PY_VERSION" +fi + # now we're done checking the environment, so disallow empty variables below: set -o nounset From ac6e6ca776af2af41eec2b30602233b1cdd409dc Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 16:20:28 +0100 Subject: [PATCH 351/678] Add GH action: pytest-python2 --- .github/workflows/pytest-python2.yml | 71 ++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/pytest-python2.yml diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml new file mode 100644 index 00000000..3e3f7dd7 --- /dev/null +++ b/.github/workflows/pytest-python2.yml @@ -0,0 +1,71 @@ +name: pytest (using Python2) + +on: + push: + branches: + - master + tags: + - run-pytest* + - "*-[0-9]+.*" + pull_request: + branches: + - master + - devel + +env: + PY_VERSION: 2.7.18 + +jobs: + pytest-poetry: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Cache APT packages + uses: awalsh128/cache-apt-pkgs-action@v1.4.2 + with: + packages: xmlstarlet + version: 1.0 + + - name: Cache pyenv installation + id: cache-pyenv + uses: actions/cache@v4 + with: + path: /opt/hostedtoolcache/pyenv_root + key: "pyenv-${{ env.PY_VERSION }}-3" + + - name: Set up pyenv + id: setup-pyenv + uses: "gabrielfalcao/pyenv-action@v18" + with: + default: "${{ env.PY_VERSION }}" + # Store the `PYENV_ROOT` var *inside* the cached directory so it can be + # retrieved consistently (independent of pyenv being freshly installed + # or coming from the cache). + # NOTE: In case pyenv was extracted from the cache, it WILL NOT BE ADDED + # to the PATH environment variable! Any follow-up task has to take + # this into account and **ACTIVELY** use the PYENV_ROOT variable! + command: | + echo $PYENV_ROOT > /opt/hostedtoolcache/pyenv_root/.pyenv_root + if: steps.cache-pyenv.outputs.cache-hit != 'true' + + - name: Identify PYENV_ROOT + id: pyenvroot + run: | + cat /opt/hostedtoolcache/pyenv_root/.pyenv_root + echo "PYENV_ROOT=$(cat /opt/hostedtoolcache/pyenv_root/.pyenv_root)" >> $GITHUB_OUTPUT + + - name: Cache Python2 virtualenv + id: cache-py2-venv + uses: actions/cache@v4 + with: + path: venv.py2 + key: "venv-py-${{ env.PY_VERSION }}--${{ hashFiles('pyproject.toml') }}-2" + + - name: Run pytest-wrapper + run: scripts/py2-pytest.sh + env: + VENV_PATH: venv.py2 + PYENV_ROOT: ${{ steps.pyenvroot.outputs.PYENV_ROOT }} + PY_VERSION: ${{ env.PY_VERSION }} + From 05439e029e25c426cf4551d9c12eed5ff2dbe830 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 17:44:13 +0100 Subject: [PATCH 352/678] Add Python-version specific triggers --- .github/workflows/pytest-poetry.yml | 1 + .github/workflows/pytest-python2.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 88e63cec..a4e28378 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -8,6 +8,7 @@ on: - master tags: - run-pytest* + - py3-pytest* - "*-[0-9]+.*" pull_request: branches: diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml index 3e3f7dd7..59cccf0d 100644 --- a/.github/workflows/pytest-python2.yml +++ b/.github/workflows/pytest-python2.yml @@ -6,6 +6,7 @@ on: - master tags: - run-pytest* + - py2-pytest* - "*-[0-9]+.*" pull_request: branches: From 7abcc92b46b49802458b75b4ea8c10b08bdeaf64 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 17:44:34 +0100 Subject: [PATCH 353/678] Use GITHUB_ENV --- .github/workflows/pytest-python2.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml index 59cccf0d..bd0a9f5e 100644 --- a/.github/workflows/pytest-python2.yml +++ b/.github/workflows/pytest-python2.yml @@ -54,7 +54,7 @@ jobs: id: pyenvroot run: | cat /opt/hostedtoolcache/pyenv_root/.pyenv_root - echo "PYENV_ROOT=$(cat /opt/hostedtoolcache/pyenv_root/.pyenv_root)" >> $GITHUB_OUTPUT + echo "PYENV_ROOT=$(cat /opt/hostedtoolcache/pyenv_root/.pyenv_root)" >> $GITHUB_ENV - name: Cache Python2 virtualenv id: cache-py2-venv @@ -67,6 +67,6 @@ jobs: run: scripts/py2-pytest.sh env: VENV_PATH: venv.py2 - PYENV_ROOT: ${{ steps.pyenvroot.outputs.PYENV_ROOT }} + # PYENV_ROOT: ${{ steps.pyenvroot.outputs.PYENV_ROOT }} PY_VERSION: ${{ env.PY_VERSION }} From 3fc6d6f29d5f61ce6827750aa019f2cf3e134038 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 17:44:45 +0100 Subject: [PATCH 354/678] Report coverage --- .github/workflows/pytest-poetry.yml | 2 +- .github/workflows/pytest-python2.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index a4e28378..0db354ba 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -87,4 +87,4 @@ jobs: - run: scripts/run-poetry.sh install --no-interaction # And finally run the tests. - - run: scripts/run-poetry.sh run pytest + - run: scripts/run-poetry.sh run pytest --cov -vv diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml index bd0a9f5e..26ebe399 100644 --- a/.github/workflows/pytest-python2.yml +++ b/.github/workflows/pytest-python2.yml @@ -64,7 +64,7 @@ jobs: key: "venv-py-${{ env.PY_VERSION }}--${{ hashFiles('pyproject.toml') }}-2" - name: Run pytest-wrapper - run: scripts/py2-pytest.sh + run: scripts/py2-pytest.sh --cov -vv env: VENV_PATH: venv.py2 # PYENV_ROOT: ${{ steps.pyenvroot.outputs.PYENV_ROOT }} From 994d3e3b34e2da052b3914bc2b00c7147c4e9f35 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 22 Nov 2024 17:49:56 +0100 Subject: [PATCH 355/678] Fix job name --- .github/workflows/pytest-python2.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml index 26ebe399..97cf9ce4 100644 --- a/.github/workflows/pytest-python2.yml +++ b/.github/workflows/pytest-python2.yml @@ -17,7 +17,7 @@ env: PY_VERSION: 2.7.18 jobs: - pytest-poetry: + pytest-python2: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 From 5fb3d8b092d09ad38d594fdb4284894289b9c803 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 26 Nov 2024 15:10:25 +0100 Subject: [PATCH 356/678] Remove commented line --- .github/workflows/pytest-python2.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml index 97cf9ce4..a8b80eb8 100644 --- a/.github/workflows/pytest-python2.yml +++ b/.github/workflows/pytest-python2.yml @@ -67,6 +67,5 @@ jobs: run: scripts/py2-pytest.sh --cov -vv env: VENV_PATH: venv.py2 - # PYENV_ROOT: ${{ steps.pyenvroot.outputs.PYENV_ROOT }} PY_VERSION: ${{ env.PY_VERSION }} From 2b17f368f014c90b0cb4974d6940c02ffdf322c2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 4 Dec 2024 16:02:40 +0100 Subject: [PATCH 357/678] Show installed package version on stdout --- scripts/py2-pytest.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index d6566074..946ed375 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -88,6 +88,9 @@ mv pyproject.toml pyproject_.toml echo "== * Installing package in editable mode..." vpip install --editable . +echo "== * Installed package version reported by pip:" +vpip show imcflibs | grep ^Version: + echo "== * Re-enabling 'pyproject.toml'..." mv pyproject_.toml pyproject.toml From 4b61ca7ee05a8773974da0b0baf0136c583349ae Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 17 Dec 2024 15:08:56 +0100 Subject: [PATCH 358/678] Work around io namespace clashes in Jython --- src/imcflibs/_jython_compat.py | 16 ++++++++++++++++ src/imcflibs/iotools.py | 10 +--------- src/imcflibs/strtools.py | 9 ++------- 3 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 src/imcflibs/_jython_compat.py diff --git a/src/imcflibs/_jython_compat.py b/src/imcflibs/_jython_compat.py new file mode 100644 index 00000000..f6b06d07 --- /dev/null +++ b/src/imcflibs/_jython_compat.py @@ -0,0 +1,16 @@ +"""Prevent namespace clashes / race conditions when running in Jython.""" + +import importlib + +# Using `import io` will heavily depend on the state of the Python engine and +# may fail when being used as a library inside Jython (as `io` might be shadowed +# by the Java package with the same name then) with something like this: +## AttributeError: 'javapackage' object has no attribute 'BufferedIOBase' +io = importlib.import_module("io") + +try: + # Python 2: "file" is built-in + file_types = file, io.IOBase +except NameError: + # Python 3: "file" fully replaced with IOBase + file_types = (io.IOBase,) diff --git a/src/imcflibs/iotools.py b/src/imcflibs/iotools.py index a47d179e..3f798c05 100644 --- a/src/imcflibs/iotools.py +++ b/src/imcflibs/iotools.py @@ -1,7 +1,5 @@ """I/O related functions.""" -import io - import glob import zipfile @@ -11,13 +9,7 @@ from .log import LOG as log from .strtools import flatten - -try: - # Python 2: "file" is built-in - file_types = file, io.IOBase -except NameError: - # Python 3: "file" fully replaced with IOBase - file_types = (io.IOBase,) +from ._jython_compat import file_types def filehandle(fname, mode="r"): diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index 2a11e3d7..462a18c9 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -1,14 +1,9 @@ """String related helper functions.""" -import io import re -try: # pragma: no cover - # Python 2: "file" is built-in - file_types = file, io.IOBase -except NameError: # pragma: no cover - # Python 3: "file" fully replaced with IOBase - file_types = (io.IOBase,) +from ._jython_compat import file_types + # this is taken from numpy's iotools: def _is_string_like(obj): From 60f06ce1b49648b0bc69e4ab51a510f621445269 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 17 Dec 2024 17:46:18 +0100 Subject: [PATCH 359/678] Only count the series without the resolution pyramid --- src/imcflibs/imagej/bioformats.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index fcee7459..303671fe 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -218,6 +218,7 @@ def get_series_count_from_ome_metadata(path_to_file): The number of Bio-Formats series detected in the image file metadata. """ reader = ImageReader() + reader.setFlattenedResolutions(False) ome_meta = MetadataTools.createOMEXMLMetadata() reader.setMetadataStore(ome_meta) reader.setId(path_to_file) From 56a6b810490129f651c9eda0eb3847d9f406d8ba Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 17 Dec 2024 17:50:39 +0100 Subject: [PATCH 360/678] Formatting --- src/imcflibs/imagej/bioformats.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 303671fe..8278efc3 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -14,10 +14,9 @@ from ij import IJ -from ._loci import BF, ImageReader, ImporterOptions, DynamicMetadataOptions, Memoizer, ZeissCZIReader - -from ..pathtools import gen_name_from_orig from ..log import LOG as log +from ..pathtools import gen_name_from_orig +from ._loci import BF, ImageReader, ImporterOptions, Memoizer, MetadataTools def import_image( From 92adac97dd1203c9f1e28da5d43a46008c1c20bc Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 18 Dec 2024 11:59:43 +0100 Subject: [PATCH 361/678] Some emojis --- .github/workflows/pytest-poetry.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 0db354ba..c256903d 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Cache APT Packages + - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages uses: awalsh128/cache-apt-pkgs-action@v1.4.2 with: packages: xmlstarlet @@ -30,7 +30,8 @@ jobs: # If you wanted to use multiple Python versions, you'd have specify a # matrix in the job and reference the matrix python version here. - - uses: actions/setup-python@v5.3.0 + - name: ๐Ÿ Set up Python + uses: actions/setup-python@v5.3.0 with: python-version: "3.10" @@ -40,7 +41,7 @@ jobs: # the "-0" suffix: this allows you to invalidate the cache manually # if/when you want to upgrade Poetry, or if something goes wrong (could be # done mildly cleaner by using an environment variable). - - name: cache poetry install + - name: ๐Ÿ—ƒ Cache Poetry install uses: actions/cache@v4 with: path: ~/.local @@ -56,7 +57,8 @@ jobs: # The key configuration value here is `virtualenvs-in-project: true`: this # creates the venv as a `.venv` in your testing directory, which allows # the next step to easily cache it. - - uses: snok/install-poetry@v1 + - name: ๐Ÿงฐ Install Poetry + uses: snok/install-poetry@v1 with: version: 1.8.2 virtualenvs-create: true @@ -66,7 +68,7 @@ jobs: # Note the cache key: if you're using multiple Python versions, or # multiple OSes, you'd need to include them in the cache key. I'm not, so # it can be simple and just depend on the poetry.lock. - - name: cache deps + - name: ๐Ÿ—ƒ Cache ๐Ÿงพ Dependencies id: cache-deps uses: actions/cache@v4 with: @@ -76,7 +78,8 @@ jobs: # Install dependencies. `--no-root` means "install all dependencies but # not the project itself", which is what you want to avoid caching _your_ # code. The `if` statement ensures this only runs on a cache miss. - - run: scripts/run-poetry.sh install --no-interaction --no-root + - name: ๐Ÿ— Install ๐Ÿงพ Dependencies + run: scripts/run-poetry.sh install --no-interaction --no-root if: steps.cache-deps.outputs.cache-hit != 'true' # Now install _your_ project. This isn't necessary for many types of @@ -84,7 +87,8 @@ jobs: # it's a good idea since it fully-exercises the pyproject.toml and makes # that if you add things like console-scripts at some point that they'll # be installed and working. - - run: scripts/run-poetry.sh install --no-interaction + - name: ๐Ÿ— Install ๐Ÿ›– project + run: scripts/run-poetry.sh install --no-interaction # And finally run the tests. - run: scripts/run-poetry.sh run pytest --cov -vv From fc4a8ba0a19ec22c9084f525d04fd0575e1736cf Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 18 Dec 2024 12:00:09 +0100 Subject: [PATCH 362/678] Create coverage report (XML) --- .github/workflows/pytest-poetry.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index c256903d..8bc845a5 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -91,4 +91,6 @@ jobs: run: scripts/run-poetry.sh install --no-interaction # And finally run the tests. - - run: scripts/run-poetry.sh run pytest --cov -vv + - name: ๐Ÿงช Run Tests + run: scripts/run-poetry.sh run pytest --color=yes --cov --cov-report=xml -vv + From eb560019c8406191e887ae15df4d7d8b4dc07b3d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 18 Dec 2024 12:04:13 +0100 Subject: [PATCH 363/678] Attempt to publish coverate on Codecov --- .github/workflows/pytest-poetry.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 8bc845a5..0b216c9e 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -94,3 +94,7 @@ jobs: - name: ๐Ÿงช Run Tests run: scripts/run-poetry.sh run pytest --color=yes --cov --cov-report=xml -vv + - name: Upload ๐Ÿ“Š coverage reports to โ˜‚ Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file From 27d8c8b3aabf15063f1f909deef667f9afd9693e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 18 Dec 2024 13:24:18 +0100 Subject: [PATCH 364/678] More emojis --- .github/workflows/pytest-python2.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml index a8b80eb8..fc10f248 100644 --- a/.github/workflows/pytest-python2.yml +++ b/.github/workflows/pytest-python2.yml @@ -22,20 +22,20 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Cache APT packages + - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages uses: awalsh128/cache-apt-pkgs-action@v1.4.2 with: packages: xmlstarlet version: 1.0 - - name: Cache pyenv installation + - name: ๐Ÿ—ƒ Cache pyenv installation id: cache-pyenv uses: actions/cache@v4 with: path: /opt/hostedtoolcache/pyenv_root key: "pyenv-${{ env.PY_VERSION }}-3" - - name: Set up pyenv + - name: ๐Ÿ๐Ÿงฐ Set up pyenv id: setup-pyenv uses: "gabrielfalcao/pyenv-action@v18" with: @@ -50,20 +50,20 @@ jobs: echo $PYENV_ROOT > /opt/hostedtoolcache/pyenv_root/.pyenv_root if: steps.cache-pyenv.outputs.cache-hit != 'true' - - name: Identify PYENV_ROOT + - name: ๐Ÿ•ต Identify PYENV_ROOT id: pyenvroot run: | cat /opt/hostedtoolcache/pyenv_root/.pyenv_root echo "PYENV_ROOT=$(cat /opt/hostedtoolcache/pyenv_root/.pyenv_root)" >> $GITHUB_ENV - - name: Cache Python2 virtualenv + - name: ๐Ÿ—ƒ๐Ÿ Cache Python2 virtualenv id: cache-py2-venv uses: actions/cache@v4 with: path: venv.py2 key: "venv-py-${{ env.PY_VERSION }}--${{ hashFiles('pyproject.toml') }}-2" - - name: Run pytest-wrapper + - name: ๐Ÿงชโš— Run pytest-wrapper run: scripts/py2-pytest.sh --cov -vv env: VENV_PATH: venv.py2 From 63101724eb4d9bed5c365136bc65f2d3fb552616 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Fri, 20 Dec 2024 10:56:34 +0100 Subject: [PATCH 365/678] Add reading addresses from Prefs and print errors Method now checks IJ preferences that has to be configured with a sender email address and an SMTP server. If either isn't present, print an error message and stop execution. --- src/imcflibs/imagej/misc.py | 68 ++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index dc783950..e7b9c52e 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -7,7 +7,7 @@ from ij import IJ # pylint: disable-msg=import-error from ij.plugin.frame import RoiManager # pylint: disable-msg=import-error from ij.measure import ResultsTable # pylint: disable-msg=import-error - +from ij import Prefs as prefs from ..log import LOG as log @@ -137,59 +137,59 @@ def find_focus(imp): def send_mail(job_name, recipient, filename, total_execution_time): - """Send an email via smtp.unibas.ch. - Will likely NOT work without connection to the unibas network. + """Send an email using the SMTP server and sender email configured in ImageJ's Preferences. Parameters ---------- - recipient : string - recipients email address job_name : string - Job name to display in the email + Job name to display in the email. + recipient : string + Recipient's email address. filename : string - the name of the file to be passed in the email + The name of the file to be passed in the email. total_execution_time : str - the time it took to process the file in the format [HH:MM:SS:ss] + The time it took to process the file in the format [HH:MM:SS:ss]. """ - server = prefs.get("imcf.smtpserver", None) - if server is None: - print( - "Mail notifications only works if configured. Please use Plugins/IMCF_Utilities" - ) + # Retrieve sender email and SMTP server from preferences + sender = prefs.get("imcf.sender_email", "").strip() + server = prefs.get("imcf.smtpserver", "").strip() + + # Ensure the sender and server are configured from Prefs + if not sender: + print("Sender email is not configured. Please check IJ_Prefs.txt.") + return + if not server: + print("SMTP server is not configured. Please check IJ_Prefs.txt.") return - sender = prefs.get("imcf.sender_email", "") - if sender is "": - print( - "Mail notifications only works if configured. Please use Plugins/IMCF_Utilities" - ) + # Ensure the recipient is provided + if not recipient.strip(): + print("Recipient email is required.") return - header = "From: %s\n" - header += "To: %s\n" - header += "Subject: Your %s job finished successfully\n\n" - text = ( + # Form the email subject and body + subject = "Your {0} job finished successfully".format(job_name) + body = ( "Dear recipient,\n\n" "This is an automated message.\n" - "Your dataset %s has been successfully processed (%s [HH:MM:SS:ss]).\n\n" + "Your dataset '{0}' has been successfully processed " + "({1} [HH:MM:SS:ss]).\n\n" "Kind regards,\n" "The IMCF-team" - ) + ).format(filename, total_execution_time) - message = header + text + # Form the complete message + message = ("From: {0}\nTo: {1}\nSubject: {2}\n\n{3}").format( + sender, recipient, subject, body + ) + # Try sending the email, print error message if it wasn't possible try: smtpObj = smtplib.SMTP(server) - smtpObj.sendmail( - sender, - job_name, - recipient, - message % (sender, recipient, job_name, filename, total_execution_time), - ) + smtpObj.sendmail(sender, recipient, message) print("Successfully sent email") - except smtplib.SMTPException: - print("Error: unable to send email") - + except smtplib.SMTPException as err: + print("Error: Unable to send email:", err) return From a17609d5ccdeceba0dc5027abc8160e53733ec36 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 14 Jan 2025 15:15:06 +0100 Subject: [PATCH 366/678] Replace print with log statement for consistency --- src/imcflibs/imagej/misc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index e7b9c52e..1ec7d187 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -156,15 +156,15 @@ def send_mail(job_name, recipient, filename, total_execution_time): # Ensure the sender and server are configured from Prefs if not sender: - print("Sender email is not configured. Please check IJ_Prefs.txt.") + log.info("Sender email is not configured. Please check IJ_Prefs.txt.") return if not server: - print("SMTP server is not configured. Please check IJ_Prefs.txt.") + log.info("SMTP server is not configured. Please check IJ_Prefs.txt.") return # Ensure the recipient is provided if not recipient.strip(): - print("Recipient email is required.") + log.info("Recipient email is required.") return # Form the email subject and body @@ -187,9 +187,9 @@ def send_mail(job_name, recipient, filename, total_execution_time): try: smtpObj = smtplib.SMTP(server) smtpObj.sendmail(sender, recipient, message) - print("Successfully sent email") + log.debug("Successfully sent email") except smtplib.SMTPException as err: - print("Error: Unable to send email:", err) + log.warning("Error: Unable to send email: %s", err) return From 6e01a09c92e821f2f3c75cfe151a3990447ba661 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 14 Jan 2025 15:31:03 +0100 Subject: [PATCH 367/678] Update CHANGELOG with `send_mail` --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0428848..e23627b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ## 1.5.0 +FIXME: complete description for `send_mail` configuration below! + ### Added * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and @@ -39,6 +41,8 @@ propagating transformation parameters to other channels. * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Dataset" command. +* `imcflibs.imagej.misc.send_mail` to send notification emails to users, e.g. + upon completion of a long running script (configurable via user preferences). ## 1.4.0 From 7227eb28297ce4cff1ca94070ae3cc5d239019f7 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 30 Jan 2025 17:10:28 +0100 Subject: [PATCH 368/678] Add some missing Bio-Formats imports --- src/imcflibs/imagej/bioformats.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 8278efc3..5954b795 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -16,7 +16,15 @@ from ..log import LOG as log from ..pathtools import gen_name_from_orig -from ._loci import BF, ImageReader, ImporterOptions, Memoizer, MetadataTools +from ._loci import ( + BF, + DynamicMetadataOptions, + ImageReader, + ImporterOptions, + Memoizer, + MetadataTools, + ZeissCZIReader, +) def import_image( From 71989c8092f02f9e0929a2408f2e75ff6ff79999 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 3 Feb 2025 09:10:51 +0100 Subject: [PATCH 369/678] Update action version --- .github/workflows/pytest-poetry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 0b216c9e..4c9371ed 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -31,7 +31,7 @@ jobs: # If you wanted to use multiple Python versions, you'd have specify a # matrix in the job and reference the matrix python version here. - name: ๐Ÿ Set up Python - uses: actions/setup-python@v5.3.0 + uses: actions/setup-python@v5.4.0 with: python-version: "3.10" From 24ad5c2e42d6f3084bd789bdfc7bb3263e168c16 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 3 Feb 2025 09:16:17 +0100 Subject: [PATCH 370/678] Action debugging --- .github/workflows/pytest-poetry.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 4c9371ed..1bb7558c 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -1,6 +1,6 @@ ## action file inspired by https://jacobian.org/til/github-actions-poetry/ -name: pytest (via poetry) +name: ๐Ÿงช pytest (via ๐ŸŽญ Poetry) on: push: @@ -22,11 +22,11 @@ jobs: steps: - uses: actions/checkout@v4 - - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages - uses: awalsh128/cache-apt-pkgs-action@v1.4.2 - with: - packages: xmlstarlet - version: 1.0 + # - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages + # uses: awalsh128/cache-apt-pkgs-action@v1.4.2 + # with: + # packages: xmlstarlet + # version: 1.0 # If you wanted to use multiple Python versions, you'd have specify a # matrix in the job and reference the matrix python version here. @@ -94,7 +94,7 @@ jobs: - name: ๐Ÿงช Run Tests run: scripts/run-poetry.sh run pytest --color=yes --cov --cov-report=xml -vv - - name: Upload ๐Ÿ“Š coverage reports to โ˜‚ Codecov - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + # - name: Upload ๐Ÿ“Š coverage reports to โ˜‚ Codecov + # uses: codecov/codecov-action@v5 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file From ac42611ff7a1b30365004ca835b89dca13440db9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 3 Feb 2025 09:24:31 +0100 Subject: [PATCH 371/678] Workflow emojis --- .github/workflows/pytest-poetry.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 1bb7558c..ec0e56f7 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -19,8 +19,11 @@ jobs: pytest-poetry: # runs-on: ubuntu-latest runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + name: ๐Ÿ“ฅ Checkout repo # - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages # uses: awalsh128/cache-apt-pkgs-action@v1.4.2 @@ -41,7 +44,7 @@ jobs: # the "-0" suffix: this allows you to invalidate the cache manually # if/when you want to upgrade Poetry, or if something goes wrong (could be # done mildly cleaner by using an environment variable). - - name: ๐Ÿ—ƒ Cache Poetry install + - name: ๐Ÿ—ƒ Cache ๐ŸŽญ Poetry install uses: actions/cache@v4 with: path: ~/.local @@ -57,7 +60,7 @@ jobs: # The key configuration value here is `virtualenvs-in-project: true`: this # creates the venv as a `.venv` in your testing directory, which allows # the next step to easily cache it. - - name: ๐Ÿงฐ Install Poetry + - name: ๐Ÿ”ฉ๐Ÿ”ง Install ๐ŸŽญ Poetry uses: snok/install-poetry@v1 with: version: 1.8.2 @@ -78,7 +81,7 @@ jobs: # Install dependencies. `--no-root` means "install all dependencies but # not the project itself", which is what you want to avoid caching _your_ # code. The `if` statement ensures this only runs on a cache miss. - - name: ๐Ÿ— Install ๐Ÿงพ Dependencies + - name: ๐ŸŽญ Install ๐Ÿงพ Dependencies run: scripts/run-poetry.sh install --no-interaction --no-root if: steps.cache-deps.outputs.cache-hit != 'true' @@ -87,11 +90,11 @@ jobs: # it's a good idea since it fully-exercises the pyproject.toml and makes # that if you add things like console-scripts at some point that they'll # be installed and working. - - name: ๐Ÿ— Install ๐Ÿ›– project + - name: ๐ŸŽญ Install ๐Ÿ›– project run: scripts/run-poetry.sh install --no-interaction # And finally run the tests. - - name: ๐Ÿงช Run Tests + - name: ๐Ÿงช๐ŸŽญ Run Tests run: scripts/run-poetry.sh run pytest --color=yes --cov --cov-report=xml -vv # - name: Upload ๐Ÿ“Š coverage reports to โ˜‚ Codecov From b09f10e5fbf0a223b8219db5e315a774a87368ce Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 3 Feb 2025 09:26:37 +0100 Subject: [PATCH 372/678] Upgrade action This is required in order to avoid the action failing due to the usage of a deprecated version of `actions/upload-artifact` (v3). --- .github/workflows/pytest-poetry.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index ec0e56f7..1739a049 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -25,11 +25,11 @@ jobs: - uses: actions/checkout@v4 name: ๐Ÿ“ฅ Checkout repo - # - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages - # uses: awalsh128/cache-apt-pkgs-action@v1.4.2 - # with: - # packages: xmlstarlet - # version: 1.0 + - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages + uses: awalsh128/cache-apt-pkgs-action@v1.4.3 + with: + packages: xmlstarlet + version: 1.0 # If you wanted to use multiple Python versions, you'd have specify a # matrix in the job and reference the matrix python version here. From d452ff65b52106af5e9049a0fccb353a1410e5d4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 3 Feb 2025 09:26:55 +0100 Subject: [PATCH 373/678] Re-activate codecov action --- .github/workflows/pytest-poetry.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 1739a049..e5d4a48a 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -97,7 +97,7 @@ jobs: - name: ๐Ÿงช๐ŸŽญ Run Tests run: scripts/run-poetry.sh run pytest --color=yes --cov --cov-report=xml -vv - # - name: Upload ๐Ÿ“Š coverage reports to โ˜‚ Codecov - # uses: codecov/codecov-action@v5 - # with: - # token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + - name: ๐Ÿ“ค Upload ๐Ÿ“Š coverage reports to โ˜‚ Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file From 7565cf3e3d901b88317b6e8e9127293315bf91b7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 3 Feb 2025 09:27:08 +0100 Subject: [PATCH 374/678] Add Poetry dynamic versioning plugin --- .github/workflows/pytest-poetry.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index e5d4a48a..05813f34 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -78,6 +78,9 @@ jobs: path: .venv key: pydeps-${{ hashFiles('**/poetry.lock') }} + - name: ๐ŸŽญ Install Poetry dynamic-versioning ๐Ÿ”Œ plugin + run: poetry self add "poetry-dynamic-versioning[plugin]" + # Install dependencies. `--no-root` means "install all dependencies but # not the project itself", which is what you want to avoid caching _your_ # code. The `if` statement ensures this only runs on a cache miss. From 1f7fc1382ccbb99c6d66cbbdffcc45712d2e1a2c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 3 Feb 2025 09:36:17 +0100 Subject: [PATCH 375/678] Fix workflow for pytest via Python2 --- .github/workflows/pytest-python2.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml index fc10f248..43381338 100644 --- a/.github/workflows/pytest-python2.yml +++ b/.github/workflows/pytest-python2.yml @@ -1,4 +1,4 @@ -name: pytest (using Python2) +name: ๐Ÿงช pytest (using ๐Ÿ Python2) on: push: @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages - uses: awalsh128/cache-apt-pkgs-action@v1.4.2 + uses: awalsh128/cache-apt-pkgs-action@v1.4.3 with: packages: xmlstarlet version: 1.0 From d7336906681856f3b260d66ed1cfa9a63259ce1c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Feb 2025 11:29:25 +0100 Subject: [PATCH 376/678] Swap method to be more instructive with container file --- src/imcflibs/imagej/bioformats.py | 66 +++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 5954b795..60865845 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -209,30 +209,70 @@ def export_using_orig_name(imp, path, orig_name, tag, suffix, overwrite=False): return out_file -def get_series_count_from_ome_metadata(path_to_file): - """Get the Bio-Formates series count from a file on disk. +def get_series_info_from_ome_metadata(path_to_file, skip_labels=False): + """Get the Bio-Formats series information from a file on disk. - Useful to access a specific image in a container format like .czi, .nd2, .lif... + Useful to access specific images in container formats like .czi, .nd2, .lif... Parameters ---------- path_to_file : str The full path to the image file. + skip_labels : bool, optional + If True, excludes label and macro images from the series count (default: False). Returns ------- - int - The number of Bio-Formats series detected in the image file metadata. + tuple + A tuple containing: + - int: The number of Bio-Formats series detected (excluding labels if skip_labels=True) + - list or range: Series indices. If skip_labels=True, returns filtered list of indices, + otherwise returns range(series_count) + + Examples + -------- + >>> count, indices = get_series_info_from_ome_metadata("image.czi") + >>> count, indices = get_series_info_from_ome_metadata("image.nd2", skip_labels=True) """ - reader = ImageReader() - reader.setFlattenedResolutions(False) - ome_meta = MetadataTools.createOMEXMLMetadata() - reader.setMetadataStore(ome_meta) - reader.setId(path_to_file) - series_count = reader.getSeriesCount() - reader.close() - return series_count + if not skip_labels: + reader = ImageReader() + reader.setFlattenedResolutions(False) + ome_meta = MetadataTools.createOMEXMLMetadata() + reader.setMetadataStore(ome_meta) + reader.setId(path_to_file) + series_count = reader.getSeriesCount() + + reader.close() + return series_count, range(series_count) + + else: + reader = ImageReader() + # reader.setFlattenedResolutions(True) + ome_meta = MetadataTools.createOMEXMLMetadata() + reader.setMetadataStore(ome_meta) + reader.setId(path_to_file) + series_count = reader.getSeriesCount() + + series_ids = [] + series_names = [] + x = 0 + y = 0 + for i in range(series_count): + reader.setSeries(i) + + if reader.getSizeX() > x and reader.getSizeY() > y: + name = ome_meta.getImageName(i) + + if name not in ["label image", "macro image"]: + series_ids.append(i) + series_names.append(name) + + x = reader.getSizeX() + y = reader.getSizeY() + + print(series_names) + return len(series_ids), series_ids def write_bf_memoryfile(path_to_file): From 3fef8e781ce5d63fc517902555d379fe44dd74f6 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 11 Feb 2025 15:37:16 +0100 Subject: [PATCH 377/678] Remove method that returns names of objects Redundant method as it was integrated into 3DImageSuite --- src/imcflibs/imagej/objects3d.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 75225a14..72ec239c 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -130,22 +130,3 @@ 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 get_objects3Dpop_names(obj_pop): - """Get the names of all the 3D objects in the specified Objects3DPopulation - - Parameters - ---------- - obj_pop : mcib3d.geom.Objects3DPopulation - Population of 3D objects - - Returns - ------- - str[] - List of the names of all the 3D objects in the population - """ - names = [] - for i in range(0, obj_pop.getNbObjects()): - names.append(obj_pop.getObject(i).getName()) - return names From e8a915c8f900bb28779f1e367b267bff612cadd6 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 11 Feb 2025 15:38:24 +0100 Subject: [PATCH 378/678] Refactor docstrings to follow convention --- src/imcflibs/imagej/objects3d.py | 43 +++++++++++++++++++------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 72ec239c..2b0f410c 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -13,14 +13,16 @@ def population3d_to_imgplus(imp, population): imp : ij.ImagePlus Original ImagePlus to derive the size of the resulting ImagePlus. population : mcib3d.geom.Objects3DPopulation - Population to use to generate the new ImagePlus. + Population of 3D objects used to generate the new ImagePlus. Returns ------- - ImagePlus - Newly created ImagePlus from the population. + ij.ImagePlus + A newly created ImagePlus representing the labeled population. """ dim = imp.getDimensions() + + # Create a new 16-bit image with the same size as the original image new_imp = IJ.createImage( "Filtered labeled stack", "16-bit black", @@ -31,6 +33,8 @@ def population3d_to_imgplus(imp, population): dim[4], ) new_imp.setCalibration(imp.getCalibration()) + + # Wrap the new image in an ImageHandler and draw the population new_img = ImageHandler.wrap(new_imp) population.drawPopulation(new_img) @@ -45,12 +49,12 @@ def imgplus_to_population3d(imp): Parameters ---------- imp : ij.ImagePlus - Labeled 3D stack or 2D image to use to get population. + Labeled 2D image or 3D stack used to get the population. Returns ------- mcib3d.geom.Objects3DPopulation - Population from the image. + The extracted population from the image. """ img = ImageHandler.wrap(imp) return Objects3DPopulation(img) @@ -62,24 +66,28 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): Parameters ---------- imp : ij.ImagePlus - Binary 3D stack. + A binary 3D stack for segmentation. title : str, optional - Title of the new image. + Title of the new image. Defaults to None. min_thresh : int, optional - Threshold to do segmentation, also allows for label filtering, by default 1. - Since the segmentation is happening on a binary stack, values are either 0 or 255 + Threshold to do segmentation, also allows for label filtering. Defaults to 1. + Since the segmentation is happening on a binary stack, values are either 0 or 255, so using 0 allows to discard only the background. min_vol : int, optional - Volume (voxels) under which to filter objects, by default None. + Minimum volume (in voxels) under which objects get filtered. + Defaults to None. max_vol : int, optional - Volume above which to filter objects, by default None. + Maximum volume (in voxels) above which objects get filtered. + Defaults to None. Returns ------- ij.ImagePlus - Segmented 3D labelled ImagePlus. + A labelled 3D ImagePlus. """ cal = imp.getCalibration() + + # Wrap through ImageHandler and apply thresholding img = ImageHandler.wrap(imp) img = img.threshold(min_thresh, False, False) @@ -89,6 +97,7 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): if max_vol: labeler.setMaxSizeCalibrated(max_vol, img) + # Generate labelled segmentation seg = labeler.getLabels(img) seg.setScale(cal.pixelWidth, cal.pixelDepth, cal.getUnits()) if title: @@ -103,14 +112,14 @@ def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): Parameters ---------- - obj_pop : Objects3DPopulation - Population of 3D objects + obj_pop : mcib3d.geom.Objects3DPopulation + A population of 3D objects. imp : ij.ImagePlus - ImagePlus on which the population is based + An ImagePlus on which the population is based. min_intensity : float - Minimum mean intensity of the objects + Minimum mean intensity threshold for filtering objects. max_intensity : float - Maximum mean intensity of the objects + Maximum mean intensity threshold for filtering objects. Returns ------- From 279e52e2f5c2fe2a2bf5c1e7a2e862e999065d23 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Feb 2025 20:54:47 +0100 Subject: [PATCH 379/678] Add note about 'simple-omero-client' dependency --- src/imcflibs/imagej/omerotools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 4c46aff6..1fcedbf5 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -2,6 +2,10 @@ Contains helpers to parse URLs and / or OMERO image IDs, connect to OMERO and fetch images from the server. + +Requires the [`simple-omero-client`][simple-omero-client] JAR to be installed. + +[simple-omero-client]: https://github.com/GReD-Clermont/simple-omero-client """ from java.lang import Long From ed245c24e4f03c963abc0b4588ea37a2cac5f7f6 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Feb 2025 20:57:50 +0100 Subject: [PATCH 380/678] Update a few 'omerotools' docstrings Conventions, typos and such. --- src/imcflibs/imagej/omerotools.py | 45 +++++++++++++++++-------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 1fcedbf5..5ce659fd 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -16,20 +16,25 @@ def parse_url(client, omero_str): - """Parse an OMERO URL with one or multiple images selected or a dataset - and return the ImageWrapper list + """Parse an OMERO URL / image IDs into the respective ImageWrapper objects. + + Assemble a list of ImageWrapper objects from one of the following inputs: + + - An OMERO URL (as retrieved e.g. from OMERO.web). + - One or more OMERO image IDs. + - An OMERO Dataset ID. Parameters ---------- client : fr.igred.omero.Client - Client used for login to OMERO + Client used for login to OMERO. omero_str : str - String which is either the link gotten from OMERO or image IDs separated by commas + Either an URL from OMERO or image IDs separated by commas. Returns ------- - list(fr.igred.omero.repositor.ImageWrapper) - List of ImageWrappers parsed from the string + list(fr.igred.omero.repository.ImageWrapper) + List of ImageWrappers parsed from the string. """ image_ids = [] dataset_ids = [] @@ -105,7 +110,7 @@ def connect(host, port, username, password): Returns ------- - client: fr.igred.omero.Client + fr.igred.omero.Client A Client object representing the connection to the OMERO server. """ # Create a new OMERO client @@ -124,9 +129,9 @@ def fetch_image(client, image_id): Parameters ---------- client : fr.igred.omero.Client - The OMERO client used to connect to the server + The client object used to connect to the OMERO server. image_id : int - The ID of the image to fetch + The ID of the image to fetch. Returns ------- @@ -142,16 +147,16 @@ def fetch_image(client, image_id): def upload_image_to_omero(user_client, path, dataset_id): - """Upload the image back to OMERO + """Upload an image to OMERO. Parameters ---------- user_client : fr.igred.omero.Client - Client used for login to OMERO + The client object used to connect to the OMERO server. path : str - Path of the file to upload back to OMERO + Path of the file to upload back to OMERO. dataset_id : Long - ID of the dataset where to upload the file + ID of the dataset where to upload the file. Returns ------- @@ -162,18 +167,18 @@ def upload_image_to_omero(user_client, path, dataset_id): def add_annotation(client, repository_wpr, dict, header): - """Add annotation to OMERO object + """Add an annotation to an OMERO object. Parameters ---------- user_client : fr.igred.omero.Client - Client used for login to OMERO + The client object used to connect to the OMERO server. repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper - Wrapper to the object for the anotation + Wrapper to the object for the anotation. dict : dict - Dictionary with the annotation to add + Dictionary with the annotation to add. header : str - Name for the annotation header + Name for the annotation header. """ # for pair in dict: # result.add @@ -183,12 +188,12 @@ def add_annotation(client, repository_wpr, dict, header): def find_dataset(client, dataset_id): - """Retrieve a dataset from the OMERO server. + """Retrieve a dataset (wrapper) from the OMERO server. Parameters ---------- client : fr.igred.omero.Client - The OMERO client used to connect to the server. + The client object used to connect to the OMERO server. dataset_id : int The ID of the dataset to retrieve. From e19e28d3fbecd10908e37104ee2e0909f6c979b5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Feb 2025 21:05:21 +0100 Subject: [PATCH 381/678] Avoid Python keywords as variable names --- src/imcflibs/imagej/omerotools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 5ce659fd..8b5a3e07 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -166,7 +166,7 @@ def upload_image_to_omero(user_client, path, dataset_id): return user_client.getDataset(Long(dataset_id)).importImage(user_client, path)[0] -def add_annotation(client, repository_wpr, dict, header): +def add_annotation(client, repository_wpr, annotations, header): """Add an annotation to an OMERO object. Parameters @@ -175,14 +175,14 @@ def add_annotation(client, repository_wpr, dict, header): The client object used to connect to the OMERO server. repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper Wrapper to the object for the anotation. - dict : dict + annotations : dict Dictionary with the annotation to add. header : str Name for the annotation header. """ # for pair in dict: # result.add - map_annotation_wpr = MapAnnotationWrapper(dict) + map_annotation_wpr = MapAnnotationWrapper(annotations) map_annotation_wpr.setNameSpace(header) repository_wpr.addMapAnnotation(client, map_annotation_wpr) From 9bb270433c9e7629bbcbd9884c0d2d0e177bcf62 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Feb 2025 21:47:59 +0100 Subject: [PATCH 382/678] Some more docstring conventions / rewordings --- src/imcflibs/imagej/objects3d.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 2b0f410c..9bcbd384 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -70,14 +70,14 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): title : str, optional Title of the new image. Defaults to None. min_thresh : int, optional - Threshold to do segmentation, also allows for label filtering. Defaults to 1. - Since the segmentation is happening on a binary stack, values are either 0 or 255, - so using 0 allows to discard only the background. + Threshold to do segmentation, also allows for label filtering. Since the + segmentation is happening on a binary stack, values are either 0 or 255, + so using 0 allows to discard only the background. Defaults to 1. min_vol : int, optional - Minimum volume (in voxels) under which objects get filtered. + Minimum volume (in voxels) under which objects get filtered. Defaults to None. max_vol : int, optional - Maximum volume (in voxels) above which objects get filtered. + Maximum volume (in voxels) above which objects get filtered. Defaults to None. Returns @@ -107,8 +107,7 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): - """Return a new population with the objects that have a mean intensity within - the specified range. + """Filter a population for objects within the given intensity range. Parameters ---------- @@ -124,7 +123,7 @@ def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): Returns ------- Objects3DPopulation - New population with the objects filtered by intensity + New population with the objects filtered by intensity. """ objects_within_intensity = [] From 85c1c403dd1342664a30ae9c89616400cd4bbec7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Feb 2025 21:55:26 +0100 Subject: [PATCH 383/678] Update changelog for objects3d --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0428848..180b638c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ an Objects3DPopulation into an ImagePlus (2D/3D). * `imcflibs.imagej.objects3d.imgplus_to_population3d` to get the Objects3DPopulation from an ImagePlus (2D/3D). + * `imcflibs.imagej.objects3d.segment_3d_image` to threshold an image into a + labeled stack. + * `imcflibs.imagej.objects3d.get_objects_within_intensity` to filter a + population of 3D objects by intensity. * New `imcflibs.imagej.bdv` submodule, providing BigDataViewer related functions: * `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files From aab26aba01ff43ea9b840ca44b72535377047169 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Wed, 12 Feb 2025 09:51:48 +0100 Subject: [PATCH 384/678] Change docstrings to follow convention --- src/imcflibs/imagej/misc.py | 166 +++++++++++++++++++++--------------- 1 file changed, 96 insertions(+), 70 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 55425b9f..5a58c70c 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -13,39 +13,63 @@ def show_status(msg): - """Wrapper to update the ImageJ status bar and the log simultaneously.""" + """Update the ImageJ status bar and issue a log message. + + Parameters + ---------- + msg : str + The message to display in the ImageJ status bar and log. + """ log.info(msg) IJ.showStatus(msg) def show_progress(cur, final): - """Wrapper to update the progress bar and issue a log message.""" - # ij.IJ.showProgress is adding 1 to the value given as first parameter... + """Update the ImageJ progress bar and log the current progress. + + Parameters + ---------- + cur : int + Current progress value. + final : int + Total value representing 100% completion. + + Notes + ----- + `ij.IJ.showProgress` internally increments the given `cur` value by 1 + """ log.info("Progress: %s / %s (%s)", cur + 1, final, (1.0 + cur) / final) IJ.showProgress(cur, final) def error_exit(msg): - """Convenience wrapper to log an error and exit then.""" + """Log an error message and exit. + + Parameters + ---------- + msg : str + The error message to log. + """ log.error(msg) sys.exit(msg) def elapsed_time_since(start, end=None): - """Generate a string with the time elapsed between the two timepoints. + """Generate a string with the time elapsed between two timepoints. Parameters ---------- - start : time.time - Start time. - end : time.time, optional - End time. If skipped the current time will be used. + start : float + The start time, typically obtained via `time.time()`. + end : float, optional + The end time. If not given, the current time is used. Returns ------- str - """ + The elapsed time formatted as `HH:MM:SS.ss`. + """ if not end: end = time.time() @@ -60,13 +84,14 @@ def percentage(part, whole): Parameters ---------- part : float - Part. + The portion value of a total. whole : float - Complete size. + The total value. Returns ------- float + The percentage value. """ return 100 * float(part) / float(whole) @@ -76,13 +101,13 @@ def calculate_mean_and_stdv(float_values): Parameters ---------- - float_values : list(float) + float_values : list of float List containing float numbers. Returns ------- - [float, float] - Mean (1st item) and standard deviation (2nd item) of the list. + tuple of (float, float) + Mean and standard deviation of the input list. """ mean = sum(float_values) / len(float_values) tot = 0.0 @@ -92,21 +117,30 @@ def calculate_mean_and_stdv(float_values): def find_focus(imp): - """Find the slice of a stack that seems to bet the best focused one. - - NOTE: currently only single-channel stacks are supported. + """Find the slice of a stack that is the best focused one. - FIXME: explain what the function is actually doing, i.e. how does it decide - what "the best focused one" is? + This function calculates the variance of the pixel values in each slice. + The slice with the highest variance is considered the best focused + because a higher variance typically indicates more contrast and sharpness. Parameters ---------- imp : ij.ImagePlus - A single-channel ImagePlus. + A single-channel ImagePlus stack. Returns ------- int + The slice number of the best focused slice. + + Raises + ------ + SystemExit + If the image has more than one channel. + + Notes + ----- + Currently only single-channel stacks are supported. """ imp_dimensions = imp.getDimensions() @@ -116,7 +150,7 @@ def find_focus(imp): if imp_dimensions[2] != 1: sys.exit("Image has more than one channel, please reduce dimensionality") - # Loop through each time points + # Loop through each time point for plane in range(1, imp_dimensions[4] + 1): focused_slice = 0 norm_var = 0 @@ -186,7 +220,7 @@ def timed_log(message, as_string=False): def get_free_memory(): - """Get the free memory thats available to ImageJ. + """Get the free memory that is available to ImageJ. Returns ------- @@ -203,13 +237,8 @@ def get_free_memory(): def setup_clean_ij_environment(rm=None, rt=None): # pylint: disable-msg=unused-argument """Set up a clean and defined ImageJ environment. - Clean active results table, roi manager and log, close any open image. - - "Fresh Start" is described in the ImageJ release notes [1] following a - suggestion by Robert Haase in the Image.sc Forum [2]. - - [1]: https://imagej.nih.gov/ij/notes.html - [2]: https://forum.image.sc/t/fresh-start-macro-command-in-imagej-fiji/43102 + This funtion clears the active results table, the ROI manager, and the log. + Additionally, it closes all open images and resets the ImageJ options, performing a "Fresh Start". Parameters ---------- @@ -217,6 +246,14 @@ def setup_clean_ij_environment(rm=None, rt=None): # pylint: disable-msg=unused- Will be ignored (kept for keeping API compatibility). rt : ResultsTable, optional Will be ignored (kept for keeping API compatibility). + + Notes + ----- + "Fresh Start" is described in the ImageJ release notes [1] following a + suggestion by Robert Haase in the Image.sc Forum [2]. + + [1]: https://imagej.nih.gov/ij/notes.html + [2]: https://forum.image.sc/t/fresh-start-macro-command-in-imagej-fiji/43102 """ IJ.run("Fresh Start", "") @@ -226,12 +263,17 @@ def setup_clean_ij_environment(rm=None, rt=None): # pylint: disable-msg=unused- def sanitize_image_title(imp): - """Remove special chars and various suffixes from an open ImagePlus. + """Remove special characters and various suffixes from the title of an open ImagePlus. Parameters ---------- imp : ImagePlus The ImagePlus to be renamed. + + Notes + ----- + - The function removes the full path of the image file (if present), retaining only + the base filename using `os.path.basename()`. """ # sometimes (unclear when) the title contains the full path, therefore we # simply call `os.path.basename()` on it to remove all up to the last "/": @@ -250,15 +292,15 @@ def subtract_images(imp1, imp2): Parameters ---------- - imp1: ImagePlus + imp1: ij.ImagePlus The ImagePlus that is to be subtracted from - imp2: ImagePlus + imp2: ij.ImagePlus The ImagePlus that is to be subtracted Returns --------- - subtract: ImagePlus - The result of the subtraction + subtracted: ij.ImagePlus + The resulting ImagePlus from the subtraction """ ic = ImageCalculator() subtracted = ic.run("Subtract create", imp1, imp2) @@ -271,7 +313,7 @@ def close_images(list_of_imps): Parameters ---------- - list_of_imps : list + list_of_imps : list of ij.ImagePlus A list of open ImagePlus objects """ @@ -280,45 +322,29 @@ def close_images(list_of_imps): imp.close() -def get_threshold_value_from_method(imp, channel, method, dark=True): - """Returns the threshold value of an ImagePlus object using the chosen - IJ AutoThreshold method in desired channel. ImagePlus Object needs to be 8 or 16 bit, 32 will throw an error. +def get_threshold_value_from_method(imp, method, ops): + """Returns the threshold value of chosen IJ AutoThreshold method from an ImagePlus stack. Parameters ---------- - imp : ImagePlus - The imp from which to get the threshold value - channel : integer - The channel in which to get the threshold + imp : ij.ImagePlus + The image from which to get the threshold value. method : string - The AutoThreshold method to use - dark: bool - Sets background to be dark or not + The AutoThreshold method to use. + Only the ones listed in the IJ AutoThreshold plugin are supported: + 'huang', 'ij1', 'intermodes', 'isoData', 'li', 'maxEntropy', 'maxLikelihood', 'mean', 'minError', + 'minimum', 'moments', 'otsu', 'percentile', 'renyiEntropy', 'rosin', 'shanbhag', 'triangle', 'yen'. + ops: ops.OpService + The ImageJ Ops service, defined as script parameter at the top of the script, as follows: + #@ OpService ops Returns ------- - list - the upper and the lower threshold (integer values) + threshold_value: int + The threshold value. """ - imp.setC(channel) # starts at 1 - max_int_val = 2 ** imp.getBitDepth() - 1 # Get maximum intensity value depending on image bit depth - if imp.getBitDepth == 32: # Raise error if 32 bit image - raise ValueError("Image is 32-bit; thresholding values will be off.") - - ip = imp.getProcessor() - if dark: - ip.setAutoThreshold(method + " dark") - else: - ip.setAutoThreshold(method + "") - - lower_thr = ip.getMinThreshold() - upper_thr = ip.getMaxThreshold() - - if lower_thr == 0: - threshold = upper_thr - elif upper_thr == max_int_val: - threshold = lower_thr - - ip.resetThreshold() - - return threshold \ No newline at end of file + histogram = ops.run("image.histogram", imp) + threshold_value = ops.run("threshold.%s" % method, histogram) + threshold_value = int(round(threshold_value.get())) + + return threshold_value From e6b521545a258a2e367a759bf7608fc9d9e22e80 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 10:28:36 +0100 Subject: [PATCH 385/678] Auto-remove trailing whitespace --- src/imcflibs/imagej/misc.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 5a58c70c..c7de0bf6 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -14,7 +14,7 @@ def show_status(msg): """Update the ImageJ status bar and issue a log message. - + Parameters ---------- msg : str @@ -34,7 +34,7 @@ def show_progress(cur, final): final : int Total value representing 100% completion. - Notes + Notes ----- `ij.IJ.showProgress` internally increments the given `cur` value by 1 """ @@ -44,7 +44,7 @@ def show_progress(cur, final): def error_exit(msg): """Log an error message and exit. - + Parameters ---------- msg : str @@ -269,7 +269,7 @@ def sanitize_image_title(imp): ---------- imp : ImagePlus The ImagePlus to be renamed. - + Notes ----- - The function removes the full path of the image file (if present), retaining only @@ -332,11 +332,11 @@ def get_threshold_value_from_method(imp, method, ops): method : string The AutoThreshold method to use. Only the ones listed in the IJ AutoThreshold plugin are supported: - 'huang', 'ij1', 'intermodes', 'isoData', 'li', 'maxEntropy', 'maxLikelihood', 'mean', 'minError', + 'huang', 'ij1', 'intermodes', 'isoData', 'li', 'maxEntropy', 'maxLikelihood', 'mean', 'minError', 'minimum', 'moments', 'otsu', 'percentile', 'renyiEntropy', 'rosin', 'shanbhag', 'triangle', 'yen'. ops: ops.OpService The ImageJ Ops service, defined as script parameter at the top of the script, as follows: - #@ OpService ops + #@ OpService ops Returns ------- @@ -346,5 +346,5 @@ def get_threshold_value_from_method(imp, method, ops): histogram = ops.run("image.histogram", imp) threshold_value = ops.run("threshold.%s" % method, histogram) threshold_value = int(round(threshold_value.get())) - + return threshold_value From 08f82f0a0f4a5518c027349b653b0a41630a05ed Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 10:35:30 +0100 Subject: [PATCH 386/678] Fix import order PEP8 import order: standard imports first, then third-party libraries, then local imports. https://pylint.readthedocs.io/en/latest/user_guide/messages/convention/wrong-import-order.html --- src/imcflibs/imagej/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index c7de0bf6..e5e45336 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -5,11 +5,11 @@ import os from ij import IJ # pylint: disable-msg=import-error +from ij.plugin import ImageCalculator, Concatenator +from ij.process import StackStatistics, ImageProcessor from . import prefs from ..log import LOG as log -from ij.plugin import ImageCalculator, Concatenator -from ij.process import StackStatistics, ImageProcessor def show_status(msg): From da3fb3a6953be0a562aa661ec0fe4d9ffab8bea2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 10:44:00 +0100 Subject: [PATCH 387/678] Clean up unused imports --- src/imcflibs/imagej/misc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index e5e45336..7e00665e 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -5,8 +5,7 @@ import os from ij import IJ # pylint: disable-msg=import-error -from ij.plugin import ImageCalculator, Concatenator -from ij.process import StackStatistics, ImageProcessor +from ij.plugin import ImageCalculator from . import prefs from ..log import LOG as log From d558d0142f91dfa1d1dc65021e556a2291a4dd84 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 10:47:01 +0100 Subject: [PATCH 388/678] Instruct pylint to not suggest using f-strings --- .pylintrc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index a02e3e5b..d1f68102 100644 --- a/.pylintrc +++ b/.pylintrc @@ -3,4 +3,7 @@ good-names=i,j,k,ex,Run,_,x,y,z,rm,rt # don't mess with black autoformatting, just let it do its job: -disable=bad-continuation \ No newline at end of file +disable=bad-continuation + +# this is (unfortunately) Python 2.7 code, so no f-strings: +disable=consider-using-f-string \ No newline at end of file From 5257e27d0b297b7357617d9afbb24084778daf16 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 11:12:31 +0100 Subject: [PATCH 389/678] Some more docstring refinements --- src/imcflibs/imagej/misc.py | 61 ++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 7e00665e..e18bebeb 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -24,7 +24,7 @@ def show_status(msg): def show_progress(cur, final): - """Update the ImageJ progress bar and log the current progress. + """Update ImageJ's progress bar and print the current progress to the log. Parameters ---------- @@ -35,7 +35,7 @@ def show_progress(cur, final): Notes ----- - `ij.IJ.showProgress` internally increments the given `cur` value by 1 + `ij.IJ.showProgress` internally increments the given `cur` value by 1. """ log.info("Progress: %s / %s (%s)", cur + 1, final, (1.0 + cur) / final) IJ.showProgress(cur, final) @@ -118,9 +118,9 @@ def calculate_mean_and_stdv(float_values): def find_focus(imp): """Find the slice of a stack that is the best focused one. - This function calculates the variance of the pixel values in each slice. - The slice with the highest variance is considered the best focused - because a higher variance typically indicates more contrast and sharpness. + First, calculate the variance of the pixel values in each slice. The slice + with the highest variance is considered the best focused as this typically + indicates more contrast and sharpness. Parameters ---------- @@ -187,7 +187,7 @@ def progressbar(progress, total, line_number, prefix=""): line_number : int Number of the line to be updated. prefix : str, optional - Text to use before the progress bar, by default ''. + Text to use before the progress bar, by default an empty string. """ size = 20 @@ -237,7 +237,8 @@ def setup_clean_ij_environment(rm=None, rt=None): # pylint: disable-msg=unused- """Set up a clean and defined ImageJ environment. This funtion clears the active results table, the ROI manager, and the log. - Additionally, it closes all open images and resets the ImageJ options, performing a "Fresh Start". + Additionally, it closes all open images and resets the ImageJ options, + performing a [*Fresh Start*][fresh_start]. Parameters ---------- @@ -248,11 +249,11 @@ def setup_clean_ij_environment(rm=None, rt=None): # pylint: disable-msg=unused- Notes ----- - "Fresh Start" is described in the ImageJ release notes [1] following a - suggestion by Robert Haase in the Image.sc Forum [2]. + "Fresh Start" is described in the [ImageJ release notes][ij_relnotes], + following a [suggestion by Robert Haase][fresh_start]. - [1]: https://imagej.nih.gov/ij/notes.html - [2]: https://forum.image.sc/t/fresh-start-macro-command-in-imagej-fiji/43102 + [ij_relnotes]: https://imagej.nih.gov/ij/notes.html + [fresh_start]: https://forum.image.sc/t/43102 """ IJ.run("Fresh Start", "") @@ -262,7 +263,7 @@ def setup_clean_ij_environment(rm=None, rt=None): # pylint: disable-msg=unused- def sanitize_image_title(imp): - """Remove special characters and various suffixes from the title of an open ImagePlus. + """Remove special chars and various suffixes from the title of an ImagePlus. Parameters ---------- @@ -271,8 +272,8 @@ def sanitize_image_title(imp): Notes ----- - - The function removes the full path of the image file (if present), retaining only - the base filename using `os.path.basename()`. + The function removes the full path of the image file (if present), retaining + only the base filename using `os.path.basename()`. """ # sometimes (unclear when) the title contains the full path, therefore we # simply call `os.path.basename()` on it to remove all up to the last "/": @@ -287,7 +288,7 @@ def sanitize_image_title(imp): def subtract_images(imp1, imp2): - """Subtract one image from the other (imp1 - imp2) + """Subtract one image from the other (imp1 - imp2). Parameters ---------- @@ -298,8 +299,8 @@ def subtract_images(imp1, imp2): Returns --------- - subtracted: ij.ImagePlus - The resulting ImagePlus from the subtraction + ij.ImagePlus + The ImagePlus resulting from the subtraction """ ic = ImageCalculator() subtracted = ic.run("Subtract create", imp1, imp2) @@ -308,13 +309,12 @@ def subtract_images(imp1, imp2): def close_images(list_of_imps): - """Closes all open image plus objects given in a list + """Close all open ImagePlus objects given in a list. Parameters ---------- - list_of_imps : list of ij.ImagePlus + list(ij.ImagePlus) A list of open ImagePlus objects - """ for imp in list_of_imps: imp.changes = False @@ -322,25 +322,30 @@ def close_images(list_of_imps): def get_threshold_value_from_method(imp, method, ops): - """Returns the threshold value of chosen IJ AutoThreshold method from an ImagePlus stack. + """Get the value of a selected AutoThreshold method for the given ImagePlus. + + This is useful to figure out which threshold value will be calculated by the + selected method for the given stack *without* actually having to apply it. Parameters ---------- imp : ij.ImagePlus The image from which to get the threshold value. - method : string + method : {'huang', 'ij1', 'intermodes', 'isoData', 'li', 'maxEntropy', + 'maxLikelihood', 'mean', 'minError', 'minimum', 'moments', 'otsu', + 'percentile', 'renyiEntropy', 'rosin', 'shanbhag', 'triangle', 'yen'} The AutoThreshold method to use. - Only the ones listed in the IJ AutoThreshold plugin are supported: - 'huang', 'ij1', 'intermodes', 'isoData', 'li', 'maxEntropy', 'maxLikelihood', 'mean', 'minError', - 'minimum', 'moments', 'otsu', 'percentile', 'renyiEntropy', 'rosin', 'shanbhag', 'triangle', 'yen'. ops: ops.OpService - The ImageJ Ops service, defined as script parameter at the top of the script, as follows: + The ImageJ Ops service instance, usually retrieved through a _Script + Parameter_ at the top of the script, as follows: + ``` #@ OpService ops + ``` Returns ------- - threshold_value: int - The threshold value. + int + The threshold value chosen by the selected method. """ histogram = ops.run("image.histogram", imp) threshold_value = ops.run("threshold.%s" % method, histogram) From 3203cf12ae49f5de563176e3a97bf5366d2a5083 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 11:39:52 +0100 Subject: [PATCH 390/678] Update changelog for changes in "misc" module --- CHANGELOG.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d14a7f..a51201ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,15 @@ FIXME: complete description for `send_mail` configuration below! ### Added -* `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and - various suffixes from an ImagePlus. +* Various additions to `imcflibs.imagej.misc`: + * `imcflibs.imagej.misc.send_mail` to send notification emails to users, e.g. + upon completion of long running scripts (configurable via user preferences). + * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and + various suffixes from an ImagePlus. + * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. + * `imcflibs.imagej.misc.close_images` for closing selected image windows. + * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that + a selected AutoThreshold method would be using. * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to get a label image (2D/3D). * New `imcflibs.imagej.objects3d` submodule, providing: @@ -45,8 +52,6 @@ FIXME: complete description for `send_mail` configuration below! propagating transformation parameters to other channels. * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Dataset" command. -* `imcflibs.imagej.misc.send_mail` to send notification emails to users, e.g. - upon completion of a long running script (configurable via user preferences). ## 1.4.0 From 9e9c934cd7212943ab9f0b807cd5c70643ffd5f1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 12:56:28 +0100 Subject: [PATCH 391/678] Add a workflow to trigger 'deploy-pages' in imcf.github.io --- .github/workflows/dispatch-deploy-pages.yml | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/dispatch-deploy-pages.yml diff --git a/.github/workflows/dispatch-deploy-pages.yml b/.github/workflows/dispatch-deploy-pages.yml new file mode 100644 index 00000000..af221adf --- /dev/null +++ b/.github/workflows/dispatch-deploy-pages.yml @@ -0,0 +1,31 @@ +name: ๐Ÿš€ Dispatch workflows in other repositories + +on: + workflow_dispatch: + + push: + branches: + - master + tags: + - "python-imcflibs-[0-9]+.*" + +jobs: + + trigger-event: + + strategy: + matrix: + repo: ['imcf/imcf.github.io'] + + runs-on: ubuntu-latest + + steps: + - name: ๐Ÿน Fire event on `${{ matrix.repo }}` + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.DISPATCH_DEPLOY_PAGES }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ matrix.repo }}/dispatches \ + -d '{"event_type":"dispatch-event"}' From c5a44da3da9cf16cb3fda29bcab8970a71874516 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 13:03:39 +0100 Subject: [PATCH 392/678] Shorten workflow name --- .github/workflows/dispatch-deploy-pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dispatch-deploy-pages.yml b/.github/workflows/dispatch-deploy-pages.yml index af221adf..5707d5c0 100644 --- a/.github/workflows/dispatch-deploy-pages.yml +++ b/.github/workflows/dispatch-deploy-pages.yml @@ -1,4 +1,4 @@ -name: ๐Ÿš€ Dispatch workflows in other repositories +name: ๐Ÿš€ Dispatch foreign workflows on: workflow_dispatch: From c2a911cd1ea81ef7e3259ba0bbd75e6511afa3b9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 14:19:04 +0100 Subject: [PATCH 393/678] Adapt XML header to what the release script is producing --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 470a4eac..71e8518e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,5 @@ - + 4.0.0 From b84ffeb1a744b7bae06bd08c0276614ab967460f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 14:43:42 +0100 Subject: [PATCH 394/678] Add script to parse POM and update pyproject.toml et al --- scripts/pom-to-pyproject.sh | 34 ++++++++++++++++++++++++++++++++++ scripts/run-poetry.sh | 31 ++++--------------------------- 2 files changed, 38 insertions(+), 27 deletions(-) create mode 100755 scripts/pom-to-pyproject.sh diff --git a/scripts/pom-to-pyproject.sh b/scripts/pom-to-pyproject.sh new file mode 100755 index 00000000..54e6a9c6 --- /dev/null +++ b/scripts/pom-to-pyproject.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -o errexit # exit on any error +set -o pipefail + +echo "Propagating version information: POM.xml -> pyproject.toml / __init__.py" + +cd "$(dirname "$0")/.." + +### parse the version from 'pom.xml': +PACKAGE_VERSION=$(xmlstarlet sel --template -m _:project -v _:version pom.xml) +PACKAGE_NAME=$(xmlstarlet sel --template -m _:project -v _:artifactId pom.xml) +PACKAGE_DIR="src/${PACKAGE_NAME#python-}" # strip 'python-' prefix if present + +echo "Package version from POM: [$PACKAGE_VERSION]" +### make sure to have a valid Python package version: +case $PACKAGE_VERSION in +*-SNAPSHOT*) + PACKAGE_VERSION=${PACKAGE_VERSION/-SNAPSHOT/} + ### calculate the distance to the last release tag: + LAST_TAG=$(git tag --list "${PACKAGE_NAME}-*" | sort | tail -n1) + # echo "Last git tag: '$LAST_TAG'" + COMMITS_SINCE=$(git rev-list "${LAST_TAG}..HEAD" | wc -l) + # echo "Nr of commits since last tag: $COMMITS_SINCE" + HEAD_ID=$(git rev-parse --short HEAD) + # echo "HEAD commit hash: $HEAD_ID" + PACKAGE_VERSION="${PACKAGE_VERSION}.dev${COMMITS_SINCE}+${HEAD_ID}" + ;; +esac + +echo "Using Python package version: [$PACKAGE_VERSION]" +### put the version into the project file and the package source: +sed -i "s/^version = \"0.0.0\"/version = \"${PACKAGE_VERSION}\"/" pyproject.toml +sed -i "s/\${project.version}/${PACKAGE_VERSION}/" "${PACKAGE_DIR}/__init__.py" diff --git a/scripts/run-poetry.sh b/scripts/run-poetry.sh index 6f51c2b4..c7001816 100755 --- a/scripts/run-poetry.sh +++ b/scripts/run-poetry.sh @@ -29,33 +29,10 @@ fi ### clean up old poetry artifacts: rm -rf dist/ -### parse the version from 'pom.xml': -PACKAGE_VERSION=$(xmlstarlet sel --template -m _:project -v _:version pom.xml) -PACKAGE_NAME=$(xmlstarlet sel --template -m _:project -v _:artifactId pom.xml) -PACKAGE_DIR="src/${PACKAGE_NAME#python-}" # strip 'python-' prefix if present +# adjust metadata version strings: +scripts/pom-to-pyproject.sh -echo "Package version from POM: [$PACKAGE_VERSION]" -### make sure to have a valid Python package version: -case $PACKAGE_VERSION in -*-SNAPSHOT*) - PACKAGE_VERSION=${PACKAGE_VERSION/-SNAPSHOT/} - ### calculate the distance to the last release tag: - LAST_TAG=$(git tag --list "${PACKAGE_NAME}-*" | sort | tail -n1) - # echo "Last git tag: '$LAST_TAG'" - COMMITS_SINCE=$(git rev-list "${LAST_TAG}..HEAD" | wc -l) - # echo "Nr of commits since last tag: $COMMITS_SINCE" - HEAD_ID=$(git rev-parse --short HEAD) - # echo "HEAD commit hash: $HEAD_ID" - PACKAGE_VERSION="${PACKAGE_VERSION}.dev${COMMITS_SINCE}+${HEAD_ID}" - ;; -esac - -echo "Using Python package version: [$PACKAGE_VERSION]" -### put the version into the project file and the package source: -sed -i "s/\"0.0.0\"/\"${PACKAGE_VERSION}\"/" pyproject.toml -sed -i "s/\${project.version}/${PACKAGE_VERSION}/" "${PACKAGE_DIR}/__init__.py" - -sed -i 's/^python = ">=2.7"$/python = ">=3.10"/' pyproject.toml +# # sed -i 's/^python = ">=2.7"$/python = ">=3.10"/' pyproject.toml set +o errexit # otherwise the script stops if poetry exits non-zero poetry "$@" # run poetry with the parameters given to the script @@ -63,6 +40,6 @@ RETVAL=$? # remember the actual exit code of poetry for returning it below! ### clean up the moved source tree and restore the previous state: git restore pyproject.toml -git restore "${PACKAGE_DIR}/__init__.py" +git restore "src/*/__init__.py" exit $RETVAL # now return the exit code from running poetry From d974dd5431e11ee0a32c5be9f350edb34235d524 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 18:14:03 +0100 Subject: [PATCH 395/678] Add check-if-release script --- scripts/check-if-release.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 scripts/check-if-release.sh diff --git a/scripts/check-if-release.sh b/scripts/check-if-release.sh new file mode 100644 index 00000000..c7f37934 --- /dev/null +++ b/scripts/check-if-release.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Very basic script checking for the existence of a file 'release.properties' in +# the root of the repository. If this is present, the state of the checkout +# indicates this is a proper release made through the 'release-version.sh' +# script from the 'scijava-scripts' repo. Will return false if the file is not +# present, can be overridden with an environment variable. + +# The idea is to include this in automated builds to prevent accidential +# attempts of publishing non-releases to PyPI. + +set -o errexit # exit on any error + +PROPERTIES="release.properties" + +test -n "$IGNORE_NO_RELEASE" && + echo "โš  ๐Ÿ”€ NOT checking for file '$PROPERTIES'... โš " && + exit 0 + +cd "$(dirname "$0")/.." + +echo "Checking if '$PROPERTIES' exists..." +test -f "$PROPERTIES" && echo "All good โœ…" && exit 0 + +echo ๐Ÿ›‘๐Ÿ›‘๐Ÿ›‘ +echo '/-----------------------------------------------------\' +echo "| Couldn't find 'release.properties', STOPPING build! |" +echo "| To ignore this, add this to the environment: |" +echo "| > export IGNORE_NO_RELEASE=true |" +echo '\-----------------------------------------------------/' +echo ๐Ÿ›‘๐Ÿ›‘๐Ÿ›‘ + +exit 1 From 71c79081ea8292bb15334f5a3fbc6bdde4b1b306 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 18:31:26 +0100 Subject: [PATCH 396/678] Major refactoring of the "build" workflow Currently the Maven/SciJava part is disabled until testing of the changes is completed. With the modifications this will now also build and publish the Python flavor package on PyPi, and will trigger the "deploy-pages" workflow in the 'imcf/imcf.github.io' repo. --- .github/workflows/build.yml | 168 +++++++++++++++++++++++++++++++----- 1 file changed, 148 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 368b50f2..02595d9f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,35 +1,163 @@ -name: build +name: ๐Ÿšš๐ŸŒ Publish to ๐Ÿ”ฌโ˜• SciJava and ๐ŸŽช PyPI on: + workflow_dispatch: + push: branches: - master tags: - - "*-[0-9]+.*" + - "python-imcflibs-[0-9]+.*" pull_request: branches: - master + jobs: - build: + + # publish-to-scijava: + + # name: ๐Ÿ”ฌโ˜• publish via โ“‚ Maven to SciJava + + # runs-on: ubuntu-latest + + # steps: + + # - uses: actions/checkout@v4 + # name: ๐Ÿ“ฅ Checkout repo + + # - name: โ˜• Set up Java + # uses: actions/setup-java@v3 + # with: + # java-version: '8' + # distribution: 'zulu' + # cache: 'maven' + + # - name: ๐Ÿช Set up CI environment + # run: .github/setup.sh + + # - name: ๐Ÿ‘ท Build and publish on ๐Ÿ”ฌโ˜• SciJava + # run: .github/build.sh + # env: + # GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} + # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + # MAVEN_USER: ${{ secrets.MAVEN_USER }} + # MAVEN_PASS: ${{ secrets.MAVEN_PASS }} + # OSSRH_PASS: ${{ secrets.OSSRH_PASS }} + # SIGNING_ASC: ${{ secrets.SIGNING_ASC }} + + + build-via-poetry: + + name: ๐Ÿ‘ท build via ๐ŸŽญ Poetry + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + name: ๐Ÿ“ฅ Checkout repo + + - name: ๐Ÿ•ต Inspect if this is a proper "scijava-scripts" release + run: scripts/check-if-release.sh + + - name: ๐Ÿ Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: ๐Ÿ—ƒ Cache ๐ŸŽญ Poetry install + uses: actions/cache@v4 + with: + path: ~/.local + key: poetry-2.0.1-0 + + # The key configuration value here is `virtualenvs-in-project: true`: this + # creates the venv as a `.venv` in your testing directory, which allows + # the next step to easily cache it. + - name: ๐Ÿ”ฉ๐Ÿ”ง Install ๐ŸŽญ Poetry + uses: snok/install-poetry@v1 + with: + version: 2.0.1 + virtualenvs-create: true + virtualenvs-in-project: true + + # Cache dependencies (i.e. all the stuff in your `pyproject.toml`). + - name: ๐Ÿ—ƒ Cache ๐Ÿงพ Dependencies + id: cache-deps + uses: actions/cache@v4 + with: + path: .venv + key: pydeps-${{ hashFiles('**/poetry.lock') }} + + # No Poetry dynamic versioning in this project, we're using the POM instead! + # - name: ๐ŸŽญ Install Poetry dynamic-versioning ๐Ÿ”Œ plugin + # run: poetry self add "poetry-dynamic-versioning[plugin]" + + # Install dependencies. `--no-root` means "install all dependencies but + # not the project itself", which is what you want to avoid caching _your_ + # code. The `if` statement ensures this only runs on a cache miss. + - name: ๐ŸŽญ Install ๐Ÿงพ Dependencies + run: scripts/run-poetry.sh install --no-interaction --no-root + if: steps.cache-deps.outputs.cache-hit != 'true' + + - name: ๐ŸŽญ Install ๐Ÿ›– project + run: scripts/run-poetry.sh install --no-interaction + + - name: ๐ŸŽญ๐Ÿ‘ท Build ๐Ÿงฑ project + run: scripts/run-poetry.sh build + + - name: ๐Ÿ“ค Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: release-dists + path: dist/ + + + publish-to-pypi: + + name: ๐Ÿšš๐ŸŒ publish to ๐ŸŽช PyPI + runs-on: ubuntu-latest + needs: + - release-build + + permissions: + id-token: write + + environment: + name: release + steps: - - uses: actions/checkout@v2 - - name: Set up Java - uses: actions/setup-java@v3 + - name: ๐Ÿ“ฅ Retrieve release ๐Ÿ“ฆ distributions + uses: actions/download-artifact@v4 with: - java-version: '8' - distribution: 'zulu' - cache: 'maven' - - name: Set up CI environment - run: .github/setup.sh - - name: Execute the build - run: .github/build.sh - env: - GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - MAVEN_USER: ${{ secrets.MAVEN_USER }} - MAVEN_PASS: ${{ secrets.MAVEN_PASS }} - OSSRH_PASS: ${{ secrets.OSSRH_PASS }} - SIGNING_ASC: ${{ secrets.SIGNING_ASC }} + name: release-dists + path: dist/ + + - name: ๐Ÿฅ Publish release distributions to ๐ŸŽช PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist/ + + + trigger-foreign-workflows: + + name: ๐Ÿš€ Dispatch foreign workflows + + strategy: + matrix: + repo: ['imcf/imcf.github.io'] + + runs-on: ubuntu-latest + + steps: + - name: ๐Ÿน Fire event on `${{ matrix.repo }}` + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.DISPATCH_DEPLOY_PAGES }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ matrix.repo }}/dispatches \ + -d '{"event_type":"dispatch-event"}' From 861d302f521a717acd9158ff4607fa42d74dd2ac Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 18:43:35 +0100 Subject: [PATCH 397/678] Disable automatic dispatching of remote workflow --- .github/workflows/dispatch-deploy-pages.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/dispatch-deploy-pages.yml b/.github/workflows/dispatch-deploy-pages.yml index 5707d5c0..88feb100 100644 --- a/.github/workflows/dispatch-deploy-pages.yml +++ b/.github/workflows/dispatch-deploy-pages.yml @@ -3,12 +3,6 @@ name: ๐Ÿš€ Dispatch foreign workflows on: workflow_dispatch: - push: - branches: - - master - tags: - - "python-imcflibs-[0-9]+.*" - jobs: trigger-event: From e865eda7a53985f7029799318f35788b554d6f6e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 18:43:47 +0100 Subject: [PATCH 398/678] Fix dependency reference --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 02595d9f..774503fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -120,7 +120,7 @@ jobs: runs-on: ubuntu-latest needs: - - release-build + - build-via-poetry permissions: id-token: write From 8e45a72f168a371303619822ea7606e1c86477ad Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 18:52:15 +0100 Subject: [PATCH 399/678] Update poetry.lock --- poetry.lock | 235 ++++++++++++++++++++++++++++------------------------ 1 file changed, 126 insertions(+), 109 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7f972446..4509ebf5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,21 +2,18 @@ [[package]] name = "asttokens" -version = "2.4.1" +version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, ] -[package.dependencies] -six = ">=1.12.0" - [package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "colorama" @@ -31,73 +28,74 @@ files = [ [[package]] name = "coverage" -version = "7.6.7" +version = "7.6.12" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"}, - {file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"}, - {file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"}, - {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"}, - {file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"}, - {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"}, - {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"}, - {file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"}, - {file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"}, - {file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"}, - {file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"}, - {file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"}, - {file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"}, - {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"}, - {file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"}, - {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"}, - {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"}, - {file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"}, - {file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"}, - {file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"}, - {file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"}, - {file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"}, - {file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"}, - {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"}, - {file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"}, - {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"}, - {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"}, - {file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"}, - {file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"}, - {file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"}, - {file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"}, - {file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"}, - {file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"}, - {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"}, - {file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"}, - {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"}, - {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"}, - {file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"}, - {file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"}, - {file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"}, - {file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"}, - {file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"}, - {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"}, - {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"}, - {file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"}, - {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"}, - {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"}, - {file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"}, - {file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"}, - {file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"}, - {file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"}, - {file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"}, - {file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"}, - {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"}, - {file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"}, - {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"}, - {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"}, - {file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"}, - {file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"}, - {file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"}, - {file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"}, - {file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, + {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674"}, + {file = "coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c"}, + {file = "coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e"}, + {file = "coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425"}, + {file = "coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015"}, + {file = "coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0"}, + {file = "coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d"}, + {file = "coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba"}, + {file = "coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f"}, + {file = "coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad"}, + {file = "coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985"}, + {file = "coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3"}, + {file = "coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a"}, + {file = "coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95"}, + {file = "coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1"}, + {file = "coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e"}, + {file = "coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3"}, + {file = "coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc"}, + {file = "coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3"}, + {file = "coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e"}, + {file = "coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924"}, + {file = "coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827"}, + {file = "coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9"}, + {file = "coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3"}, + {file = "coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e7575ab65ca8399c8c4f9a7d61bbd2d204c8b8e447aab9d355682205c9dd948d"}, + {file = "coverage-7.6.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8161d9fbc7e9fe2326de89cd0abb9f3599bccc1287db0aba285cb68d204ce929"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a1e465f398c713f1b212400b4e79a09829cd42aebd360362cd89c5bdc44eb87"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f25d8b92a4e31ff1bd873654ec367ae811b3a943583e05432ea29264782dc32c"}, + {file = "coverage-7.6.12-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a936309a65cc5ca80fa9f20a442ff9e2d06927ec9a4f54bcba9c14c066323f2"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa6f302a3a0b5f240ee201297fff0bbfe2fa0d415a94aeb257d8b461032389bd"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f973643ef532d4f9be71dd88cf7588936685fdb576d93a79fe9f65bc337d9d73"}, + {file = "coverage-7.6.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:78f5243bb6b1060aed6213d5107744c19f9571ec76d54c99cc15938eb69e0e86"}, + {file = "coverage-7.6.12-cp39-cp39-win32.whl", hash = "sha256:69e62c5034291c845fc4df7f8155e8544178b6c774f97a99e2734b05eb5bed31"}, + {file = "coverage-7.6.12-cp39-cp39-win_amd64.whl", hash = "sha256:b01a840ecc25dce235ae4c1b6a0daefb2a203dba0e6e980637ee9c2f6ee0df57"}, + {file = "coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf"}, + {file = "coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953"}, + {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, ] [package.dependencies] @@ -133,13 +131,13 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "2.1.0" +version = "2.2.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" files = [ - {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, - {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, + {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, + {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, ] [package.extras] @@ -147,13 +145,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "imcf-fiji-mocks" -version = "0.3.0" +version = "0.4.0" description = "Mocks collection for Fiji-Python. Zero functional code." optional = false python-versions = ">=2.7" files = [ - {file = "imcf_fiji_mocks-0.3.0-py2.py3-none-any.whl", hash = "sha256:53c26891ee9b71cb0af047e0b95baf8997d244746bb997ab50f24a9062114fdb"}, - {file = "imcf_fiji_mocks-0.3.0.tar.gz", hash = "sha256:acd16358f6ca5a7ded6b7c7a29d7006b2d363ba430eb72981f99997fed40d5a8"}, + {file = "imcf_fiji_mocks-0.4.0-py2.py3-none-any.whl", hash = "sha256:98b4184820994275ffe35be55450333b5b4fcdb584b75284339c3ef9f3e7b34a"}, + {file = "imcf_fiji_mocks-0.4.0.tar.gz", hash = "sha256:f16cf5f7b6c043a495797ec838b321fe7e2f30127d7c7ff85baeb53c9d02ab49"}, ] [[package]] @@ -169,13 +167,13 @@ files = [ [[package]] name = "ipython" -version = "8.29.0" +version = "8.32.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.29.0-py3-none-any.whl", hash = "sha256:0188a1bd83267192123ccea7f4a8ed0a78910535dbaa3f37671dca76ebd429c8"}, - {file = "ipython-8.29.0.tar.gz", hash = "sha256:40b60e15b22591450eef73e40a027cf77bd652e757523eebc5bd7c7c498290eb"}, + {file = "ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa"}, + {file = "ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251"}, ] [package.dependencies] @@ -185,16 +183,16 @@ exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt-toolkit = ">=3.0.41,<3.1.0" +prompt_toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" -stack-data = "*" +stack_data = "*" traitlets = ">=5.13.0" -typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} +typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"] kernel = ["ipykernel"] matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] @@ -305,13 +303,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prompt-toolkit" -version = "3.0.48" +version = "3.0.50" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, - {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, + {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, + {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, ] [package.dependencies] @@ -344,13 +342,13 @@ tests = ["pytest"] [[package]] name = "pygments" -version = "2.18.0" +version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ] [package.extras] @@ -358,13 +356,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.3.3" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -411,17 +409,6 @@ files = [ imcflibs = ">=1.4,<2.0" olefile = ">=0.46,<0.47" -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - [[package]] name = "sjlogging" version = "0.5.4" @@ -454,13 +441,43 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "tomli" -version = "2.1.0" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] [[package]] From d9f027d70b50e653299624cb33bd4554a1ca5abb Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 19:05:42 +0100 Subject: [PATCH 400/678] Fix executable flag --- scripts/check-if-release.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/check-if-release.sh diff --git a/scripts/check-if-release.sh b/scripts/check-if-release.sh old mode 100644 new mode 100755 From 1053738aff66e525158b5ead52ab561d703fa6d1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 19:05:58 +0100 Subject: [PATCH 401/678] Add missing job dependency --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 774503fc..8f22d125 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,6 +151,9 @@ jobs: runs-on: ubuntu-latest + needs: + - publish-to-pypi + steps: - name: ๐Ÿน Fire event on `${{ matrix.repo }}` run: | From 980c39c99d2a9398a8f55793d2861e6fec92fa95 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Feb 2025 19:36:07 +0100 Subject: [PATCH 402/678] Add missing build-dependency --- .github/workflows/build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8f22d125..26c011c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,6 +57,12 @@ jobs: - uses: actions/checkout@v4 name: ๐Ÿ“ฅ Checkout repo + - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages + uses: awalsh128/cache-apt-pkgs-action@v1.4.3 + with: + packages: xmlstarlet + version: 1.0 + - name: ๐Ÿ•ต Inspect if this is a proper "scijava-scripts" release run: scripts/check-if-release.sh From ef2c3da2301a8822860e62afafe1a5b91509a0f4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Feb 2025 13:59:21 +0100 Subject: [PATCH 403/678] Declutter --- scripts/run-poetry.sh | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/run-poetry.sh b/scripts/run-poetry.sh index c7001816..4ffb9abd 100755 --- a/scripts/run-poetry.sh +++ b/scripts/run-poetry.sh @@ -8,16 +8,11 @@ STATUS=$(git status --porcelain) if [ -z "$RUN_ON_UNCLEAN" ]; then if [ -n "$STATUS" ]; then - echo "================================================================" - echo "============= ERROR: repository unclean, stopping! =============" - echo "================================================================" + echo "==== ERROR: repository unclean, stopping! ====" echo git status echo - echo "================================================================" - echo "============= ERROR: repository unclean, stopping! =============" - echo "================================================================" - echo + echo "--------" echo "To ignore this (you have been warned!), set an environment var:" echo echo "> export RUN_ON_UNCLEAN=true" From 5d7dc950480f1d42ff285f7ea19bbec23ba0a55e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Feb 2025 14:03:32 +0100 Subject: [PATCH 404/678] Disable steps not required for packaging-only --- .github/workflows/build.yml | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26c011c7..b500f18f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,27 +87,29 @@ jobs: virtualenvs-create: true virtualenvs-in-project: true - # Cache dependencies (i.e. all the stuff in your `pyproject.toml`). - - name: ๐Ÿ—ƒ Cache ๐Ÿงพ Dependencies - id: cache-deps - uses: actions/cache@v4 - with: - path: .venv - key: pydeps-${{ hashFiles('**/poetry.lock') }} - - # No Poetry dynamic versioning in this project, we're using the POM instead! + ### No dependencies and project installation required, we're just packaging. + # # Cache dependencies (i.e. all the stuff in your `pyproject.toml`). + # - name: ๐Ÿ—ƒ Cache ๐Ÿงพ Dependencies + # id: cache-deps + # uses: actions/cache@v4 + # with: + # path: .venv + # key: pydeps-${{ hashFiles('**/poetry.lock') }} + + ### No poetry-dynamic-versioning here, we're using the POM instead! # - name: ๐ŸŽญ Install Poetry dynamic-versioning ๐Ÿ”Œ plugin # run: poetry self add "poetry-dynamic-versioning[plugin]" - # Install dependencies. `--no-root` means "install all dependencies but - # not the project itself", which is what you want to avoid caching _your_ - # code. The `if` statement ensures this only runs on a cache miss. - - name: ๐ŸŽญ Install ๐Ÿงพ Dependencies - run: scripts/run-poetry.sh install --no-interaction --no-root - if: steps.cache-deps.outputs.cache-hit != 'true' + ### No dependencies and project installation required, we're just packaging. + # # Install dependencies. `--no-root` means "install all dependencies but + # # not the project itself", which is what you want to avoid caching _your_ + # # code. The `if` statement ensures this only runs on a cache miss. + # - name: ๐ŸŽญ Install ๐Ÿงพ Dependencies + # run: scripts/run-poetry.sh install --no-interaction --no-root + # if: steps.cache-deps.outputs.cache-hit != 'true' - - name: ๐ŸŽญ Install ๐Ÿ›– project - run: scripts/run-poetry.sh install --no-interaction + # - name: ๐ŸŽญ Install ๐Ÿ›– project + # run: scripts/run-poetry.sh install --no-interaction - name: ๐ŸŽญ๐Ÿ‘ท Build ๐Ÿงฑ project run: scripts/run-poetry.sh build From 436ec5f956e0d08a062401ab5697731d84a084a8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Feb 2025 14:35:32 +0100 Subject: [PATCH 405/678] No need for the poetry plugin in this workflow --- .github/workflows/pytest-poetry.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index 05813f34..c9151ba4 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -78,8 +78,9 @@ jobs: path: .venv key: pydeps-${{ hashFiles('**/poetry.lock') }} - - name: ๐ŸŽญ Install Poetry dynamic-versioning ๐Ÿ”Œ plugin - run: poetry self add "poetry-dynamic-versioning[plugin]" + ### No poetry-dynamic-versioning here, we're using the POM instead! + # - name: ๐ŸŽญ Install Poetry dynamic-versioning ๐Ÿ”Œ plugin + # run: poetry self add "poetry-dynamic-versioning[plugin]" # Install dependencies. `--no-root` means "install all dependencies but # not the project itself", which is what you want to avoid caching _your_ From 6e9f8a10284acd4ede683c73c7ae7f7c0b4aad80 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Feb 2025 14:35:51 +0100 Subject: [PATCH 406/678] Modify Python version dependency by default This is required for all "complex" Poetry operations, e.g. when installing the project to run pytest or similar. To allow for building packages that can be installed on Python 2, this can be turned off explicitly by setting the environment variable IGNORE_DEPS_PYTHON. --- .github/workflows/build.yml | 2 ++ scripts/run-poetry.sh | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b500f18f..7fa9d94d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -113,6 +113,8 @@ jobs: - name: ๐ŸŽญ๐Ÿ‘ท Build ๐Ÿงฑ project run: scripts/run-poetry.sh build + env: + IGNORE_DEPS_PYTHON: true # required to build "py2.py3" wheels - name: ๐Ÿ“ค Upload build artifacts uses: actions/upload-artifact@v4 diff --git a/scripts/run-poetry.sh b/scripts/run-poetry.sh index 4ffb9abd..a7ecdeae 100755 --- a/scripts/run-poetry.sh +++ b/scripts/run-poetry.sh @@ -27,7 +27,19 @@ rm -rf dist/ # adjust metadata version strings: scripts/pom-to-pyproject.sh -# # sed -i 's/^python = ">=2.7"$/python = ">=3.10"/' pyproject.toml +# in case the project needs to be "installed" (e.g. to run pytest and generate +# coverage reports), the Python version specified in the project's dependencies +# needs to be set to a version satisfying the other dependencies requirements - +# this can be turned off e.g. for simply packaging for PyPi. +PYPROJ="pyproject.toml" +DEPS_PYTHON='>=3.10' +if [ -z "$IGNORE_DEPS_PYTHON" ]; then + echo "$PYPROJ: setting [python = \"$DEPS_PYTHON\"]" + echo "Use 'export IGNORE_DEPS_PYTHON=true' to skip this step." + sed -i 's/^python = ">=2.7"$/python = "'"$DEPS_PYTHON"'"/' $PYPROJ +else + echo "$PYPROJ: found 'IGNORE_DEPS_PYTHON' envvar, not modifying." +fi set +o errexit # otherwise the script stops if poetry exits non-zero poetry "$@" # run poetry with the parameters given to the script From 0672fe2f6de2d6604b7f3f6189ac037c104b8140 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Feb 2025 15:27:30 +0100 Subject: [PATCH 407/678] Require newer mocks --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0beb2732..4e25ea94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ version = "0.0.0" # - or: python = ">=3.10" [tool.poetry.dependencies] -imcf-fiji-mocks = ">=0.3.0" +imcf-fiji-mocks = ">=0.4.0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From ef35138cb86c7e305a88dfb31ecccb2b87d00fb1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Feb 2025 15:27:40 +0100 Subject: [PATCH 408/678] Add verbose option to poetry calls --- .github/workflows/pytest-poetry.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest-poetry.yml b/.github/workflows/pytest-poetry.yml index c9151ba4..a53043f8 100644 --- a/.github/workflows/pytest-poetry.yml +++ b/.github/workflows/pytest-poetry.yml @@ -86,7 +86,7 @@ jobs: # not the project itself", which is what you want to avoid caching _your_ # code. The `if` statement ensures this only runs on a cache miss. - name: ๐ŸŽญ Install ๐Ÿงพ Dependencies - run: scripts/run-poetry.sh install --no-interaction --no-root + run: scripts/run-poetry.sh install --no-interaction --no-root --verbose if: steps.cache-deps.outputs.cache-hit != 'true' # Now install _your_ project. This isn't necessary for many types of @@ -95,7 +95,7 @@ jobs: # that if you add things like console-scripts at some point that they'll # be installed and working. - name: ๐ŸŽญ Install ๐Ÿ›– project - run: scripts/run-poetry.sh install --no-interaction + run: scripts/run-poetry.sh install --no-interaction --verbose # And finally run the tests. - name: ๐Ÿงช๐ŸŽญ Run Tests From 6ba52bf4c4892e1a183044558af5f982b57f9d8c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Feb 2025 15:46:11 +0100 Subject: [PATCH 409/678] Update poetry.lock --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 4509ebf5..dc5abf7c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -520,4 +520,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "5eab384b395adebc2d3b05d2cceb6beccd1090491891713bd7e97e05d23e6c93" +content-hash = "1223bd786f31ebd935b7872c33b9ae236f8a5b88a7da9cb9e14d76367660f470" From 357d60b67d4f0f2ea317eadce287807ac27e43a3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Feb 2025 17:30:00 +0100 Subject: [PATCH 410/678] Re-enable the Maven/SciJava job The entire job was disabled in commit 71c7908 to facilitate testing the integrated Maven/SciJava/Poetry/PyPI/apidocs workflow. --- .github/workflows/build.yml | 50 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7fa9d94d..b9a44fde 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,36 +15,36 @@ on: jobs: - # publish-to-scijava: + publish-to-scijava: - # name: ๐Ÿ”ฌโ˜• publish via โ“‚ Maven to SciJava + name: ๐Ÿ”ฌโ˜• publish via โ“‚ Maven to SciJava - # runs-on: ubuntu-latest - - # steps: - - # - uses: actions/checkout@v4 - # name: ๐Ÿ“ฅ Checkout repo + runs-on: ubuntu-latest - # - name: โ˜• Set up Java - # uses: actions/setup-java@v3 - # with: - # java-version: '8' - # distribution: 'zulu' - # cache: 'maven' + steps: - # - name: ๐Ÿช Set up CI environment - # run: .github/setup.sh + - uses: actions/checkout@v4 + name: ๐Ÿ“ฅ Checkout repo - # - name: ๐Ÿ‘ท Build and publish on ๐Ÿ”ฌโ˜• SciJava - # run: .github/build.sh - # env: - # GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} - # GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - # MAVEN_USER: ${{ secrets.MAVEN_USER }} - # MAVEN_PASS: ${{ secrets.MAVEN_PASS }} - # OSSRH_PASS: ${{ secrets.OSSRH_PASS }} - # SIGNING_ASC: ${{ secrets.SIGNING_ASC }} + - name: โ˜• Set up Java + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'zulu' + cache: 'maven' + + - name: ๐Ÿช Set up CI environment + run: .github/setup.sh + + - name: ๐Ÿ‘ท Build and publish on ๐Ÿ”ฌโ˜• SciJava + run: .github/build.sh + env: + GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + MAVEN_USER: ${{ secrets.MAVEN_USER }} + MAVEN_PASS: ${{ secrets.MAVEN_PASS }} + OSSRH_PASS: ${{ secrets.OSSRH_PASS }} + SIGNING_ASC: ${{ secrets.SIGNING_ASC }} build-via-poetry: From a7e0cc10ed985d0fe8cddf6dd797f14c878124b6 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Feb 2025 15:43:40 +0100 Subject: [PATCH 411/678] Shorten job name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9a44fde..96357707 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,7 @@ jobs: publish-to-scijava: - name: ๐Ÿ”ฌโ˜• publish via โ“‚ Maven to SciJava + name: ๐Ÿ”ฌโ˜• publish to SciJava runs-on: ubuntu-latest From 401e06a6066eb445965a85b62addd32477f16a6d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Mar 2025 12:40:53 +0100 Subject: [PATCH 412/678] Update mocks dependency version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4e25ea94..ec133026 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ version = "0.0.0" # - or: python = ">=3.10" [tool.poetry.dependencies] -imcf-fiji-mocks = ">=0.4.0" +imcf-fiji-mocks = ">=0.5.0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From 78d03c5b7fd332cddca8903fc270f81579e6018d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Mar 2025 13:47:42 +0100 Subject: [PATCH 413/678] No version warning flag fails on GitHub --- scripts/py2-pytest.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 946ed375..78fe9de3 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -30,10 +30,12 @@ fi # now we're done checking the environment, so disallow empty variables below: set -o nounset +pip2 --version + if ! [ -d "$VENV" ]; then - pip2 --no-python-version-warning show virtualenv > /dev/null || { + pip2 show virtualenv > /dev/null || { echo "== Installing 'virtualenv' for Python2..." - pip2 --no-python-version-warning install virtualenv + pip2 install virtualenv } echo "== Creating a Python2 venv in [$VENV]..." python2 -m virtualenv --always-copy "$VENV" @@ -41,7 +43,7 @@ if ! [ -d "$VENV" ]; then fi function vpip() { - "$VENV/bin/pip" --no-python-version-warning "$@" + "$VENV/bin/pip" "$@" } echo From 3e618f62628592daafea9b0c2dc4ac530a5d151d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Mar 2025 14:06:32 +0100 Subject: [PATCH 414/678] Add note about pip flag removal --- scripts/py2-pytest.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index 78fe9de3..f840f4a8 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -30,7 +30,11 @@ fi # now we're done checking the environment, so disallow empty variables below: set -o nounset -pip2 --version +# NOTE: the `pip2` calls below were initially using a flag to prevent the +# deprecation warning for Python 2.7 (`--no-python-version-warning`), alas this +# flag only got into pip as of version 20.0, which is newer than the default one +# provided by GitHub's "ubuntu-22.04" image, so these commands would fail - +# therefore we'll have to live with the warnings for now. if ! [ -d "$VENV" ]; then pip2 show virtualenv > /dev/null || { From 9d8f0b6bcb7394ea93ef17fdb1e22e1144ddb8ea Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Mar 2025 16:20:05 +0100 Subject: [PATCH 415/678] Add instructions / notes on updating poetry.lock --- poetry.lock.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 poetry.lock.md diff --git a/poetry.lock.md b/poetry.lock.md new file mode 100644 index 00000000..2eefdcfa --- /dev/null +++ b/poetry.lock.md @@ -0,0 +1,16 @@ +## Updating `poetry.lock` ๐ŸŽญ๐Ÿ” + +Every time dependencies in `pyproject.toml` have been modified (e.g. when +pulling in a newer version of the [`imcf-fiji-mocks`][1] package), [Poetry's +lockfile][2] has to be updated (otherwise the build workflow will start to +fail, complaining about the outdated file). + +To do so, it's not sufficient to simply call `poetry lock --no-update` but +rather the Poetry wrapper script has to be used like this: + +```bash +scripts/run-poetry.sh lock --no-update +``` + +[1]: https://pypi.org/project/imcf-fiji-mocks +[2]: https://python-poetry.org/docs/basic-usage/#committing-your-poetrylock-file-to-version-control From f8ae7327bb786ed15eb6f7674dd8699c3c2c1856 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Mar 2025 16:21:10 +0100 Subject: [PATCH 416/678] =?UTF-8?q?Update=20poetry.lock=20=F0=9F=8E=AD?= =?UTF-8?q?=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Necessary due to the mocks dependency change in 401e06a --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index dc5abf7c..fef3901a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -145,13 +145,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "imcf-fiji-mocks" -version = "0.4.0" +version = "0.5.0" description = "Mocks collection for Fiji-Python. Zero functional code." optional = false python-versions = ">=2.7" files = [ - {file = "imcf_fiji_mocks-0.4.0-py2.py3-none-any.whl", hash = "sha256:98b4184820994275ffe35be55450333b5b4fcdb584b75284339c3ef9f3e7b34a"}, - {file = "imcf_fiji_mocks-0.4.0.tar.gz", hash = "sha256:f16cf5f7b6c043a495797ec838b321fe7e2f30127d7c7ff85baeb53c9d02ab49"}, + {file = "imcf_fiji_mocks-0.5.0-py2.py3-none-any.whl", hash = "sha256:e6f94b2a7419559fe4aeb785ae5dbf1d8f06d67c037e1ec588e5952d7f1f5aa7"}, + {file = "imcf_fiji_mocks-0.5.0.tar.gz", hash = "sha256:3d08293c427a95d814c78c8313786f1547fc0a815fdfaba0d7ef5020ebbdf8cb"}, ] [[package]] @@ -520,4 +520,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "1223bd786f31ebd935b7872c33b9ae236f8a5b88a7da9cb9e14d76367660f470" +content-hash = "d12b0bc978ea87b47bb0acd781f733630c4b98d2247ad0a70c3cf24195ac0169" From e0c2108b5e0e1b5ba52eeb92891875eec5c91c93 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 11 Mar 2025 16:55:02 +0100 Subject: [PATCH 417/678] =?UTF-8?q?Update=20mocks=20dependency=20version?= =?UTF-8?q?=20and=20lock=20=F0=9F=8E=AD=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index fef3901a..287530d6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -145,13 +145,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "imcf-fiji-mocks" -version = "0.5.0" +version = "0.6.0" description = "Mocks collection for Fiji-Python. Zero functional code." optional = false python-versions = ">=2.7" files = [ - {file = "imcf_fiji_mocks-0.5.0-py2.py3-none-any.whl", hash = "sha256:e6f94b2a7419559fe4aeb785ae5dbf1d8f06d67c037e1ec588e5952d7f1f5aa7"}, - {file = "imcf_fiji_mocks-0.5.0.tar.gz", hash = "sha256:3d08293c427a95d814c78c8313786f1547fc0a815fdfaba0d7ef5020ebbdf8cb"}, + {file = "imcf_fiji_mocks-0.6.0-py2.py3-none-any.whl", hash = "sha256:77396edd71133b8a7b2c3c4257d176c1e695f6b158e73af80a4441658988f332"}, + {file = "imcf_fiji_mocks-0.6.0.tar.gz", hash = "sha256:cc48debf0d9eb26d932ea29364370caa6dc46fe26e2e992080ba1cf3a087b204"}, ] [[package]] @@ -520,4 +520,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "d12b0bc978ea87b47bb0acd781f733630c4b98d2247ad0a70c3cf24195ac0169" +content-hash = "a626402e812f99ff5599089b35498cd1146ced3c5e952f4cf0caf81e473f42b2" diff --git a/pyproject.toml b/pyproject.toml index ec133026..ea76ce15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ version = "0.0.0" # - or: python = ">=3.10" [tool.poetry.dependencies] -imcf-fiji-mocks = ">=0.5.0" +imcf-fiji-mocks = ">=0.6.0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From 86a34472b120f5dde94070032d2df94871a17e5f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 16:57:18 +0100 Subject: [PATCH 418/678] 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 97084b62abdfb4cac1afec2318e200a21e7410c5 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:01:08 +0100 Subject: [PATCH 419/678] 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 408131ccd06627f470f51bf7159515ca2dc0efae Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:01:28 +0100 Subject: [PATCH 420/678] 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 c542b19c5727530b21eed8a973674e6da00ed338 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:01:59 +0100 Subject: [PATCH 421/678] 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 886613a606ab9dc4ca891e22bff48a2694e4909f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:02:27 +0100 Subject: [PATCH 422/678] Add method to write results to CSV --- src/imcflibs/imagej/misc.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 10994a92..b0a0154c 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -4,6 +4,7 @@ import time import smtplib import os +import csv from ij import IJ # pylint: disable-msg=import-error from ij.plugin import ImageCalculator @@ -410,3 +411,30 @@ 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 831d771af57e90627c83520d359202227879c3d5 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:03:02 +0100 Subject: [PATCH 423/678] Add methods for 3D Maxima Finder and 3D Watershed --- src/imcflibs/imagej/objects3d.py | 96 ++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 9bcbd384..c1000d8d 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -1,6 +1,10 @@ +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): @@ -138,3 +142,95 @@ 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 4dc45044d63289aeb94bf9efae6ad87dfe2c4b20 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Mar 2025 17:04:33 +0100 Subject: [PATCH 424/678] 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 cac9a9f980287c2b9cbe25618b4552eda2f68136 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 15:36:49 +0100 Subject: [PATCH 425/678] =?UTF-8?q?Update=20poetry.lock=20=F0=9F=8E=AD?= =?UTF-8?q?=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 287530d6..8b95696a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -145,13 +145,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "imcf-fiji-mocks" -version = "0.6.0" +version = "0.7.0" description = "Mocks collection for Fiji-Python. Zero functional code." optional = false python-versions = ">=2.7" files = [ - {file = "imcf_fiji_mocks-0.6.0-py2.py3-none-any.whl", hash = "sha256:77396edd71133b8a7b2c3c4257d176c1e695f6b158e73af80a4441658988f332"}, - {file = "imcf_fiji_mocks-0.6.0.tar.gz", hash = "sha256:cc48debf0d9eb26d932ea29364370caa6dc46fe26e2e992080ba1cf3a087b204"}, + {file = "imcf_fiji_mocks-0.7.0-py2.py3-none-any.whl", hash = "sha256:643c3d4cc916d1573f1f3490885e37822c11c40c09b6a1e06c2fc561c8aeb20e"}, + {file = "imcf_fiji_mocks-0.7.0.tar.gz", hash = "sha256:65eab1974629ef72bbe7b8d76e81e067d5fb55b65aab8fb7e0f24755e0f7ac6b"}, ] [[package]] @@ -520,4 +520,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "a626402e812f99ff5599089b35498cd1146ced3c5e952f4cf0caf81e473f42b2" +content-hash = "010a8923c68fb6e367da60506e1937f570cd37633e42c44ba312012be4aa2172" From 736a18a5813c0aa2131dc4f9057f12b1da0061ff Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 17:27:58 +0100 Subject: [PATCH 426/678] Rephrase function summary Avoiding potential confusion with ImageJ "Results" and the "results" word used before. It's about saving / exporting the projection(s). --- src/imcflibs/imagej/projections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/projections.py b/src/imcflibs/imagej/projections.py index 1a6d0501..d2f75c37 100644 --- a/src/imcflibs/imagej/projections.py +++ b/src/imcflibs/imagej/projections.py @@ -51,7 +51,7 @@ def maximum(imp): def create_and_save(imp, projections, path, filename, export_format): - """Wrapper to create one or more projections and export the results. + """Create one or more projections and export (save) them. Parameters ---------- From ed9b1fb6637f355ad8d0a3849108e027f670faa3 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Wed, 12 Mar 2025 08:32:50 +0100 Subject: [PATCH 427/678] Add function to project along any axis --- src/imcflibs/imagej/projections.py | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/imcflibs/imagej/projections.py b/src/imcflibs/imagej/projections.py index d2f75c37..dcfd5d35 100644 --- a/src/imcflibs/imagej/projections.py +++ b/src/imcflibs/imagej/projections.py @@ -5,6 +5,11 @@ from .bioformats import export_using_orig_name # pylint: disable-msg=E0401 from ..log import LOG as log +from net.imagej.axis import Axes +from net.imagej.ops import Ops +from ij import ImagePlus, IJ +from net.imagej import Dataset + def average(imp): """Create an average intensity projection. @@ -100,3 +105,73 @@ def create_and_save(imp, projections, path, filename, export_format): proj.close() return True + + +def project_stack(imp, projected_dimension, projection_type, ops, ds, cs): + """ + Project a multi-dimensional dataset along a specified axis using a specified projection type. + + Parameters + ---------- + imp : ImagePlus + The input image to be projected. + projected_dimension : str + The dimension along which to project the data. Must be one of {"X", "Y", "Z", "TIME", "CHANNEL"}. + projection_type : str + The type of projection to perform. Must be one of {"Max", "Mean", "Median", "Min", "StdDev", "Sum"}. + ops : OpService + The service used to access image processing operations. Use e.g. from script parameter: #@ OpService ops + ds : DatasetService + The service used to create new datasets. Use e.g. from script parameter: #@ DatasetService ds + cs : ConvertService + The service used to convert between formats. Use e.g. from script parameter: #@ ConvertService cs + + Returns + ------- + ImagePlus + The resulting projected image as an ImagePlus object. + + Raises + ------ + Exception + If the specified dimension is not found or if the dimension has only one frame. + """ + bit_depth = imp.getBitDepth() + data = cs.convert(imp, Dataset) + # Select which dimension to project + dim = data.dimensionIndex(getattr(Axes, projected_dimension)) + if dim == -1: + raise Exception("%s dimension not found." % projected_dimension) + if data.dimension(dim) < 2: + raise Exception("%s dimension has only one frame." % projected_dimension) + + # Write the output dimensions + new_dimensions = [ + data.dimension(d) for d in range(0, data.numDimensions()) if d != dim + ] + + # Create the output image + projected = ops.create().img(new_dimensions) + + # Create the op and run it + proj_op = ops.op(getattr(Ops.Stats, projection_type), data) + ops.transform().project(projected, data, proj_op, dim) + + # Create the output Dataset and convert to ImagePlus + output = ds.create(projected) + output_imp = cs.convert(output, ImagePlus) + output_imp = output_imp.duplicate() + output_imp.setTitle("%s %s projection" % (projected_dimension, projection_type)) + IJ.run(output_imp, "Enhance Contrast", "saturated=0.35") + + # Rescale bit depth if possible + if projection_type in ["Max", "Min", "Median"]: + IJ.run("Conversions...", " ") + if bit_depth in [8, 16]: + IJ.run(output_imp, str(bit_depth) + "-bit", "") + if bit_depth == 12: + IJ.run(output_imp, "16-bit", "") + + IJ.run("Conversions...", "scale") + + return output_imp From cf9c9c9d65fbc1d06cdbec320af8245a91bc399d Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Wed, 12 Mar 2025 08:33:47 +0100 Subject: [PATCH 428/678] Precises description --- src/imcflibs/imagej/projections.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/projections.py b/src/imcflibs/imagej/projections.py index dcfd5d35..100d1974 100644 --- a/src/imcflibs/imagej/projections.py +++ b/src/imcflibs/imagej/projections.py @@ -1,4 +1,4 @@ -"""Functions for creating Z projections.""" +"""Functions for creating projections.""" from ij.plugin import ZProjector # pylint: disable-msg=E0401 @@ -12,7 +12,7 @@ def average(imp): - """Create an average intensity projection. + """Create an average intensity Z projection. Parameters ---------- @@ -28,13 +28,13 @@ def average(imp): log.warn("ImagePlus is not a z-stack, not creating a projection!") return imp - log.debug("Creating average projection...") + log.debug("Creating average Z projection...") proj = ZProjector.run(imp, "avg") return proj def maximum(imp): - """Create a maximum intensity projection. + """Create a maximum intensity Z projection. Parameters ---------- @@ -50,7 +50,7 @@ def maximum(imp): log.warn("ImagePlus is not a z-stack, not creating a projection!") return imp - log.debug("Creating maximum intensity projection...") + log.debug("Creating maximum intensity Z projection...") proj = ZProjector.run(imp, "max") return proj From 7a3a8f5d0d47ca772fcb42232a502235254165d9 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Wed, 12 Mar 2025 15:19:27 +0100 Subject: [PATCH 429/678] Update dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ea76ce15..8d534db5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ version = "0.0.0" # - or: python = ">=3.10" [tool.poetry.dependencies] -imcf-fiji-mocks = ">=0.6.0" +imcf-fiji-mocks = ">=0.7.0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From 28d2184f8882d0fa5806e9db17f39053155983d2 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Wed, 12 Mar 2025 16:53:57 +0100 Subject: [PATCH 430/678] Increase docstring readability --- src/imcflibs/imagej/projections.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/projections.py b/src/imcflibs/imagej/projections.py index 100d1974..7aea535f 100644 --- a/src/imcflibs/imagej/projections.py +++ b/src/imcflibs/imagej/projections.py @@ -108,23 +108,27 @@ def create_and_save(imp, projections, path, filename, export_format): def project_stack(imp, projected_dimension, projection_type, ops, ds, cs): - """ - Project a multi-dimensional dataset along a specified axis using a specified projection type. + """Project along a defined axis using the given projection type. Parameters ---------- imp : ImagePlus The input image to be projected. projected_dimension : str - The dimension along which to project the data. Must be one of {"X", "Y", "Z", "TIME", "CHANNEL"}. + The dimension along which to project the data. Must be one of {"X", "Y", "Z", + "TIME", "CHANNEL"}. projection_type : str - The type of projection to perform. Must be one of {"Max", "Mean", "Median", "Min", "StdDev", "Sum"}. + The type of projection to perform. Must be one of {"Max", "Mean", "Median", + "Min", "StdDev", "Sum"}. ops : OpService - The service used to access image processing operations. Use e.g. from script parameter: #@ OpService ops + The service used to access image processing operations. Use e.g. from script + parameter: `#@ OpService ops` ds : DatasetService - The service used to create new datasets. Use e.g. from script parameter: #@ DatasetService ds + The service used to create new datasets. Use e.g. from script parameter: + `#@ DatasetService ds` cs : ConvertService - The service used to convert between formats. Use e.g. from script parameter: #@ ConvertService cs + The service used to convert between formats. Use e.g. from script parameter: + `#@ ConvertService cs` Returns ------- From 70a843c182cadb6031a63947ea63c0a61d0ee8bd Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 17:47:43 +0100 Subject: [PATCH 431/678] Add docstring --- conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conftest.py b/conftest.py index e8b929e2..8197bee9 100644 --- a/conftest.py +++ b/conftest.py @@ -1 +1,3 @@ +"""Pytest configuration.""" + collect_ignore = ["tests/interactive-imagej"] From c1b3d58705ae0b71928d08a275080f140fc5b0e6 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 18:37:25 +0100 Subject: [PATCH 432/678] Add module docstrings --- tests/test_iotools.py | 3 +-- tests/test_pathtools.py | 3 +-- tests/test_strtools.py | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/test_iotools.py b/tests/test_iotools.py index 75708fce..8a9807d8 100644 --- a/tests/test_iotools.py +++ b/tests/test_iotools.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +"""Tests for `imcflibs.iotools`.""" import pytest import zipfile diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index b83b8390..7a35c406 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +"""Tests for `imcflibs.pathtools`.""" import pytest from imcflibs.pathtools import parse_path diff --git a/tests/test_strtools.py b/tests/test_strtools.py index 9c61c1b8..f8242066 100644 --- a/tests/test_strtools.py +++ b/tests/test_strtools.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +"""Tests for `imcflibs.strtools`.""" import pytest import os From c720274333c8437dad08a7d1b396cc8f3b611abf Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 18:37:57 +0100 Subject: [PATCH 433/678] Clean up unused imports in the shading module --- src/imcflibs/imagej/shading.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py index c7ebb698..3b818542 100644 --- a/src/imcflibs/imagej/shading.py +++ b/src/imcflibs/imagej/shading.py @@ -4,8 +4,8 @@ import ij # pylint: disable-msg=import-error from ij import IJ -from ij.plugin import ImageCalculator, Concatenator -from ij.process import StackStatistics, ImageProcessor +from ij.plugin import ImageCalculator +from ij.process import StackStatistics from ..imagej import bioformats # pylint: disable-msg=no-name-in-module from ..imagej import misc, projections from ..log import LOG as log From f36ad085d31b3504c66ed22d39d69d3f7c7f507e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 18:38:20 +0100 Subject: [PATCH 434/678] Clean up unused imports in the iotools module --- src/imcflibs/iotools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/imcflibs/iotools.py b/src/imcflibs/iotools.py index 3f798c05..480822f6 100644 --- a/src/imcflibs/iotools.py +++ b/src/imcflibs/iotools.py @@ -1,9 +1,7 @@ """I/O related functions.""" -import glob import zipfile -import os from os.path import splitext, join from .log import LOG as log From 19f82c71b9139781cb96551a625d55986a530da8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 18:38:36 +0100 Subject: [PATCH 435/678] Clean up unused imports in the trackmate module --- src/imcflibs/imagej/trackmate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index aef61ac8..78899ba5 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -8,7 +8,7 @@ from fiji.plugin.trackmate.detection import LogDetectorFactory from fiji.plugin.trackmate.features import FeatureFilter from fiji.plugin.trackmate.stardist import StarDistDetectorFactory -from fiji.plugin.trackmate.tracking.jaqaman import LAPUtils, SparseLAPTrackerFactory +from fiji.plugin.trackmate.tracking.jaqaman import SparseLAPTrackerFactory from ij import IJ From 92771c098516100d685f9c789d1371069e0f9947 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 11:58:11 +0100 Subject: [PATCH 436/678] Add test docstring --- tests/test_pathtools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index b78b3e41..2e842eee 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -44,6 +44,7 @@ def test_parse_path_windows(): def test_parse_path_windows_tabs_and_lines(): + r"""Test non-raw string containing newline \n and tab \t sequences.""" path = "C:\new_folder\test" parsed = parse_path(path) From 6fa4d699d8aadcc843f27a9b60f81d76f880f5e0 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:06:01 +0100 Subject: [PATCH 437/678] Add methods to get different metadata using bioformats --- src/imcflibs/imagej/bioformats.py | 249 ++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 60865845..154e7f2f 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -289,3 +289,252 @@ def write_bf_memoryfile(path_to_file): reader = Memoizer(ImageReader()) reader.setId(path_to_file) reader.close() + + +def get_metadata_from_image(path_to_image): + """Extract metadata from an image file using Bio-Formats. + + This function reads an image file using the Bio-Formats library and extracts + various metadata properties including physical dimensions, pixel dimensions, + and other image characteristics. + + Parameters + ---------- + path_to_image : str or pathlib.Path + Path to the image file from which metadata should be extracted. + + Returns + ------- + dict + A dictionary containing the following metadata: + - unit_width : float + Physical width of a pixel. + - unit_height : float + Physical height of a pixel. + - unit_depth : float + Physical depth of a voxel. + - pixel_width : int + Width of the image in pixels. + - pixel_height : int + Height of the image in pixels. + - slice_count : int + Number of Z-slices. + - channel_count : int + Number of channels. + - timepoints_count : int + Number of timepoints. + - dimension_order : str + Order of dimensions in the image (e.g., 'XYZCT'). + - pixel_type : str + Data type of the pixel values. + """ + reader = ImageReader() + ome_meta = MetadataTools.createOMEXMLMetadata() + reader.setMetadataStore(ome_meta) + reader.setId(str(path_to_image)) + + phys_size_x = ome_meta.getPixelsPhysicalSizeX(0) + phys_size_y = ome_meta.getPixelsPhysicalSizeY(0) + phys_size_z = ome_meta.getPixelsPhysicalSizeZ(0) + pixel_size_x = ome_meta.getPixelsSizeX(0) + pixel_size_y = ome_meta.getPixelsSizeY(0) + pixel_size_z = ome_meta.getPixelsSizeZ(0) + channel_count = ome_meta.getPixelsSizeC(0) + timepoints_count = ome_meta.getPixelsSizeT(0) + dimension_order = ome_meta.getPixelsDimensionOrder(0) + pixel_type = ome_meta.getPixelsType(0) + + image_calibration = { + "unit_width": phys_size_x.value(), + "unit_height": phys_size_y.value(), + "unit_depth": phys_size_z.value(), + "pixel_width": pixel_size_x.getNumberValue(), + "pixel_height": pixel_size_y.getNumberValue(), + "slice_count": pixel_size_z.getNumberValue(), + "channel_count": channel_count.getNumberValue(), + "timepoints_count": timepoints_count.getNumberValue(), + "dimension_order": dimension_order, + "pixel_type": pixel_type, + } + + reader.close() + + return image_calibration + + +def get_stage_coordinates_from_ome_metadata(source, imagenames): + """Get the stage coordinates and calibration from the ome-xml for a given list of images + + Parameters + ---------- + source : str + Path to the images + imagenames : list of str + List of images filenames + + Returns + ------- + tuple + Contains + dimensions : int + Number of dimensions (2D or 3D) + stage_coordinates_x : list + The absolute stage x-coordinated from ome-xml metadata + stage_coordinates_y : list + The absolute stage y-coordinated from ome-xml metadata + stage_coordinates_z : list + The absolute stage z-coordinated from ome-xml metadata + relative_coordinates_x : list + The relative stage x-coordinates in px + relative_coordinates_y : list + The relative stage y-coordinates in px + relative_coordinates_z : list + The relative stage z-coordinates in px + image_calibration : list + x,y,z image calibration in unit/px + calibration_unit : str + Image calibration unit + image_dimensions_czt : list + Number of images in dimensions c,z,t + series_names : list of str + Names of all series contained in the files + max_size : list of int + Maximum size across all files in dimensions x,y,z + """ + + # open an array to store the abosolute stage coordinates from metadata + stage_coordinates_x = [] + stage_coordinates_y = [] + stage_coordinates_z = [] + series_names = [] + + for counter, image in enumerate(imagenames): + # parse metadata + reader = ImageReader() + reader.setFlattenedResolutions(False) + omeMeta = MetadataTools.createOMEXMLMetadata() + reader.setMetadataStore(omeMeta) + reader.setId(source + str(image)) + series_count = reader.getSeriesCount() + + # get hyperstack dimensions from the first image + if counter == 0: + frame_size_x = reader.getSizeX() + frame_size_y = reader.getSizeY() + frame_size_z = reader.getSizeZ() + frame_size_c = reader.getSizeC() + frame_size_t = reader.getSizeT() + + # note the dimensions + if frame_size_z == 1: + dimensions = 2 + if frame_size_z > 1: + dimensions = 3 + + # get the physical calibration for the first image series + physSizeX = omeMeta.getPixelsPhysicalSizeX(0) + physSizeY = omeMeta.getPixelsPhysicalSizeY(0) + physSizeZ = omeMeta.getPixelsPhysicalSizeZ(0) + + # workaround to get the z-interval if physSizeZ.value() returns None. + z_interval = 1 + if physSizeZ is not None: + z_interval = physSizeZ.value() + + if frame_size_z > 1 and physSizeZ is None: + print("no z calibration found, trying to recover") + first_plane = omeMeta.getPlanePositionZ(0, 0) + next_plane_imagenumber = frame_size_c + frame_size_t - 1 + second_plane = omeMeta.getPlanePositionZ(0, next_plane_imagenumber) + z_interval = abs(abs(first_plane.value()) - abs(second_plane.value())) + print("z-interval seems to be: " + str(z_interval)) + + # create an image calibration + image_calibration = [physSizeX.value(), physSizeY.value(), z_interval] + calibration_unit = physSizeX.unit().getSymbol() + image_dimensions_czt = [frame_size_c, frame_size_z, frame_size_t] + + reader.close() + + for series in range(series_count): + if omeMeta.getImageName(series) == "macro image": + continue + + if series_count > 1 and not str(image).endswith(".vsi"): + series_names.append(omeMeta.getImageName(series)) + else: + series_names.append(str(image)) + # get the plane position in calibrated units + current_position_x = omeMeta.getPlanePositionX(series, 0) + current_position_y = omeMeta.getPlanePositionY(series, 0) + current_position_z = omeMeta.getPlanePositionZ(series, 0) + + physSizeX_max = ( + physSizeX.value() + if physSizeX.value() >= omeMeta.getPixelsPhysicalSizeX(series).value() + else omeMeta.getPixelsPhysicalSizeX(series).value() + ) + physSizeY_max = ( + physSizeY.value() + if physSizeY.value() >= omeMeta.getPixelsPhysicalSizeY(series).value() + else omeMeta.getPixelsPhysicalSizeY(series).value() + ) + if omeMeta.getPixelsPhysicalSizeZ(series): + physSizeZ_max = ( + physSizeZ.value() + if physSizeZ.value() + >= omeMeta.getPixelsPhysicalSizeZ(series).value() + else omeMeta.getPixelsPhysicalSizeZ(series).value() + ) + + else: + physSizeZ_max = 1.0 + + # get the absolute stage positions and store them + pos_x = current_position_x.value() + pos_y = current_position_y.value() + + if current_position_z is None: + print("the z-position is missing in the ome-xml metadata.") + pos_z = 1.0 + else: + pos_z = current_position_z.value() + + stage_coordinates_x.append(pos_x) + stage_coordinates_y.append(pos_y) + stage_coordinates_z.append(pos_z) + + max_size = [physSizeX_max, physSizeY_max, physSizeZ_max] + + # calculate the store the relative stage movements in px (for the grid/collection stitcher) + relative_coordinates_x_px = [] + relative_coordinates_y_px = [] + relative_coordinates_z_px = [] + + for i in range(len(stage_coordinates_x)): + rel_pos_x = ( + stage_coordinates_x[i] - stage_coordinates_x[0] + ) / physSizeX.value() + rel_pos_y = ( + stage_coordinates_y[i] - stage_coordinates_y[0] + ) / physSizeY.value() + rel_pos_z = (stage_coordinates_z[i] - stage_coordinates_z[0]) / z_interval + + relative_coordinates_x_px.append(rel_pos_x) + relative_coordinates_y_px.append(rel_pos_y) + relative_coordinates_z_px.append(rel_pos_z) + + return ( + dimensions, + stage_coordinates_x, + stage_coordinates_y, + stage_coordinates_z, + relative_coordinates_x_px, + relative_coordinates_y_px, + relative_coordinates_z_px, + image_calibration, + calibration_unit, + image_dimensions_czt, + series_names, + max_size, + ) From 0bdd98ec5bdc44eb6be66ad19905f6338eb3bbfc Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:07:29 +0100 Subject: [PATCH 438/678] Update method to support NaN (discarding them) and rounding results --- src/imcflibs/imagej/misc.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index b0a0154c..363a8e8f 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -97,24 +97,31 @@ def percentage(part, whole): return 100 * float(part) / float(whole) -def calculate_mean_and_stdv(float_values): +def calculate_mean_and_stdv(values_list, round_decimals=0): """Calculate mean and standard deviation from a list of floats. Parameters ---------- - float_values : list of float - List containing float numbers. + values_list : list of int,float + List containing numbers. + round_decimals : int, optional + Rounding decimal to use for the result, by default 0 Returns ------- tuple of (float, float) Mean and standard deviation of the input list. """ - mean = sum(float_values) / len(float_values) + filtered_list = filter(None, values_list) + + try: + mean = round(sum(filtered_list) / len(filtered_list), round_decimals) + except ZeroDivisionError: + mean = 0 tot = 0.0 - for x in float_values: + for x in filtered_list: tot = tot + (x - mean) ** 2 - return [mean, (tot / (len(float_values))) ** 0.5] + return [mean, (tot / (len(filtered_list))) ** 0.5] def find_focus(imp): From 34d5880db0e8f268a69e4357fce4fc2f110369ef Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:07:58 +0100 Subject: [PATCH 439/678] Add method to save in different file format This method could be improved to be used in the stitching script --- src/imcflibs/imagej/misc.py | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 363a8e8f..446bc4b8 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -445,3 +445,71 @@ def write_results(out_file, content): with open(out_file, "ab") as f: dict_writer = csv.DictWriter(f, content[0].keys(), delimiter=";") dict_writer.writerows(content) +def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): + """Function to save an image + + Parameters + ---------- + imageplus : ImagePlus + ImagePlus to save + extension : str + Extension to use for the output + out_dir : str + Path for the output + series : int + Series to open + pad_number : int + Number of 0 to use for padding + split_channels : bool + Bool to split or not the channels + """ + + out_ext = {} + out_ext["ImageJ-TIF"] = ".tif" + out_ext["ICS-1"] = ".ids" + out_ext["ICS-2"] = ".ics" + out_ext["OME-TIFF"] = ".ome.tif" + out_ext["CellH5"] = ".ch5" + out_ext["BMP"] = ".bmp" + + imp_to_use = [] + dir_to_save = [] + + if split_channels: + for channel in range(1, imageplus.getNChannels() + 1): + imp_to_use.append( + Duplicator().run( + imageplus, + channel, + channel, + 1, + imageplus.getNSlices(), + 1, + imageplus.getNFrames(), + ) + ) + dir_to_save.append(os.path.join(out_dir, "C" + str(channel))) + else: + imp_to_use.append(imageplus) + dir_to_save.append(out_dir) + + for index, current_imp in enumerate(imp_to_use): + basename = imageplus.getShortTitle() + + out_path = os.path.join( + dir_to_save[index], basename + "_series_" + str(series).zfill(pad_number) + ) + + if extension == "ImageJ-TIF": + check_folder(dir_to_save[index]) + IJ.saveAs(current_imp, "Tiff", out_path + ".tif") + + elif extension == "BMP": + out_folder = os.path.join(out_dir, basename + os.path.sep) + check_folder(out_folder) + StackWriter.save(current_imp, out_folder, "format=bmp") + + else: + bf.export(current_imp, out_path + out_ext[extension]) + + current_imp.close() From 15bc0c4521fbaff3cc3be0b1ba52e5b94c366dd2 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:10:17 +0100 Subject: [PATCH 440/678] Add method to pad a string --- src/imcflibs/imagej/misc.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 446bc4b8..0dffc03f 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -513,3 +513,26 @@ def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): bf.export(current_imp, out_path + out_ext[extension]) current_imp.close() +def pad_number(index, pad_length=2): + """Pad a number with leading zeros to a specified length. + + Parameters + ---------- + index : int or str + The number to be padded + pad_length : int, optional + The total length of the resulting string after padding, by default 2 + + Returns + ------- + str + The padded number as a string + + Examples + -------- + >>> pad_number(7) + '07' + >>> pad_number(42, 4) + '0042' + """ + return str(index).zfill(pad_length) From 618908834280e4c813b69ae7885a0354c1f4edd2 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:12:57 +0100 Subject: [PATCH 441/678] Add methods to find imaris and convert an image to IMS --- src/imcflibs/imagej/misc.py | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 0dffc03f..07c7e1d2 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -536,3 +536,68 @@ def pad_number(index, pad_length=2): '0042' """ return str(index).zfill(pad_length) + + +def locate_latest_imaris(paths_to_check=None): + """Find paths to latest installed Imaris or ImarisFileConverter version. + + Parameters + ---------- + paths_to_check: list of str, optional + A list of paths that should be used to look for the installations, by default + `None` which will fall back to the standard installation locations of Bitplane. + + Returns + ------- + str + Full path to the most recent (as in "version number") ImarisFileConverter + or Imaris installation folder with the latter one having priority. + Will be empty if nothing is found. + """ + + if not paths_to_check: + paths_to_check = [ + r"C:\Program Files\Bitplane\ImarisFileConverter ", + r"C:\Program Files\Bitplane\Imaris ", + ] + + imaris_paths = [""] + + for check in paths_to_check: + hits = glob.glob(check + "*") + imaris_paths += sorted( + hits, key=lambda x: float(x.replace(check, "").replace(".", "")) + ) + + return imaris_paths[-1] + + +def convert_to_imaris(path_to_image): + """Convert a given file to Imaris5 .ims using ImarisConvert.exe via subprocess. + + Parameters + ---------- + path_to_image : str + Absolute path to the input image file. + + Notes + ----- + The function handles special case for .ids files by converting them to .ics before + processing. It uses the latest installed Imaris application to perform the conversion. + """ + + path_root, file_extension = os.path.splitext(path_to_image) + if file_extension == ".ids": + file_extension = ".ics" + path_to_image = path_root + file_extension + + imaris_path = locate_latest_imaris() + + command = 'ImarisConvert.exe -i "%s" -of Imaris5 -o "%s"' % ( + path_to_image, + path_to_image.replace(file_extension, ".ims"), + ) + print("\n%s" % command) + IJ.log("Converting to Imaris5 .ims...") + subprocess.call(command, shell=True, cwd=imaris_path) + IJ.log("Conversion to .ims is finished") From 272df1f0009d412a40fadea8f00b19a50ee5e2cd Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:13:18 +0100 Subject: [PATCH 442/678] Add missing imports and formatting --- src/imcflibs/imagej/misc.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 07c7e1d2..396a54e3 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -1,16 +1,18 @@ """Miscellaneous ImageJ related functions, mostly convenience wrappers.""" +import csv +import os +import smtplib +import subprocess import sys import time -import smtplib -import os -import csv from ij import IJ # pylint: disable-msg=import-error -from ij.plugin import ImageCalculator +from ij.plugin import Duplicator, ImageCalculator, StackWriter -from . import prefs from ..log import LOG as log +from . import bioformats as bf +from . import prefs def show_status(msg): @@ -445,6 +447,8 @@ def write_results(out_file, content): with open(out_file, "ab") as f: dict_writer = csv.DictWriter(f, content[0].keys(), delimiter=";") dict_writer.writerows(content) + + def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): """Function to save an image @@ -513,6 +517,8 @@ def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): bf.export(current_imp, out_path + out_ext[extension]) current_imp.close() + + def pad_number(index, pad_length=2): """Pad a number with leading zeros to a specified length. From c1a933250bf1a40510d3c6526493e5438d5772f0 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:13:54 +0100 Subject: [PATCH 443/678] Add methods to get different metadata from OMERO --- src/imcflibs/imagej/omerotools.py | 76 +++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 8b5a3e07..0968021f 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -204,3 +204,79 @@ def find_dataset(client, dataset_id): """ # Fetch the dataset from the OMERO server using the provided dataset ID return client.getDataset(Long(dataset_id)) +def get_acquisition_metadata_from_imageid(user_client, image_wpr): + """Get acquisition metadata from OMERO based on an image ID + + Parameters + ---------- + user_client : fr.igred.omero.Client + Client used for login to OMERO + image_wpr : fr.igred.omero.repositor.ImageWrapper + Wrapper to the image for the ROIs + + Returns + ------- + tuple of (int, int, str, int) + List of info about the acquisition + """ + ctx = user_client.getCtx() + instrument_data = ( + user_client.getGateway() + .getMetadataService(ctx) + .loadInstrument(image_wpr.asDataObject().getInstrumentId()) + ) + objective_data = instrument_data.copyObjective().get(0) + if objective_data.getNominalMagnification() is None: + obj_mag = 0 + else: + obj_mag = objective_data.getNominalMagnification().getValue() + if objective_data.getLensNA() is None: + obj_na = 0 + else: + obj_na = objective_data.getLensNA().getValue() + if image_wpr.getAcquisitionDate() is None: + if image_wpr.asDataObject().getFormat() == "ZeissCZI": + field = "Information|Document|CreationDate" + date_field = get_info_from_original_metadata(user_client, image_wpr, field) + acq_date = date_field.split("T")[0] + acq_date_number = int(acq_date.replace("-", "")) + else: + acq_date = "NA" + acq_date_number = 0 + + else: + sdf = SimpleDateFormat("yyyy-MM-dd") + acq_date = sdf.format( + image_wpr.getAcquisitionDate() + ) # image_wpr.getAcquisitionDate() + acq_date_number = int(acq_date.replace("-", "")) + + return obj_mag, obj_na, acq_date, acq_date_number + + +def get_info_from_original_metadata(user_client, image_wpr, field): + """Recovers information from the original metadata + + In some cases, some information aren't parsed correctly by BF and have to + get recovered directly from the original metadata. This gets the value + based on the field string. + + Parameters + ---------- + user_client : fr.igred.omero.Client + Client used for login to OMERO + image_id : int + ID of the image to look. + field : str + Field to look for in the original metadata. Needs to be found beforehand. + + Returns + ------- + str + Value of the field + """ + omr = OriginalMetadataRequest(Long(image_wpr.getId())) + cmd = user_client.getGateway().submit(user_client.getCtx(), omr) + rsp = cmd.loop(5, 500) + gm = rsp.globalMetadata + return gm.get(field).getValue() From 05e6a2efa6d91bad6ba570b6a1f2163330d74f98 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:14:08 +0100 Subject: [PATCH 444/678] Add method to delete annotation from OMERO --- src/imcflibs/imagej/omerotools.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 0968021f..2fc83508 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -187,6 +187,21 @@ def add_annotation(client, repository_wpr, annotations, header): repository_wpr.addMapAnnotation(client, map_annotation_wpr) +def delete_annotation(user_client, repository_wpr): + """Delete annotations linked to object + + Parameters + ---------- + user_client : fr.igred.omero.Client + Client used for login to OMERO + repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper + Wrapper to the object for the anotation + + """ + kv_pairs = repository_wpr.getMapAnnotations(user_client) + user_client.delete(kv_pairs) + + def find_dataset(client, dataset_id): """Retrieve a dataset (wrapper) from the OMERO server. From fae0930d4efa08819bbda977c3ccb4d23ccc34dd Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:14:25 +0100 Subject: [PATCH 445/678] Add methods to save an OMERO.table --- src/imcflibs/imagej/omerotools.py | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 2fc83508..f83414cd 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -295,3 +295,52 @@ def get_info_from_original_metadata(user_client, image_wpr, field): rsp = cmd.loop(5, 500) gm = rsp.globalMetadata return gm.get(field).getValue() + + +def create_table_columns(headings): + """Create the table headings from the ImageJ results table + + Parameters + ---------- + headings : list(str) + List of columns names + + Returns + ------- + list(omero.gateway.model.TableDataColumn) + List of columns formatted to be uploaded to OMERO + """ + table_columns = [] + # populate the headings + for h in range(len(headings)): + heading = headings.keys()[h] + type = headings.values()[h] + # OMERO.tables queries don't handle whitespace well + heading = heading.replace(" ", "_") + # title_heading = ["Slice", "Label"] + table_columns.append(TableDataColumn(heading, h, type)) + # table_columns.append(TableDataColumn("Image", size, ImageData)) + return table_columns + + +def upload_array_as_omero_table(user_client, table_title, data, columns, image_wpr): + """Upload a table to OMERO plus from a list of lists + + Parameters + ---------- + user_client : fr.igred.omero.Client + Client used for login to OMERO + data : list(list()) + List of lists of results to upload + columns : list(str) + List of columns names + image_wpr : fr.igred.omero.repositor.ImageWrapper + Wrapper to the image to be uploaded + """ + dataset_wpr = image_wpr.getDatasets(user_client)[0] + + table_columns = create_table_columns(columns) + table_data = TableData(table_columns, data) + table_wpr = TableWrapper(table_data) + table_wpr.setName(table_title) + dataset_wpr.addTable(user_client, table_wpr) From de5c84a009dc6c0b116a8bcff33a3cb52ce97e80 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:14:37 +0100 Subject: [PATCH 446/678] Add method to save IJ-ROIs to OMERO --- src/imcflibs/imagej/omerotools.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index f83414cd..3ac17754 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -344,3 +344,24 @@ def upload_array_as_omero_table(user_client, table_title, data, columns, image_w table_wpr = TableWrapper(table_data) table_wpr.setName(table_title) dataset_wpr.addTable(user_client, table_wpr) + + +def save_rois_to_omero(user_client, image_wpr, rm): + """Save ROIs to OMERO linked to the image + + Parameters + ---------- + user_client : fr.igred.omero.Client + Client used for login to OMERO + image_wpr : fr.igred.omero.repositor.ImageWrapper + Wrapper to the image for the ROIs + rm : ij.plugin.frame.RoiManager + ROI Manager containing the ROIs + + """ + rois_list = rm.getRoisAsArray() + rois_arraylist = ArrayList(len(rois_list)) + for roi in rois_list: + rois_arraylist.add(roi) + rois_to_upload = ROIWrapper.fromImageJ(rois_arraylist) + image_wpr.saveROIs(user_client, rois_to_upload) From 16dd9616bea40112f14f6104aaf8de77beee4769 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:16:37 +0100 Subject: [PATCH 447/678] Add missing imports and formatting --- src/imcflibs/imagej/omerotools.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 3ac17754..dc8d51ce 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -8,11 +8,14 @@ [simple-omero-client]: https://github.com/GReD-Clermont/simple-omero-client """ -from java.lang import Long - - from fr.igred.omero import Client -from fr.igred.omero.annotations import MapAnnotationWrapper +from fr.igred.omero.annotations import MapAnnotationWrapper, TableWrapper +from fr.igred.omero.roi import ROIWrapper +from java.lang import Long +from java.text import SimpleDateFormat +from java.util import ArrayList +from omero.cmd import OriginalMetadataRequest +from omero.gateway.model import TableData, TableDataColumn def parse_url(client, omero_str): @@ -219,6 +222,8 @@ def find_dataset(client, dataset_id): """ # Fetch the dataset from the OMERO server using the provided dataset ID return client.getDataset(Long(dataset_id)) + + def get_acquisition_metadata_from_imageid(user_client, image_wpr): """Get acquisition metadata from OMERO based on an image ID From 04a9f847202e6efbadb96ebf47d7fc6b171d8c99 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 13 Mar 2025 16:16:46 +0100 Subject: [PATCH 448/678] Formatting --- src/imcflibs/pathtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 7053b2ae..db65e3a4 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -2,8 +2,8 @@ import os.path import platform -from os import sep import re +from os import sep from . import strtools from .log import LOG as log From 0ea34d9b4615a3a58011aab10633bc8c1e759133 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 20:54:46 +0100 Subject: [PATCH 449/678] Bring back encoding statement This is required for Python2-Pytest. --- tests/test_iotools.py | 1 + tests/test_pathtools.py | 1 + tests/test_strtools.py | 1 + 3 files changed, 3 insertions(+) diff --git a/tests/test_iotools.py b/tests/test_iotools.py index 8a9807d8..62ab9854 100644 --- a/tests/test_iotools.py +++ b/tests/test_iotools.py @@ -1,4 +1,5 @@ """Tests for `imcflibs.iotools`.""" +# -*- coding: utf-8 -*- import pytest import zipfile diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index 7a35c406..f8049de1 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -1,4 +1,5 @@ """Tests for `imcflibs.pathtools`.""" +# -*- coding: utf-8 -*- import pytest from imcflibs.pathtools import parse_path diff --git a/tests/test_strtools.py b/tests/test_strtools.py index f8242066..2729eb40 100644 --- a/tests/test_strtools.py +++ b/tests/test_strtools.py @@ -1,4 +1,5 @@ """Tests for `imcflibs.strtools`.""" +# -*- coding: utf-8 -*- import pytest import os From 770fe1e8bc721e54d3fa6cb70f631ba0d51c136a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 22:24:06 +0100 Subject: [PATCH 450/678] Add note about Windows paths and raw strings As discussed in issue #10 it is tedious trying to guard against funny escape sequences, instead it is explicitly pointed out to use a raw string. --- src/imcflibs/pathtools.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 78090e8a..7f693d85 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -10,7 +10,7 @@ def parse_path(path, prefix=""): - """Parse a path into its components. + r"""Parse a path into its components. If the path doesn't end with the pathsep, it is assumed being a file! No tests based on existing files are done, as this is supposed to also work @@ -20,6 +20,11 @@ def parse_path(path, prefix=""): *Script Parameter* `#@ File`) for either of the parameters, so it is safe to use this in ImageJ Python scripts without additional measures. + **WARNING**: when passing in **Windows paths** literally, make sure to + declare them as **raw strings** using the `r""` notation, otherwise + unexpected things might happen if the path contains sections that Python + will interpret as escape sequences (e.g. `\n`, `\t`, `\u2324`, ...). + Parameters ---------- path : str or str-like @@ -64,6 +69,7 @@ def parse_path(path, prefix=""): 'parent': '/tmp/', 'path': '/tmp/foo/'} + POSIX-style path to a directory: >>> parse_path('/tmp/foo/') @@ -76,17 +82,19 @@ def parse_path(path, prefix=""): 'parent': '/tmp/', 'path': '/tmp/foo/'} + Windows-style path to a file: - >>> parse_path('C:\\Temp\\foo\\file.ext') - {'dname': 'foo', + >>> parse_path(r'C:\Temp\new\file.ext') + {'dname': 'new', 'ext': '.ext', 'fname': 'file.ext', - 'full': 'C:/Temp/foo/file.ext', + 'full': 'C:/Temp/new/file.ext', 'basename': 'file', - 'orig': 'C:\\Temp\\foo\\file.ext', + 'orig': 'C:\\Temp\\new\\file.ext', 'parent': 'C:/Temp/', - 'path': 'C:/Temp/foo/'} + 'path': 'C:/Temp/new/'} + Special treatment for *OME-TIFF* suffixes: From 70ed32de625f92dbbcc10d24e32c25cac61a9e1f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 22:27:23 +0100 Subject: [PATCH 451/678] Do not mess with escape sequences As discussed in issue #10 --- src/imcflibs/pathtools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 7f693d85..4e68efc9 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -109,7 +109,6 @@ def parse_path(path, prefix=""): 'path': '/path/to/some/'} """ path = str(path) - path = path.replace("\n", "\\n").replace("\t", "\\t") if prefix: # remove leading slash, otherwise join() will discard the first path: if path.startswith("/"): From 47834445e0f06ccd2c294516ec3d9af767f38cd7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 22:27:38 +0100 Subject: [PATCH 452/678] Test for Windows raw-string paths Relates to #10 --- tests/test_pathtools.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index 2e842eee..d1904ace 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -43,6 +43,23 @@ def test_parse_path_windows(): assert parsed['dname'] == 'foo' +def test_parse_path_windows_newline(): + path = r'C:\Temp\new\file.ext' + parsed = parse_path(path) + + assert parsed == { + 'dname': 'new', + 'ext': '.ext', + 'fname': 'file.ext', + 'full': 'C:/Temp/new/file.ext', + 'basename': 'file', + 'orig': 'C:\\Temp\\new\\file.ext', + 'parent': 'C:/Temp', + 'path': 'C:/Temp/new/', + } + + + def test_parse_path_windows_tabs_and_lines(): r"""Test non-raw string containing newline \n and tab \t sequences.""" path = "C:\new_folder\test" From e338326d9d95e285fc1f6ebc9ebb627f56e5a6da Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 22:40:00 +0100 Subject: [PATCH 453/678] Adjust non-raw Windows escape-sequence path test Relates to #10 --- tests/test_pathtools.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index d1904ace..4b7e1d0b 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -59,14 +59,17 @@ def test_parse_path_windows_newline(): } - def test_parse_path_windows_tabs_and_lines(): - r"""Test non-raw string containing newline \n and tab \t sequences.""" + r"""Test non-raw string containing newline `\n` and tab `\t` sequences. + + As `parse_path()` cannot work on non-raw strings containing escape + sequences, the parsed result will not be the expected one. + """ path = "C:\new_folder\test" parsed = parse_path(path) - assert parsed["full"] == r"C:\new_folder\test" - assert parsed["fname"] == "test" + assert parsed["full"] != r"C:\new_folder\test" + assert parsed["fname"] != "test" def test_jython_fiji_exists(tmpdir): From 6ef9896d170839ebfa532d81be47436f456f6c2d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 23:34:52 +0100 Subject: [PATCH 454/678] Reformat docstring code --- src/imcflibs/pathtools.py | 72 ++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 4e68efc9..b08d0672 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -60,53 +60,61 @@ def parse_path(path, prefix=""): POSIX-style path to a file with a suffix: >>> parse_path('/tmp/foo/file.suffix') - {'dname': 'foo', - 'ext': '', - 'fname': 'file', - 'full': '/tmp/foo/file', - 'basename': 'file', - 'orig': '/tmp/foo/file', - 'parent': '/tmp/', - 'path': '/tmp/foo/'} + { + "dname": "foo", + "ext": "", + "fname": "file", + "full": "/tmp/foo/file", + "basename": "file", + "orig": "/tmp/foo/file", + "parent": "/tmp/", + "path": "/tmp/foo/", + } POSIX-style path to a directory: >>> parse_path('/tmp/foo/') - {'dname': 'foo', - 'ext': '', - 'fname': '', - 'full': '/tmp/foo/', - 'basename': '', - 'orig': '/tmp/foo/', - 'parent': '/tmp/', - 'path': '/tmp/foo/'} + { + "dname": "foo", + "ext": "", + "fname": "", + "full": "/tmp/foo/", + "basename": "", + "orig": "/tmp/foo/", + "parent": "/tmp/", + "path": "/tmp/foo/", + } Windows-style path to a file: >>> parse_path(r'C:\Temp\new\file.ext') - {'dname': 'new', - 'ext': '.ext', - 'fname': 'file.ext', - 'full': 'C:/Temp/new/file.ext', - 'basename': 'file', - 'orig': 'C:\\Temp\\new\\file.ext', - 'parent': 'C:/Temp/', - 'path': 'C:/Temp/new/'} + { + "dname": "new", + "ext": ".ext", + "fname": "file.ext", + "full": "C:/Temp/new/file.ext", + "basename": "file", + "orig": "C:\\Temp\\new\\file.ext", + "parent": "C:/Temp", + "path": "C:/Temp/new/", + } Special treatment for *OME-TIFF* suffixes: >>> parse_path("/path/to/some/nice.OME.tIf") - {'basename': 'nice', - 'dname': 'some', - 'ext': '.OME.tIf', - 'fname': 'nice.OME.tIf', - 'full': '/path/to/some/nice.OME.tIf', - 'orig': '/path/to/some/nice.OME.tIf', - 'parent': '/path/to/', - 'path': '/path/to/some/'} + { + "basename": "nice", + "dname": "some", + "ext": ".OME.tIf", + "fname": "nice.OME.tIf", + "full": "/path/to/some/nice.OME.tIf", + "orig": "/path/to/some/nice.OME.tIf", + "parent": "/path/to/", + "path": "/path/to/some/", + } """ path = str(path) if prefix: From e2c3a7140e5b70999ccb309a430ff8d58db18379 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 23:50:09 +0100 Subject: [PATCH 455/678] Add tests docstrings --- tests/test_pathtools.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index 4b7e1d0b..4a2bf6cf 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -11,6 +11,7 @@ def test_parse_path(): + """Tests using regular POSIX-style paths.""" path = '/tmp/foo/' path_to_dir = parse_path(path) path_to_file = parse_path(path + 'file.ext') @@ -35,6 +36,7 @@ def test_parse_path(): def test_parse_path_windows(): path = r'C:\foo\bar' + """Test using a Windows-style path.""" parsed = parse_path(path) assert parsed['orig'] == path @@ -44,6 +46,7 @@ def test_parse_path_windows(): def test_parse_path_windows_newline(): + """Test a Windows path with newline and tab sequences as raw string.""" path = r'C:\Temp\new\file.ext' parsed = parse_path(path) @@ -73,10 +76,12 @@ def test_parse_path_windows_tabs_and_lines(): def test_jython_fiji_exists(tmpdir): + """Test the Jython/Fiji `os.path.exists()` workaround.""" assert jython_fiji_exists(str(tmpdir)) == True def test_image_basename(): + """Test basename extraction for various image file names.""" assert image_basename('/path/to/image_file_01.png') == 'image_file_01' assert image_basename('more-complex-stack.ome.tif') == 'more-complex-stack' assert image_basename('/tmp/FoObAr.OMe.tIf') == 'FoObAr' From a47732f4f5627844a911905b2a179cac8edf2e9c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 23:50:28 +0100 Subject: [PATCH 456/678] Rename tests to reflect their function --- tests/test_pathtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index 4a2bf6cf..e8c661ec 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -45,7 +45,7 @@ def test_parse_path_windows(): assert parsed['dname'] == 'foo' -def test_parse_path_windows_newline(): +def test_parse_path_windows_newline_tab(): """Test a Windows path with newline and tab sequences as raw string.""" path = r'C:\Temp\new\file.ext' parsed = parse_path(path) @@ -62,7 +62,7 @@ def test_parse_path_windows_newline(): } -def test_parse_path_windows_tabs_and_lines(): +def test_parse_path_windows_nonraw(): r"""Test non-raw string containing newline `\n` and tab `\t` sequences. As `parse_path()` cannot work on non-raw strings containing escape From ca0cc6ceb46025262de666930f59c6626d5d2e69 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 13 Mar 2025 23:51:16 +0100 Subject: [PATCH 457/678] Minor Windows path adjustments for test --- tests/test_pathtools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index e8c661ec..bbcfacd1 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -35,14 +35,14 @@ def test_parse_path(): def test_parse_path_windows(): - path = r'C:\foo\bar' """Test using a Windows-style path.""" + path = r'C:\Foo\Bar' parsed = parse_path(path) assert parsed['orig'] == path - assert parsed['full'] == r'C:/foo/bar' - assert parsed['fname'] == 'bar' - assert parsed['dname'] == 'foo' + assert parsed['full'] == 'C:/Foo/Bar' + assert parsed['fname'] == 'Bar' + assert parsed['dname'] == 'Foo' def test_parse_path_windows_newline_tab(): From 2f801f41823807169ef66c94b6a64db2caa11474 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 00:11:12 +0100 Subject: [PATCH 458/678] Remove metadata variables The information is all in the package metadata. --- tests/test_iotools.py | 4 ---- tests/test_pathtools.py | 4 ---- tests/test_strtools.py | 4 ---- 3 files changed, 12 deletions(-) diff --git a/tests/test_iotools.py b/tests/test_iotools.py index 62ab9854..d1d5f9f7 100644 --- a/tests/test_iotools.py +++ b/tests/test_iotools.py @@ -19,10 +19,6 @@ file_types = (io.IOBase,) -__author__ = "Niko Ehrenfeuchter" -__copyright__ = "Niko Ehrenfeuchter" -__license__ = "gpl3" - def test_filehandle(tmpdir): tmpfile = tmpdir.join("testfile") diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index cc40e4bb..b52db8e0 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -5,10 +5,6 @@ from imcflibs.pathtools import jython_fiji_exists from imcflibs.pathtools import image_basename -__author__ = "Niko Ehrenfeuchter" -__copyright__ = "Niko Ehrenfeuchter" -__license__ = "gpl3" - def test_parse_path(): """Tests using regular POSIX-style paths.""" diff --git a/tests/test_strtools.py b/tests/test_strtools.py index 2729eb40..21d69ff7 100644 --- a/tests/test_strtools.py +++ b/tests/test_strtools.py @@ -9,10 +9,6 @@ from imcflibs.strtools import flatten from imcflibs.strtools import strip_prefix -__author__ = "Niko Ehrenfeuchter" -__copyright__ = "Niko Ehrenfeuchter" -__license__ = "gpl3" - def test__is_string_like(): assert _is_string_like("foo") == True From edbef7da6a2ddf6d702da49024c380d006e11997 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 00:11:22 +0100 Subject: [PATCH 459/678] Add docstrings to tests --- tests/test_strtools.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_strtools.py b/tests/test_strtools.py index 21d69ff7..87cf17c5 100644 --- a/tests/test_strtools.py +++ b/tests/test_strtools.py @@ -11,24 +11,29 @@ def test__is_string_like(): + """Test `_is_string_like()`.""" assert _is_string_like("foo") == True assert _is_string_like(12345) == False def test_filename_from_string(): + """Test `filename()` using a string.""" assert filename("test_file_name") == "test_file_name" def test_filename_from_handle(tmpdir): + """Test `filename()` using a file handle.""" path = str(tmpdir) fhandle = tmpdir.join("foo.txt") assert filename(fhandle) == os.path.join(path, "foo.txt") def test_flatten(): + """Test `flatten()` using a tuple.""" assert flatten(("foo", "bar")) == "foobar" def test_strip_prefix(): + """Test `strip_prefix()`.""" assert strip_prefix("foobar", "foo") == "bar" assert strip_prefix("foobar", "bar") == "foobar" From c81ef1271329c4baba03097e50b66d7af3685e9d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 00:19:46 +0100 Subject: [PATCH 460/678] More tests docstrings --- tests/test_iotools.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_iotools.py b/tests/test_iotools.py index d1d5f9f7..3e503600 100644 --- a/tests/test_iotools.py +++ b/tests/test_iotools.py @@ -21,6 +21,7 @@ def test_filehandle(tmpdir): + """Test instance types of objects returned by `filehandle()`.""" tmpfile = tmpdir.join("testfile") tmpname = str(tmpfile) # print(tmpname) @@ -37,6 +38,11 @@ def test_filehandle(tmpdir): def test_readtxt(tmpdir): + """Test the `readtxt()` function. + + Read text from a regular file as well as from a zip file, both straight and + using the `flat` option. + """ content = [ "lorem\n", "ipsum\n", From 1d832b56799c1137c5e5931003cd37a2953b0629 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 00:23:12 +0100 Subject: [PATCH 461/678] Improve test-flow readability --- tests/test_iotools.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_iotools.py b/tests/test_iotools.py index 3e503600..bf8c6aae 100644 --- a/tests/test_iotools.py +++ b/tests/test_iotools.py @@ -27,12 +27,16 @@ def test_filehandle(tmpdir): # print(tmpname) tmphandle = open(str(tmpfile), "w") print(type(tmphandle)) + assert isinstance(tmpname, str) print("tmpname is str-like ๐Ÿฆ‹") + assert isinstance(tmphandle, file_types) print("tmphandle is file/io-like ๐Ÿฆ‹") + assert isinstance(filehandle(tmpname), file_types) print("filehandle(tmpname) is file/io-like ๐Ÿฆ‹") + assert isinstance(filehandle(tmphandle, "w"), file_types) print("filehandle(tmphandle) is file/io-like ๐Ÿฆ‹") From 9c24420fe1634d0d3395ce415431a6b78b581bb5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 00:34:49 +0100 Subject: [PATCH 462/678] Fix docstrings --- src/imcflibs/pathtools.py | 6 +++--- src/imcflibs/strtools.py | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 1b8ab400..a6b0d936 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -142,7 +142,7 @@ def parse_path(path, prefix=""): def join2(path1, path2): - """Join two paths into one, much like os.path.join(). + r"""Join two paths into one, much like os.path.join(). The main difference is that `join2()` takes exactly two arguments, but they can be non-str (as long as they're having a `__str__()` method), so this is @@ -358,12 +358,12 @@ def folder_size(source): def create_directory(new_path): - """create a new directory if it does not already exist + """create a new directory if it does not already exist. Parameters ---------- new_path : str - Path to the new directory + Path to the new directory. """ if not os.path.exists(new_path): os.makedirs(new_path) diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index 462a18c9..10e731a6 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -14,6 +14,11 @@ def _is_string_like(obj): instance of it (or a subclass thereof). So it's more generic than using isinstance(obj, str). + Parameters + ---------- + obj : any + The object to be checked for being string-like. + Example ------- >>> _is_string_like('foo') @@ -41,6 +46,7 @@ def filename(name): Parameters ---------- name : str or filehandle or java.io.File + The object to retrieve the filename from. Returns ------- @@ -78,6 +84,7 @@ def flatten(lst): Parameters ---------- lst : list(str) + The list of strings to be flattened. Returns ------- From e70e2c2e27c48c9bbe5f4adcb9e29aa76edbbaae Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 00:57:28 +0100 Subject: [PATCH 463/678] More docstring fixes --- src/imcflibs/pathtools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index a6b0d936..dff92699 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -56,7 +56,6 @@ def parse_path(path, prefix=""): Examples -------- - POSIX-style path to a file with a suffix: >>> parse_path('/tmp/foo/file.suffix') @@ -168,7 +167,7 @@ def join2(path1, path2): def jython_fiji_exists(path): - """Wrapper to work around problems with Jython 2.7 in Fiji. + """Work around problems with `os.path.exists()` in Jython 2.7 in Fiji. In current Fiji, the Jython implementation of os.path.exists(path) raises a java.lang.AbstractMethodError iff 'path' doesn't exist. This function @@ -235,6 +234,7 @@ def image_basename(orig_name): Parameters ---------- orig_name : str + The original name, possibly containing paths and filename suffix. Examples -------- @@ -358,7 +358,7 @@ def folder_size(source): def create_directory(new_path): - """create a new directory if it does not already exist. + """Create a new directory if it does not already exist. Parameters ---------- From 9b5a4d426d2a12caac6806eeb4d7f0d91cfb67c1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 14:03:50 +0100 Subject: [PATCH 464/678] Configure ruff docstring linting --- pyproject.toml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8d534db5..dd9f673f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,3 +34,18 @@ pytest-cov = "^6.0.0" [build-system] build-backend = "poetry.core.masonry.api" requires = ["poetry-core"] + +[tool.ruff.lint] +select = [ + "D", + # imperative mood for all docstrings + "D401", +] + +ignore = [ + # no blank lines allowed after function docstring + "D202", +] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" \ No newline at end of file From cb94e29e7f554727cf30f552ab2425f4c384741f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 14:10:09 +0100 Subject: [PATCH 465/678] Add a linting workflow --- .github/workflows/lint.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..b9ed3671 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: Lint ๐Ÿ•ต code โšก + +on: + push: + workflow_dispatch: + +jobs: + + lint: + name: Ruff โšก๐Ÿ•ต + + runs-on: ubuntu-latest + + steps: + + - name: ๐Ÿ“ฅ Checkout repo + uses: actions/checkout@v4 + + - name: Run Ruff โšก + uses: astral-sh/ruff-action@v3 + with: + args: check + src: "./src" From c8e482ae5fa18fc5e0034f99998d151b75d41b0f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 14:21:48 +0100 Subject: [PATCH 466/678] Rename workflow --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b9ed3671..0906c906 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Lint ๐Ÿ•ต code โšก +name: ๐Ÿ”Ž Lint code โšก on: push: From 8cd64c20b5753e6f21c57c74ea7791977f50d3cb Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 14:22:23 +0100 Subject: [PATCH 467/678] Rename step, remove "src" argument --- .github/workflows/lint.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0906c906..c904bdca 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,8 +16,7 @@ jobs: - name: ๐Ÿ“ฅ Checkout repo uses: actions/checkout@v4 - - name: Run Ruff โšก + - name: Run Ruff checks โšก uses: astral-sh/ruff-action@v3 with: args: check - src: "./src" From 6f40e6be661505484046560a2d5eb8bcf5b90ec4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 14:22:46 +0100 Subject: [PATCH 468/678] Enforce docstring summary on 1st line --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index dd9f673f..a421ce42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,8 @@ requires = ["poetry-core"] [tool.ruff.lint] select = [ "D", + # summary lines have to be placed on the first physical line of the docstring + "D212", # imperative mood for all docstrings "D401", ] From 7defedc1a5b418f26c9a2a598eee3c62055fb4bf Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 14:23:38 +0100 Subject: [PATCH 469/678] Summary line has to end in a punctuation mark --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a421ce42..4adcc79a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,8 @@ select = [ "D212", # imperative mood for all docstrings "D401", + # summary line has to end in a punctuation mark + "D415", ] ignore = [ From 9361259f9dc36b4f8d9219a524e32ae8f55a9f06 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 12 Mar 2025 14:25:29 +0100 Subject: [PATCH 470/678] Require documentation for all function parameters --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4adcc79a..204ea2ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,8 @@ select = [ "D401", # summary line has to end in a punctuation mark "D415", + # require documentation for _all_ function parameters + "D417", ] ignore = [ From 918819e45d4f48817d8fa0aa02d2e299d78c3ad0 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 00:30:28 +0100 Subject: [PATCH 471/678] Exclude interactive test scripts from linting --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 204ea2ae..0ab51dd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,12 @@ pytest-cov = "^6.0.0" build-backend = "poetry.core.masonry.api" requires = ["poetry-core"] + [tool.ruff.lint] +exclude = [ + "tests/interactive-imagej/*" +] + select = [ "D", # summary lines have to be placed on the first physical line of the docstring @@ -53,5 +58,6 @@ ignore = [ "D202", ] + [tool.ruff.lint.pydocstyle] convention = "numpy" \ No newline at end of file From 6d7a84fbb8910edb2fbc4bccfbd6da6fcca6da01 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 09:30:58 +0100 Subject: [PATCH 472/678] Docstring fixes --- src/imcflibs/imagej/bioformats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 60865845..9767b54d 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -140,7 +140,7 @@ def import_image( def export(imp, filename, overwrite=False): - """Simple wrapper to export an image to a given file. + """Export an ImagePlus object to a given file. Parameters ---------- @@ -283,7 +283,7 @@ def write_bf_memoryfile(path_to_file): Parameters ---------- - string + path_to_file : str The full path to the image file. """ reader = Memoizer(ImageReader()) From b8ba9601e66c0776936b6b3058e2423304578574 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 09:31:06 +0100 Subject: [PATCH 473/678] Docstring fixes --- src/imcflibs/imagej/gpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/gpu.py b/src/imcflibs/imagej/gpu.py index b3b1f04c..a5711b4d 100644 --- a/src/imcflibs/imagej/gpu.py +++ b/src/imcflibs/imagej/gpu.py @@ -62,7 +62,7 @@ def dilate_labels(clij2_instance, label_image, dilation_radius, channel=None): Instance of CLIJ to communicate with the GPU. label_image : ij.ImagePlus Label Image to be dilated. - erosion_radius : int + dilation_radius : int Radius for dilation. channel : int, optional Specific channel to apply dilation. From a9ab08fb10d84dbc8ae9374bb5c323d6caaa2a22 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 09:32:47 +0100 Subject: [PATCH 474/678] Docstring fixes --- src/imcflibs/imagej/misc.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 10994a92..7fe63c7b 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -264,12 +264,18 @@ def progressbar(progress, total, line_number, prefix=""): def timed_log(message, as_string=False): - """Print a message to the ImageJ log window with a timestamp added. + """Print a message to the ImageJ log window, prefixed with a timestamp. + + If `as_string` is set to True, nothgin will be printed to the log window, + instead the formatted log message will be returned as a string. Parameters ---------- message : str Message to print + as_string : bool, optional + Flag to request the formatted string to be returned instead of printing + it to the log. By default False. """ if as_string: return time.strftime("%H:%M:%S", time.localtime()) + ": " + message + " " @@ -351,14 +357,14 @@ def subtract_images(imp1, imp2): Parameters ---------- imp1: ij.ImagePlus - The ImagePlus that is to be subtracted from + The ImagePlus that is to be subtracted from. imp2: ij.ImagePlus - The ImagePlus that is to be subtracted + The ImagePlus that is to be subtracted. Returns - --------- + ------- ij.ImagePlus - The ImagePlus resulting from the subtraction + The ImagePlus resulting from the subtraction. """ ic = ImageCalculator() subtracted = ic.run("Subtract create", imp1, imp2) @@ -371,8 +377,8 @@ def close_images(list_of_imps): Parameters ---------- - list(ij.ImagePlus) - A list of open ImagePlus objects + list_of_imps: list(ij.ImagePlus) + A list of open ImagePlus objects. """ for imp in list_of_imps: imp.changes = False From 1ed0b0c6eeb19f11ecdb3435e75da6d534d08998 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 09:33:00 +0100 Subject: [PATCH 475/678] Avoid code duplication --- src/imcflibs/imagej/misc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 7fe63c7b..1ccf35cc 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -277,9 +277,10 @@ def timed_log(message, as_string=False): Flag to request the formatted string to be returned instead of printing it to the log. By default False. """ + formatted = time.strftime("%H:%M:%S", time.localtime()) + ": " + message + " " if as_string: - return time.strftime("%H:%M:%S", time.localtime()) + ": " + message + " " - IJ.log(time.strftime("%H:%M:%S", time.localtime()) + ": " + message + " ") + return formatted + IJ.log(formatted) def get_free_memory(): From c0990d44405d8b93f4b75e573adca3c9083edc99 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 10:09:46 +0100 Subject: [PATCH 476/678] Add module docstring --- src/imcflibs/imagej/objects3d.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 9bcbd384..cf738743 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -1,3 +1,10 @@ +"""Functions to work with 3D objects. + +Mostly (although not exclusively) related to the [`mcib3d`][mcib3d] package. + +[mcib3d]: https://mcib3d.frama.io/3d-suite-imagej/ +""" + from ij import IJ from mcib3d.geom import Objects3DPopulation from mcib3d.image3d import ImageHandler, ImageLabeller From 264ee75055c82997b78804038dbfcd9470faafc1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 10:09:53 +0100 Subject: [PATCH 477/678] Docstring fixes --- src/imcflibs/imagej/omerotools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 8b5a3e07..6fdfb7ef 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -171,7 +171,7 @@ def add_annotation(client, repository_wpr, annotations, header): Parameters ---------- - user_client : fr.igred.omero.Client + client : fr.igred.omero.Client The client object used to connect to the OMERO server. repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper Wrapper to the object for the anotation. From a5ea57e524f3bc6e1dc3f7b8ede549a6dacc5519 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 10:10:56 +0100 Subject: [PATCH 478/678] Docstring fixes --- src/imcflibs/imagej/prefs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/prefs.py b/src/imcflibs/imagej/prefs.py index 0e3bde9a..aefc1a8b 100644 --- a/src/imcflibs/imagej/prefs.py +++ b/src/imcflibs/imagej/prefs.py @@ -4,7 +4,7 @@ def debug_mode(): - """Wrapper to check if 'imcf.debugging' is enabled. + """Check if the 'imcf.debugging' setting is enabled. This is a workaround for a Jython issue in ImageJ with values that are stored in the "IJ_Prefs.txt" file being cast to the wrong types and / or @@ -21,7 +21,7 @@ def debug_mode(): def fix_ij_options(): - """Wrapper to setup ImageJ default options.""" + """Set up ImageJ default options.""" # disable inverting LUT IJ.run("Appearance...", " menu=0 16-bit=Automatic") From f7047690c6522c826056944244ce149854eab69b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 10:11:29 +0100 Subject: [PATCH 479/678] Add FIXME --- src/imcflibs/imagej/prefs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/prefs.py b/src/imcflibs/imagej/prefs.py index aefc1a8b..b1882358 100644 --- a/src/imcflibs/imagej/prefs.py +++ b/src/imcflibs/imagej/prefs.py @@ -21,7 +21,10 @@ def debug_mode(): def fix_ij_options(): - """Set up ImageJ default options.""" + """Set up ImageJ default options. + + FIXME: Explain the rationale / idea! + """ # disable inverting LUT IJ.run("Appearance...", " menu=0 16-bit=Automatic") From aeaab0d2a62de0d8b2a966bbe8e46d99cc9ee346 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 10:15:25 +0100 Subject: [PATCH 480/678] Docstring fixes --- src/imcflibs/imagej/shading.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py index 3b818542..02a11af1 100644 --- a/src/imcflibs/imagej/shading.py +++ b/src/imcflibs/imagej/shading.py @@ -183,22 +183,19 @@ def process_files(files, outpath, model_file, fmt): model.close() def simple_flatfield_correction(imp, sigma=20.0): - """Performs a simple flatfield correction to a given ImagePlus stack. - - The function returns a 32-bit corrected flatfield image. + """Perform a simple flatfield correction to a given ImagePlus stack. Parameters ---------- imp : ij.ImagePlus The input stack to be projected. sigma: float, optional - The sigma value for the Gaussian blur, default=20.0 + The sigma value for the Gaussian blur, default=20.0. Returns ------- ij.ImagePlus - The 32-bit image result of flatfield correction - + The 32-bit image resulting from the flatfield correction. """ flatfield = imp.duplicate() sigma_str = "sigma=" + str(sigma) From abe8178903d076021a50a81c311e549099b4d231 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 10:17:12 +0100 Subject: [PATCH 481/678] Docstring fixes --- src/imcflibs/imagej/split.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/split.py b/src/imcflibs/imagej/split.py index 5184e91b..0d3112c8 100644 --- a/src/imcflibs/imagej/split.py +++ b/src/imcflibs/imagej/split.py @@ -8,7 +8,7 @@ def split_by_c_and_z(log, dname, imgf, skip_top, skip_bottom): - """Helper function to open, split and save a file. + """Open a file, split by Z and C and save the result into individual TIFFs. Load the file specified, split by channels and z-slices, create a directory for each channel using the channel number as a name suffix and export From 22299f1936dc37dd7c5a4f77020c01948817d341 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 10:20:29 +0100 Subject: [PATCH 482/678] Docstring fixes --- src/imcflibs/imagej/trackmate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 78899ba5..9fe8ce99 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -1,3 +1,8 @@ +"""Functions working with [TrackMate]. + +[TrackMate]: https://imagej.net/plugins/trackmate/ +""" + import os import sys @@ -301,7 +306,7 @@ def run_trackmate( crop_roi=None, ): # sourcery skip: merge-else-if-into-elif, swap-if-else-branches - """Function to run TrackMate on already opened data. + """Run TrackMate on an open ImagePlus object. Parameters ---------- From 2214a2d10f932ad96847b390afc8f72f7cbf8997 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 14 Mar 2025 10:22:02 +0100 Subject: [PATCH 483/678] Docstring fixes --- src/imcflibs/iotools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/imcflibs/iotools.py b/src/imcflibs/iotools.py index 480822f6..1891545e 100644 --- a/src/imcflibs/iotools.py +++ b/src/imcflibs/iotools.py @@ -22,6 +22,7 @@ def filehandle(fname, mode="r"): Parameters ---------- fname : str or filehandle + The object to ensure it is a file handle, or to create one from it. mode : str The desired mode of the filehandle (default=read). From e870bea6b36e5cc3de9686ee48e16ee5a0e517b5 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 11 Feb 2025 17:23:15 +0100 Subject: [PATCH 484/678] 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 485/678] 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 486/678] 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 487/678] 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 488/678] 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 489/678] 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 490/678] 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 491/678] 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 492/678] 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 493/678] 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 494/678] 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 495/678] 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 496/678] 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 497/678] 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 498/678] 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 499/678] 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 500/678] 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 501/678] 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 502/678] 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 503/678] 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 504/678] 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 505/678] 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 506/678] 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 507/678] 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 508/678] 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 509/678] 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 510/678] 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 511/678] 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 512/678] 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 513/678] 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 514/678] 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 7b9b5792ea5c4bb3891bbf1729f5eeea9658f542 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 17 Mar 2025 15:38:56 +0100 Subject: [PATCH 515/678] Run linting also on pull requsts --- .github/workflows/lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c904bdca..88871c75 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,6 +2,7 @@ name: ๐Ÿ”Ž Lint code โšก on: push: + pull_request: workflow_dispatch: jobs: From c44aeb84c7709cbc3abe5581edcaa2c9bf8e0fad Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 17 Mar 2025 16:01:28 +0100 Subject: [PATCH 516/678] Exclude Java-related workaround from coverage --- src/imcflibs/pathtools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index dff92699..f29eb417 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -175,7 +175,7 @@ def jython_fiji_exists(path): """ try: return os.path.exists(path) - except java.lang.AbstractMethodError: + except java.lang.AbstractMethodError: # pragma: no cover return False From 607c29e613507a0f63b782c88aaf7b3f3406d173 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 17 Mar 2025 16:13:09 +0100 Subject: [PATCH 517/678] Add test_parse_path_with_prefix() --- tests/test_pathtools.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index b52db8e0..7be6b989 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -30,6 +30,19 @@ def test_parse_path(): assert path_to_dir["ext"] == "" +def test_parse_path_with_prefix(): + """Test parse_path with a prefix parameter.""" + exp_full = "/FOO/BAR/tmp/foo/file.suffix" + prefix = "/FOO/BAR/" + path = "/tmp/foo/file.suffix" + assert parse_path(path, prefix)["full"] == exp_full + + # test again without trailing / leading slashes: + prefix = "/FOO/BAR" + path = "tmp/foo/file.suffix" + assert parse_path(path, prefix)["full"] == exp_full + + def test_parse_path_windows(): """Test using a Windows-style path.""" path = r"C:\Foo\Bar" From 7a1d8de732db084491b318f5f2fb7d451d55a4f6 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 17 Mar 2025 18:01:52 +0100 Subject: [PATCH 518/678] Add test_gen_name_from_orig() --- tests/test_pathtools.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index 7be6b989..19acda57 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -4,6 +4,7 @@ from imcflibs.pathtools import parse_path from imcflibs.pathtools import jython_fiji_exists from imcflibs.pathtools import image_basename +from imcflibs.pathtools import gen_name_from_orig def test_parse_path(): @@ -94,3 +95,14 @@ def test_image_basename(): assert image_basename("/path/to/image_file_01.png") == "image_file_01" assert image_basename("more-complex-stack.ome.tif") == "more-complex-stack" assert image_basename("/tmp/FoObAr.OMe.tIf") == "FoObAr" + + +def test_gen_name_from_orig(): + """Test assembling an output name from input, tag and suffix.""" + outpath = "/outpath" + inpath = "/inpath/to/foobar.tif" + tag = "-avg" + suffix = ".h5" + generated = gen_name_from_orig(outpath, inpath, tag, suffix) + assert generated == "/outpath/foobar-avg.h5" + From f0fdf76584fc55616dce80b57b3ccdb6fe578204 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Mon, 17 Mar 2025 18:11:16 +0100 Subject: [PATCH 519/678] Add test_gen_name_from_orig() --- tests/test_pathtools.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_pathtools.py b/tests/test_pathtools.py index 19acda57..2a0338e6 100644 --- a/tests/test_pathtools.py +++ b/tests/test_pathtools.py @@ -5,6 +5,7 @@ from imcflibs.pathtools import jython_fiji_exists from imcflibs.pathtools import image_basename from imcflibs.pathtools import gen_name_from_orig +from imcflibs.pathtools import derive_out_dir def test_parse_path(): @@ -106,3 +107,10 @@ def test_gen_name_from_orig(): generated = gen_name_from_orig(outpath, inpath, tag, suffix) assert generated == "/outpath/foobar-avg.h5" + +def test_derive_out_dir(): + """Test derive_out_dir() using various parameter combinations.""" + assert derive_out_dir("/foo", "-") == "/foo" + assert derive_out_dir("/foo", "none") == "/foo" + assert derive_out_dir("/foo", "NONE") == "/foo" + assert derive_out_dir("/foo", "/bar") == "/bar" From 8a041dedd95e51e6ab936579dba81d14fd92b653 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 09:42:52 +0100 Subject: [PATCH 520/678] Use `imp` instead of `imageplus` --- src/imcflibs/imagej/misc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 396a54e3..a0a0eb3c 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -454,7 +454,7 @@ def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): Parameters ---------- - imageplus : ImagePlus + imp : ij.ImagePlus ImagePlus to save extension : str Extension to use for the output @@ -480,16 +480,16 @@ def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): dir_to_save = [] if split_channels: - for channel in range(1, imageplus.getNChannels() + 1): + for channel in range(1, imp.getNChannels() + 1): imp_to_use.append( Duplicator().run( - imageplus, + imp, channel, channel, 1, - imageplus.getNSlices(), + imp.getNSlices(), 1, - imageplus.getNFrames(), + imp.getNFrames(), ) ) dir_to_save.append(os.path.join(out_dir, "C" + str(channel))) @@ -498,7 +498,7 @@ def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): dir_to_save.append(out_dir) for index, current_imp in enumerate(imp_to_use): - basename = imageplus.getShortTitle() + basename = imp.getShortTitle() out_path = os.path.join( dir_to_save[index], basename + "_series_" + str(series).zfill(pad_number) From 309360fa98041b2ace1627f518ea120a327d3fe0 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 09:43:14 +0100 Subject: [PATCH 521/678] Improve method name and docstring --- src/imcflibs/imagej/misc.py | 59 +++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index a0a0eb3c..20a11caf 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -449,23 +449,64 @@ def write_results(out_file, content): dict_writer.writerows(content) -def save_as(imageplus, extension, out_dir, series, pad_number, split_channels): - """Function to save an image +def save_image_with_extension( + imp, extension, out_dir, series, pad_number, split_channels +): + """Save an ImagePlus object in the specified format. + + This function provides flexible options for saving ImageJ images in various + formats with customizable naming conventions. It supports different + Bio-Formats compatible formats as well as ImageJ-native formats, and can + handle multi-channel images by either saving them as a single file or + splitting channels into separate files. + + The function automatically creates necessary directories and uses consistent + naming patterns with series numbers. For split channels, separate + subdirectories are created for each channel (C1, C2, etc.). Parameters ---------- imp : ij.ImagePlus - ImagePlus to save - extension : str - Extension to use for the output + ImagePlus object to save. + extension : {'ImageJ-TIF', 'ICS-1', 'ICS-2', 'OME-TIFF', 'CellH5', 'BMP'} + Output format to use: - ImageJ-TIF: Saves as ImageJ TIFF format (.tif) - + ICS-1: Saves as ICS version 1 format (.ids) - ICS-2: Saves as ICS + version 2 format (.ics) - OME-TIFF: Saves as OME-TIFF format (.ome.tif) + - CellH5: Saves as CellH5 format (.ch5) - BMP: Saves as BMP format (one + file per slice) out_dir : str - Path for the output + Directory path where the image(s) will be saved. series : int - Series to open + Series number to append to the filename. pad_number : int - Number of 0 to use for padding + Number of digits to use when zero-padding the series number. split_channels : bool - Bool to split or not the channels + If True, splits channels and saves them individually in separate folders + named "C1", "C2", etc. inside out_dir. If False, saves all channels in a + single file. + + Notes + ----- + - For "ImageJ-TIF" format, uses IJ.saveAs function + - For "BMP" format, saves images using StackWriter.save with one BMP file + per slice in a subfolder named after the original image + - For all other formats, uses Bio-Formats exporter (bf.export) + - The original ImagePlus is not modified, but any duplicate images created + for channel splitting are closed after saving + - Metadata is preserved when using Bio-Formats exporters + + Examples + -------- + Save a multichannel image as OME-TIFF without splitting channels: + + >>> save_image_with_extension(imp, "OME-TIFF", "/output/path", 1, 3, False) + # Saves as: /output/path/image_title_series_001.ome.tif + + Save with channels split: + + >>> save_image_with_extension(imp, "OME-TIFF", "/output/path", 1, 3, True) + # Saves as: /output/path/C1/image_title_series_001.ome.tif + # /output/path/C2/image_title_series_001.ome.tif """ out_ext = {} From 170ac0c9ddef95d1912e42a7b371de06d9ce18b4 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 09:43:36 +0100 Subject: [PATCH 522/678] Improve method name and docstring --- src/imcflibs/imagej/misc.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 20a11caf..96c5f6b8 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -422,17 +422,41 @@ def get_threshold_value_from_method(imp, method, ops): return threshold_value -def write_results(out_file, content): - """ - Write the results to a csv file. +def write_ordereddict_to_csv(out_file, content): + """Write data from a list of OrderedDicts to a CSV file. + + This function writes data to a CSV file, preserving the order of columns + as defined in the OrderedDict objects. If the output file doesn't exist, + it creates a new file with a header row. If the file exists, it appends + the data without repeating the header. Parameters ---------- out_file : str - Path to the output file. + Path to the output CSV file. content : list of OrderedDict - List of dictionaries representing the results. + List of OrderedDict objects representing the data rows to be written. + All dictionaries should have the same keys. + Examples + -------- + >>> from collections import OrderedDict + >>> results = [ + ... OrderedDict([('id', 1), ('name', 'Sample A'), ('value', 42.5)]), + ... OrderedDict([('id', 2), ('name', 'Sample B'), ('value', 37.2)]) + ... ] + >>> write_ordereddict_to_csv('results.csv', results) + + The resulting CSV file will contain: + id;name;value + 1;Sample A;42.5 + 2;Sample B;37.2 + + Notes + ----- + - Uses semicolon (;) as delimiter + - When appending to an existing file, assumes the column structure matches + - Opens files in binary mode for compatibility """ # Check if the output file exists From cca38ba4d8955e87ed9e23bd83b0263f4c2e948a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 09:43:46 +0100 Subject: [PATCH 523/678] Add missing import --- src/imcflibs/imagej/misc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 96c5f6b8..47935fd9 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -1,6 +1,7 @@ """Miscellaneous ImageJ related functions, mostly convenience wrappers.""" import csv +import glob import os import smtplib import subprocess From e8589000916924af414c44cfc304f711beb99b8f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 09:44:35 +0100 Subject: [PATCH 524/678] Change method to use the one added to pathtools --- src/imcflibs/imagej/misc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 47935fd9..59445455 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -11,6 +11,7 @@ from ij import IJ # pylint: disable-msg=import-error from ij.plugin import Duplicator, ImageCalculator, StackWriter +from .. import pathtools from ..log import LOG as log from . import bioformats as bf from . import prefs @@ -571,12 +572,12 @@ def save_image_with_extension( ) if extension == "ImageJ-TIF": - check_folder(dir_to_save[index]) + pathtools.create_directory(dir_to_save[index]) IJ.saveAs(current_imp, "Tiff", out_path + ".tif") elif extension == "BMP": out_folder = os.path.join(out_dir, basename + os.path.sep) - check_folder(out_folder) + pathtools.create_directory(out_folder) StackWriter.save(current_imp, out_folder, "format=bmp") else: From b42cec32098b386a3569052333de335c4b42916c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 09:44:45 +0100 Subject: [PATCH 525/678] Formatting --- src/imcflibs/imagej/misc.py | 62 ++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 59445455..b5dfdc03 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -43,7 +43,9 @@ def show_progress(cur, final): ----- `ij.IJ.showProgress` internally increments the given `cur` value by 1. """ - log.info("Progress: %s / %s (%s)", cur + 1, final, (1.0 + cur) / final) + log.info( + "Progress: %s / %s (%s)", cur + 1, final, (1.0 + cur) / final + ) IJ.showProgress(cur, final) @@ -80,7 +82,9 @@ def elapsed_time_since(start, end=None): hours, rem = divmod(end - start, 3600) minutes, seconds = divmod(rem, 60) - return "{:0>2}:{:0>2}:{:05.2f}".format(int(hours), int(minutes), seconds) + return "{:0>2}:{:0>2}:{:05.2f}".format( + int(hours), int(minutes), seconds + ) def percentage(part, whole): @@ -119,7 +123,9 @@ def calculate_mean_and_stdv(values_list, round_decimals=0): filtered_list = filter(None, values_list) try: - mean = round(sum(filtered_list) / len(filtered_list), round_decimals) + mean = round( + sum(filtered_list) / len(filtered_list), round_decimals + ) except ZeroDivisionError: mean = 0 tot = 0.0 @@ -160,7 +166,9 @@ def find_focus(imp): # Check if more than 1 channel # FUTURE Could be improved for multi channel if imp_dimensions[2] != 1: - sys.exit("Image has more than one channel, please reduce dimensionality") + sys.exit( + "Image has more than one channel, please reduce dimensionality" + ) # Loop through each time point for plane in range(1, imp_dimensions[4] + 1): @@ -176,7 +184,9 @@ def find_focus(imp): # pix_array = pix_array*pix_array sumpix_array = sum(pix_array) - var = sumpix_array / (imp_dimensions[0] * imp_dimensions[1] * mean) + var = sumpix_array / ( + imp_dimensions[0] * imp_dimensions[1] * mean + ) if var > norm_var: norm_var = var @@ -205,10 +215,14 @@ def send_mail(job_name, recipient, filename, total_execution_time): # Ensure the sender and server are configured from Prefs if not sender: - log.info("Sender email is not configured. Please check IJ_Prefs.txt.") + log.info( + "Sender email is not configured. Please check IJ_Prefs.txt." + ) return if not server: - log.info("SMTP server is not configured. Please check IJ_Prefs.txt.") + log.info( + "SMTP server is not configured. Please check IJ_Prefs.txt." + ) return # Ensure the recipient is provided @@ -284,8 +298,18 @@ def timed_log(message, as_string=False): Message to print """ if as_string: - return time.strftime("%H:%M:%S", time.localtime()) + ": " + message + " " - IJ.log(time.strftime("%H:%M:%S", time.localtime()) + ": " + message + " ") + return ( + time.strftime("%H:%M:%S", time.localtime()) + + ": " + + message + + " " + ) + IJ.log( + time.strftime("%H:%M:%S", time.localtime()) + + ": " + + message + + " " + ) def get_free_memory(): @@ -465,13 +489,17 @@ def write_ordereddict_to_csv(out_file, content): 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 = 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 = csv.DictWriter( + f, content[0].keys(), delimiter=";" + ) dict_writer.writerows(content) @@ -559,16 +587,19 @@ def save_image_with_extension( imp.getNFrames(), ) ) - dir_to_save.append(os.path.join(out_dir, "C" + str(channel))) + dir_to_save.append( + os.path.join(out_dir, "C" + str(channel)) + ) else: - imp_to_use.append(imageplus) + imp_to_use.append(imp) dir_to_save.append(out_dir) for index, current_imp in enumerate(imp_to_use): basename = imp.getShortTitle() out_path = os.path.join( - dir_to_save[index], basename + "_series_" + str(series).zfill(pad_number) + dir_to_save[index], + basename + "_series_" + str(series).zfill(pad_number), ) if extension == "ImageJ-TIF": @@ -639,7 +670,8 @@ def locate_latest_imaris(paths_to_check=None): for check in paths_to_check: hits = glob.glob(check + "*") imaris_paths += sorted( - hits, key=lambda x: float(x.replace(check, "").replace(".", "")) + hits, + key=lambda x: float(x.replace(check, "").replace(".", "")), ) return imaris_paths[-1] From 184a9438ff176d538cce5006aa1aab2e40ce0ba7 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 10:43:32 +0100 Subject: [PATCH 526/678] Update the formatting --- src/imcflibs/imagej/misc.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index b5dfdc03..a6f9fc3e 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -523,11 +523,13 @@ def save_image_with_extension( imp : ij.ImagePlus ImagePlus object to save. extension : {'ImageJ-TIF', 'ICS-1', 'ICS-2', 'OME-TIFF', 'CellH5', 'BMP'} - Output format to use: - ImageJ-TIF: Saves as ImageJ TIFF format (.tif) - - ICS-1: Saves as ICS version 1 format (.ids) - ICS-2: Saves as ICS - version 2 format (.ics) - OME-TIFF: Saves as OME-TIFF format (.ome.tif) - - CellH5: Saves as CellH5 format (.ch5) - BMP: Saves as BMP format (one - file per slice) + Output format to use: + - ImageJ-TIF: Saves as ImageJ TIFF format (.tif) + - ICS-1: Saves as ICS version 1 format (.ids) + - ICS-2: Saves as ICS version 2 format (.ics) + - OME-TIFF: Saves as OME-TIFF format (.ome.tif) + - CellH5: Saves as CellH5 format (.ch5) + - BMP: Saves as BMP format (one file per slice) out_dir : str Directory path where the image(s) will be saved. series : int From a49dac355d3e10153433ed3888c2bfdbc56b4dee Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Wed, 19 Mar 2025 13:14:25 +0100 Subject: [PATCH 527/678] Remove create_directory() for issue #43 --- src/imcflibs/pathtools.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index f29eb417..40977b22 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -357,18 +357,6 @@ def folder_size(source): return total_size -def create_directory(new_path): - """Create a new directory if it does not already exist. - - Parameters - ---------- - new_path : str - Path to the new directory. - """ - if not os.path.exists(new_path): - os.makedirs(new_path) - - # pylint: disable-msg=C0103 # we use the variable name 'exists' in its common spelling (lowercase), so # removing this workaround will be straightforward at a later point From 54ee5f9d76f3246f0c928c7fa5b0956ebdcd0b83 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 19 Mar 2025 14:55:54 +0100 Subject: [PATCH 528/678] Format docstring's Returns `dict` That's an attempt to use a format that would render the dict description in the docstring in a way so it's easily readable in VS Code as well as through the pdoc HTML. --- src/imcflibs/imagej/bioformats.py | 33 ++++++++++++------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 0eced928..83d19728 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -307,26 +307,19 @@ def get_metadata_from_image(path_to_image): ------- dict A dictionary containing the following metadata: - - unit_width : float - Physical width of a pixel. - - unit_height : float - Physical height of a pixel. - - unit_depth : float - Physical depth of a voxel. - - pixel_width : int - Width of the image in pixels. - - pixel_height : int - Height of the image in pixels. - - slice_count : int - Number of Z-slices. - - channel_count : int - Number of channels. - - timepoints_count : int - Number of timepoints. - - dimension_order : str - Order of dimensions in the image (e.g., 'XYZCT'). - - pixel_type : str - Data type of the pixel values. + + { + unit_width : float, # physical width of a pixel + unit_height : float, # physical height of a pixel + unit_depth : float, # physical depth of a voxel + pixel_width : int, # width of the image in pixels + pixel_height : int, # height of the image in pixels + slice_count : int, # number of Z-slices + channel_count : int, # number of channels + timepoints_count : int, # number of timepoints + dimension_order : str, # order of dimensions, e.g. "XYZCT" + pixel_type : str, # data type of the pixel values + } """ reader = ImageReader() ome_meta = MetadataTools.createOMEXMLMetadata() From 88ee3a864e2217f2ad3c429cc180c7420e6fa33b Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 14:58:47 +0100 Subject: [PATCH 529/678] Add back `create_directory` with better docstring --- src/imcflibs/pathtools.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index a5fba12e..956c8997 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -179,7 +179,9 @@ def jython_fiji_exists(path): return False -def listdir_matching(path, suffix, fullpath=False, sort=False, regex=False): +def listdir_matching( + path, suffix, fullpath=False, sort=False, regex=False +): """Get a list of files in a directory matching a given suffix. Parameters @@ -295,9 +297,13 @@ def derive_out_dir(in_dir, out_dir): """ if out_dir.upper() in ["-", "NONE"]: out_dir = in_dir - log.info("No output directory given, using input dir [%s].", out_dir) + log.info( + "No output directory given, using input dir [%s].", out_dir + ) else: - log.info("Using directory [%s] for results and temp files.", out_dir) + log.info( + "Using directory [%s] for results and temp files.", out_dir + ) return out_dir @@ -357,6 +363,27 @@ def folder_size(source): return total_size +def create_directory(new_path): + """Create a new directory at the specified path. + + This function first checks if the directory already exists and only + attempts to create it if it doesn't exist. + + Parameters + ---------- + new_path : str + Path where the new directory should be created. + + Notes + ----- + This approach is used as a workaround for Python 2.7 which doesn't + have the exist_ok' parameter in os.makedirs(). + """ + + if not os.path.exists(new_path): + os.makedirs(new_path) + + # pylint: disable-msg=C0103 # we use the variable name 'exists' in its common spelling (lowercase), so # removing this workaround will be straightforward at a later point From 74668385346fb6619d8c8af0272fa930ce65d3c1 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 15:02:15 +0100 Subject: [PATCH 530/678] 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 7dfa1325..4d7f6825 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -199,7 +199,7 @@ def process_angle(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : angle_select(). """ @@ -225,7 +225,7 @@ def process_channel(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : channel_select(). """ @@ -251,7 +251,7 @@ def process_illumination(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : illumination_select(). """ @@ -277,7 +277,7 @@ def process_tile(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ------ + ----- Previous function name : tile_select(). """ @@ -303,7 +303,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 5bea0adc37583eeef84e8074211361223db2d503 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 15:02:27 +0100 Subject: [PATCH 531/678] Formatting --- src/imcflibs/imagej/bdv.py | 161 ++++++++++++++++++++++++++++--------- 1 file changed, 121 insertions(+), 40 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 4d7f6825..b3d0f2d2 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -130,7 +130,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*. @@ -146,8 +148,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*. @@ -181,7 +188,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 @@ -403,16 +412,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): @@ -432,12 +447,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): @@ -455,7 +474,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): @@ -470,13 +491,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 + " " @@ -514,7 +544,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" @@ -535,7 +567,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, @@ -553,7 +587,9 @@ def set_angle_definition(self, value): """ choices = self.check_definition_option(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 @@ -565,7 +601,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 @@ -576,9 +615,12 @@ def set_illumination_definition(self, value): One of `single`, `multi_single` or `multi_multi`. """ choices = self.check_definition_option(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): @@ -591,7 +633,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 @@ -603,7 +647,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. @@ -624,7 +671,9 @@ def fmt_acitt_options(self): "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 + " " @@ -648,10 +697,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" @@ -689,7 +742,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 @@ -732,7 +791,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) @@ -792,7 +853,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 = "" @@ -959,7 +1022,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: @@ -1049,10 +1114,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 = "" @@ -1076,7 +1144,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( @@ -1188,7 +1258,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") @@ -1366,8 +1438,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": @@ -1375,9 +1451,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] " @@ -1413,7 +1493,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 0e81c37d5d3d3902b8c5211efde931a21f3d162f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 15:09:01 +0100 Subject: [PATCH 532/678] 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 b3d0f2d2..0f1a42a4 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -207,7 +207,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(). """ @@ -233,7 +233,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(). """ @@ -259,7 +259,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(). """ @@ -285,7 +285,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(). """ @@ -311,7 +311,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(). """ @@ -578,7 +578,7 @@ def check_definition_option(self, value): } def set_angle_definition(self, value): - """Set the value for the angle definition + """Set the value for the angle definition. Parameters ---------- @@ -592,7 +592,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 ---------- @@ -607,7 +607,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 ---------- @@ -624,7 +624,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 ---------- @@ -638,7 +638,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 ---------- @@ -688,6 +688,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 @@ -809,8 +810,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...". @@ -993,8 +994,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 @@ -1279,10 +1282,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 @@ -1337,14 +1340,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 1b222d019d928db7f4a64f1bc7f658217e449258 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 19 Mar 2025 18:02:21 +0100 Subject: [PATCH 533/678] Update docstring of write_ordereddict_to_csv() By indenting the example content block it renders nicely in VS Code and pdoc, plus it's also fairly readable in plain-text. --- src/imcflibs/imagej/misc.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index d5558eab..0a62d925 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -470,16 +470,17 @@ def write_ordereddict_to_csv(out_file, content): ... ] >>> write_ordereddict_to_csv('results.csv', results) - The resulting CSV file will contain: - id;name;value - 1;Sample A;42.5 - 2;Sample B;37.2 + The resulting CSV file will have the following content: + + id;name;value + 1;Sample A;42.5 + 2;Sample B;37.2 Notes ----- - - Uses semicolon (;) as delimiter - - When appending to an existing file, assumes the column structure matches - - Opens files in binary mode for compatibility + - Uses the semicolon charachter (`;`) as delimiter. + - When appending to an existing file, the column structure has to match. + - Output file is opened in binary mode for compatibility. """ # Check if the output file exists From f9375c5b98251e9998912f164819ce3a9a9621ca Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 19 Mar 2025 21:28:02 +0100 Subject: [PATCH 534/678] Rework docstring --- src/imcflibs/imagej/misc.py | 39 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 0a62d925..f67b1e9b 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -521,13 +521,7 @@ def save_image_with_extension( imp : ij.ImagePlus ImagePlus object to save. extension : {'ImageJ-TIF', 'ICS-1', 'ICS-2', 'OME-TIFF', 'CellH5', 'BMP'} - Output format to use: - - ImageJ-TIF: Saves as ImageJ TIFF format (.tif) - - ICS-1: Saves as ICS version 1 format (.ids) - - ICS-2: Saves as ICS version 2 format (.ics) - - OME-TIFF: Saves as OME-TIFF format (.ome.tif) - - CellH5: Saves as CellH5 format (.ch5) - - BMP: Saves as BMP format (one file per slice) + Output format to use, see Notes section below for details. out_dir : str Directory path where the image(s) will be saved. series : int @@ -535,32 +529,37 @@ def save_image_with_extension( pad_number : int Number of digits to use when zero-padding the series number. split_channels : bool - If True, splits channels and saves them individually in separate folders - named "C1", "C2", etc. inside out_dir. If False, saves all channels in a + If True, split channels and save them individually in separate folders + named "C1", "C2", etc. inside out_dir. If False, save all channels in a single file. Notes ----- - - For "ImageJ-TIF" format, uses IJ.saveAs function - - For "BMP" format, saves images using StackWriter.save with one BMP file - per slice in a subfolder named after the original image - - For all other formats, uses Bio-Formats exporter (bf.export) - - The original ImagePlus is not modified, but any duplicate images created - for channel splitting are closed after saving - - Metadata is preserved when using Bio-Formats exporters + Depending on the value of the `extension` parameter, one of the following + output formats and saving strategies will be used: + - Bio-Formats based formats will be produced by calling `bf.export()`, note + that these formats will preserve metadata (which is **not** the case for + the other formats using different saving strategies): + - `ICS-1`: Save as ICS version 1 format (a pair of `.ics` and `.ids`). + - `ICS-2`: Save as ICS version 2 format (single `.ics` file). + - `OME-TIFF`: Save in OME-TIFF format (`.ome.tif`). + - `CellH5`: Save as CellH5 format (`.ch5`). + - `ImageJ-TIF`: Save in ImageJ TIFF format (`.tif`) using `IJ.saveAs()`. + - `BMP`: Save in BMP format using `StackWriter.save()`, producing one `.bmp` + per slice in a subfolder named after the original image. Examples -------- Save a multichannel image as OME-TIFF without splitting channels: >>> save_image_with_extension(imp, "OME-TIFF", "/output/path", 1, 3, False) - # Saves as: /output/path/image_title_series_001.ome.tif + # resulting file: /output/path/image_title_series_001.ome.tif - Save with channels split: + Save with channel splitting: >>> save_image_with_extension(imp, "OME-TIFF", "/output/path", 1, 3, True) - # Saves as: /output/path/C1/image_title_series_001.ome.tif - # /output/path/C2/image_title_series_001.ome.tif + # resulting files: /output/path/C1/image_title_series_001.ome.tif + # /output/path/C2/image_title_series_001.ome.tif """ out_ext = {} From e99dcdb3a67461922ceea6c2e7e89a7c46b934d2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 19 Mar 2025 21:59:31 +0100 Subject: [PATCH 535/678] Update docstring of locate_latest_imaris() Details of the functionality are preferably explained in the first section of the docstring, as this is what another person will read first. --- src/imcflibs/imagej/misc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index f67b1e9b..3127120b 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -644,6 +644,10 @@ def pad_number(index, pad_length=2): def locate_latest_imaris(paths_to_check=None): """Find paths to latest installed Imaris or ImarisFileConverter version. + Identify the full path to the most recent (as in "version number") + ImarisFileConverter or Imaris installation folder with the latter one having + priority. In case nothing is found, an empty string is returned. + Parameters ---------- paths_to_check: list of str, optional @@ -653,11 +657,7 @@ def locate_latest_imaris(paths_to_check=None): Returns ------- str - Full path to the most recent (as in "version number") ImarisFileConverter - or Imaris installation folder with the latter one having priority. - Will be empty if nothing is found. """ - if not paths_to_check: paths_to_check = [ r"C:\Program Files\Bitplane\ImarisFileConverter ", From d2a49913d11a7f515fde4458c889039bb14f7fea Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 19 Mar 2025 22:41:25 +0100 Subject: [PATCH 536/678] Improve docstring --- src/imcflibs/imagej/omerotools.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index f41580a7..ec2cf5d1 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -275,11 +275,11 @@ def get_acquisition_metadata_from_imageid(user_client, image_wpr): def get_info_from_original_metadata(user_client, image_wpr, field): - """Recovers information from the original metadata + """Retrieve information from the original metadata (as opposed to OME-MD). - In some cases, some information aren't parsed correctly by BF and have to - get recovered directly from the original metadata. This gets the value - based on the field string. + In some cases not all information is parsed correctly by BF and has to be + recovered / identified directly from the *original* metadata. This function + extracts the corresponding value based on the field identifier. Parameters ---------- From 6eb32db147cc1ec786a2492e805332b94b2a7e0b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 19 Mar 2025 22:45:53 +0100 Subject: [PATCH 537/678] Improve docstring --- src/imcflibs/imagej/omerotools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index ec2cf5d1..824d1c6a 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -303,17 +303,17 @@ def get_info_from_original_metadata(user_client, image_wpr, field): def create_table_columns(headings): - """Create the table headings from the ImageJ results table + """Create OMERO table headings from an ImageJ results table. Parameters ---------- headings : list(str) - List of columns names + List of columns names. Returns ------- list(omero.gateway.model.TableDataColumn) - List of columns formatted to be uploaded to OMERO + List of columns formatted to be uploaded to OMERO. """ table_columns = [] # populate the headings From bfbe58599258d631d4d19e29a7a2164a5776c8ec Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 19 Mar 2025 23:00:38 +0100 Subject: [PATCH 538/678] Shorten docstring --- src/imcflibs/pathtools.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 956c8997..bd8965e5 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -366,18 +366,13 @@ def folder_size(source): def create_directory(new_path): """Create a new directory at the specified path. - This function first checks if the directory already exists and only - attempts to create it if it doesn't exist. + This is a workaround for Python 2.7 where `os.makedirs()` is lacking + the `exist_ok` parameter that is present in Python 3.2 and newer. Parameters ---------- new_path : str Path where the new directory should be created. - - Notes - ----- - This approach is used as a workaround for Python 2.7 which doesn't - have the exist_ok' parameter in os.makedirs(). """ if not os.path.exists(new_path): From 18131a87fd6301f9ab4e48cb7a57e02459127de1 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 20 Mar 2025 13:35:37 +0100 Subject: [PATCH 539/678] Update dependency for mocks to 0.8.0.a0 --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8b95696a..03015af8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -145,13 +145,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "imcf-fiji-mocks" -version = "0.7.0" +version = "0.8.0a0" description = "Mocks collection for Fiji-Python. Zero functional code." optional = false python-versions = ">=2.7" files = [ - {file = "imcf_fiji_mocks-0.7.0-py2.py3-none-any.whl", hash = "sha256:643c3d4cc916d1573f1f3490885e37822c11c40c09b6a1e06c2fc561c8aeb20e"}, - {file = "imcf_fiji_mocks-0.7.0.tar.gz", hash = "sha256:65eab1974629ef72bbe7b8d76e81e067d5fb55b65aab8fb7e0f24755e0f7ac6b"}, + {file = "imcf_fiji_mocks-0.8.0a0-py2.py3-none-any.whl", hash = "sha256:d4841b32725a2b81790e1051da264bfb72b7e275c23d87a04053462420184f78"}, + {file = "imcf_fiji_mocks-0.8.0a0.tar.gz", hash = "sha256:d00ca538bf49dde5386d148a7b71c68cbee0397edc6ce4ce2547f7f26c803478"}, ] [[package]] @@ -520,4 +520,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "010a8923c68fb6e367da60506e1937f570cd37633e42c44ba312012be4aa2172" +content-hash = "26f88742f5c3e83b8a39f299398e5784b3689404e83870520d80db6aaa91d6ff" diff --git a/pyproject.toml b/pyproject.toml index 0ab51dd3..dda9e34b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ version = "0.0.0" # - or: python = ">=3.10" [tool.poetry.dependencies] -imcf-fiji-mocks = ">=0.7.0" +imcf-fiji-mocks = ">=0.8.0.a0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From d3b0cca3f97705fc7098e7f30b925058fee427aa Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 20 Mar 2025 13:35:37 +0100 Subject: [PATCH 540/678] Update dependency for mocks to 0.8.0.a0 --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8b95696a..03015af8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -145,13 +145,13 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "imcf-fiji-mocks" -version = "0.7.0" +version = "0.8.0a0" description = "Mocks collection for Fiji-Python. Zero functional code." optional = false python-versions = ">=2.7" files = [ - {file = "imcf_fiji_mocks-0.7.0-py2.py3-none-any.whl", hash = "sha256:643c3d4cc916d1573f1f3490885e37822c11c40c09b6a1e06c2fc561c8aeb20e"}, - {file = "imcf_fiji_mocks-0.7.0.tar.gz", hash = "sha256:65eab1974629ef72bbe7b8d76e81e067d5fb55b65aab8fb7e0f24755e0f7ac6b"}, + {file = "imcf_fiji_mocks-0.8.0a0-py2.py3-none-any.whl", hash = "sha256:d4841b32725a2b81790e1051da264bfb72b7e275c23d87a04053462420184f78"}, + {file = "imcf_fiji_mocks-0.8.0a0.tar.gz", hash = "sha256:d00ca538bf49dde5386d148a7b71c68cbee0397edc6ce4ce2547f7f26c803478"}, ] [[package]] @@ -520,4 +520,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "010a8923c68fb6e367da60506e1937f570cd37633e42c44ba312012be4aa2172" +content-hash = "26f88742f5c3e83b8a39f299398e5784b3689404e83870520d80db6aaa91d6ff" diff --git a/pyproject.toml b/pyproject.toml index 0ab51dd3..dda9e34b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ version = "0.0.0" # - or: python = ">=3.10" [tool.poetry.dependencies] -imcf-fiji-mocks = ">=0.7.0" +imcf-fiji-mocks = ">=0.8.0.a0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From 5b53f684f0efe0a77aecf1918177882d67e6eca6 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 20 Mar 2025 14:40:19 +0100 Subject: [PATCH 541/678] Remove ipython from dev-dependencies --- poetry.lock | 251 +------------------------------------------------ pyproject.toml | 1 - 2 files changed, 1 insertion(+), 251 deletions(-) diff --git a/poetry.lock b/poetry.lock index 03015af8..b0ee2a39 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,20 +1,5 @@ # This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. -[[package]] -name = "asttokens" -version = "3.0.0" -description = "Annotate AST trees with source code positions" -optional = false -python-versions = ">=3.8" -files = [ - {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, - {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, -] - -[package.extras] -astroid = ["astroid (>=2,<4)"] -test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] - [[package]] name = "colorama" version = "0.4.6" @@ -104,17 +89,6 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -optional = false -python-versions = ">=3.5" -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] - [[package]] name = "exceptiongroup" version = "1.2.2" @@ -129,20 +103,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "executing" -version = "2.2.0" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = ">=3.8" -files = [ - {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, - {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, -] - -[package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] - [[package]] name = "imcf-fiji-mocks" version = "0.8.0a0" @@ -165,77 +125,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "ipython" -version = "8.32.0" -description = "IPython: Productive Interactive Computing" -optional = false -python-versions = ">=3.10" -files = [ - {file = "ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa"}, - {file = "ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt_toolkit = ">=3.0.41,<3.1.0" -pygments = ">=2.4.0" -stack_data = "*" -traitlets = ">=5.13.0" -typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} - -[package.extras] -all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] -black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"] -kernel = ["ipykernel"] -matplotlib = ["matplotlib"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] - -[[package]] -name = "jedi" -version = "0.19.2" -description = "An autocompletion tool for Python that can be used for text editors." -optional = false -python-versions = ">=3.6" -files = [ - {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, - {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, -] - -[package.dependencies] -parso = ">=0.8.4,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] - -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -description = "Inline Matplotlib backend for Jupyter" -optional = false -python-versions = ">=3.8" -files = [ - {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, - {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, -] - -[package.dependencies] -traitlets = "*" - [[package]] name = "olefile" version = "0.46" @@ -257,35 +146,6 @@ files = [ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, ] -[[package]] -name = "parso" -version = "0.8.4" -description = "A Python Parser" -optional = false -python-versions = ">=3.6" -files = [ - {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, - {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, -] - -[package.extras] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["docopt", "pytest"] - -[[package]] -name = "pexpect" -version = "4.9.0" -description = "Pexpect allows easy control of interactive console applications." -optional = false -python-versions = "*" -files = [ - {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, - {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, -] - -[package.dependencies] -ptyprocess = ">=0.5" - [[package]] name = "pluggy" version = "1.5.0" @@ -301,59 +161,6 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] -[[package]] -name = "prompt-toolkit" -version = "3.0.50" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, - {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -optional = false -python-versions = "*" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -description = "Safely evaluate AST nodes without side effects" -optional = false -python-versions = "*" -files = [ - {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, - {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "pygments" -version = "2.19.1" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - [[package]] name = "pytest" version = "8.3.4" @@ -420,25 +227,6 @@ files = [ {file = "sjlogging-0.5.4.tar.gz", hash = "sha256:5fb1a4e6338088bdbf9d943a867bf1bc6f77031ec48dbb7dd96f09abb0aaaa92"}, ] -[[package]] -name = "stack-data" -version = "0.6.3" -description = "Extract data from python stack frames and tracebacks for informative displays" -optional = false -python-versions = "*" -files = [ - {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, - {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, -] - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - [[package]] name = "tomli" version = "2.2.1" @@ -480,44 +268,7 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -[[package]] -name = "traitlets" -version = "5.14.3" -description = "Traitlets Python configuration system" -optional = false -python-versions = ">=3.8" -files = [ - {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, - {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, -] - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "26f88742f5c3e83b8a39f299398e5784b3689404e83870520d80db6aaa91d6ff" +content-hash = "11318171d20040fce90144782e5dced220d59595cd672cee1037d5f386648eff" diff --git a/pyproject.toml b/pyproject.toml index dda9e34b..50832442 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,6 @@ python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" [tool.poetry.group.dev.dependencies] -ipython = "^8.17.2" pytest = "^8.0.1" pytest-cov = "^6.0.0" From f3789209665927c6029f3dc64e10a95434dfe8a8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 20 Mar 2025 15:02:34 +0100 Subject: [PATCH 542/678] Add script to parse dependencies from pyproject.toml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: this was created in order to facilitate the project setup using Python 2 ๐Ÿ๐Ÿชฆ but is currently UNUSED as this opens Pandora's box ๐Ÿชค of versions specified via ๐ŸŽญ Poetry but incompatible with Python 2 etc. --- .github/workflows/pytest-python2.yml | 4 +++- scripts/parse-python-deps.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 scripts/parse-python-deps.py diff --git a/.github/workflows/pytest-python2.yml b/.github/workflows/pytest-python2.yml index 43381338..91fa23ba 100644 --- a/.github/workflows/pytest-python2.yml +++ b/.github/workflows/pytest-python2.yml @@ -25,7 +25,9 @@ jobs: - name: ๐Ÿ—ƒ Cache ๐Ÿ“ฆ APT Packages uses: awalsh128/cache-apt-pkgs-action@v1.4.3 with: - packages: xmlstarlet + packages: + xmlstarlet + # python3-tomli # required only for 'parse-python-deps.py' version: 1.0 - name: ๐Ÿ—ƒ Cache pyenv installation diff --git a/scripts/parse-python-deps.py b/scripts/parse-python-deps.py new file mode 100644 index 00000000..2b076114 --- /dev/null +++ b/scripts/parse-python-deps.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +"""Parse project dependencies and format them for usage with `pip install`.""" + +# NOTE: requires Ubuntu package 'python3-tomli' to be installed! + +import tomli + +with open("pyproject.toml", "rb") as tomlfile: + pyproject = tomli.load(tomlfile) + +deps_pkg = pyproject["tool"]["poetry"]["dependencies"] +deps_dev = pyproject["tool"]["poetry"]["group"]["dev"]["dependencies"] + +output = "" + +for deps in deps_pkg, deps_dev: + for pkg, ver in deps.items(): + if ver[0] == "^": + ver = f">={ver[1:]}" + output = f'{output} {pkg}{ver}' + +print(output) From b72e685d8070706b3116c72d51cff31e1607d19e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 20 Mar 2025 15:05:26 +0100 Subject: [PATCH 543/678] Install pre-releases of "imcf-fiji-mocks" if available --- scripts/py2-pytest.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/py2-pytest.sh b/scripts/py2-pytest.sh index f840f4a8..3c8557c1 100755 --- a/scripts/py2-pytest.sh +++ b/scripts/py2-pytest.sh @@ -103,11 +103,14 @@ mv pyproject_.toml pyproject.toml echo "== * Removing 'setup.py'..." rm setup.py +echo "== * Installing dependencies (incl. pre-release and dev versions)..." +vpip install --upgrade --pre \ + imcf-fiji-mocks + echo "== * Installing dependencies..." vpip install \ python-micrometa \ sjlogging \ - "imcf-fiji-mocks>=0.3.0" \ olefile==0.46 \ pytest \ pytest-cov \ From 69a6122716660e02e8d080f4371b5bd9a48da694 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 20 Mar 2025 17:53:31 +0100 Subject: [PATCH 544/678] Change method name to be more descriptive --- src/imcflibs/imagej/misc.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 1ccf35cc..3ca2606b 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -173,8 +173,11 @@ def find_focus(imp): return focused_slice -def send_mail(job_name, recipient, filename, total_execution_time): - """Send an email using the SMTP server and sender email configured in ImageJ's Preferences. +def send_notification_email(job_name, recipient, filename, total_execution_time, subject = "", message=""): + """Send an automated email notification with optional details of the processed job. + + This function retrieves the sender email and SMTP server settings from + ImageJ's preferences and uses them to send an email notification with job details. Parameters ---------- From 4d4c4c89f1abde34f03cc19a07a0f8a97d197e3e Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 20 Mar 2025 17:55:07 +0100 Subject: [PATCH 545/678] Allow for user specified subject and message --- src/imcflibs/imagej/misc.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 3ca2606b..7056afa7 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -208,15 +208,21 @@ def send_notification_email(job_name, recipient, filename, total_execution_time, return # Form the email subject and body - subject = "Your {0} job finished successfully".format(job_name) - body = ( - "Dear recipient,\n\n" - "This is an automated message.\n" - "Your dataset '{0}' has been successfully processed " - "({1} [HH:MM:SS:ss]).\n\n" - "Kind regards,\n" - "The IMCF-team" - ).format(filename, total_execution_time) + if subject == "": + subject = "Your {0} job has finished".format(job_name) + else: + subject = subject + + if message == "": + body = ( + "Dear recipient,\n\n" + "This is an automated message.\n" + "Your workflow '{0}' has been processed " + "({1} [HH:MM:SS:ss]).\n\n" + "Kind regards.\n" + ).format(filename, total_execution_time) + else: + body = message # Form the complete message message = ("From: {0}\nTo: {1}\nSubject: {2}\n\n{3}").format( From 45622da097c81a7a3abdad82413e3e110d56785f Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 20 Mar 2025 17:55:23 +0100 Subject: [PATCH 546/678] Change docstring to be more accurate --- src/imcflibs/imagej/misc.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 7056afa7..0c116c69 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -189,7 +189,21 @@ def send_notification_email(job_name, recipient, filename, total_execution_time, The name of the file to be passed in the email. total_execution_time : str The time it took to process the file in the format [HH:MM:SS:ss]. + subject : string, optional + Subject of the email, by default says job finished. + message : string, optional + Message to be included in the email, by default says job processed. + + Notes + ----- + - The function requires two preferences to be set in `~/.imagej/IJ_Prefs.txt`: + `imcf.sender_email` (the sender's email address) and + `imcf.smtpserver` (the SMTP server address). + - If these preferences are not set or if required parameters are missing, + the function logs a message and exits without sending an email. + - In case of an SMTP error, the function logs a warning. """ + # Retrieve sender email and SMTP server from preferences sender = prefs.Prefs.get("imcf.sender_email", "").strip() server = prefs.Prefs.get("imcf.smtpserver", "").strip() From b520c2836be3d96d973f28a0c6fc676a4418f8ed Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 14:24:10 +0100 Subject: [PATCH 547/678] Revert "Fix linting issues" This reverts commit 0e81c37d5d3d3902b8c5211efde931a21f3d162f. --- 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 0f1a42a4..b3d0f2d2 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -207,7 +207,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(). """ @@ -233,7 +233,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(). """ @@ -259,7 +259,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(). """ @@ -285,7 +285,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(). """ @@ -311,7 +311,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(). """ @@ -578,7 +578,7 @@ def check_definition_option(self, value): } def set_angle_definition(self, value): - """Set the value for the angle definition. + """Set the value for the angle definition Parameters ---------- @@ -592,7 +592,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 ---------- @@ -607,7 +607,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 ---------- @@ -624,7 +624,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 ---------- @@ -638,7 +638,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 ---------- @@ -688,7 +688,6 @@ 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 @@ -810,8 +809,8 @@ def define_dataset_auto( subsampling_factors=None, hdf5_chunk_sizes=None, ): - """Define a dataset using the Autoloader or Multi-View loader. - + """Will run the corresponding "Define Dataset" using the "Auto-Loader" + option. If the series is tiles, will run "Define Dataset...", otherwise will run "Define Multi-View Dataset...". @@ -994,10 +993,8 @@ def resave_as_h5( XML input file. output_h5_file_path : str Export path for the output file including the `.xml `extension. - 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 : str, optional + The timepoints that should be exported, by default `All Timepoints`. timepoints_per_partition : int, optional How many timepoints to export per partition, by default `1`. use_deflate_compression : bool, optional @@ -1282,10 +1279,10 @@ def detect_interest_points( ---------- project_path : str Path to the `.xml` project. - 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. + process_timepoint : str, optional + Timepoint to be processed, by default `All Timepoints`. + process_channel : str, optional + Channel to be processed, by default `All channels`. sigma : float, optional Minimum sigma for interest points detection, by default `1.8`. threshold : float, optional @@ -1340,11 +1337,14 @@ def interest_points_registration( ---------- project_path : str Path to the `.xml` project. - 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. + 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`. 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 2b6a8d8162989005d28c9af63f3450c9afdc8e64 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 14:24:21 +0100 Subject: [PATCH 548/678] Revert "Formatting" This reverts commit 5bea0adc37583eeef84e8074211361223db2d503. --- src/imcflibs/imagej/bdv.py | 161 +++++++++---------------------------- 1 file changed, 40 insertions(+), 121 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b3d0f2d2..4d7f6825 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -130,9 +130,7 @@ 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*. @@ -148,13 +146,8 @@ 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*. @@ -188,9 +181,7 @@ 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 @@ -412,22 +403,16 @@ 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): @@ -447,16 +432,12 @@ 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): @@ -474,9 +455,7 @@ 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): @@ -491,22 +470,13 @@ 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 + " " @@ -544,9 +514,7 @@ 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" @@ -567,9 +535,7 @@ 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, @@ -587,9 +553,7 @@ def set_angle_definition(self, value): """ choices = self.check_definition_option(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 @@ -601,10 +565,7 @@ 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 @@ -615,12 +576,9 @@ def set_illumination_definition(self, value): One of `single`, `multi_single` or `multi_multi`. """ choices = self.check_definition_option(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): @@ -633,9 +591,7 @@ 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 @@ -647,10 +603,7 @@ 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. @@ -671,9 +624,7 @@ def fmt_acitt_options(self): "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 + " " @@ -697,14 +648,10 @@ 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" @@ -742,13 +689,7 @@ 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 @@ -791,9 +732,7 @@ 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) @@ -853,9 +792,7 @@ 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 = "" @@ -1022,9 +959,7 @@ 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: @@ -1114,13 +1049,10 @@ 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 = "" @@ -1144,9 +1076,7 @@ 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( @@ -1258,9 +1188,7 @@ 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") @@ -1438,12 +1366,8 @@ 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": @@ -1451,13 +1375,9 @@ 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] " @@ -1493,8 +1413,7 @@ 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 82b86833336fcffa791f766f5ba5041743c8aa14 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 14:24:40 +0100 Subject: [PATCH 549/678] Revert "Fix linting" This reverts commit 74668385346fb6619d8c8af0272fa930ce65d3c1. --- 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 4d7f6825..7dfa1325 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -199,7 +199,7 @@ def process_angle(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ----- + ------ Previous function name : angle_select(). """ @@ -225,7 +225,7 @@ def process_channel(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ----- + ------ Previous function name : channel_select(). """ @@ -251,7 +251,7 @@ def process_illumination(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ----- + ------ Previous function name : illumination_select(). """ @@ -277,7 +277,7 @@ def process_tile(self, value, range_end=None): Contains the end of the range, by default None. Notes: - ----- + ------ Previous function name : tile_select(). """ @@ -303,7 +303,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 2e9ad4a5a75dbf5d12e64848db7a5a66726cb817 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 19 Mar 2025 15:02:15 +0100 Subject: [PATCH 550/678] 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 551/678] 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 552/678] 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 ebb141379a8d2a35eefb4bf666a852d106f2b58c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:00:59 +0100 Subject: [PATCH 553/678] Rename functions --- src/imcflibs/imagej/bioformats.py | 4 ++-- src/imcflibs/imagej/labelimage.py | 4 ++-- src/imcflibs/imagej/misc.py | 6 +++--- src/imcflibs/imagej/objects3d.py | 2 +- src/imcflibs/imagej/omerotools.py | 3 ++- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 83d19728..80a4b9ee 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -291,7 +291,7 @@ def write_bf_memoryfile(path_to_file): reader.close() -def get_metadata_from_image(path_to_image): +def get_metadata_from_file(path_to_image): """Extract metadata from an image file using Bio-Formats. This function reads an image file using the Bio-Formats library and extracts @@ -355,7 +355,7 @@ def get_metadata_from_image(path_to_image): return image_calibration -def get_stage_coordinates_from_ome_metadata(source, imagenames): +def get_stage_coords(source, filenames): """Get the stage coordinates and calibration from the ome-xml for a given list of images Parameters diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index b68c4de8..767161ae 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -67,7 +67,7 @@ def label_image_to_roi_list(label_image, low_thresh=None): return roi_list, max_value -def relate_label_images(label_image_ref, label_image_to_relate): +def cookie_cut_labels(label_image_ref, label_image_to_relate): """Relate label images, giving the same label to objects belonging together. โ— NOTE: Won't work with touching labels โ— @@ -97,7 +97,7 @@ 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): +def relate_label_images(outer_label_imp, inner_label_imp): """ Associate two label images. diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 3127120b..6256f692 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -501,8 +501,8 @@ def write_ordereddict_to_csv(out_file, content): dict_writer.writerows(content) -def save_image_with_extension( - imp, extension, out_dir, series, pad_number, split_channels +def save_image_in_format( + imp, format, out_dir, series, pad_number, split_channels ): """Save an ImagePlus object in the specified format. @@ -676,7 +676,7 @@ def locate_latest_imaris(paths_to_check=None): return imaris_paths[-1] -def convert_to_imaris(path_to_image): +def run_imarisconvert(file_path): """Convert a given file to Imaris5 .ims using ImarisConvert.exe via subprocess. Parameters diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 845e304c..17d0d083 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -151,7 +151,7 @@ def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): return Objects3DPopulation(objects_within_intensity) -def maxima_finder_3D(imageplus, min_threshold=0, noise=100, rxy=1.5, rz=1.5): +def maxima_finder_3d(imp, min_threshold=0, noise=100, rxy=1.5, rz=1.5): """ Find local maxima in a 3D image. diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 824d1c6a..a646258c 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -190,7 +190,7 @@ def add_annotation(client, repository_wpr, annotations, header): repository_wpr.addMapAnnotation(client, map_annotation_wpr) -def delete_annotation(user_client, repository_wpr): +def delete_keyvalue_annotations(user_client, object_wrapper): """Delete annotations linked to object Parameters @@ -223,6 +223,7 @@ def find_dataset(client, dataset_id): # Fetch the dataset from the OMERO server using the provided dataset ID return client.getDataset(Long(dataset_id)) + def get_acquisition_metadata(user_client, image_wpr): def get_acquisition_metadata_from_imageid(user_client, image_wpr): """Get acquisition metadata from OMERO based on an image ID From b59b59729f787cad41d1fa63e611ab9d0d2097ea Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:02:17 +0100 Subject: [PATCH 554/678] Change returns to use dictionaries instead --- src/imcflibs/imagej/bioformats.py | 66 +++++++++---------- src/imcflibs/imagej/omerotools.py | 102 +++++++++++++++++------------- 2 files changed, 90 insertions(+), 78 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 80a4b9ee..0f6f2a99 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -356,42 +356,42 @@ def get_metadata_from_file(path_to_image): def get_stage_coords(source, filenames): - """Get the stage coordinates and calibration from the ome-xml for a given list of images + """Get stage coordinates and calibration for a given list of images. Parameters ---------- source : str Path to the images - imagenames : list of str + filenames : list of str List of images filenames Returns ------- - tuple - Contains - dimensions : int + dict + A dictionary containing the following metadata: + - `dimensions` : int Number of dimensions (2D or 3D) - stage_coordinates_x : list - The absolute stage x-coordinated from ome-xml metadata - stage_coordinates_y : list - The absolute stage y-coordinated from ome-xml metadata - stage_coordinates_z : list - The absolute stage z-coordinated from ome-xml metadata - relative_coordinates_x : list + - `stage_coordinates_x` : list + The absolute stage x-coordinated + - `stage_coordinates_y` : list + The absolute stage y-coordinated + - `stage_coordinates_z` : list + The absolute stage z-coordinated + - `relative_coordinates_x` : list The relative stage x-coordinates in px - relative_coordinates_y : list + - `relative_coordinates_y` : list The relative stage y-coordinates in px - relative_coordinates_z : list + - `relative_coordinates_z` : list The relative stage z-coordinates in px - image_calibration : list + - `image_calibration` : list x,y,z image calibration in unit/px - calibration_unit : str + - `calibration_unit` : str Image calibration unit - image_dimensions_czt : list + - `image_dimensions_czt` : list Number of images in dimensions c,z,t - series_names : list of str + - `series_names` : list of str Names of all series contained in the files - max_size : list of int + - `max_size` : list of int Maximum size across all files in dimensions x,y,z """ @@ -517,17 +517,17 @@ def get_stage_coords(source, filenames): relative_coordinates_y_px.append(rel_pos_y) relative_coordinates_z_px.append(rel_pos_z) - return ( - dimensions, - stage_coordinates_x, - stage_coordinates_y, - stage_coordinates_z, - relative_coordinates_x_px, - relative_coordinates_y_px, - relative_coordinates_z_px, - image_calibration, - calibration_unit, - image_dimensions_czt, - series_names, - max_size, - ) + return { + "dimensions": dimensions, + "stage_coordinates_x": stage_coordinates_x, + "stage_coordinates_y": stage_coordinates_y, + "stage_coordinates_z": stage_coordinates_z, + "relative_coordinates_x": relative_coordinates_x_px, + "relative_coordinates_y": relative_coordinates_y_px, + "relative_coordinates_z": relative_coordinates_z_px, + "image_calibration": image_calibration, + "calibration_unit": calibration_unit, + "image_dimensions_czt": image_dimensions_czt, + "series_names": series_names, + "max_size": max_size, + } diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index a646258c..f365b73d 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -224,55 +224,67 @@ def find_dataset(client, dataset_id): return client.getDataset(Long(dataset_id)) def get_acquisition_metadata(user_client, image_wpr): + """Get acquisition metadata from OMERO based on an image ID. + + Parameters + ---------- + user_client : fr.igred.omero.Client + Client used for login to OMERO + image_wpr : fr.igred.omero.repositor.ImageWrapper + Wrapper to the image for the metadata + + Returns + ------- + dict + Dictionary containing acquisition metadata: + - `objective_magnification`: Objective magnification + - `objective_na`: Objective NA + - `acquisition_date`: Acquisition date + - `acquisition_date_number`: Acquisition date as a number + """ + ctx = user_client.getCtx() + instrument_data = ( + user_client.getGateway() + .getMetadataService(ctx) + .loadInstrument(image_wpr.asDataObject().getInstrumentId()) + ) + objective_data = instrument_data.copyObjective().get(0) + metadata = {} -def get_acquisition_metadata_from_imageid(user_client, image_wpr): - """Get acquisition metadata from OMERO based on an image ID - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - image_wpr : fr.igred.omero.repositor.ImageWrapper - Wrapper to the image for the ROIs + metadata["objective_magnification"] = ( + objective_data.getNominalMagnification().getValue() + if objective_data.getNominalMagnification() is not None + else 0 + ) + metadata["objective_na"] = ( + objective_data.getLensNA().getValue() + if objective_data.getLensNA() is not None + else 0 + ) - Returns - ------- - tuple of (int, int, str, int) - List of info about the acquisition - """ - ctx = user_client.getCtx() - instrument_data = ( - user_client.getGateway() - .getMetadataService(ctx) - .loadInstrument(image_wpr.asDataObject().getInstrumentId()) - ) - objective_data = instrument_data.copyObjective().get(0) - if objective_data.getNominalMagnification() is None: - obj_mag = 0 - else: - obj_mag = objective_data.getNominalMagnification().getValue() - if objective_data.getLensNA() is None: - obj_na = 0 - else: - obj_na = objective_data.getLensNA().getValue() - if image_wpr.getAcquisitionDate() is None: - if image_wpr.asDataObject().getFormat() == "ZeissCZI": - field = "Information|Document|CreationDate" - date_field = get_info_from_original_metadata(user_client, image_wpr, field) - acq_date = date_field.split("T")[0] - acq_date_number = int(acq_date.replace("-", "")) + if image_wpr.getAcquisitionDate() is None: + if image_wpr.asDataObject().getFormat() == "ZeissCZI": + field = "Information|Document|CreationDate" + date_field = get_info_from_original_metadata( + user_client, image_wpr, field + ) + metadata["acquisition_date"] = date_field.split("T")[0] + metadata["acquisition_date_number"] = int( + metadata["acquisition_date"].replace("-", "") + ) + else: + metadata["acquisition_date"] = "NA" + metadata["acquisition_date_number"] = 0 else: - acq_date = "NA" - acq_date_number = 0 - - else: - sdf = SimpleDateFormat("yyyy-MM-dd") - acq_date = sdf.format( - image_wpr.getAcquisitionDate() - ) # image_wpr.getAcquisitionDate() - acq_date_number = int(acq_date.replace("-", "")) + sdf = SimpleDateFormat("yyyy-MM-dd") + metadata["acquisition_date"] = sdf.format( + image_wpr.getAcquisitionDate() + ) + metadata["acquisition_date_number"] = int( + metadata["acquisition_date"].replace("-", "") + ) - return obj_mag, obj_na, acq_date, acq_date_number + return metadata def get_info_from_original_metadata(user_client, image_wpr, field): From 98b9ca8136b75a3177ad4829fff1bfccfe7caa1e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:04:37 +0100 Subject: [PATCH 555/678] Change input variable name --- src/imcflibs/imagej/bioformats.py | 2 +- src/imcflibs/imagej/misc.py | 25 +++++++++++-------------- src/imcflibs/imagej/objects3d.py | 6 +++--- src/imcflibs/imagej/omerotools.py | 10 +++++----- src/imcflibs/imagej/processing.py | 8 ++++---- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 0f6f2a99..8d874c11 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -401,7 +401,7 @@ def get_stage_coords(source, filenames): stage_coordinates_z = [] series_names = [] - for counter, image in enumerate(imagenames): + for counter, image in enumerate(filenames): # parse metadata reader = ImageReader() reader.setFlattenedResolutions(False) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 6256f692..3dbfce13 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -520,7 +520,7 @@ def save_image_in_format( ---------- imp : ij.ImagePlus ImagePlus object to save. - extension : {'ImageJ-TIF', 'ICS-1', 'ICS-2', 'OME-TIFF', 'CellH5', 'BMP'} + format : {'ImageJ-TIF', 'ICS-1', 'ICS-2', 'OME-TIFF', 'CellH5', 'BMP'} Output format to use, see Notes section below for details. out_dir : str Directory path where the image(s) will be saved. @@ -535,7 +535,7 @@ def save_image_in_format( Notes ----- - Depending on the value of the `extension` parameter, one of the following + Depending on the value of the `format` parameter, one of the following output formats and saving strategies will be used: - Bio-Formats based formats will be produced by calling `bf.export()`, note that these formats will preserve metadata (which is **not** the case for @@ -601,17 +601,17 @@ def save_image_in_format( basename + "_series_" + str(series).zfill(pad_number), ) - if extension == "ImageJ-TIF": + if format == "ImageJ-TIF": pathtools.create_directory(dir_to_save[index]) IJ.saveAs(current_imp, "Tiff", out_path + ".tif") - elif extension == "BMP": + elif format == "BMP": out_folder = os.path.join(out_dir, basename + os.path.sep) pathtools.create_directory(out_folder) StackWriter.save(current_imp, out_folder, "format=bmp") else: - bf.export(current_imp, out_path + out_ext[extension]) + bf.export(current_imp, out_path + out_ext[format]) current_imp.close() @@ -681,25 +681,22 @@ def run_imarisconvert(file_path): Parameters ---------- - path_to_image : str + file_path : str Absolute path to the input image file. - Notes - ----- - The function handles special case for .ids files by converting them to .ics before - processing. It uses the latest installed Imaris application to perform the conversion. + """ - path_root, file_extension = os.path.splitext(path_to_image) + path_root, file_extension = os.path.splitext(file_path) if file_extension == ".ids": file_extension = ".ics" - path_to_image = path_root + file_extension + file_path = path_root + file_extension imaris_path = locate_latest_imaris() command = 'ImarisConvert.exe -i "%s" -of Imaris5 -o "%s"' % ( - path_to_image, - path_to_image.replace(file_extension, ".ims"), + file_path, + file_path.replace(file_extension, ".ims"), ) print("\n%s" % command) IJ.log("Converting to Imaris5 .ims...") diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index 17d0d083..c3122fe3 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -160,7 +160,7 @@ def maxima_finder_3d(imp, min_threshold=0, noise=100, rxy=1.5, rz=1.5): Parameters ---------- - imageplus : ij.ImagePlus + imp : 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. @@ -177,7 +177,7 @@ def maxima_finder_3d(imp, min_threshold=0, noise=100, rxy=1.5, rz=1.5): An ImagePlus object containing the detected maxima as peaks. """ # Wrap the input ImagePlus into an ImageHandler - img = ImageHandler.wrap(imageplus) + img = ImageHandler.wrap(imp) # Duplicate the image and apply a threshold cut-off thresholded = img.duplicate() @@ -196,7 +196,7 @@ def maxima_finder_3d(imp, min_threshold=0, noise=100, rxy=1.5, rz=1.5): imp_peaks = img_peaks.getImagePlus() # Set the calibration of the peaks image to match the input image - imp_peaks.setCalibration(imageplus.getCalibration()) + imp_peaks.setCalibration(imp.getCalibration()) # Set the title of the peaks image imp_peaks.setTitle("Peaks") diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index f365b73d..f6eb3d21 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -191,17 +191,17 @@ def add_annotation(client, repository_wpr, annotations, header): def delete_keyvalue_annotations(user_client, object_wrapper): - """Delete annotations linked to object + """Delete annotations linked to object. Parameters ---------- user_client : fr.igred.omero.Client Client used for login to OMERO - repository_wpr : fr.igred.omero.repositor.GenericRepositoryObjectWrapper + object_wrapper : fr.igred.omero.repositor.GenericRepositoryObjectWrapper Wrapper to the object for the anotation """ - kv_pairs = repository_wpr.getMapAnnotations(user_client) + kv_pairs = object_wrapper.getMapAnnotations(user_client) user_client.delete(kv_pairs) @@ -298,8 +298,8 @@ def get_info_from_original_metadata(user_client, image_wpr, field): ---------- user_client : fr.igred.omero.Client Client used for login to OMERO - image_id : int - ID of the image to look. + image_wpr : fr.igred.omero.repositor.ImageWrapper + Wrapper to the image field : str Field to look for in the original metadata. Needs to be found beforehand. diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index de34a348..af503d64 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -3,7 +3,7 @@ from ..log import LOG as log def apply_filter(imp, filter_method, filter_radius, do_3D=False): - """ +def apply_filter(imp, filter_method, filter_radius, do_3d=False): Make a specific filter followed by a threshold method of choice Parameters @@ -54,7 +54,7 @@ def apply_filter(imp, filter_method, filter_radius, do_3D=False): return imageplus def apply_background_subtraction(imp, rolling_ball_radius, do_3D=False): - """ +def apply_background_subtraction(imp, rolling_ball_radius, do_3d=False): Perform background subtraction using a rolling ball method Parameters @@ -75,7 +75,7 @@ def apply_background_subtraction(imp, rolling_ball_radius, do_3D=False): options = ( "rolling=" + str(rolling_ball_radius) - + " stack" if do_3D else "" + if do_3d ) log.debug("Background subtraction options: %s" % options) @@ -86,7 +86,7 @@ def apply_background_subtraction(imp, rolling_ball_radius, do_3D=False): return imageplus def apply_threshold(imp, threshold_method): - """ +def apply_threshold(imp, threshold_method, do_3d=True): Apply a threshold method to the input ImagePlus Parameters From 14d2efc0a8fe36403b570cb7c0faab10494683bc Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:09:04 +0100 Subject: [PATCH 556/678] Use logging instead --- src/imcflibs/imagej/bioformats.py | 5 +++-- src/imcflibs/imagej/misc.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 8d874c11..72aa867e 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -435,12 +435,13 @@ def get_stage_coords(source, filenames): z_interval = physSizeZ.value() if frame_size_z > 1 and physSizeZ is None: - print("no z calibration found, trying to recover") + log.debug("no z calibration found, trying to recover") first_plane = omeMeta.getPlanePositionZ(0, 0) next_plane_imagenumber = frame_size_c + frame_size_t - 1 second_plane = omeMeta.getPlanePositionZ(0, next_plane_imagenumber) z_interval = abs(abs(first_plane.value()) - abs(second_plane.value())) print("z-interval seems to be: " + str(z_interval)) + log.debug("z-interval seems to be: " + str(z_interval)) # create an image calibration image_calibration = [physSizeX.value(), physSizeY.value(), z_interval] @@ -488,7 +489,7 @@ def get_stage_coords(source, filenames): pos_y = current_position_y.value() if current_position_z is None: - print("the z-position is missing in the ome-xml metadata.") + log.debug( pos_z = 1.0 else: pos_z = current_position_z.value() diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 3dbfce13..e3774e83 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -698,7 +698,7 @@ def run_imarisconvert(file_path): file_path, file_path.replace(file_extension, ".ims"), ) - print("\n%s" % command) + log.debug("\n%s" % command) IJ.log("Converting to Imaris5 .ims...") subprocess.call(command, shell=True, cwd=imaris_path) IJ.log("Conversion to .ims is finished") From 0045e6594483270dcf33f3d4a016a37cec108753 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:12:15 +0100 Subject: [PATCH 557/678] Format using Ruff --- src/imcflibs/imagej/bioformats.py | 41 ++++++++++++++++++------ src/imcflibs/imagej/labelimage.py | 24 ++++++++++---- src/imcflibs/imagej/misc.py | 7 +++- src/imcflibs/imagej/objects3d.py | 31 ++++++++++++------ src/imcflibs/imagej/omerotools.py | 43 ++++++++++++++++++------- src/imcflibs/imagej/processing.py | 53 +++++++++++++++++++------------ 6 files changed, 140 insertions(+), 59 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 72aa867e..9d7181c5 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -159,7 +159,9 @@ def export(imp, filename, overwrite=False): log.debug("Detected calibration unit: %s", unit) except Exception as err: log.error("Unable to detect spatial unit: %s", err) - raise RuntimeError("Error detecting image calibration: %s" % err) + raise RuntimeError( + "Error detecting image calibration: %s" % err + ) if unit == "pixel" and (suffix == "ics" or suffix == "ids"): log.warn( "Forcing unit to be 'm' instead of 'pixel' to avoid " @@ -176,7 +178,9 @@ def export(imp, filename, overwrite=False): log.debug("Exporting finished.") -def export_using_orig_name(imp, path, orig_name, tag, suffix, overwrite=False): +def export_using_orig_name( + imp, path, orig_name, tag, suffix, overwrite=False +): """Export an image to a given path, deriving the name from the input file. The input filename is stripped to its pure file name, without any path or @@ -438,15 +442,26 @@ def get_stage_coords(source, filenames): log.debug("no z calibration found, trying to recover") first_plane = omeMeta.getPlanePositionZ(0, 0) next_plane_imagenumber = frame_size_c + frame_size_t - 1 - second_plane = omeMeta.getPlanePositionZ(0, next_plane_imagenumber) - z_interval = abs(abs(first_plane.value()) - abs(second_plane.value())) - print("z-interval seems to be: " + str(z_interval)) + second_plane = omeMeta.getPlanePositionZ( + 0, next_plane_imagenumber + ) + z_interval = abs( + abs(first_plane.value()) - abs(second_plane.value()) + ) log.debug("z-interval seems to be: " + str(z_interval)) # create an image calibration - image_calibration = [physSizeX.value(), physSizeY.value(), z_interval] + image_calibration = [ + physSizeX.value(), + physSizeY.value(), + z_interval, + ] calibration_unit = physSizeX.unit().getSymbol() - image_dimensions_czt = [frame_size_c, frame_size_z, frame_size_t] + image_dimensions_czt = [ + frame_size_c, + frame_size_z, + frame_size_t, + ] reader.close() @@ -465,12 +480,14 @@ def get_stage_coords(source, filenames): physSizeX_max = ( physSizeX.value() - if physSizeX.value() >= omeMeta.getPixelsPhysicalSizeX(series).value() + if physSizeX.value() + >= omeMeta.getPixelsPhysicalSizeX(series).value() else omeMeta.getPixelsPhysicalSizeX(series).value() ) physSizeY_max = ( physSizeY.value() - if physSizeY.value() >= omeMeta.getPixelsPhysicalSizeY(series).value() + if physSizeY.value() + >= omeMeta.getPixelsPhysicalSizeY(series).value() else omeMeta.getPixelsPhysicalSizeY(series).value() ) if omeMeta.getPixelsPhysicalSizeZ(series): @@ -490,6 +507,8 @@ def get_stage_coords(source, filenames): if current_position_z is None: log.debug( + "the z-position is missing in the ome-xml metadata." + ) pos_z = 1.0 else: pos_z = current_position_z.value() @@ -512,7 +531,9 @@ def get_stage_coords(source, filenames): rel_pos_y = ( stage_coordinates_y[i] - stage_coordinates_y[0] ) / physSizeY.value() - rel_pos_z = (stage_coordinates_z[i] - stage_coordinates_z[0]) / z_interval + rel_pos_z = ( + stage_coordinates_z[i] - stage_coordinates_z[0] + ) / z_interval relative_coordinates_x_px.append(rel_pos_x) relative_coordinates_y_px.append(rel_pos_y) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 767161ae..0d32c0a5 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -31,7 +31,9 @@ def label_image_to_roi_list(label_image, low_thresh=None): max_value = 0 for slice in range(1, label_image.getNSlices() + 1): - label_image_slice = Duplicator().run(label_image, 1, 1, slice, slice, 1, 1) + label_image_slice = Duplicator().run( + label_image, 1, 1, slice, slice, 1, 1 + ) image_processor = label_image_slice.getProcessor() pixels = image_processor.getFloatArray() @@ -57,7 +59,9 @@ def label_image_to_roi_list(label_image, low_thresh=None): elif value == 0: continue # print(value) - float_processor.setThreshold(value, value, ImageProcessor.NO_LUT_UPDATE) + float_processor.setThreshold( + value, value, ImageProcessor.NO_LUT_UPDATE + ) roi = ThresholdToSelection.run(img_float_copy) roi.setName(str(value)) roi.setPosition(slice) @@ -94,7 +98,9 @@ def cookie_cut_labels(label_image_ref, label_image_to_relate): Prefs.blackBackground = True IJ.run(imp_dup, "Convert to Mask", "") IJ.run(imp_dup, "Divide...", "value=255") - return ImageCalculator.run(label_image_ref, imp_dup, "Multiply create") + return ImageCalculator.run( + label_image_ref, imp_dup, "Multiply create" + ) def relate_label_images(outer_label_imp, inner_label_imp): @@ -190,7 +196,9 @@ def measure_objects_size_shape_2d(label_image): return regions.process(label_image) -def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): +def binary_to_label( + imp, title, min_thresh=1, min_vol=None, max_vol=None +): """Segment a binary image to get a label image (2D/3D). Works on: 2D and 3D binary data. @@ -271,10 +279,14 @@ def dilate_labels_2d(imp, dilation_radius): # Iterate over each slice of the input ImagePlus for i in range(1, imp.getNSlices() + 1): # Duplicate the current slice - current_imp = Duplicator().run(imp, 1, 1, i, imp.getNSlices(), 1, 1) + 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) + dilated_labels_imp = li.dilateLabels( + current_imp, dilation_radius + ) # Append the dilated labels to the list dilated_labels_list.append(dilated_labels_imp) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index e3774e83..80f072ac 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -303,7 +303,12 @@ def timed_log(message, as_string=False): Flag to request the formatted string to be returned instead of printing it to the log. By default False. """ - formatted = time.strftime("%H:%M:%S", time.localtime()) + ": " + message + " " + formatted = ( + time.strftime("%H:%M:%S", time.localtime()) + + ": " + + message + + " " + ) if as_string: return formatted IJ.log(formatted) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index c3122fe3..df2aecf8 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -5,8 +5,12 @@ [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 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 @@ -71,7 +75,9 @@ def imgplus_to_population3d(imp): return Objects3DPopulation(img) -def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): +def segment_3d_image( + imp, title=None, min_thresh=1, min_vol=None, max_vol=None +): """Segment a 3D binary image to get a labelled stack. Parameters @@ -117,7 +123,9 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): return seg.getImagePlus() -def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): +def get_objects_within_intensity( + obj_pop, imp, min_intensity, max_intensity +): """Filter a population for objects within the given intensity range. Parameters @@ -144,7 +152,10 @@ def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): # Calculate the mean intensity of the object mean_intensity = obj.getPixMeanValue(ImageHandler.wrap(imp)) # Check if the object is within the specified intensity range - if mean_intensity >= min_intensity and mean_intensity < max_intensity: + if ( + mean_intensity >= min_intensity + and mean_intensity < max_intensity + ): objects_within_intensity.append(obj) # Return the new population with the filtered objects @@ -152,8 +163,7 @@ def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): def maxima_finder_3d(imp, min_threshold=0, noise=100, rxy=1.5, rz=1.5): - """ - Find local maxima in a 3D image. + """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. @@ -205,8 +215,7 @@ def maxima_finder_3d(imp, min_threshold=0, noise=100, rxy=1.5, rz=1.5): def seeded_watershed(imp_binary, imp_peaks, threshold=10): - """ - Perform a seeded watershed segmentation on a binary image using seed points. + """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. @@ -230,7 +239,9 @@ def seeded_watershed(imp_binary, imp_peaks, threshold=10): img_seed = ImagePlusAdapter.convertFloat(imp_peaks).copy() if threshold: - watersheded_result = WatershedLabeling.watershed(img, img_seed, threshold) + watersheded_result = WatershedLabeling.watershed( + img, img_seed, threshold + ) else: watersheded_result = WatershedLabeling.watershed(img, img_seed) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index f6eb3d21..92e48fc1 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -9,7 +9,10 @@ """ from fr.igred.omero import Client -from fr.igred.omero.annotations import MapAnnotationWrapper, TableWrapper +from fr.igred.omero.annotations import ( + MapAnnotationWrapper, + TableWrapper, +) from fr.igred.omero.roi import ROIWrapper from java.lang import Long from java.text import SimpleDateFormat @@ -54,23 +57,33 @@ def parse_url(client, omero_str): [ image for image in client.getDataset( - Long(part.split("dataset-")[1].split("/")[0]) + Long( + part.split("dataset-")[1].split( + "/" + )[0] + ) ).getImages() ] ) - dataset_id = Long(part.split("dataset-")[1].split("/")[0]) + dataset_id = Long( + part.split("dataset-")[1].split("/")[0] + ) dataset_ids.append(dataset_id) else: image_ids.extend( [ image for image in client.getDataset( - Long(omero_str.split("dataset-")[1].split("/")[0]) + Long( + omero_str.split("dataset-")[1].split("/")[0] + ) ).getImages() ] ) # If there is only one dataset - dataset_id = Long(omero_str.split("dataset-")[1].split("/")[0]) + dataset_id = Long( + omero_str.split("dataset-")[1].split("/")[0] + ) dataset_ids.append(dataset_id) # Get the images from the dataset @@ -84,10 +97,15 @@ def parse_url(client, omero_str): elif "image-" in omero_str: image_ids = omero_str.split("image-") image_ids.pop(0) - image_ids = [s.split("%")[0].replace("|", "") for s in image_ids] + image_ids = [ + s.split("%")[0].replace("|", "") for s in image_ids + ] else: image_ids = ( - [s.split("%")[0].replace("|", "") for s in omero_str.split("image-")[1:]] + [ + s.split("%")[0].replace("|", "") + for s in omero_str.split("image-")[1:] + ] if "image-" in omero_str else omero_str.split(",") ) @@ -166,7 +184,9 @@ def upload_image_to_omero(user_client, path, dataset_id): Long ID of the uploaded image """ - return user_client.getDataset(Long(dataset_id)).importImage(user_client, path)[0] + return user_client.getDataset(Long(dataset_id)).importImage( + user_client, path + )[0] def add_annotation(client, repository_wpr, annotations, header): @@ -341,8 +361,9 @@ def create_table_columns(headings): return table_columns -def upload_array_as_omero_table(user_client, table_title, data, columns, image_wpr): - """Upload a table to OMERO plus from a list of lists +def upload_array_as_omero_table( + user_client, table_title, data, columns, image_wpr +): Parameters ---------- @@ -365,7 +386,7 @@ def upload_array_as_omero_table(user_client, table_title, data, columns, image_w def save_rois_to_omero(user_client, image_wpr, rm): - """Save ROIs to OMERO linked to the image + """Save ROIs to OMERO linked to the image. Parameters ---------- diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index af503d64..83dc6257 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -2,9 +2,9 @@ from ..log import LOG as log -def apply_filter(imp, filter_method, filter_radius, do_3D=False): + def apply_filter(imp, filter_method, filter_radius, do_3d=False): - Make a specific filter followed by a threshold method of choice + """Make a specific filter followed by a threshold method of choice. Parameters ---------- @@ -28,9 +28,18 @@ def apply_filter(imp, filter_method, filter_radius, do_3d=False): ij.ImagePlus Filtered ImagePlus """ - log.info("Applying filter %s with radius %d" % (filter_method, filter_radius)) + log.info( + "Applying filter %s with radius %d" + % (filter_method, filter_radius) + ) - if filter_method not in ["Median", "Mean", "Gaussian Blur", "Minimum", "Maximum"]: + 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" ) @@ -41,9 +50,9 @@ def apply_filter(imp, filter_method, filter_radius, do_3d=False): filter = filter_method + "..." options = ( - "sigma=" if filter_method == "Gaussian Blur" else "radius=" - + str(filter_radius) - + " stack" + "sigma=" + if filter_method == "Gaussian Blur" + else "radius=" + str(filter_radius) + " stack" ) log.debug("Filter: <%s> with options <%s>" % (filter, options)) @@ -53,9 +62,9 @@ def apply_filter(imp, filter_method, filter_radius, do_3d=False): return imageplus -def apply_background_subtraction(imp, rolling_ball_radius, do_3D=False): + def apply_background_subtraction(imp, rolling_ball_radius, do_3d=False): - Perform background subtraction using a rolling ball method + """Perform background subtraction using a rolling ball method. Parameters ---------- @@ -71,11 +80,14 @@ def apply_background_subtraction(imp, rolling_ball_radius, do_3d=False): ij.ImagePlus Filtered ImagePlus """ - log.info("Applying rolling ball with radius %d" % rolling_ball_radius) + log.info( + "Applying rolling ball with radius %d" % rolling_ball_radius + ) options = ( - "rolling=" + str(rolling_ball_radius) + "rolling=" + str(rolling_ball_radius) + " stack" if do_3d + else "" ) log.debug("Background subtraction options: %s" % options) @@ -85,9 +97,9 @@ def apply_background_subtraction(imp, rolling_ball_radius, do_3d=False): return imageplus -def apply_threshold(imp, threshold_method): + def apply_threshold(imp, threshold_method, do_3d=True): - Apply a threshold method to the input ImagePlus + """Apply a threshold method to the input ImagePlus. Parameters ---------- @@ -109,11 +121,7 @@ def apply_threshold(imp, threshold_method, do_3d=True): imageplus = imp.duplicate() auto_threshold_options = ( - threshold_method - + " " - + "dark" - + " " - + "stack" if do_3D else "" + threshold_method + " " + "dark" + " " + "stack" if do_3D else "" ) log.debug("Auto threshold options: %s" % auto_threshold_options) @@ -121,15 +129,18 @@ def apply_threshold(imp, threshold_method, do_3d=True): IJ.setAutoThreshold(imageplus, auto_threshold_options) convert_to_binary_options = ( - "method=" + threshold_method + "method=" + + threshold_method + " " + "background=Dark" + " " + "black" ) - log.debug("Convert to binary options: %s" % convert_to_binary_options) + 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 + return imageplus From 0ff343341d2568979844a501b00c361b6eaa3999 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:13:18 +0100 Subject: [PATCH 558/678] Add comments and notes to docstring, fix missing inputs --- src/imcflibs/imagej/labelimage.py | 13 ++++++--- src/imcflibs/imagej/misc.py | 44 ++++++++++++++++++++++++------- src/imcflibs/imagej/omerotools.py | 36 +++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 0d32c0a5..74991b91 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -104,10 +104,17 @@ def cookie_cut_labels(label_image_ref, label_image_to_relate): def relate_label_images(outer_label_imp, inner_label_imp): - """ - Associate two label images. + """Relate label images, giving the same label to objects belonging together. + + Given two label images, this function will create a new label image + with the same labels as the reference image, but with the objects + of the second image using the 3D Association plugin from the + 3DImageJSuite. - Uses the 3D Association plugin from the 3DImageJSuite. + Notes + ----- + Unlike `cookie_cut_labels`, this should work with touching labels by + using MereoTopology algorithms. Parameters ---------- diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 80f072ac..a38da080 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -453,10 +453,13 @@ def get_threshold_value_from_method(imp, method, ops): def write_ordereddict_to_csv(out_file, content): """Write data from a list of OrderedDicts to a CSV file. - This function writes data to a CSV file, preserving the order of columns - as defined in the OrderedDict objects. If the output file doesn't exist, - it creates a new file with a header row. If the file exists, it appends - the data without repeating the header. + In order to save the results of an analysis, looping over files, + making an OrderedDict help structure the data. If a list is made + containing all these OrderedDict, this function will write the data to + a CSV file, preserving the order of columns as defined in the + OrderedDict objects. If the output file doesn't exist, it creates a + new file with a header row. If the file exists, it appends the data + without repeating the header. Parameters ---------- @@ -464,7 +467,7 @@ def write_ordereddict_to_csv(out_file, content): Path to the output CSV file. content : list of OrderedDict List of OrderedDict objects representing the data rows to be written. - All dictionaries should have the same keys. + All dictionaries must have the same keys. Examples -------- @@ -477,9 +480,22 @@ def write_ordereddict_to_csv(out_file, content): The resulting CSV file will have the following content: - id;name;value - 1;Sample A;42.5 - 2;Sample B;37.2 + id;name;value + 1;Sample A;42.5 + 2;Sample B;37.2 + + >>> results = [] + >>> for i in range(1, 3): + ... results.append(OrderedDict([('id', i), ('name', f'Sample {chr(64+i)}'), ('value', 30 + i*7.5)])) + >>> write_ordereddict_to_csv('results.csv', results) + + The resulting CSV file will have the following content: + + + id;name;value + 1;Sample A;37.5 + 2;Sample B;45.0 + 3;Sample C;52.5 Notes ----- @@ -682,7 +698,17 @@ def locate_latest_imaris(paths_to_check=None): def run_imarisconvert(file_path): - """Convert a given file to Imaris5 .ims using ImarisConvert.exe via subprocess. + """Convert a given file to Imaris format using ImarisConvert. + + Convert the input image file to Imaris format (Imaris5) using the + ImarisConvert utility. The function uses the latest installed Imaris + application to perform the conversion using subprocess. + + Notes + ----- + The function handles special case for .ids files by converting them + to .ics before processing. It uses the latest installed Imaris + application to perform the conversion. Parameters ---------- diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 92e48fc1..833f74e3 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -364,18 +364,54 @@ def create_table_columns(headings): def upload_array_as_omero_table( user_client, table_title, data, columns, image_wpr ): + """Upload a table to OMERO from a list of lists. Parameters ---------- user_client : fr.igred.omero.Client Client used for login to OMERO + table_title : str + Title of the table to be uploaded. data : list(list()) List of lists of results to upload columns : list(str) List of columns names image_wpr : fr.igred.omero.repositor.ImageWrapper Wrapper to the image to be uploaded + + Examples + -------- + >>> from fr.igred.omero import Client + >>> from java.lang import String, Double, Long + >>> + >>> # Connect to OMERO + >>> client = Client() + >>> client.connect("omero.example.org", 4064, "username", "password") + >>> + >>> # Get an image + >>> image_id = 123456 + >>> image_wpr = client.getImage(Long(image_id)) + >>> + >>> # Prepare column definitions (name-type pairs) + >>> columns = { + ... "Row_ID": Long, + ... "Cell_Area": Double, + ... "Cell_Type": String + ... } + >>> + >>> # Prepare data (list of rows, each row is a list of values) + >>> data = [ + ... [1, 250.5, "Neuron"], + ... [2, 180.2, "Astrocyte"], + ... [3, 310.7, "Neuron"] + ... ] + >>> + >>> # Upload the table + >>> upload_array_as_omero_table( + ... client, "Cell Measurements", data, columns, image_wpr + ... ) """ + dataset_wpr = image_wpr.getDatasets(user_client)[0] table_columns = create_table_columns(columns) From 74ffe426199c8bfeec033eca3c26faa4d0fe2b31 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:13:43 +0100 Subject: [PATCH 559/678] Change output to inform if command has failed --- src/imcflibs/imagej/misc.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index a38da080..9a8ec48a 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -731,5 +731,8 @@ def run_imarisconvert(file_path): ) log.debug("\n%s" % command) IJ.log("Converting to Imaris5 .ims...") - subprocess.call(command, shell=True, cwd=imaris_path) - IJ.log("Conversion to .ims is finished") + result = subprocess.call(command, shell=True, cwd=imaris_path) + if result == 0: + IJ.log("Conversion to .ims is finished") + else: + IJ.log("Conversion failed with error code: %d" % result) From a094dc928e7cf4b466128b3dc7b5cf27e017678a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:13:59 +0100 Subject: [PATCH 560/678] Change method to return faster in case of empty list --- src/imcflibs/imagej/misc.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 9a8ec48a..2563936a 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -120,18 +120,20 @@ def calculate_mean_and_stdv(values_list, round_decimals=0): tuple of (float, float) Mean and standard deviation of the input list. """ - filtered_list = filter(None, values_list) + filtered_list = [x for x in values_list if x is not None] - try: - mean = round( - sum(filtered_list) / len(filtered_list), round_decimals - ) - except ZeroDivisionError: - mean = 0 - tot = 0.0 - for x in filtered_list: - tot = tot + (x - mean) ** 2 - return [mean, (tot / (len(filtered_list))) ** 0.5] + if not filtered_list: + return 0, 0 + + mean = round( + sum(filtered_list) / len(filtered_list), round_decimals + ) + variance = sum((x - mean) ** 2 for x in filtered_list) / len( + filtered_list + ) + std_dev = round(variance**0.5, round_decimals) + + return mean, std_dev def find_focus(imp): From c8fcff33d1d1129a9402ef2e7f246f41829c9c9c Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:25:31 +0100 Subject: [PATCH 561/678] 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 562/678] 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 563/678] 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 564/678] 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 From 25ee6ed1b01a5b978a48624ee4b22f08aa783772 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 24 Mar 2025 16:30:13 +0100 Subject: [PATCH 565/678] Add module docstring for ImageJ processing utilities --- src/imcflibs/imagej/processing.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index 83dc6257..00a718ef 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -1,3 +1,9 @@ +"""ImageJ processing utilities for filtering and thresholding images. + +This module provides functions to apply various image processing operations +using ImageJ, including filters, background subtraction, and thresholding. +""" + from ij import IJ from ..log import LOG as log From 00dc6413819de0f23ecf5ab02702cb45caeea450 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 15:20:47 +0100 Subject: [PATCH 566/678] Move minor implementation detail into code comment Unless this makes a real difference from the perspective of _using_ the function, it doesn't need to be in the docstring. --- src/imcflibs/imagej/misc.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 2563936a..74873be4 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -704,22 +704,15 @@ def run_imarisconvert(file_path): Convert the input image file to Imaris format (Imaris5) using the ImarisConvert utility. The function uses the latest installed Imaris - application to perform the conversion using subprocess. - - Notes - ----- - The function handles special case for .ids files by converting them - to .ics before processing. It uses the latest installed Imaris - application to perform the conversion. + application to perform the conversion via `subprocess.call()`. Parameters ---------- file_path : str Absolute path to the input image file. - - """ - + # in case the given file has the suffix `.ids` (meaning it is part of an + # ICS-1 `.ics`+`.ids` pair), point ImarisConvert to the `.ics` file instead: path_root, file_extension = os.path.splitext(file_path) if file_extension == ".ids": file_extension = ".ics" @@ -735,6 +728,6 @@ def run_imarisconvert(file_path): IJ.log("Converting to Imaris5 .ims...") result = subprocess.call(command, shell=True, cwd=imaris_path) if result == 0: - IJ.log("Conversion to .ims is finished") + IJ.log("Conversion to .ims is finished.") else: IJ.log("Conversion failed with error code: %d" % result) From 51248a486923e9a94bedc4e5f2477ce73418aede Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 15:24:35 +0100 Subject: [PATCH 567/678] Docstring cleanups --- src/imcflibs/imagej/omerotools.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 833f74e3..a84a60e3 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -216,9 +216,9 @@ def delete_keyvalue_annotations(user_client, object_wrapper): Parameters ---------- user_client : fr.igred.omero.Client - Client used for login to OMERO + Client used for login to OMERO. object_wrapper : fr.igred.omero.repositor.GenericRepositoryObjectWrapper - Wrapper to the object for the anotation + Wrapper to the object for the anotation. """ kv_pairs = object_wrapper.getMapAnnotations(user_client) @@ -256,11 +256,13 @@ def get_acquisition_metadata(user_client, image_wpr): Returns ------- dict - Dictionary containing acquisition metadata: - - `objective_magnification`: Objective magnification - - `objective_na`: Objective NA - - `acquisition_date`: Acquisition date - - `acquisition_date_number`: Acquisition date as a number + + { + objective_magnification : float, + objective_na : float, + acquisition_date : str, + acquisition_date_number : str, + } """ ctx = user_client.getCtx() instrument_data = ( From d81e682a4ee3f7fe277ed583ad1d106c7fb1a687 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 15:24:49 +0100 Subject: [PATCH 568/678] Shorten docstring example --- src/imcflibs/imagej/omerotools.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index a84a60e3..ef2290f2 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -385,30 +385,24 @@ def upload_array_as_omero_table( -------- >>> from fr.igred.omero import Client >>> from java.lang import String, Double, Long - >>> - >>> # Connect to OMERO - >>> client = Client() + ... + >>> client = Client() # connect to OMERO >>> client.connect("omero.example.org", 4064, "username", "password") - >>> - >>> # Get an image - >>> image_id = 123456 - >>> image_wpr = client.getImage(Long(image_id)) - >>> - >>> # Prepare column definitions (name-type pairs) - >>> columns = { + ... + >>> image_wpr = client.getImage(Long(123456)) # get an image + ... + >>> columns = { # prepare column definitions (name-type pairs) ... "Row_ID": Long, ... "Cell_Area": Double, - ... "Cell_Type": String + ... "Cell_Type": String, ... } - >>> - >>> # Prepare data (list of rows, each row is a list of values) - >>> data = [ + ... + >>> data = [ # prepare data (list of rows, each row is a list of values) ... [1, 250.5, "Neuron"], ... [2, 180.2, "Astrocyte"], - ... [3, 310.7, "Neuron"] + ... [3, 310.7, "Neuron"], ... ] - >>> - >>> # Upload the table + ... >>> upload_array_as_omero_table( ... client, "Cell Measurements", data, columns, image_wpr ... ) From 6b641bb21b6176489ae3d3c014b096bde671efef Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 15:42:21 +0100 Subject: [PATCH 569/678] Indent to show as a fixed-width block --- src/imcflibs/imagej/misc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 74873be4..d07e8af9 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -482,9 +482,9 @@ def write_ordereddict_to_csv(out_file, content): The resulting CSV file will have the following content: - id;name;value - 1;Sample A;42.5 - 2;Sample B;37.2 + id;name;value + 1;Sample A;42.5 + 2;Sample B;37.2 >>> results = [] >>> for i in range(1, 3): From 51f2cec2ec16fa4bb63c4b57f2978adec7e12f80 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 15:45:47 +0100 Subject: [PATCH 570/678] One example should be enough As the 2nd example was not using the function in a different way, there is no real need for it. If it's worth having the example with the `range()`, `append` etc., we should make it the only one (and also make it valid Python 2.7 syntax, i.e. no f-strings). --- src/imcflibs/imagej/misc.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index d07e8af9..2b4dca9e 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -486,19 +486,6 @@ def write_ordereddict_to_csv(out_file, content): 1;Sample A;42.5 2;Sample B;37.2 - >>> results = [] - >>> for i in range(1, 3): - ... results.append(OrderedDict([('id', i), ('name', f'Sample {chr(64+i)}'), ('value', 30 + i*7.5)])) - >>> write_ordereddict_to_csv('results.csv', results) - - The resulting CSV file will have the following content: - - - id;name;value - 1;Sample A;37.5 - 2;Sample B;45.0 - 3;Sample C;52.5 - Notes ----- - Uses the semicolon charachter (`;`) as delimiter. From a65fe410d5f9fe2bfa670a6c5bcc276778828ae8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 15:46:30 +0100 Subject: [PATCH 571/678] Add empty line to fix docstring rendering --- src/imcflibs/pathtools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index bd8965e5..7f738b6d 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -38,6 +38,7 @@ def parse_path(path, prefix=""): dict The parsed (and possibly combined) path split into its components, with the following keys: + - `orig` : The full string as passed into this function (possibly combined with the prefix in case one was specified). - `full` : The same as `orig` with separators adjusted to the current From c35e312e37c41ea477885a7da4f125b844c27161 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 15:49:24 +0100 Subject: [PATCH 572/678] Run black formatting (restore default line length of 88) --- src/imcflibs/imagej/bioformats.py | 30 +++++----------- src/imcflibs/imagej/labelimage.py | 24 ++++--------- src/imcflibs/imagej/misc.py | 57 ++++++++----------------------- src/imcflibs/imagej/objects3d.py | 17 +++------ src/imcflibs/imagej/omerotools.py | 39 +++++---------------- src/imcflibs/imagej/processing.py | 26 +++----------- src/imcflibs/imagej/shading.py | 6 ++-- src/imcflibs/pathtools.py | 12 ++----- 8 files changed, 51 insertions(+), 160 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 9d7181c5..45e1b84d 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -159,9 +159,7 @@ def export(imp, filename, overwrite=False): log.debug("Detected calibration unit: %s", unit) except Exception as err: log.error("Unable to detect spatial unit: %s", err) - raise RuntimeError( - "Error detecting image calibration: %s" % err - ) + raise RuntimeError("Error detecting image calibration: %s" % err) if unit == "pixel" and (suffix == "ics" or suffix == "ids"): log.warn( "Forcing unit to be 'm' instead of 'pixel' to avoid " @@ -178,9 +176,7 @@ def export(imp, filename, overwrite=False): log.debug("Exporting finished.") -def export_using_orig_name( - imp, path, orig_name, tag, suffix, overwrite=False -): +def export_using_orig_name(imp, path, orig_name, tag, suffix, overwrite=False): """Export an image to a given path, deriving the name from the input file. The input filename is stripped to its pure file name, without any path or @@ -442,12 +438,8 @@ def get_stage_coords(source, filenames): log.debug("no z calibration found, trying to recover") first_plane = omeMeta.getPlanePositionZ(0, 0) next_plane_imagenumber = frame_size_c + frame_size_t - 1 - second_plane = omeMeta.getPlanePositionZ( - 0, next_plane_imagenumber - ) - z_interval = abs( - abs(first_plane.value()) - abs(second_plane.value()) - ) + second_plane = omeMeta.getPlanePositionZ(0, next_plane_imagenumber) + z_interval = abs(abs(first_plane.value()) - abs(second_plane.value())) log.debug("z-interval seems to be: " + str(z_interval)) # create an image calibration @@ -480,14 +472,12 @@ def get_stage_coords(source, filenames): physSizeX_max = ( physSizeX.value() - if physSizeX.value() - >= omeMeta.getPixelsPhysicalSizeX(series).value() + if physSizeX.value() >= omeMeta.getPixelsPhysicalSizeX(series).value() else omeMeta.getPixelsPhysicalSizeX(series).value() ) physSizeY_max = ( physSizeY.value() - if physSizeY.value() - >= omeMeta.getPixelsPhysicalSizeY(series).value() + if physSizeY.value() >= omeMeta.getPixelsPhysicalSizeY(series).value() else omeMeta.getPixelsPhysicalSizeY(series).value() ) if omeMeta.getPixelsPhysicalSizeZ(series): @@ -506,9 +496,7 @@ def get_stage_coords(source, filenames): pos_y = current_position_y.value() if current_position_z is None: - log.debug( - "the z-position is missing in the ome-xml metadata." - ) + log.debug("the z-position is missing in the ome-xml metadata.") pos_z = 1.0 else: pos_z = current_position_z.value() @@ -531,9 +519,7 @@ def get_stage_coords(source, filenames): rel_pos_y = ( stage_coordinates_y[i] - stage_coordinates_y[0] ) / physSizeY.value() - rel_pos_z = ( - stage_coordinates_z[i] - stage_coordinates_z[0] - ) / z_interval + rel_pos_z = (stage_coordinates_z[i] - stage_coordinates_z[0]) / z_interval relative_coordinates_x_px.append(rel_pos_x) relative_coordinates_y_px.append(rel_pos_y) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 74991b91..470f5107 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -31,9 +31,7 @@ def label_image_to_roi_list(label_image, low_thresh=None): max_value = 0 for slice in range(1, label_image.getNSlices() + 1): - label_image_slice = Duplicator().run( - label_image, 1, 1, slice, slice, 1, 1 - ) + label_image_slice = Duplicator().run(label_image, 1, 1, slice, slice, 1, 1) image_processor = label_image_slice.getProcessor() pixels = image_processor.getFloatArray() @@ -59,9 +57,7 @@ def label_image_to_roi_list(label_image, low_thresh=None): elif value == 0: continue # print(value) - float_processor.setThreshold( - value, value, ImageProcessor.NO_LUT_UPDATE - ) + float_processor.setThreshold(value, value, ImageProcessor.NO_LUT_UPDATE) roi = ThresholdToSelection.run(img_float_copy) roi.setName(str(value)) roi.setPosition(slice) @@ -98,9 +94,7 @@ def cookie_cut_labels(label_image_ref, label_image_to_relate): Prefs.blackBackground = True IJ.run(imp_dup, "Convert to Mask", "") IJ.run(imp_dup, "Divide...", "value=255") - return ImageCalculator.run( - label_image_ref, imp_dup, "Multiply create" - ) + return ImageCalculator.run(label_image_ref, imp_dup, "Multiply create") def relate_label_images(outer_label_imp, inner_label_imp): @@ -203,9 +197,7 @@ def measure_objects_size_shape_2d(label_image): return regions.process(label_image) -def binary_to_label( - imp, title, min_thresh=1, min_vol=None, max_vol=None -): +def binary_to_label(imp, title, min_thresh=1, min_vol=None, max_vol=None): """Segment a binary image to get a label image (2D/3D). Works on: 2D and 3D binary data. @@ -286,14 +278,10 @@ def dilate_labels_2d(imp, dilation_radius): # Iterate over each slice of the input ImagePlus for i in range(1, imp.getNSlices() + 1): # Duplicate the current slice - current_imp = Duplicator().run( - imp, 1, 1, i, imp.getNSlices(), 1, 1 - ) + 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 - ) + dilated_labels_imp = li.dilateLabels(current_imp, dilation_radius) # Append the dilated labels to the list dilated_labels_list.append(dilated_labels_imp) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 2b4dca9e..467fd9bb 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -43,9 +43,7 @@ def show_progress(cur, final): ----- `ij.IJ.showProgress` internally increments the given `cur` value by 1. """ - log.info( - "Progress: %s / %s (%s)", cur + 1, final, (1.0 + cur) / final - ) + log.info("Progress: %s / %s (%s)", cur + 1, final, (1.0 + cur) / final) IJ.showProgress(cur, final) @@ -82,9 +80,7 @@ def elapsed_time_since(start, end=None): hours, rem = divmod(end - start, 3600) minutes, seconds = divmod(rem, 60) - return "{:0>2}:{:0>2}:{:05.2f}".format( - int(hours), int(minutes), seconds - ) + return "{:0>2}:{:0>2}:{:05.2f}".format(int(hours), int(minutes), seconds) def percentage(part, whole): @@ -125,13 +121,9 @@ def calculate_mean_and_stdv(values_list, round_decimals=0): if not filtered_list: return 0, 0 - mean = round( - sum(filtered_list) / len(filtered_list), round_decimals - ) - variance = sum((x - mean) ** 2 for x in filtered_list) / len( - filtered_list - ) - std_dev = round(variance**0.5, round_decimals) + mean = round(sum(filtered_list) / len(filtered_list), round_decimals) + variance = sum((x - mean) ** 2 for x in filtered_list) / len(filtered_list) + std_dev = round(variance ** 0.5, round_decimals) return mean, std_dev @@ -168,9 +160,7 @@ def find_focus(imp): # Check if more than 1 channel # FUTURE Could be improved for multi channel if imp_dimensions[2] != 1: - sys.exit( - "Image has more than one channel, please reduce dimensionality" - ) + sys.exit("Image has more than one channel, please reduce dimensionality") # Loop through each time point for plane in range(1, imp_dimensions[4] + 1): @@ -186,9 +176,7 @@ def find_focus(imp): # pix_array = pix_array*pix_array sumpix_array = sum(pix_array) - var = sumpix_array / ( - imp_dimensions[0] * imp_dimensions[1] * mean - ) + var = sumpix_array / (imp_dimensions[0] * imp_dimensions[1] * mean) if var > norm_var: norm_var = var @@ -217,14 +205,10 @@ def send_mail(job_name, recipient, filename, total_execution_time): # Ensure the sender and server are configured from Prefs if not sender: - log.info( - "Sender email is not configured. Please check IJ_Prefs.txt." - ) + log.info("Sender email is not configured. Please check IJ_Prefs.txt.") return if not server: - log.info( - "SMTP server is not configured. Please check IJ_Prefs.txt." - ) + log.info("SMTP server is not configured. Please check IJ_Prefs.txt.") return # Ensure the recipient is provided @@ -305,12 +289,7 @@ def timed_log(message, as_string=False): Flag to request the formatted string to be returned instead of printing it to the log. By default False. """ - formatted = ( - time.strftime("%H:%M:%S", time.localtime()) - + ": " - + message - + " " - ) + formatted = time.strftime("%H:%M:%S", time.localtime()) + ": " + message + " " if as_string: return formatted IJ.log(formatted) @@ -497,23 +476,17 @@ def write_ordereddict_to_csv(out_file, content): 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 = 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 = csv.DictWriter(f, content[0].keys(), delimiter=";") dict_writer.writerows(content) -def save_image_in_format( - imp, format, out_dir, series, pad_number, split_channels -): +def save_image_in_format(imp, format, out_dir, series, pad_number, split_channels): """Save an ImagePlus object in the specified format. This function provides flexible options for saving ImageJ images in various @@ -596,9 +569,7 @@ def save_image_in_format( imp.getNFrames(), ) ) - dir_to_save.append( - os.path.join(out_dir, "C" + str(channel)) - ) + dir_to_save.append(os.path.join(out_dir, "C" + str(channel))) else: imp_to_use.append(imp) dir_to_save.append(out_dir) diff --git a/src/imcflibs/imagej/objects3d.py b/src/imcflibs/imagej/objects3d.py index df2aecf8..8388e8c2 100644 --- a/src/imcflibs/imagej/objects3d.py +++ b/src/imcflibs/imagej/objects3d.py @@ -75,9 +75,7 @@ def imgplus_to_population3d(imp): return Objects3DPopulation(img) -def segment_3d_image( - imp, title=None, min_thresh=1, min_vol=None, max_vol=None -): +def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None): """Segment a 3D binary image to get a labelled stack. Parameters @@ -123,9 +121,7 @@ def segment_3d_image( return seg.getImagePlus() -def get_objects_within_intensity( - obj_pop, imp, min_intensity, max_intensity -): +def get_objects_within_intensity(obj_pop, imp, min_intensity, max_intensity): """Filter a population for objects within the given intensity range. Parameters @@ -152,10 +148,7 @@ def get_objects_within_intensity( # Calculate the mean intensity of the object mean_intensity = obj.getPixMeanValue(ImageHandler.wrap(imp)) # Check if the object is within the specified intensity range - if ( - mean_intensity >= min_intensity - and mean_intensity < max_intensity - ): + if mean_intensity >= min_intensity and mean_intensity < max_intensity: objects_within_intensity.append(obj) # Return the new population with the filtered objects @@ -239,9 +232,7 @@ def seeded_watershed(imp_binary, imp_peaks, threshold=10): img_seed = ImagePlusAdapter.convertFloat(imp_peaks).copy() if threshold: - watersheded_result = WatershedLabeling.watershed( - img, img_seed, threshold - ) + watersheded_result = WatershedLabeling.watershed(img, img_seed, threshold) else: watersheded_result = WatershedLabeling.watershed(img, img_seed) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index ef2290f2..2e2c4325 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -57,33 +57,23 @@ def parse_url(client, omero_str): [ image for image in client.getDataset( - Long( - part.split("dataset-")[1].split( - "/" - )[0] - ) + Long(part.split("dataset-")[1].split("/")[0]) ).getImages() ] ) - dataset_id = Long( - part.split("dataset-")[1].split("/")[0] - ) + dataset_id = Long(part.split("dataset-")[1].split("/")[0]) dataset_ids.append(dataset_id) else: image_ids.extend( [ image for image in client.getDataset( - Long( - omero_str.split("dataset-")[1].split("/")[0] - ) + Long(omero_str.split("dataset-")[1].split("/")[0]) ).getImages() ] ) # If there is only one dataset - dataset_id = Long( - omero_str.split("dataset-")[1].split("/")[0] - ) + dataset_id = Long(omero_str.split("dataset-")[1].split("/")[0]) dataset_ids.append(dataset_id) # Get the images from the dataset @@ -97,15 +87,10 @@ def parse_url(client, omero_str): elif "image-" in omero_str: image_ids = omero_str.split("image-") image_ids.pop(0) - image_ids = [ - s.split("%")[0].replace("|", "") for s in image_ids - ] + image_ids = [s.split("%")[0].replace("|", "") for s in image_ids] else: image_ids = ( - [ - s.split("%")[0].replace("|", "") - for s in omero_str.split("image-")[1:] - ] + [s.split("%")[0].replace("|", "") for s in omero_str.split("image-")[1:]] if "image-" in omero_str else omero_str.split(",") ) @@ -184,9 +169,7 @@ def upload_image_to_omero(user_client, path, dataset_id): Long ID of the uploaded image """ - return user_client.getDataset(Long(dataset_id)).importImage( - user_client, path - )[0] + return user_client.getDataset(Long(dataset_id)).importImage(user_client, path)[0] def add_annotation(client, repository_wpr, annotations, header): @@ -299,9 +282,7 @@ def get_acquisition_metadata(user_client, image_wpr): metadata["acquisition_date_number"] = 0 else: sdf = SimpleDateFormat("yyyy-MM-dd") - metadata["acquisition_date"] = sdf.format( - image_wpr.getAcquisitionDate() - ) + metadata["acquisition_date"] = sdf.format(image_wpr.getAcquisitionDate()) metadata["acquisition_date_number"] = int( metadata["acquisition_date"].replace("-", "") ) @@ -363,9 +344,7 @@ def create_table_columns(headings): return table_columns -def upload_array_as_omero_table( - user_client, table_title, data, columns, image_wpr -): +def upload_array_as_omero_table(user_client, table_title, data, columns, image_wpr): """Upload a table to OMERO from a list of lists. Parameters diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index 00a718ef..c50e9c5f 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -34,10 +34,7 @@ def apply_filter(imp, filter_method, filter_radius, do_3d=False): ij.ImagePlus Filtered ImagePlus """ - log.info( - "Applying filter %s with radius %d" - % (filter_method, filter_radius) - ) + log.info("Applying filter %s with radius %d" % (filter_method, filter_radius)) if filter_method not in [ "Median", @@ -86,15 +83,9 @@ def apply_background_subtraction(imp, rolling_ball_radius, do_3d=False): ij.ImagePlus Filtered ImagePlus """ - log.info( - "Applying rolling ball with radius %d" % rolling_ball_radius - ) + log.info("Applying rolling ball with radius %d" % rolling_ball_radius) - options = ( - "rolling=" + str(rolling_ball_radius) + " stack" - if do_3d - else "" - ) + options = "rolling=" + str(rolling_ball_radius) + " stack" if do_3d else "" log.debug("Background subtraction options: %s" % options) @@ -135,17 +126,10 @@ def apply_threshold(imp, threshold_method, do_3d=True): IJ.setAutoThreshold(imageplus, auto_threshold_options) convert_to_binary_options = ( - "method=" - + threshold_method - + " " - + "background=Dark" - + " " - + "black" + "method=" + threshold_method + " " + "background=Dark" + " " + "black" ) - log.debug( - "Convert to binary options: %s" % convert_to_binary_options - ) + log.debug("Convert to binary options: %s" % convert_to_binary_options) IJ.run(imageplus, "Convert to Mask", convert_to_binary_options) diff --git a/src/imcflibs/imagej/shading.py b/src/imcflibs/imagej/shading.py index 02a11af1..1e3a62fc 100644 --- a/src/imcflibs/imagej/shading.py +++ b/src/imcflibs/imagej/shading.py @@ -182,6 +182,7 @@ def process_files(files, outpath, model_file, fmt): if model: model.close() + def simple_flatfield_correction(imp, sigma=20.0): """Perform a simple flatfield correction to a given ImagePlus stack. @@ -205,10 +206,7 @@ def simple_flatfield_correction(imp, sigma=20.0): # Normalize image to the highest value of original (requires 32-bit image) IJ.run(flatfield, "32-bit", "") - IJ.run( - flatfield, - "Divide...", - "value=" + str(stats.max)) + IJ.run(flatfield, "Divide...", "value=" + str(stats.max)) ic = ImageCalculator() flatfield_corrected = ic.run("Divide create", imp, flatfield) diff --git a/src/imcflibs/pathtools.py b/src/imcflibs/pathtools.py index 7f738b6d..166a5186 100644 --- a/src/imcflibs/pathtools.py +++ b/src/imcflibs/pathtools.py @@ -180,9 +180,7 @@ def jython_fiji_exists(path): return False -def listdir_matching( - path, suffix, fullpath=False, sort=False, regex=False -): +def listdir_matching(path, suffix, fullpath=False, sort=False, regex=False): """Get a list of files in a directory matching a given suffix. Parameters @@ -298,13 +296,9 @@ def derive_out_dir(in_dir, out_dir): """ if out_dir.upper() in ["-", "NONE"]: out_dir = in_dir - log.info( - "No output directory given, using input dir [%s].", out_dir - ) + log.info("No output directory given, using input dir [%s].", out_dir) else: - log.info( - "Using directory [%s] for results and temp files.", out_dir - ) + log.info("Using directory [%s] for results and temp files.", out_dir) return out_dir From caa1370ca6a6f8b07174d82e8e0631e20d097089 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 15:59:59 +0100 Subject: [PATCH 573/678] Notes section comes before Examples https://numpydoc.readthedocs.io/en/latest/format.html#sections --- src/imcflibs/imagej/misc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 467fd9bb..453f6791 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -450,6 +450,12 @@ def write_ordereddict_to_csv(out_file, content): List of OrderedDict objects representing the data rows to be written. All dictionaries must have the same keys. + Notes + ----- + - Uses the semicolon charachter (`;`) as delimiter. + - When appending to an existing file, the column structure has to match. + - Output file is opened in binary mode for compatibility. + Examples -------- >>> from collections import OrderedDict @@ -464,12 +470,6 @@ def write_ordereddict_to_csv(out_file, content): id;name;value 1;Sample A;42.5 2;Sample B;37.2 - - Notes - ----- - - Uses the semicolon charachter (`;`) as delimiter. - - When appending to an existing file, the column structure has to match. - - Output file is opened in binary mode for compatibility. """ # Check if the output file exists From 99ab0f0815cbdce58be166cd516cab35acc2c20a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 16:01:25 +0100 Subject: [PATCH 574/678] Minor updates on Notes section --- src/imcflibs/imagej/misc.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 453f6791..5718ff1f 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -452,9 +452,10 @@ def write_ordereddict_to_csv(out_file, content): Notes ----- - - Uses the semicolon charachter (`;`) as delimiter. - - When appending to an existing file, the column structure has to match. - - Output file is opened in binary mode for compatibility. + - The CSV file will use the semicolon charachter (`;`) as delimiter. + - When appending to an existing file, the column structure has to match. No + sanity checking is being done on this by the function! + - The output file is opened in binary mode for compatibility. Examples -------- From 07f3064dc923c7cd46d234eaeaa578ccedd71678 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 16:02:03 +0100 Subject: [PATCH 575/678] Update function details --- src/imcflibs/imagej/misc.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 5718ff1f..704abbff 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -434,13 +434,13 @@ def get_threshold_value_from_method(imp, method, ops): def write_ordereddict_to_csv(out_file, content): """Write data from a list of OrderedDicts to a CSV file. - In order to save the results of an analysis, looping over files, - making an OrderedDict help structure the data. If a list is made - containing all these OrderedDict, this function will write the data to - a CSV file, preserving the order of columns as defined in the - OrderedDict objects. If the output file doesn't exist, it creates a - new file with a header row. If the file exists, it appends the data - without repeating the header. + When performing measurements in an analysis that is e.g. looping over + multiple files, it's useful to keep the results in `OrderedDict` objects, + e.g. one per analyzed file / dataset. This function can be used to create a + CSV file (or append to an existing one) from a list of `OrderedDict`s. The + structure inside the dicts is entirely up to the calling code (i.e. it's not + related to ImageJ's *Results* window or such), the only requirement is + type-consistency among all the `OrderedDict`s provided to the function. Parameters ---------- From 433308686721fa0cfafc1d7dcdaf40f380e5c339 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 16:15:27 +0100 Subject: [PATCH 576/678] Place Notes section after Returns https://numpydoc.readthedocs.io/en/latest/format.html#sections --- src/imcflibs/imagej/labelimage.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 470f5107..22b5dc37 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -105,11 +105,6 @@ def relate_label_images(outer_label_imp, inner_label_imp): of the second image using the 3D Association plugin from the 3DImageJSuite. - Notes - ----- - Unlike `cookie_cut_labels`, this should work with touching labels by - using MereoTopology algorithms. - Parameters ---------- outer_label_imp : ij.ImagePlus @@ -121,6 +116,11 @@ def relate_label_images(outer_label_imp, inner_label_imp): ------- related_inner_imp : ij.ImagePlus The related inner label image + + Notes + ----- + Unlike `cookie_cut_labels`, this should work with touching labels by + using MereoTopology algorithms. """ outer_label_imp.show() From ec158ff901ca3c1b2a395935dc57c0caa2e636df Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 16:15:45 +0100 Subject: [PATCH 577/678] Docstring conventions --- src/imcflibs/imagej/labelimage.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/imcflibs/imagej/labelimage.py b/src/imcflibs/imagej/labelimage.py index 22b5dc37..15481e14 100644 --- a/src/imcflibs/imagej/labelimage.py +++ b/src/imcflibs/imagej/labelimage.py @@ -100,27 +100,26 @@ def cookie_cut_labels(label_image_ref, label_image_to_relate): def relate_label_images(outer_label_imp, inner_label_imp): """Relate label images, giving the same label to objects belonging together. - Given two label images, this function will create a new label image - with the same labels as the reference image, but with the objects - of the second image using the 3D Association plugin from the - 3DImageJSuite. + Given two label images, this function will create a new label image with the + same labels as the reference image, but with the objects of the second image + using the 3D Association plugin from the 3DImageJSuite. Parameters ---------- outer_label_imp : ij.ImagePlus - The outer label image + The outer label image. inner_label_imp : ij.ImagePlus - The inner label image + The inner label image. Returns ------- related_inner_imp : ij.ImagePlus - The related inner label image + The related inner label image. Notes ----- - Unlike `cookie_cut_labels`, this should work with touching labels by - using MereoTopology algorithms. + Unlike `cookie_cut_labels`, this should work with touching labels by using + MereoTopology algorithms. """ outer_label_imp.show() From 047d913e9bac4d5922c68219986e3ad63b55d1cd Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 16:24:55 +0100 Subject: [PATCH 578/678] Docstring conventions --- src/imcflibs/imagej/bioformats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 45e1b84d..5c3816f3 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -361,9 +361,9 @@ def get_stage_coords(source, filenames): Parameters ---------- source : str - Path to the images + Path to the images. filenames : list of str - List of images filenames + List of images filenames. Returns ------- From 98cfaffb65cb5d4b62bd45dbf15ba7a0b3457942 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 16:34:06 +0100 Subject: [PATCH 579/678] Reformat dict in Returns section Tries to address the issue discussed in https://github.com/imcf/python-imcflibs/pull/49#discussion_r2003514256 --- src/imcflibs/imagej/bioformats.py | 40 ++++++++++++------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 5c3816f3..758cc072 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -368,31 +368,21 @@ def get_stage_coords(source, filenames): Returns ------- dict - A dictionary containing the following metadata: - - `dimensions` : int - Number of dimensions (2D or 3D) - - `stage_coordinates_x` : list - The absolute stage x-coordinated - - `stage_coordinates_y` : list - The absolute stage y-coordinated - - `stage_coordinates_z` : list - The absolute stage z-coordinated - - `relative_coordinates_x` : list - The relative stage x-coordinates in px - - `relative_coordinates_y` : list - The relative stage y-coordinates in px - - `relative_coordinates_z` : list - The relative stage z-coordinates in px - - `image_calibration` : list - x,y,z image calibration in unit/px - - `calibration_unit` : str - Image calibration unit - - `image_dimensions_czt` : list - Number of images in dimensions c,z,t - - `series_names` : list of str - Names of all series contained in the files - - `max_size` : list of int - Maximum size across all files in dimensions x,y,z + + { + dimensions : int, # number of dimensions (2D or 3D) + stage_coordinates_x : list, # absolute stage x-coordinated + stage_coordinates_y : list, # absolute stage y-coordinated + stage_coordinates_z : list, # absolute stage z-coordinated + relative_coordinates_x : list, # relative stage x-coordinates in px + relative_coordinates_y : list, # relative stage y-coordinates in px + relative_coordinates_z : list, # relative stage z-coordinates in px + image_calibration : list, # x,y,z image calibration in unit/px + calibration_unit : str, # image calibration unit + image_dimensions_czt : list, # number of images in dimensions c,z,t + series_names : list of str, # names of all series in the files + max_size : list of int, # max size (x/y/z) across all files + } """ # open an array to store the abosolute stage coordinates from metadata From 862fde719e9e90f54ff32a5780c4caf07775e81b Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 17:24:34 +0100 Subject: [PATCH 580/678] Remove docstrings for internal globals As requested in #17, they are not intended to be used directly (i.e. imported into code elsewhere) and therefore should be omitted in the generated API documentation. --- src/imcflibs/imagej/bdv.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index e5a67498..b13779d5 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -21,15 +21,22 @@ from .. import pathtools from ..log import LOG as log + +# internal template strings used in string formatting (note: the `"""@private"""` +# pseudo-decorator is there to instruct [pdoc] to omit those variables when generating +# API documentation): SINGLE = "[Single %s (Select from List)]" -"""Template string to use to select only one value for the current dimension.""" +"""@private""" MULTIPLE = "[Multiple %ss (Select from List)]" -"""Template string to use to select specified multiple values for the current -dimension.""" +"""@private""" RANGE = "[Range of %ss (Specify by Name)]" -"""Template string to use to select a range of values for the current -dimension.""" - +"""@private""" +SINGLE_FILE = "[NO (one %s)]" +"""@private""" +MULTI_SINGLE_FILE = "[YES (all %ss in one file)]" +"""@private""" +MULTI_MULTI_FILE = "[YES (one file per %s)]" +"""@private""" class ProcessingOptions(object): """Helper to store processing options and generate parameter strings. @@ -513,17 +520,6 @@ def fmt_use_acitt(self): return parameter_string + " " -SINGLE_FILE = "[NO (one %s)]" -"""Template string to use if the current dimension is singular (like only one -angle).""" -MULTI_SINGLE_FILE = "[YES (all %ss in one file)]" -"""Template string to use if the current dimension is plural (like multiple -angles) contained in a single file.""" -MULTI_MULTI_FILE = "[YES (one file per %s)]" -"""Template string to use if the current dimension is plural (like multiple -angles) contained in multiple files.""" - - class DefinitionOptions(object): """Helper to store definition options and generate parameters strings. From adbce1dbee956405927d29142f7078d4fae2c92e Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 17:27:04 +0100 Subject: [PATCH 581/678] Reformat for docstring line length consistency --- src/imcflibs/imagej/bdv.py | 48 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index b13779d5..f31c78c4 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -113,8 +113,8 @@ def reference_angle(self, value): Select the angle(s) to use for the operation, by default empty (`""`). - NOTE: this value will be used to render `angles=[use Angle VALUE]` when calling - the `fmt_use_acitt()` method. + NOTE: this value will be used to render `angles=[use Angle VALUE]` when + calling the `fmt_use_acitt()` method. Parameters ---------- @@ -127,11 +127,11 @@ def reference_angle(self, value): def reference_channel(self, value): """Set the reference channel when using *Expert Grouping Options*. - Select the channel(s) to use for the operation, by default the averaging mode - will be used (`channels=[Average Channels]`). + Select the channel(s) to use for the operation, by default the averaging + mode will be used (`channels=[Average Channels]`). - NOTE: this value will be used to render `channels=[use Channel VALUE]` when - calling the `fmt_use_acitt()` method. + NOTE: this value will be used to render `channels=[use Channel VALUE]` + when calling the `fmt_use_acitt()` method. Parameters ---------- @@ -147,11 +147,11 @@ def reference_channel(self, value): def reference_illumination(self, value): """Set the reference illumination when using *Expert Grouping Options*. - Select the illumination(s) to use for the operation, by default the averaging - mode will be used (`illuminations=[Average Illuminations]`). + Select the illumination(s) to use for the operation, by default the + averaging mode will be used (`illuminations=[Average Illuminations]`). - NOTE: this value will be used to render `illuminations=[use Illumination VALUE]` - when calling the `fmt_use_acitt()` method. + NOTE: this value will be used to render `illuminations=[use Illumination + VALUE]` when calling the `fmt_use_acitt()` method. Parameters ---------- @@ -169,11 +169,11 @@ def reference_illumination(self, value): def reference_tile(self, value): """Set the reference tile when using *Expert Grouping Options*. - Select the tile(s) to use for the operation, by default the averaging mode will - be used (`tiles=[Average Tiles]`). + Select the tile(s) to use for the operation, by default the averaging + mode will be used (`tiles=[Average Tiles]`). - NOTE: this value will be used to render `tiles=[use Tile VALUE]` when calling - the `fmt_use_acitt()` method. + NOTE: this value will be used to render `tiles=[use Tile VALUE]` when + calling the `fmt_use_acitt()` method. Parameters ---------- @@ -186,11 +186,11 @@ def reference_tile(self, value): def reference_timepoint(self, value): """Set the reference timepoint when using *Expert Grouping Options*. - Select the timepoint(s) to use for the operation, by default the averaging mode - will be used (`timepoints=[Average Timepoints]`). + Select the timepoint(s) to use for the operation, by default the + averaging mode will be used (`timepoints=[Average Timepoints]`). - NOTE: this value will be used to render `timepoints=[use Timepoint VALUE]` when - calling the `fmt_use_acitt()` method. + NOTE: this value will be used to render `timepoints=[use Timepoint + VALUE]` when calling the `fmt_use_acitt()` method. Parameters ---------- @@ -239,7 +239,7 @@ def process_channel(self, value, range_end=None): Parameters ---------- value : str, int, list of int or list of str - The channel(s) to use for processing, either a single value or a list. + The channel(s) to use for processing, a single value or a list. range_end : int, optional Contains the end of the range, by default None. @@ -259,13 +259,13 @@ def process_channel(self, value, range_end=None): def process_illumination(self, value, range_end=None): """Set the processing option for illuminations. - Update the illumination processing option and selection depending on input. - If the range_end is not None, it is considered as a range. + Update the illumination processing option and selection depending on + input. If the range_end is not None, it is considered as a range. Parameters ---------- value : str, int, list of int or list of str - The illumination(s) to use for processing, either a single value or a list. + The illumination(s) to use for processing, a single value or a list. range_end : int, optional Contains the end of the range, by default None. @@ -291,7 +291,7 @@ def process_tile(self, value, range_end=None): Parameters ---------- value : str, int, list of int or list of str - The tile(s) to use for processing, either a single value or a list. + The tile(s) to use for processing, a single value or a list. range_end : int, optional Contains the end of the range, by default None. @@ -317,7 +317,7 @@ def process_timepoint(self, value, range_end=None): Parameters ---------- value : str, int, list of int or list of str - The timepoint(s) to use for processing, either a single value or a list. + The timepoint(s) to use for processing, a single value or a list. range_end : int, optional Contains the end of the range, by default None. From 815795490e3a2fad63ae6e95ff39a0acc7630e53 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 17:46:32 +0100 Subject: [PATCH 582/678] List fixed sets of valid values As described in https://numpydoc.readthedocs.io/en/latest/format.html#parameters --- src/imcflibs/imagej/bdv.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index f31c78c4..e61c2d98 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -754,10 +754,12 @@ def get_processing_settings(dimension, selection, value, range_end): Parameters ---------- - dimension : str - "angle", "channel", "illumination", "tile" or "timepoint" - selection : str - "single", "multiple", or "range" + dimension : {`angle`, `channel`, `illumination`, `tile`, `timepoint`} + The dimension selection to use. + selection : {`single`, `multiple`, `range`} + The *selector* name ("processing mode"), used to derive how the + generated string needs to be assembled according to the given dimension + and value / range settings. value : str, int, list of int or list of str Contains the list of input dimensions, the first input dimension of a range or a single channel range_end : int or None From 2cf89998af8bd8a9fd0922c61315525bbe9e2253 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 17:46:54 +0100 Subject: [PATCH 583/678] Some more docstring reworks --- src/imcflibs/imagej/bdv.py | 53 +++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index e61c2d98..691b4830 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -683,8 +683,9 @@ def fmt_acitt_options(self): """Format Angle / Channel / Illumination / Tile / Timepoint options. Build a string providing the `multiple_angles`, `multiple_channels`, - `multiple_illuminations_directions`, `multiple_tiles` and `multiple_timepoints` options - that can be used in a BDV-related `IJ.run` call. + `multiple_illuminations_directions`, `multiple_tiles` and + `multiple_timepoints` options that can be used in a BDV-related `IJ.run` + call. Returns ------- @@ -708,14 +709,15 @@ def fmt_acitt_options(self): def check_processing_input(value, range_end): """Sanitize and clarifies the acitt input selection. - Check if the input is valid by checking the type and returning the expected output. + Validate the input by checking the type and returning the expected output. Parameters ---------- value : str, int, list of int or list of str - Contains the list of input dimensions, the first input dimension of a range or a single channel + 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 + Contains the end of the range if need be. Returns ------- @@ -747,10 +749,11 @@ def check_processing_input(value, range_end): def get_processing_settings(dimension, selection, value, range_end): - """Get the variables corresponding to the dimension selection and processing mode. + """Generate processing strings for selected dimension and processing mode. - Get the processing option and dimension selection string that corresponds - to the selected processing mode. + Generate the processing option and dimension selection strings that + correspond to the selected processing mode and the given dimension + selection. Parameters ---------- @@ -761,14 +764,16 @@ def get_processing_settings(dimension, selection, value, range_end): generated string needs to be assembled according to the given dimension and value / range settings. value : str, int, list of int or list of str - Contains the list of input dimensions, the first input dimension of a range or a single channel + The list of input dimensions, the first input dimension of a range or a + single dimension value in case `selection == "single"` (e.g. for + selecting a single channel). range_end : int or None - Contains the end of the range if need be + Contains the end of the range if need be. Returns ------- - list of str - processing options string, dimension selection string + tuple of str + processing_option, dimension_select """ if selection == "single": @@ -806,9 +811,9 @@ def get_processing_settings(dimension, selection, value, range_end): def backup_xml_files(source_directory, subfolder_name): """Create a backup of BDV-XML files inside a subfolder of `xml-backup`. - Copies all `.xml` and `.xml~` files to a subfolder with the given name inside a - folder called `xml-backup` in the source directory. Uses the `shutil.copy2()` - command, which will overwrite existing files. + Copies all `.xml` and `.xml~` files to a subfolder with the given name + inside a folder called `xml-backup` in the source directory. Uses the + `shutil.copy2()` command, which will overwrite existing files. Parameters ---------- @@ -854,8 +859,8 @@ def define_dataset_auto( with an extension. dataset_save_path : str Output path for the `.xml`. - bf_series_type : str - One of "Angles" or "Tiles", specifying how Bio-Formats interprets the series. + bf_series_type : {`Angles`,`Tiles`} + Defines how Bio-Formats interprets the series. timepoints_per_partition : int, optional Split the output dataset by timepoints. Use `0` for no split, resulting in a single HDF5 file containing all timepoints. By default `1`, @@ -959,8 +964,8 @@ def define_dataset_manual( source_directory : str Path to the folder containing the file(s). image_file_pattern : str - Regular expression corresponding to the names of your files and how to read the - different dimensions. + Regular expression corresponding to the names of your files and how to + read the different dimensions. dataset_organisation : str Organisation of the dataset and the dimensions to process. Allows for defining the range of interest of the different dimensions. @@ -1658,7 +1663,7 @@ def fuse_dataset_bdvp( ): """Export a BigDataViewer project using the BIOP Kheops exporter. - This function uses the BIOP Kheops exporter to convert a BigDataViewer project into a + Use the BIOP Kheops exporter to convert a BigDataViewer project into OME-TIFF files, with optional compression. Parameters @@ -1668,11 +1673,11 @@ def fuse_dataset_bdvp( 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.). + 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. + 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". From d4f5d9e9b693fed8e0e9fcafe228e4bda774b4a4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 18:45:48 +0100 Subject: [PATCH 584/678] Automatic formatting --- src/imcflibs/imagej/bdv.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 691b4830..387e5dc7 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -733,13 +733,9 @@ def check_processing_input(value, range_end): ) 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("Range start needs to be an int.") elif len(value) != 1: - raise ValueError( - "Invalid input type. Expected a single number for the range start" - ) + raise ValueError("Range start needs to be single number.") else: return "range" elif len(value) == 1: From b02198ef6b8cd46dfdaa2c1e76c51b8a6eeeb5c3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 18:46:27 +0100 Subject: [PATCH 585/678] Fix line lengths --- src/imcflibs/imagej/bdv.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 387e5dc7..9168bb92 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -915,7 +915,8 @@ def define_dataset_auto( + "bioformats_series_are?=" + bf_series_type + " " - + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " + + "move_tiles_to_grid_(per_angle)?=[" + + "Do not move Tiles to Grid (use Metadata if available)] " + "how_to_store_input_images=[" + resave + "] " @@ -1394,8 +1395,10 @@ def interest_points_registration( + processing_opts.fmt_acitt_options() + processing_opts.fmt_acitt_selectors() + "registration_algorithm=[Precise descriptor-based (translation invariant)] " - + "registration_over_time=[Match against one reference timepoint (no global optimization)] " - + "registration_in_between_views=[Only compare overlapping views (according to current transformations)] " + + "registration_over_time=[" + + "Match against one reference timepoint (no global optimization)] " + + "registration_in_between_views=[" + + "Only compare overlapping views (according to current transformations)] " + "interest_point_inclusion=[Compare all interest point of overlapping views] " + "interest_points=beads " + "group_tiles " @@ -1412,8 +1415,11 @@ def interest_points_registration( + "significance=3 " + "allowed_error_for_ransac=5 " + "ransac_iterations=Normal " - + "global_optimization_strategy=[Two-Round: Handle unconnected tiles, remove wrong links RELAXED (5.0x / 7.0px)] " - + "interestpoint_grouping=[Group interest points (simply combine all in one virtual view)] " + + "global_optimization_strategy=[" + + "Two-Round: Handle unconnected tiles, " + + "remove wrong links RELAXED (5.0x / 7.0px)] " + + "interestpoint_grouping=[" + + "Group interest points (simply combine all in one virtual view)] " + "interest=5" ) From 1c33512e4e8f8a43c551fc304ae23a5edf875c21 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 22:46:37 +0100 Subject: [PATCH 586/678] Simplify validation and exception code logic --- src/imcflibs/imagej/bdv.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index 9168bb92..ba32b662 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -561,14 +561,9 @@ def check_definition_option(self, value): ------- dict(str, str): dictionary containing the correct string definition. """ - if value not in [ - "single", - "multi_single", - "multi_multi", - ]: - raise ValueError( - "Value must be one of single, multi_multi or multi_single" - ) + valid = ["single", "multi_single", "multi_multi"] + if value not in valid: + raise ValueError("Value must be one of: %s" % valid) return { "single": SINGLE_FILE, @@ -591,12 +586,14 @@ def check_definition_option_ang_ill(self, value): ------- dict(str, str): dictionary containing the correct string definition. """ - if value not in [ - "single", - "multi_multi", - ]: + valid = ["single", "multi_multi"] + if value not in valid: raise ValueError( - "Value must be one of single, multi_multi. Support for multi_single is not available for angles and illuminations" + ( + "Value must be one of: %s. Support for 'multi_single' is " + "not available for angles and illuminations." + ) + % valid ) return { From 7a11785b08ac56c1378747a23d940f462f15d651 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 22:48:47 +0100 Subject: [PATCH 587/678] Automatic formatting --- src/imcflibs/imagej/bdv.py | 131 ++++++++++--------------------------- 1 file changed, 34 insertions(+), 97 deletions(-) diff --git a/src/imcflibs/imagej/bdv.py b/src/imcflibs/imagej/bdv.py index ba32b662..5cc7b412 100644 --- a/src/imcflibs/imagej/bdv.py +++ b/src/imcflibs/imagej/bdv.py @@ -38,6 +38,7 @@ MULTI_MULTI_FILE = "[YES (one file per %s)]" """@private""" + class ProcessingOptions(object): """Helper to store processing options and generate parameter strings. @@ -140,9 +141,7 @@ 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*. @@ -158,9 +157,7 @@ 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 - ) + self._use_illumination = "illuminations=[use Illumination %s]" % value log.debug( "New reference illumination setting: %s", self._use_illumination, @@ -198,9 +195,7 @@ 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 @@ -422,22 +417,16 @@ 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("Invalid 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): @@ -457,16 +446,12 @@ 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): @@ -484,9 +469,7 @@ 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): @@ -501,16 +484,10 @@ 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( @@ -543,9 +520,7 @@ 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" @@ -611,9 +586,7 @@ 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. @@ -639,9 +612,7 @@ 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, @@ -657,9 +628,7 @@ 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. @@ -691,15 +660,12 @@ 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 + " " @@ -725,9 +691,7 @@ 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, all values must be of the same type.") if type(range_end) is int: if type(value[0]) is not int: raise TypeError("Range start needs to be an int.") @@ -771,13 +735,7 @@ 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 @@ -820,9 +778,7 @@ 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) @@ -882,9 +838,7 @@ 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 = "" @@ -1051,9 +1005,7 @@ 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: @@ -1143,13 +1095,10 @@ 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 = "" @@ -1173,9 +1122,7 @@ 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( @@ -1287,9 +1234,7 @@ 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") @@ -1469,12 +1414,8 @@ 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": @@ -1482,13 +1423,9 @@ 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] " From 1ea92fd6e95704914825584d00fa95e0a2a64ea4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 25 Mar 2025 23:21:16 +0100 Subject: [PATCH 588/678] Update test to reflect recent modifications --- tests/bdv/test_definitionoptions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/bdv/test_definitionoptions.py b/tests/bdv/test_definitionoptions.py index c8875d2d..74d4ce99 100644 --- a/tests/bdv/test_definitionoptions.py +++ b/tests/bdv/test_definitionoptions.py @@ -28,9 +28,9 @@ 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. Support for multi_single is not available for angles and illuminations" + assert str(excinfo.value) == ( + "Value must be one of: ['single', 'multi_multi']. Support for " + "'multi_single' is not available for angles and illuminations." ) From 35ee4e85efa072e608f5ac1610b7fa3e6a52c0de Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 11:42:19 +0100 Subject: [PATCH 589/678] Auto formatting --- src/imcflibs/imagej/misc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 0c116c69..0419b098 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -173,7 +173,9 @@ def find_focus(imp): return focused_slice -def send_notification_email(job_name, recipient, filename, total_execution_time, subject = "", message=""): +def send_notification_email( + job_name, recipient, filename, total_execution_time, subject="", message="" +): """Send an automated email notification with optional details of the processed job. This function retrieves the sender email and SMTP server settings from From 5ccdb79a3b4ee0de0d4537e8538df2730d09d73d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 14:36:41 +0100 Subject: [PATCH 590/678] =?UTF-8?q?Imperative=20mood=20FTW=20=F0=9F=97=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/imcflibs/imagej/misc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 2ed51dbb..e40f9631 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -188,10 +188,10 @@ def find_focus(imp): def send_notification_email( job_name, recipient, filename, total_execution_time, subject="", message="" ): - """Send an automated email notification with optional details of the processed job. + """Send an email notification with optional details of the processed job. - This function retrieves the sender email and SMTP server settings from - ImageJ's preferences and uses them to send an email notification with job details. + Retrieve the sender email and SMTP server settings from ImageJ's preferences + and use them to send an email notification with job details. Parameters ---------- From 378cf4b46c5a81c4a6a132681a35c67975886684 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 15:37:28 +0100 Subject: [PATCH 591/678] Be a bit more verbose in messages --- src/imcflibs/imagej/misc.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index e40f9631..fd74f000 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -224,15 +224,17 @@ def send_notification_email( # Ensure the sender and server are configured from Prefs if not sender: - log.info("Sender email is not configured. Please check IJ_Prefs.txt.") + log.info("[.imcf.sender_email] is not configured in '~/.imagej/IJ_Prefs.txt'.") return if not server: - log.info("SMTP server is not configured. Please check IJ_Prefs.txt.") + log.info("[.imcf.smtpserver] is not configured in '~/.imagej/IJ_Prefs.txt'.") return + log.debug("Using SMTP server [%s].", server) + # Ensure the recipient is provided if not recipient.strip(): - log.info("Recipient email is required.") + log.info("Recipient email is required, not sending email notification.") return # Form the email subject and body @@ -261,7 +263,7 @@ def send_notification_email( try: smtpObj = smtplib.SMTP(server) smtpObj.sendmail(sender, recipient, message) - log.debug("Successfully sent email") + log.debug("Successfully sent email to <%s>.", recipient) except smtplib.SMTPException as err: log.warning("Error: Unable to send email: %s", err) return From cdf7ddf8237a7475fa66a0bb1d9979fa15863bfb Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 15:39:59 +0100 Subject: [PATCH 592/678] Add note on Prefs.get() call vs. dot prefix --- src/imcflibs/imagej/misc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index fd74f000..83ba6fbf 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -219,6 +219,8 @@ def send_notification_email( """ # Retrieve sender email and SMTP server from preferences + # NOTE: the leading dot "." has to be omitted in the `Prefs.get()` call, + # despite being present in the `IJ_Prefs.txt` file! sender = prefs.Prefs.get("imcf.sender_email", "").strip() server = prefs.Prefs.get("imcf.smtpserver", "").strip() From e14bbb332ca42248d25249f5da8ae7e7b61a8a9c Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 15:40:12 +0100 Subject: [PATCH 593/678] Minor docstring modification --- src/imcflibs/imagej/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 83ba6fbf..7b9d2630 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -211,8 +211,8 @@ def send_notification_email( Notes ----- - The function requires two preferences to be set in `~/.imagej/IJ_Prefs.txt`: - `imcf.sender_email` (the sender's email address) and - `imcf.smtpserver` (the SMTP server address). + - `.imcf.sender_email`: the sender's email address + - `.imcf.smtpserver`: the SMTP server address - If these preferences are not set or if required parameters are missing, the function logs a message and exits without sending an email. - In case of an SMTP error, the function logs a warning. From 20275588ab7ec6e7c3c1659df6419067dde3eea2 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 15:45:19 +0100 Subject: [PATCH 594/678] Add test instructions for send_notification_email --- .../send-notification-email.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/interactive-imagej/send-notification-email.md diff --git a/tests/interactive-imagej/send-notification-email.md b/tests/interactive-imagej/send-notification-email.md new file mode 100644 index 00000000..0ad05ccb --- /dev/null +++ b/tests/interactive-imagej/send-notification-email.md @@ -0,0 +1,27 @@ +# Test the send_notification_email function + +```Python +from imcflibs.imagej.misc import send_notification_email + +from imcflibs.log import LOG as log +from imcflibs.log import enable_console_logging +from imcflibs.log import set_loglevel + + +enable_console_logging() +set_loglevel(2) + + +# see if logging works: +log.warn("warn") +log.debug("DEBUG") + +send_notification_email( + job_name="my job", + recipient="nikolaus.ehrenfeuchter@unibas.ch", + filename="magic-segmentation.py", + total_execution_time="5 years", +) + +log.info("DONE") +``` From ef8e531d774b1013ff0c76931e9e89ef0980178d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 16:06:04 +0100 Subject: [PATCH 595/678] Rename workflow, add badge --- .github/workflows/lint.yml | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 88871c75..6a7fd051 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: ๐Ÿ”Ž Lint code โšก +name: ๐Ÿ”Ž Code Linting โšก on: push: diff --git a/README.md b/README.md index 449e4024..db18ef35 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ # IMCFlibs ๐Ÿ โ˜• ๐Ÿ”ฉ ๐Ÿ”ง ๐Ÿช› [![Build Status](https://github.com/imcf/python-imcflibs/actions/workflows/build.yml/badge.svg)][build] +[![Linting โšก](https://github.com/imcf/python-imcflibs/actions/workflows/lint.yml/badge.svg)](https://github.com/imcf/python-imcflibs/actions/workflows/lint.yml) [![DOI](https://zenodo.org/badge/156891364.svg)][doi] This package contains a diverse collection of Python functions dealing with From 1c93eedf37bb19ab95200b3a1fef4434e58fa549 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 17:37:54 +0100 Subject: [PATCH 596/678] Update dependency required for apidocs generation --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50832442..32de1840 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ version = "0.0.0" # - or: python = ">=3.10" [tool.poetry.dependencies] -imcf-fiji-mocks = ">=0.8.0.a0" +imcf-fiji-mocks = ">=0.9.0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From 17cc9d460acf7931e7530c3533751297b7f1e67d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 17:47:25 +0100 Subject: [PATCH 597/678] Automatic formatting --- DEVELOPMENT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 9678b746..d80d560f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -30,7 +30,7 @@ git push origin $RELEASE_TAG Building and deploying the package can be greatly simplified using "tasks" in [Visual Studio Code][www_vscode]. By adding the following settings to the `.vscode/tasks.json` file, you can simply press `Ctrl+Shift+B` in VS Code and -select the *deploy* task for running Maven and have the resulting JAR file being +select the _deploy_ task for running Maven and have the resulting JAR file being placed in `/opt/fiji-packaging/Fiji.app/jars/` (adjust to your path as necessary): @@ -67,7 +67,7 @@ necessary): ## Linting Python 2.7 with VS Code For being able to lint the old Python code properly, you'll need to set up an -appropriate *virtualenv* with `pylint` being installed. +appropriate _virtualenv_ with `pylint` being installed. Using [`fish`][www_fish] and [virtualfish][www_vf], this can be done as follows: From b6e594b47364b8e13e97b5243506179f62fdb2d4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 26 Mar 2025 17:43:12 +0100 Subject: [PATCH 598/678] Instructions for creating pre-releases --- DEVELOPMENT.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d80d560f..76eb3759 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -14,17 +14,30 @@ cd - RELEASE_SCRIPT="$BASE_DIR/scijava-scripts/release-version.sh" -$RELEASE_SCRIPT --skip-push --skip-gpg --skip-license-update +############## ONLY FOR PRE-RELEASES ############## +PRE_RELEASE="1.5.0.a17" # <-- adjust this to the desired version +EXTRA_FLAGS="--skip-branch-check --skip-version-check $PRE_RELEASE" +############## ONLY FOR PRE-RELEASES ############## + +$RELEASE_SCRIPT --skip-push --skip-gpg --skip-license-update $EXTRA_FLAGS ``` -**IMPORTANT**: after the release has been built, the corresponding tag needs to -be pushed to github, e.g. like this: +**IMPORTANT 1**: after the release has been built, the corresponding tag needs +to be pushed to github, e.g. like this: ```bash RELEASE_TAG=$(git tag -l "python-imcflibs-*" | tail -n 1) git push origin $RELEASE_TAG ``` +**IMPORTANT 2**: in case a **pre-releaes** was created, the last commit needs to +be discarded as the _release-script_ places a wrong version / snapshot +combination in the `pom.xml`: + +```bash +git reset --hard HEAD~1 +``` + ## Build & Deploy with Maven using VS Code Building and deploying the package can be greatly simplified using "tasks" in From 5250a33b00c99484e264e2a78823b0625be64f80 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 27 Mar 2025 14:02:43 +0100 Subject: [PATCH 599/678] Update change log describing additions to misc --- CHANGELOG.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a51201ec..0051efda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,21 +4,28 @@ ## 1.5.0 -FIXME: complete description for `send_mail` configuration below! +As this is a major release, not all changes and functions are listed below. For detailed information, please refer to the updated API documentation. ### Added * Various additions to `imcflibs.imagej.misc`: - * `imcflibs.imagej.misc.send_mail` to send notification emails to users, e.g. - upon completion of long running scripts (configurable via user preferences). + * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon completion of long-running scripts. + * Sends a mail with job details, such as recipient, file name, execution time & an optional message. + * To enable email notifications, the following preferences must be set in `~/.imagej/IJ_Prefs.txt`: + * .imcf.sender_email: sender's email address. + * .imcf.smtpserver: the SMTP server used for sending emails. + * If the sender email or SMTP server is not configured, method logs a message and exits. * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and various suffixes from an ImagePlus. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. * `imcflibs.imagej.misc.close_images` for closing selected image windows. - * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that - a selected AutoThreshold method would be using. + * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. + *`imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered dictionary to a CSV file. * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to get a label image (2D/3D). +* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. +*`imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `misc.locate_latest_imaris` to find the path to the Imaris installation. + * New `imcflibs.imagej.objects3d` submodule, providing: * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn an Objects3DPopulation into an ImagePlus (2D/3D). @@ -53,6 +60,17 @@ FIXME: complete description for `send_mail` configuration below! * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Dataset" command. +* New `imcflibs.imagej.trackmate` submodule to provide helper functions to interface with Trackmate: + * Multiple functions to set up Trackmate settings with different detectors, such as `cellpose`, `StarDist` or a `sparseLAP tracker`. + * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. + * `imcflibs.imagej.trackmate.track_filtering` to create settings to filter detected tracks based upon optional distances, such as maximum linking, gap closing, track splitting & merging and maximum frame gap. + * `imcflibs.imagej.trackmate.run_trackmate` to run Fiji's Trackmate plugin on an open ImagePlus with given settings, which can be set up with available methods in the `imcflibs.imagej.trackmate` submodule. The method then returns a label image. + +* New `imcflibs.imagej.omerotools` submodule, providing helper functions to connect to OMERO using user credentials, fetch and upload an image, retrieve a dataset, or save ROIs to OMERO. + +* `imcflibs.pathtools.create_directory` to create a new directory at the specified path. + + ## 1.4.0 ### Added From 0d02cafe8e1cc32da1ba69cbcb5569bcc9987279 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 27 Mar 2025 15:19:09 +0100 Subject: [PATCH 600/678] Move `pad_number()` to `strtools` --- src/imcflibs/imagej/misc.py | 25 ------------------------- src/imcflibs/strtools.py | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 7b9d2630..346e1f9f 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -627,31 +627,6 @@ def save_image_in_format(imp, format, out_dir, series, pad_number, split_channel current_imp.close() -def pad_number(index, pad_length=2): - """Pad a number with leading zeros to a specified length. - - Parameters - ---------- - index : int or str - The number to be padded - pad_length : int, optional - The total length of the resulting string after padding, by default 2 - - Returns - ------- - str - The padded number as a string - - Examples - -------- - >>> pad_number(7) - '07' - >>> pad_number(42, 4) - '0042' - """ - return str(index).zfill(pad_length) - - def locate_latest_imaris(paths_to_check=None): """Find paths to latest installed Imaris or ImarisFileConverter version. diff --git a/src/imcflibs/strtools.py b/src/imcflibs/strtools.py index 10e731a6..d522c460 100644 --- a/src/imcflibs/strtools.py +++ b/src/imcflibs/strtools.py @@ -150,3 +150,28 @@ def alphanum_key(key): return [convert(c) for c in re.split("([0-9]+)", key)] return sorted(data, key=alphanum_key) + + +def pad_number(index, pad_length=2): + """Pad a number with leading zeros to a specified length. + + Parameters + ---------- + index : int or str + The number to be padded + pad_length : int, optional + The total length of the resulting string after padding, by default 2 + + Returns + ------- + str + The padded number as a string + + Examples + -------- + >>> pad_number(7) + '07' + >>> pad_number(42, 4) + '0042' + """ + return str(index).zfill(pad_length) From 5e8d99d165b63f10ef47cc0c2411b7ff13ae3d66 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 27 Mar 2025 15:19:24 +0100 Subject: [PATCH 601/678] Format using Ruff --- src/imcflibs/imagej/misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 346e1f9f..5496e7e3 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -123,7 +123,7 @@ def calculate_mean_and_stdv(values_list, round_decimals=0): mean = round(sum(filtered_list) / len(filtered_list), round_decimals) variance = sum((x - mean) ** 2 for x in filtered_list) / len(filtered_list) - std_dev = round(variance ** 0.5, round_decimals) + std_dev = round(variance**0.5, round_decimals) return mean, std_dev From 7b79ffc9d584eadefc3b0a1513ef82ae43e2522d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 27 Mar 2025 15:29:31 +0100 Subject: [PATCH 602/678] =?UTF-8?q?Update=20poetry.lock=20=F0=9F=8E=AD?= =?UTF-8?q?=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index b0ee2a39..3005414a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -105,13 +105,13 @@ test = ["pytest (>=6)"] [[package]] name = "imcf-fiji-mocks" -version = "0.8.0a0" +version = "0.9.0" description = "Mocks collection for Fiji-Python. Zero functional code." optional = false python-versions = ">=2.7" files = [ - {file = "imcf_fiji_mocks-0.8.0a0-py2.py3-none-any.whl", hash = "sha256:d4841b32725a2b81790e1051da264bfb72b7e275c23d87a04053462420184f78"}, - {file = "imcf_fiji_mocks-0.8.0a0.tar.gz", hash = "sha256:d00ca538bf49dde5386d148a7b71c68cbee0397edc6ce4ce2547f7f26c803478"}, + {file = "imcf_fiji_mocks-0.9.0-py2.py3-none-any.whl", hash = "sha256:e911436cb922ee3296f6ea6ccb515d9712e6456ea887670575cd8a53416444b1"}, + {file = "imcf_fiji_mocks-0.9.0.tar.gz", hash = "sha256:c21ebb51128d315d7bfe56bfd09c8b51590772f9f0afeceba7b1272a92365a48"}, ] [[package]] @@ -271,4 +271,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "11318171d20040fce90144782e5dced220d59595cd672cee1037d5f386648eff" +content-hash = "a7683e17cd18448063dda6b81b8a75c6873a4be3f3c0326e53bfb887a2763dbb" From 26ea037b67617b4d30df3294a40b235701edbe4a Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 27 Mar 2025 15:58:50 +0100 Subject: [PATCH 603/678] Rename function for background subtraction to clarify method --- src/imcflibs/imagej/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index c50e9c5f..0e3d2224 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -66,7 +66,7 @@ def apply_filter(imp, filter_method, filter_radius, do_3d=False): return imageplus -def apply_background_subtraction(imp, rolling_ball_radius, do_3d=False): +def apply_rollingball_bg_subtraction(imp, rolling_ball_radius, do_3d=False): """Perform background subtraction using a rolling ball method. Parameters From cef4203d6e02876de9e8eb57ccf4176cf968adac Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 27 Mar 2025 16:10:48 +0100 Subject: [PATCH 604/678] Update documentation for OMERO tools requirements Clarify that both `simple-omero-client` and `omero-insight` JARs are required for functionality. Add links for better reference. --- src/imcflibs/imagej/omerotools.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 2e2c4325..a7143947 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -3,9 +3,16 @@ Contains helpers to parse URLs and / or OMERO image IDs, connect to OMERO and fetch images from the server. -Requires the [`simple-omero-client`][simple-omero-client] JAR to be installed. +Requires both the [`simple-omero-client`][simple-omero-client] and the +[`omero-insight`][omero-insight] JARs to be installed. -[simple-omero-client]: https://github.com/GReD-Clermont/simple-omero-client +Most of the functions will use the [`simple-omero-client`][simple-omero-client] +to interact with the OMERO server. However, there are still some that +requires the [`omero-insight`][omero-insight] plugin to read metadata. + +[simple-omero-client]: +https://github.com/GReD-Clermont/simple-omero-client +[omero]: https://github.com/ome/omero-insight """ from fr.igred.omero import Client From 5cba0260909da1ae29c04f84e4ddc7bd8fa7e0f0 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 27 Mar 2025 16:14:30 +0100 Subject: [PATCH 605/678] Fix formatting of references in omerotools.py documentation --- src/imcflibs/imagej/omerotools.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index a7143947..1534f137 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -10,9 +10,8 @@ to interact with the OMERO server. However, there are still some that requires the [`omero-insight`][omero-insight] plugin to read metadata. -[simple-omero-client]: -https://github.com/GReD-Clermont/simple-omero-client -[omero]: https://github.com/ome/omero-insight +[simple-omero-client]: https://github.com/GReD-Clermont/simple-omero-client +[omero-insight]: https://github.com/ome/omero-insight """ from fr.igred.omero import Client From 4b0b4386d4ae14202d172a7ab29520c39c924333 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 27 Mar 2025 16:22:04 +0100 Subject: [PATCH 606/678] Add notes to calculate_mean_and_stdv for empty input handling Clarify the behavior of the function when the input list is empty or contains only None values, specifying that it returns (0, 0). --- src/imcflibs/imagej/misc.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 5496e7e3..e864534e 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -115,7 +115,14 @@ def calculate_mean_and_stdv(values_list, round_decimals=0): ------- tuple of (float, float) Mean and standard deviation of the input list. + + Notes + ----- + Returns (0, 0) when: + - The input list is empty + - After filtering out None values, no elements remain """ + filtered_list = [x for x in values_list if x is not None] if not filtered_list: From 169613e7103e21d587d98f0ceee89a9526ef61e8 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Thu, 27 Mar 2025 16:25:53 +0100 Subject: [PATCH 607/678] Fix indentation of function --- src/imcflibs/imagej/omerotools.py | 111 +++++++++++++++--------------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index 2e2c4325..7810357a 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -226,68 +226,67 @@ def find_dataset(client, dataset_id): # Fetch the dataset from the OMERO server using the provided dataset ID return client.getDataset(Long(dataset_id)) - def get_acquisition_metadata(user_client, image_wpr): - """Get acquisition metadata from OMERO based on an image ID. - - Parameters - ---------- - user_client : fr.igred.omero.Client - Client used for login to OMERO - image_wpr : fr.igred.omero.repositor.ImageWrapper - Wrapper to the image for the metadata - - Returns - ------- - dict - - { - objective_magnification : float, - objective_na : float, - acquisition_date : str, - acquisition_date_number : str, - } - """ - ctx = user_client.getCtx() - instrument_data = ( - user_client.getGateway() - .getMetadataService(ctx) - .loadInstrument(image_wpr.asDataObject().getInstrumentId()) - ) - objective_data = instrument_data.copyObjective().get(0) - metadata = {} - metadata["objective_magnification"] = ( - objective_data.getNominalMagnification().getValue() - if objective_data.getNominalMagnification() is not None - else 0 - ) - metadata["objective_na"] = ( - objective_data.getLensNA().getValue() - if objective_data.getLensNA() is not None - else 0 - ) +def get_acquisition_metadata(user_client, image_wpr): + """Get acquisition metadata from OMERO based on an image ID. - if image_wpr.getAcquisitionDate() is None: - if image_wpr.asDataObject().getFormat() == "ZeissCZI": - field = "Information|Document|CreationDate" - date_field = get_info_from_original_metadata( - user_client, image_wpr, field - ) - metadata["acquisition_date"] = date_field.split("T")[0] - metadata["acquisition_date_number"] = int( - metadata["acquisition_date"].replace("-", "") - ) - else: - metadata["acquisition_date"] = "NA" - metadata["acquisition_date_number"] = 0 - else: - sdf = SimpleDateFormat("yyyy-MM-dd") - metadata["acquisition_date"] = sdf.format(image_wpr.getAcquisitionDate()) + Parameters + ---------- + user_client : fr.igred.omero.Client + Client used for login to OMERO + image_wpr : fr.igred.omero.repositor.ImageWrapper + Wrapper to the image for the metadata + + Returns + ------- + dict + + { + objective_magnification : float, + objective_na : float, + acquisition_date : str, + acquisition_date_number : str, + } + """ + ctx = user_client.getCtx() + instrument_data = ( + user_client.getGateway() + .getMetadataService(ctx) + .loadInstrument(image_wpr.asDataObject().getInstrumentId()) + ) + objective_data = instrument_data.copyObjective().get(0) + metadata = {} + + metadata["objective_magnification"] = ( + objective_data.getNominalMagnification().getValue() + if objective_data.getNominalMagnification() is not None + else 0 + ) + metadata["objective_na"] = ( + objective_data.getLensNA().getValue() + if objective_data.getLensNA() is not None + else 0 + ) + + if image_wpr.getAcquisitionDate() is None: + if image_wpr.asDataObject().getFormat() == "ZeissCZI": + field = "Information|Document|CreationDate" + date_field = get_info_from_original_metadata(user_client, image_wpr, field) + metadata["acquisition_date"] = date_field.split("T")[0] metadata["acquisition_date_number"] = int( metadata["acquisition_date"].replace("-", "") ) + else: + metadata["acquisition_date"] = "NA" + metadata["acquisition_date_number"] = 0 + else: + sdf = SimpleDateFormat("yyyy-MM-dd") + metadata["acquisition_date"] = sdf.format(image_wpr.getAcquisitionDate()) + metadata["acquisition_date_number"] = int( + metadata["acquisition_date"].replace("-", "") + ) - return metadata + return metadata def get_info_from_original_metadata(user_client, image_wpr, field): From 0c97f2a353efa72ab08fb4d7410be9c5ef1174eb Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Thu, 27 Mar 2025 16:32:31 +0100 Subject: [PATCH 608/678] Dot dot --- src/imcflibs/imagej/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index e864534e..94805c64 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -119,8 +119,8 @@ def calculate_mean_and_stdv(values_list, round_decimals=0): Notes ----- Returns (0, 0) when: - - The input list is empty - - After filtering out None values, no elements remain + - The input list is empty. + - After filtering out None values, no elements remain. """ filtered_list = [x for x in values_list if x is not None] From b9a22c1efcb03f774f361bb53249bf47cfe3fc4c Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 27 Mar 2025 16:34:54 +0100 Subject: [PATCH 609/678] Change method name and update options --- src/imcflibs/imagej/prefs.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/imcflibs/imagej/prefs.py b/src/imcflibs/imagej/prefs.py index b1882358..154eff56 100644 --- a/src/imcflibs/imagej/prefs.py +++ b/src/imcflibs/imagej/prefs.py @@ -20,22 +20,28 @@ def debug_mode(): return debug == "true" -def fix_ij_options(): - """Set up ImageJ default options. - - FIXME: Explain the rationale / idea! +def set_default_ij_options(): + """Configure ImageJ default options for consistency. + + Set the following options: + - Ensure ImageJ appearance settings are default values. + - Set foreground color to white and background to black. + - Set black background for binary images. + - Set default file saving format to .txt files. + - Ensure images are scaled appropriately when converting between different bit depths. """ - # disable inverting LUT - IJ.run("Appearance...", " menu=0 16-bit=Automatic") - # set foreground color to be white, background black + # Set all appearance settings to default values (untick all options) + IJ.run("Appearance...", " ") + + # Set foreground color to be white and background black IJ.run("Colors...", "foreground=white background=black selection=red") - # black BG for binary images and pad edges when eroding - IJ.run("Options...", "black pad") - # set saving format to .txt files + + # Set black background for binary images and set pad edges to false to prevent eroding from image edge + IJ.run("Options...", "black ") + + # Set default saving format to .txt files IJ.run("Input/Output...", "file=.txt save_column save_row") - # ============= DON'T MOVE UPWARDS ============= - # set "Black Background" in "Binary Options" - IJ.run("Options...", "black") - # scale when converting = checked + + # Scale when converting = checked IJ.run("Conversions...", "scale") From 7c3a89a750c36b0e1413c6deddc7c3e4ff6079e3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 28 Mar 2025 10:04:34 +0100 Subject: [PATCH 610/678] Attempt to fix docstring rendering in pdoc Refers to issue #61 --- src/imcflibs/imagej/misc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 94805c64..edd4509e 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -573,13 +573,13 @@ def save_image_in_format(imp, format, out_dir, series, pad_number, split_channel Save a multichannel image as OME-TIFF without splitting channels: >>> save_image_with_extension(imp, "OME-TIFF", "/output/path", 1, 3, False) - # resulting file: /output/path/image_title_series_001.ome.tif + ... # resulting file: /output/path/image_title_series_001.ome.tif Save with channel splitting: >>> save_image_with_extension(imp, "OME-TIFF", "/output/path", 1, 3, True) - # resulting files: /output/path/C1/image_title_series_001.ome.tif - # /output/path/C2/image_title_series_001.ome.tif + ... # resulting files: /output/path/C1/image_title_series_001.ome.tif + ... # /output/path/C2/image_title_series_001.ome.tif """ out_ext = {} From 69f0a60e19d91962428d830169137dcba2ba69b8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 28 Mar 2025 10:20:00 +0100 Subject: [PATCH 611/678] Automatic formatting --- CHANGELOG.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0051efda..ad2bb117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,22 +9,29 @@ As this is a major release, not all changes and functions are listed below. For ### Added * Various additions to `imcflibs.imagej.misc`: - * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon completion of long-running scripts. - * Sends a mail with job details, such as recipient, file name, execution time & an optional message. - * To enable email notifications, the following preferences must be set in `~/.imagej/IJ_Prefs.txt`: + * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon + completion of long-running scripts. + * Sends a mail with job details, such as recipient, file name, execution time & an + optional message. + * To enable email notifications, the following preferences must be set in + `~/.imagej/IJ_Prefs.txt`: * .imcf.sender_email: sender's email address. * .imcf.smtpserver: the SMTP server used for sending emails. - * If the sender email or SMTP server is not configured, method logs a message and exits. - * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and - various suffixes from an ImagePlus. + * If the sender email or SMTP server is not configured, method logs a message and + exits. + * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and various + suffixes from an ImagePlus. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. * `imcflibs.imagej.misc.close_images` for closing selected image windows. - * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. - *`imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered dictionary to a CSV file. -* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image - to get a label image (2D/3D). -* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. *`imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `misc.locate_latest_imaris` to find the path to the Imaris installation. + * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a + selected AutoThreshold method would be using. + *`imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered + dictionary to a CSV file. +* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to get a + label image (2D/3D). +* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a specified + format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. * New `imcflibs.imagej.objects3d` submodule, providing: * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn @@ -70,7 +77,6 @@ As this is a major release, not all changes and functions are listed below. For * `imcflibs.pathtools.create_directory` to create a new directory at the specified path. - ## 1.4.0 ### Added From c9fbd77831e02337a7c8a052bf7b8a0594e89322 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 28 Mar 2025 10:20:44 +0100 Subject: [PATCH 612/678] Add missing whitespace + FQ reference --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2bb117..9414511c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,6 @@ As this is a major release, not all changes and functions are listed below. For suffixes from an ImagePlus. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. * `imcflibs.imagej.misc.close_images` for closing selected image windows. -*`imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `misc.locate_latest_imaris` to find the path to the Imaris installation. * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. *`imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered @@ -32,6 +31,7 @@ As this is a major release, not all changes and functions are listed below. For label image (2D/3D). * `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. +* `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris installation. * New `imcflibs.imagej.objects3d` submodule, providing: * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn From 6d74e6feb8ea67cf04085ca56a169b24ed958aa9 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Fri, 28 Mar 2025 10:22:45 +0100 Subject: [PATCH 613/678] Another typo and missing whitespace --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9414511c..816a70ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,7 @@ As this is a major release, not all changes and functions are listed below. For * `imcflibs.imagej.misc.close_images` for closing selected image windows. * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. - *`imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered + * `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an ordered dictionary to a CSV file. * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to get a label image (2D/3D). From 4b73fcb3bbccdd3d24f2b9a428f7ecde1b9675e6 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 31 Mar 2025 17:19:56 +0200 Subject: [PATCH 614/678] Add info about changes for BDV --- CHANGELOG.md | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 816a70ff..5e7758ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,29 +9,22 @@ As this is a major release, not all changes and functions are listed below. For ### Added * Various additions to `imcflibs.imagej.misc`: - * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon - completion of long-running scripts. - * Sends a mail with job details, such as recipient, file name, execution time & an - optional message. - * To enable email notifications, the following preferences must be set in - `~/.imagej/IJ_Prefs.txt`: + * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon completion of long-running scripts. + * Sends a mail with job details, such as recipient, file name, execution time & an optional message. + * To enable email notifications, the following preferences must be set in `~/.imagej/IJ_Prefs.txt`: * .imcf.sender_email: sender's email address. * .imcf.smtpserver: the SMTP server used for sending emails. - * If the sender email or SMTP server is not configured, method logs a message and - exits. - * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and various - suffixes from an ImagePlus. + * If the sender email or SMTP server is not configured, method logs a message and exits. + * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and + various suffixes from an ImagePlus. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. * `imcflibs.imagej.misc.close_images` for closing selected image windows. - * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a - selected AutoThreshold method would be using. - * `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an ordered - dictionary to a CSV file. -* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to get a - label image (2D/3D). -* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a specified - format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. -* `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris installation. + * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. + *`imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered dictionary to a CSV file. +* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image + to get a label image (2D/3D). +* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. +*`imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris installation. * New `imcflibs.imagej.objects3d` submodule, providing: * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn @@ -44,13 +37,20 @@ As this is a major release, not all changes and functions are listed below. For population of 3D objects by intensity. * New `imcflibs.imagej.bdv` submodule, providing BigDataViewer related functions: - * `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files + * New classes: + * `ProcessingOptions` to store all options on how to process the dataset. + * `DefinitionOptions` to store all options on how to define the dataset. + * `imcflibs.imagej.bdv.check_processing_input` to sanitize and clarify + the acitt input selection. + * `imcflibs.imagej.bdv.get_processing_settings` to generate the strings + needed for the processing. + * `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files. * `imcflibs.imagej.bdv.define_dataset_auto` to run "Define Multi-View Dataset" using the "Auto-Loader" option. * `imcflibs.imagej.bdv.define_dataset_manual` to run "Define Multi-View Dataset" using the "Manual Loader" option. - * `imcflibs.imagej.bdv.resave_as_h5` to resave the xml dataset in a new format - (either all or single timepoints). + * `imcflibs.imagej.bdv.resave_as_h5` to resave the dataset in H5 to + make it compatible with BigDataViewer/BigStitcher. * `imcflibs.imagej.bdv.flip_axes` tocall BigStitcher's "Flip Axes" command. * `imcflibs.imagej.bdv.phase_correlation_pairwise_shifts_calculation` to calculate pairwise shifts using Phase Correlation. @@ -64,19 +64,18 @@ As this is a major release, not all changes and functions are listed below. For Dataset based on Interest Points" command. * `imcflibs.imagej.bdv.duplicate_transformations` for duplicating / propagating transformation parameters to other channels. - * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Dataset" + * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Multi-View Dataset" command. - * New `imcflibs.imagej.trackmate` submodule to provide helper functions to interface with Trackmate: * Multiple functions to set up Trackmate settings with different detectors, such as `cellpose`, `StarDist` or a `sparseLAP tracker`. * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. * `imcflibs.imagej.trackmate.track_filtering` to create settings to filter detected tracks based upon optional distances, such as maximum linking, gap closing, track splitting & merging and maximum frame gap. * `imcflibs.imagej.trackmate.run_trackmate` to run Fiji's Trackmate plugin on an open ImagePlus with given settings, which can be set up with available methods in the `imcflibs.imagej.trackmate` submodule. The method then returns a label image. - * New `imcflibs.imagej.omerotools` submodule, providing helper functions to connect to OMERO using user credentials, fetch and upload an image, retrieve a dataset, or save ROIs to OMERO. * `imcflibs.pathtools.create_directory` to create a new directory at the specified path. + ## 1.4.0 ### Added From 32ee7b8a28fd18e77cf57baebb45faa699b87d60 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 14:16:17 +0200 Subject: [PATCH 615/678] Add ImageMetadata class to store metadata --- src/imcflibs/imagej/bioformats.py | 147 ++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 758cc072..8b381e42 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -27,6 +27,153 @@ ) +class ImageMetadata(object): + """A class to store metadata information from an image. + + This class stores metadata information extracted from an image file, such as image dimensions, + pixel dimensions, and calibration units. It provides a method to convert the attributes to a + dictionary and a string representation of the object. + + Attributes + ---------- + unit_width : float or None + Physical width of a pixel in the given unit. + unit_height : float or None + Physical height of a pixel in the given unit. + unit_depth : float or None + Physical depth of a voxel in the given unit. + pixel_width : int or None + Width of the image in pixels. + pixel_height : int or None + Height of the image in pixels. + slice_count : int or None + Number of Z-slices in the image. + channel_count : int or None + Number of channels in the image. + timepoints_count : int or None + Number of timepoints in the image. + dimension_order : str or None + Order of dimensions (e.g., "XYZCT"). + pixel_type : str or None + Data type of the pixel values (e.g., "uint16"). + + Examples + -------- + >>> metadata = ImageMetadata( + ... unit_width=0.1, + ... unit_height=0.1 + ... ) + >>> print(metadata) + + """ + + def __init__( + self, + unit_width=None, + unit_height=None, + unit_depth=None, + pixel_width=None, + pixel_height=None, + slice_count=None, + channel_count=None, + timepoints_count=None, + dimension_order=None, + pixel_type=None, + ): + self.unit_width = unit_width + self.unit_height = unit_height + self.unit_depth = unit_depth + self.pixel_width = pixel_width + self.pixel_height = pixel_height + self.slice_count = slice_count + self.channel_count = channel_count + self.timepoints_count = timepoints_count + self.dimension_order = dimension_order + self.pixel_type = pixel_type + + def to_dict(self): + """Convert the object attributes to a dictionary. + + Returns + ------- + dict + A dictionary representation of the object attributes. + """ + return self.__dict__ + + def __repr__(self): + """Return a string representation of the object.""" + return "".format( + ", ".join("{}={}".format(k, v) for k, v in self.__dict__.items()) + ) + + +class StageMetadata: + """A class to store stage coordinates and calibration metadata for a set of images. + + Attributes + ---------- + dimensions : int + Number of dimensions (2D or 3D). + stage_coordinates_x : list of float + Absolute stage x-coordinates. + stage_coordinates_y : list of float + Absolute stage y-coordinates. + stage_coordinates_z : list of float + Absolute stage z-coordinates. + relative_coordinates_x : list of float + Relative stage x-coordinates in pixels. + relative_coordinates_y : list of float + Relative stage y-coordinates in pixels. + relative_coordinates_z : list of float + Relative stage z-coordinates in pixels. + image_calibration : list of float + Calibration values for x, y, and z in unit/px. + calibration_unit : str + Unit used for image calibration. + image_dimensions_czt : list of int + Number of images in dimensions (channels, z-slices, timepoints). + series_names : list of str + Names of all series in the image files. + max_size : list of float + Maximum physical size (x/y/z) across all files. + """ + + def __init__( + self, + dimensions=2, + stage_coordinates_x=None, + stage_coordinates_y=None, + stage_coordinates_z=None, + relative_coordinates_x=None, + relative_coordinates_y=None, + relative_coordinates_z=None, + image_calibration=None, + calibration_unit="unknown", + image_dimensions_czt=None, + series_names=None, + max_size=None, + ): + self.dimensions = dimensions + self.stage_coordinates_x = stage_coordinates_x or [] + self.stage_coordinates_y = stage_coordinates_y or [] + self.stage_coordinates_z = stage_coordinates_z or [] + self.relative_coordinates_x = relative_coordinates_x or [] + self.relative_coordinates_y = relative_coordinates_y or [] + self.relative_coordinates_z = relative_coordinates_z or [] + self.image_calibration = image_calibration or [1.0, 1.0, 1.0] + self.calibration_unit = calibration_unit or "unknown" + self.image_dimensions_czt = image_dimensions_czt or [1, 1, 1] + self.series_names = series_names or [] + self.max_size = max_size or [1.0, 1.0, 1.0] + + def __repr__(self): + """Return a string representation of the object.""" + return "".format( + self.dimensions, self.calibration_unit + ) + + def import_image( filename, color_mode="color", From a13dc5cbae4b2d58399909fe60b911f8a99a56b5 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 14:20:54 +0200 Subject: [PATCH 616/678] Replace dict returns with ImageMetadata class --- src/imcflibs/imagej/bioformats.py | 56 +++++++++---------------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 8b381e42..0111be4a 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -452,54 +452,30 @@ def get_metadata_from_file(path_to_image): Returns ------- - dict - A dictionary containing the following metadata: - - { - unit_width : float, # physical width of a pixel - unit_height : float, # physical height of a pixel - unit_depth : float, # physical depth of a voxel - pixel_width : int, # width of the image in pixels - pixel_height : int, # height of the image in pixels - slice_count : int, # number of Z-slices - channel_count : int, # number of channels - timepoints_count : int, # number of timepoints - dimension_order : str, # order of dimensions, e.g. "XYZCT" - pixel_type : str, # data type of the pixel values - } + ImageMetadata + An instance of `imcflibs.imagej.bioformats.ImageMetadata` containing the extracted metadata. """ + reader = ImageReader() ome_meta = MetadataTools.createOMEXMLMetadata() reader.setMetadataStore(ome_meta) reader.setId(str(path_to_image)) - phys_size_x = ome_meta.getPixelsPhysicalSizeX(0) - phys_size_y = ome_meta.getPixelsPhysicalSizeY(0) - phys_size_z = ome_meta.getPixelsPhysicalSizeZ(0) - pixel_size_x = ome_meta.getPixelsSizeX(0) - pixel_size_y = ome_meta.getPixelsSizeY(0) - pixel_size_z = ome_meta.getPixelsSizeZ(0) - channel_count = ome_meta.getPixelsSizeC(0) - timepoints_count = ome_meta.getPixelsSizeT(0) - dimension_order = ome_meta.getPixelsDimensionOrder(0) - pixel_type = ome_meta.getPixelsType(0) - - image_calibration = { - "unit_width": phys_size_x.value(), - "unit_height": phys_size_y.value(), - "unit_depth": phys_size_z.value(), - "pixel_width": pixel_size_x.getNumberValue(), - "pixel_height": pixel_size_y.getNumberValue(), - "slice_count": pixel_size_z.getNumberValue(), - "channel_count": channel_count.getNumberValue(), - "timepoints_count": timepoints_count.getNumberValue(), - "dimension_order": dimension_order, - "pixel_type": pixel_type, - } - + metadata = ImageMetadata( + unit_width=ome_meta.getPixelsPhysicalSizeX(0), + unit_height=ome_meta.getPixelsPhysicalSizeY(0), + unit_depth=ome_meta.getPixelsPhysicalSizeZ(0), + pixel_width=ome_meta.getPixelsSizeX(0), + pixel_height=ome_meta.getPixelsSizeY(0), + slice_count=ome_meta.getPixelsSizeZ(0), + channel_count=ome_meta.getPixelsSizeC(0), + timepoints_count=ome_meta.getPixelsSizeT(0), + dimension_order=ome_meta.getPixelsDimensionOrder(0), + pixel_type=ome_meta.getPixelsType(0), + ) reader.close() - return image_calibration + return metadata def get_stage_coords(source, filenames): From daa23c3ce2296aa5b4113eed594e2ec022ae3a93 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 14:21:35 +0200 Subject: [PATCH 617/678] Replace dict return with StageMetadata class --- src/imcflibs/imagej/bioformats.py | 220 ++++++++++++------------------ 1 file changed, 86 insertions(+), 134 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 0111be4a..1a31298a 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -486,44 +486,40 @@ def get_stage_coords(source, filenames): source : str Path to the images. filenames : list of str - List of images filenames. + List of image filenames. Returns ------- - dict - - { - dimensions : int, # number of dimensions (2D or 3D) - stage_coordinates_x : list, # absolute stage x-coordinated - stage_coordinates_y : list, # absolute stage y-coordinated - stage_coordinates_z : list, # absolute stage z-coordinated - relative_coordinates_x : list, # relative stage x-coordinates in px - relative_coordinates_y : list, # relative stage y-coordinates in px - relative_coordinates_z : list, # relative stage z-coordinates in px - image_calibration : list, # x,y,z image calibration in unit/px - calibration_unit : str, # image calibration unit - image_dimensions_czt : list, # number of images in dimensions c,z,t - series_names : list of str, # names of all series in the files - max_size : list of int, # max size (x/y/z) across all files - } + StageMetadata + An object containing extracted stage metadata. """ - - # open an array to store the abosolute stage coordinates from metadata + # Initialize lists to store stage coordinates and series names stage_coordinates_x = [] stage_coordinates_y = [] stage_coordinates_z = [] series_names = [] + # Intiialize default values + dimensions = 2 + image_calibration = [] + calibration_unit = "unknown" + image_dimensions_czt = [] + max_size = [] + + # Initialize max_size variables to track the maximums + max_phys_size_x = 0.0 + max_phys_size_y = 0.0 + max_phys_size_z = 0.0 + for counter, image in enumerate(filenames): - # parse metadata reader = ImageReader() reader.setFlattenedResolutions(False) - omeMeta = MetadataTools.createOMEXMLMetadata() - reader.setMetadataStore(omeMeta) + ome_meta = MetadataTools.createOMEXMLMetadata() + reader.setMetadataStore(ome_meta) reader.setId(source + str(image)) series_count = reader.getSeriesCount() - # get hyperstack dimensions from the first image + # Process only the first image to get values not dependent on series if counter == 0: frame_size_x = reader.getSizeX() frame_size_y = reader.getSizeY() @@ -531,124 +527,80 @@ def get_stage_coords(source, filenames): frame_size_c = reader.getSizeC() frame_size_t = reader.getSizeT() - # note the dimensions - if frame_size_z == 1: - dimensions = 2 - if frame_size_z > 1: - dimensions = 3 - - # get the physical calibration for the first image series - physSizeX = omeMeta.getPixelsPhysicalSizeX(0) - physSizeY = omeMeta.getPixelsPhysicalSizeY(0) - physSizeZ = omeMeta.getPixelsPhysicalSizeZ(0) - - # workaround to get the z-interval if physSizeZ.value() returns None. - z_interval = 1 - if physSizeZ is not None: - z_interval = physSizeZ.value() - - if frame_size_z > 1 and physSizeZ is None: - log.debug("no z calibration found, trying to recover") - first_plane = omeMeta.getPlanePositionZ(0, 0) - next_plane_imagenumber = frame_size_c + frame_size_t - 1 - second_plane = omeMeta.getPlanePositionZ(0, next_plane_imagenumber) - z_interval = abs(abs(first_plane.value()) - abs(second_plane.value())) - log.debug("z-interval seems to be: " + str(z_interval)) - - # create an image calibration - image_calibration = [ - physSizeX.value(), - physSizeY.value(), - z_interval, - ] - calibration_unit = physSizeX.unit().getSymbol() - image_dimensions_czt = [ - frame_size_c, - frame_size_z, - frame_size_t, - ] + dimensions = 2 if frame_size_z == 1 else 3 + + # Retrieve physical size coordinates safely + phys_size_x = getattr(ome_meta.getPixelsPhysicalSizeX(0), "value", lambda: 1.0)() + phys_size_y = getattr(ome_meta.getPixelsPhysicalSizeY(0), "value", lambda: 1.0)() + phys_size_z = getattr(ome_meta.getPixelsPhysicalSizeZ(0), "value", lambda: None)() + + z_interval = phys_size_z if phys_size_z is not None else 1.0 + + # Handle missing Z calibration + if phys_size_z is None and frame_size_z > 1: + first_plane = getattr(ome_meta.getPlanePositionZ(0, 0), "value", lambda: 0)() + next_plane_index = frame_size_c + frame_size_t - 1 + second_plane = getattr(ome_meta.getPlanePositionZ(0, next_plane_index), "value", lambda: 0)() + z_interval = abs(first_plane - second_plane) + + image_calibration = [phys_size_x, phys_size_y, z_interval] + calibration_unit = ( + getattr(ome_meta.getPixelsPhysicalSizeX(0).unit(), "getSymbol", lambda: "unknown")() + if phys_size_x + else "unknown" + ) + image_dimensions_czt = [frame_size_c, frame_size_z, frame_size_t] reader.close() for series in range(series_count): - if omeMeta.getImageName(series) == "macro image": + if ome_meta.getImageName(series) == "macro image": continue if series_count > 1 and not str(image).endswith(".vsi"): - series_names.append(omeMeta.getImageName(series)) + series_names.append(ome_meta.getImageName(series)) else: series_names.append(str(image)) - # get the plane position in calibrated units - current_position_x = omeMeta.getPlanePositionX(series, 0) - current_position_y = omeMeta.getPlanePositionY(series, 0) - current_position_z = omeMeta.getPlanePositionZ(series, 0) - - physSizeX_max = ( - physSizeX.value() - if physSizeX.value() >= omeMeta.getPixelsPhysicalSizeX(series).value() - else omeMeta.getPixelsPhysicalSizeX(series).value() - ) - physSizeY_max = ( - physSizeY.value() - if physSizeY.value() >= omeMeta.getPixelsPhysicalSizeY(series).value() - else omeMeta.getPixelsPhysicalSizeY(series).value() - ) - if omeMeta.getPixelsPhysicalSizeZ(series): - physSizeZ_max = ( - physSizeZ.value() - if physSizeZ.value() - >= omeMeta.getPixelsPhysicalSizeZ(series).value() - else omeMeta.getPixelsPhysicalSizeZ(series).value() - ) - - else: - physSizeZ_max = 1.0 - # get the absolute stage positions and store them - pos_x = current_position_x.value() - pos_y = current_position_y.value() - - if current_position_z is None: - log.debug("the z-position is missing in the ome-xml metadata.") - pos_z = 1.0 - else: - pos_z = current_position_z.value() - - stage_coordinates_x.append(pos_x) - stage_coordinates_y.append(pos_y) - stage_coordinates_z.append(pos_z) - - max_size = [physSizeX_max, physSizeY_max, physSizeZ_max] - - # calculate the store the relative stage movements in px (for the grid/collection stitcher) - relative_coordinates_x_px = [] - relative_coordinates_y_px = [] - relative_coordinates_z_px = [] - - for i in range(len(stage_coordinates_x)): - rel_pos_x = ( - stage_coordinates_x[i] - stage_coordinates_x[0] - ) / physSizeX.value() - rel_pos_y = ( - stage_coordinates_y[i] - stage_coordinates_y[0] - ) / physSizeY.value() - rel_pos_z = (stage_coordinates_z[i] - stage_coordinates_z[0]) / z_interval - - relative_coordinates_x_px.append(rel_pos_x) - relative_coordinates_y_px.append(rel_pos_y) - relative_coordinates_z_px.append(rel_pos_z) - - return { - "dimensions": dimensions, - "stage_coordinates_x": stage_coordinates_x, - "stage_coordinates_y": stage_coordinates_y, - "stage_coordinates_z": stage_coordinates_z, - "relative_coordinates_x": relative_coordinates_x_px, - "relative_coordinates_y": relative_coordinates_y_px, - "relative_coordinates_z": relative_coordinates_z_px, - "image_calibration": image_calibration, - "calibration_unit": calibration_unit, - "image_dimensions_czt": image_dimensions_czt, - "series_names": series_names, - "max_size": max_size, - } + current_position_x = getattr(ome_meta.getPlanePositionX(series, 0), "value", lambda: 0)() + current_position_y = getattr(ome_meta.getPlanePositionY(series, 0), "value", lambda: 0)() + current_position_z = getattr(ome_meta.getPlanePositionZ(series, 0), "value", lambda: 1.0)() + + max_phys_size_x = max(max_phys_size_x, ome_meta.getPixelsPhysicalSizeX(series).value()) + max_phys_size_y = max(max_phys_size_y, ome_meta.getPixelsPhysicalSizeY(series).value()) + max_phys_size_z = max(max_phys_size_z, ome_meta.getPixelsPhysicalSizeZ(series).value() + if phys_size_z else z_interval) + + stage_coordinates_x.append(current_position_x) + stage_coordinates_y.append(current_position_y) + stage_coordinates_z.append(current_position_z) + + max_size = [max_phys_size_x, max_phys_size_y, max_phys_size_z] + + relative_coordinates_x_px = [ + (stage_coordinates_x[i] - stage_coordinates_x[0]) / (phys_size_x or 1.0) + for i in range(len(stage_coordinates_x)) + ] + relative_coordinates_y_px = [ + (stage_coordinates_y[i] - stage_coordinates_y[0]) / (phys_size_y or 1.0) + for i in range(len(stage_coordinates_y)) + ] + relative_coordinates_z_px = [ + (stage_coordinates_z[i] - stage_coordinates_z[0]) / (z_interval or 1.0) + for i in range(len(stage_coordinates_z)) + ] + + return StageMetadata( + dimensions=dimensions, + stage_coordinates_x=stage_coordinates_x, + stage_coordinates_y=stage_coordinates_y, + stage_coordinates_z=stage_coordinates_z, + relative_coordinates_x=relative_coordinates_x_px, + relative_coordinates_y=relative_coordinates_y_px, + relative_coordinates_z=relative_coordinates_z_px, + image_calibration=image_calibration, + calibration_unit=calibration_unit, + image_dimensions_czt=image_dimensions_czt, + series_names=series_names, + max_size=max_size, + ) From f2908fe78c322111d847dad6924e6e35c1b24258 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 14:21:57 +0200 Subject: [PATCH 618/678] Add additions to imagej.bioformats --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 816a70ff..6df28b5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,11 @@ As this is a major release, not all changes and functions are listed below. For * `imcflibs.pathtools.create_directory` to create a new directory at the specified path. +* Additions to `imcflibs.imagej.bioformats`: + * `imcflibs.imagej.bioformats.export` to export an image to a given file. + * `imcflibs.imagej.bioformats.get_metadata_from_file` to extract various metadata from a given file using BioFormats. + * `imcflibs.imagej.bioformats.get_stage_coords`to get stage coordinates and calibration for one or more given images. + ## 1.4.0 ### Added From 14934a09ffc60b464e71b220ad1cbec46617a676 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 16:44:35 +0200 Subject: [PATCH 619/678] Add unit as attribute --- src/imcflibs/imagej/bioformats.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 1a31298a..77d952b0 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -72,6 +72,7 @@ def __init__( unit_width=None, unit_height=None, unit_depth=None, + unit=None, pixel_width=None, pixel_height=None, slice_count=None, @@ -83,6 +84,7 @@ def __init__( self.unit_width = unit_width self.unit_height = unit_height self.unit_depth = unit_depth + self.unit = unit self.pixel_width = pixel_width self.pixel_height = pixel_height self.slice_count = slice_count From ed87e582fa3ca4a31d1f17c6f861b62e5787d67d Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 16:45:32 +0200 Subject: [PATCH 620/678] Remove redundant source folder parameter --- src/imcflibs/imagej/bioformats.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 77d952b0..8cdecce0 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -480,15 +480,13 @@ def get_metadata_from_file(path_to_image): return metadata -def get_stage_coords(source, filenames): +def get_stage_coords(filenames): """Get stage coordinates and calibration for a given list of images. Parameters ---------- - source : str - Path to the images. filenames : list of str - List of image filenames. + List of image filepaths. Returns ------- @@ -518,7 +516,7 @@ def get_stage_coords(source, filenames): reader.setFlattenedResolutions(False) ome_meta = MetadataTools.createOMEXMLMetadata() reader.setMetadataStore(ome_meta) - reader.setId(source + str(image)) + reader.setId(str(image)) series_count = reader.getSeriesCount() # Process only the first image to get values not dependent on series From e8f1a01ff79a1f02b36ef6f479f8c3054204c437 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 16:46:08 +0200 Subject: [PATCH 621/678] Fix method for string representation --- src/imcflibs/imagej/bioformats.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 8cdecce0..11dbb57c 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -110,7 +110,7 @@ def __repr__(self): ) -class StageMetadata: +class StageMetadata(object): """A class to store stage coordinates and calibration metadata for a set of images. Attributes @@ -171,11 +171,10 @@ def __init__( def __repr__(self): """Return a string representation of the object.""" - return "".format( - self.dimensions, self.calibration_unit + return "".format( + ", ".join("{}={}".format(k, v) for k, v in self.__dict__.items()) ) - def import_image( filename, color_mode="color", @@ -467,6 +466,7 @@ def get_metadata_from_file(path_to_image): unit_width=ome_meta.getPixelsPhysicalSizeX(0), unit_height=ome_meta.getPixelsPhysicalSizeY(0), unit_depth=ome_meta.getPixelsPhysicalSizeZ(0), + unit=ome_meta.getPixelsPhysicalSizeX(0).unit().symbol, pixel_width=ome_meta.getPixelsSizeX(0), pixel_height=ome_meta.getPixelsSizeY(0), slice_count=ome_meta.getPixelsSizeZ(0), @@ -562,9 +562,12 @@ def get_stage_coords(filenames): else: series_names.append(str(image)) - current_position_x = getattr(ome_meta.getPlanePositionX(series, 0), "value", lambda: 0)() - current_position_y = getattr(ome_meta.getPlanePositionY(series, 0), "value", lambda: 0)() - current_position_z = getattr(ome_meta.getPlanePositionZ(series, 0), "value", lambda: 1.0)() + current_position_x = getattr(ome_meta.getPlanePositionX(series, 0), "value", + lambda: 0)() + current_position_y = getattr(ome_meta.getPlanePositionY(series, 0), "value", + lambda: 0)() + current_position_z = getattr(ome_meta.getPlanePositionZ(series, 0), "value", + lambda: 1.0)() max_phys_size_x = max(max_phys_size_x, ome_meta.getPixelsPhysicalSizeX(series).value()) max_phys_size_y = max(max_phys_size_y, ome_meta.getPixelsPhysicalSizeY(series).value()) From a33b2b1735cb121feb3a113547c983e8770fee37 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 16:48:50 +0200 Subject: [PATCH 622/678] Run black formatting --- src/imcflibs/imagej/bioformats.py | 60 ++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 11dbb57c..24281e54 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -175,6 +175,7 @@ def __repr__(self): ", ".join("{}={}".format(k, v) for k, v in self.__dict__.items()) ) + def import_image( filename, color_mode="color", @@ -530,22 +531,36 @@ def get_stage_coords(filenames): dimensions = 2 if frame_size_z == 1 else 3 # Retrieve physical size coordinates safely - phys_size_x = getattr(ome_meta.getPixelsPhysicalSizeX(0), "value", lambda: 1.0)() - phys_size_y = getattr(ome_meta.getPixelsPhysicalSizeY(0), "value", lambda: 1.0)() - phys_size_z = getattr(ome_meta.getPixelsPhysicalSizeZ(0), "value", lambda: None)() + phys_size_x = getattr( + ome_meta.getPixelsPhysicalSizeX(0), "value", lambda: 1.0 + )() + phys_size_y = getattr( + ome_meta.getPixelsPhysicalSizeY(0), "value", lambda: 1.0 + )() + phys_size_z = getattr( + ome_meta.getPixelsPhysicalSizeZ(0), "value", lambda: None + )() z_interval = phys_size_z if phys_size_z is not None else 1.0 # Handle missing Z calibration if phys_size_z is None and frame_size_z > 1: - first_plane = getattr(ome_meta.getPlanePositionZ(0, 0), "value", lambda: 0)() + first_plane = getattr( + ome_meta.getPlanePositionZ(0, 0), "value", lambda: 0 + )() next_plane_index = frame_size_c + frame_size_t - 1 - second_plane = getattr(ome_meta.getPlanePositionZ(0, next_plane_index), "value", lambda: 0)() + second_plane = getattr( + ome_meta.getPlanePositionZ(0, next_plane_index), "value", lambda: 0 + )() z_interval = abs(first_plane - second_plane) image_calibration = [phys_size_x, phys_size_y, z_interval] calibration_unit = ( - getattr(ome_meta.getPixelsPhysicalSizeX(0).unit(), "getSymbol", lambda: "unknown")() + getattr( + ome_meta.getPixelsPhysicalSizeX(0).unit(), + "getSymbol", + lambda: "unknown", + )() if phys_size_x else "unknown" ) @@ -562,17 +577,28 @@ def get_stage_coords(filenames): else: series_names.append(str(image)) - current_position_x = getattr(ome_meta.getPlanePositionX(series, 0), "value", - lambda: 0)() - current_position_y = getattr(ome_meta.getPlanePositionY(series, 0), "value", - lambda: 0)() - current_position_z = getattr(ome_meta.getPlanePositionZ(series, 0), "value", - lambda: 1.0)() - - max_phys_size_x = max(max_phys_size_x, ome_meta.getPixelsPhysicalSizeX(series).value()) - max_phys_size_y = max(max_phys_size_y, ome_meta.getPixelsPhysicalSizeY(series).value()) - max_phys_size_z = max(max_phys_size_z, ome_meta.getPixelsPhysicalSizeZ(series).value() - if phys_size_z else z_interval) + current_position_x = getattr( + ome_meta.getPlanePositionX(series, 0), "value", lambda: 0 + )() + current_position_y = getattr( + ome_meta.getPlanePositionY(series, 0), "value", lambda: 0 + )() + current_position_z = getattr( + ome_meta.getPlanePositionZ(series, 0), "value", lambda: 1.0 + )() + + max_phys_size_x = max( + max_phys_size_x, ome_meta.getPixelsPhysicalSizeX(series).value() + ) + max_phys_size_y = max( + max_phys_size_y, ome_meta.getPixelsPhysicalSizeY(series).value() + ) + max_phys_size_z = max( + max_phys_size_z, + ome_meta.getPixelsPhysicalSizeZ(series).value() + if phys_size_z + else z_interval, + ) stage_coordinates_x.append(current_position_x) stage_coordinates_y.append(current_position_y) From ab0e72087ca893b5ee495e51b62ef2bdf3eccfd9 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Thu, 3 Apr 2025 17:29:54 +0200 Subject: [PATCH 623/678] Fix unit retrieval, caused formatting issues --- src/imcflibs/imagej/bioformats.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index 24281e54..c766f331 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -464,9 +464,9 @@ def get_metadata_from_file(path_to_image): reader.setId(str(path_to_image)) metadata = ImageMetadata( - unit_width=ome_meta.getPixelsPhysicalSizeX(0), - unit_height=ome_meta.getPixelsPhysicalSizeY(0), - unit_depth=ome_meta.getPixelsPhysicalSizeZ(0), + unit_width=ome_meta.getPixelsPhysicalSizeX(0).value(), + unit_height=ome_meta.getPixelsPhysicalSizeY(0).value(), + unit_depth=ome_meta.getPixelsPhysicalSizeZ(0).value(), unit=ome_meta.getPixelsPhysicalSizeX(0).unit().symbol, pixel_width=ome_meta.getPixelsSizeX(0), pixel_height=ome_meta.getPixelsSizeY(0), From f9b29875079d0b7efaf935670f8c3a0439c73400 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 11:06:38 +0200 Subject: [PATCH 624/678] Fix markdown syntax for pdoc --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e7758ac..8724addb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,11 +20,11 @@ As this is a major release, not all changes and functions are listed below. For * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. * `imcflibs.imagej.misc.close_images` for closing selected image windows. * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. - *`imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered dictionary to a CSV file. + * `imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered dictionary to a CSV file. * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to get a label image (2D/3D). * `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. -*`imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris installation. +* `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris installation. * New `imcflibs.imagej.objects3d` submodule, providing: * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn From d932efba41539e22abb6441dc0028c63bae5d09a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 11:09:10 +0200 Subject: [PATCH 625/678] Fix line length --- CHANGELOG.md | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8724addb..c7f8fdd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,27 +4,38 @@ ## 1.5.0 -As this is a major release, not all changes and functions are listed below. For detailed information, please refer to the updated API documentation. +As this is a major release, not all changes and functions are listed below. For +detailed information, please refer to the updated API documentation. ### Added * Various additions to `imcflibs.imagej.misc`: - * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon completion of long-running scripts. - * Sends a mail with job details, such as recipient, file name, execution time & an optional message. - * To enable email notifications, the following preferences must be set in `~/.imagej/IJ_Prefs.txt`: + * `imcflibs.imagej.misc.send_notification_email` to send email notifications + upon completion of long-running scripts. + * Sends a mail with job details, such as recipient, file name, execution + time & an optional message. + * To enable email notifications, the following preferences must be set in + `~/.imagej/IJ_Prefs.txt`: * .imcf.sender_email: sender's email address. * .imcf.smtpserver: the SMTP server used for sending emails. - * If the sender email or SMTP server is not configured, method logs a message and exits. + * If the sender email or SMTP server is not configured, method logs a + message and exits. * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and various suffixes from an ImagePlus. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. * `imcflibs.imagej.misc.close_images` for closing selected image windows. - * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. - * `imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered dictionary to a CSV file. -* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image - to get a label image (2D/3D). -* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. -* `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility ImarisConvert. Method uses `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris installation. + * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that + a selected AutoThreshold method would be using. + * `imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered + dictionary to a CSV file. +* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to + get a label image (2D/3D). +* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a + specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. +* `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris + format using the utility ImarisConvert. Method uses + `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris + installation. * New `imcflibs.imagej.objects3d` submodule, providing: * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn @@ -64,8 +75,8 @@ As this is a major release, not all changes and functions are listed below. For Dataset based on Interest Points" command. * `imcflibs.imagej.bdv.duplicate_transformations` for duplicating / propagating transformation parameters to other channels. - * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Multi-View Dataset" - command. + * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Multi-View + Dataset" command. * New `imcflibs.imagej.trackmate` submodule to provide helper functions to interface with Trackmate: * Multiple functions to set up Trackmate settings with different detectors, such as `cellpose`, `StarDist` or a `sparseLAP tracker`. * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. From 37f3cd27a7ed15c5ca4bc6def49b4583f8e6e1fd Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 11:09:20 +0200 Subject: [PATCH 626/678] Markdownlint --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f8fdd8..802e9c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,7 +86,6 @@ detailed information, please refer to the updated API documentation. * `imcflibs.pathtools.create_directory` to create a new directory at the specified path. - ## 1.4.0 ### Added From c76b740478019e74af88be5e717de55941beeccb Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 8 Apr 2025 11:12:56 +0200 Subject: [PATCH 627/678] Set pad option to ticked to prevent edge erosion --- src/imcflibs/imagej/prefs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/prefs.py b/src/imcflibs/imagej/prefs.py index 154eff56..5162e89d 100644 --- a/src/imcflibs/imagej/prefs.py +++ b/src/imcflibs/imagej/prefs.py @@ -37,8 +37,8 @@ def set_default_ij_options(): # Set foreground color to be white and background black IJ.run("Colors...", "foreground=white background=black selection=red") - # Set black background for binary images and set pad edges to false to prevent eroding from image edge - IJ.run("Options...", "black ") + # Set black background for binary images and set pad edges to true to prevent eroding from image edge + IJ.run("Options...", "iterations=1 count=1 black pad") # Set default saving format to .txt files IJ.run("Input/Output...", "file=.txt save_column save_row") From 86aa032e442e077e486e5eff57d2f5d19bdfd351 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 11:39:25 +0200 Subject: [PATCH 628/678] Adjust basedir path --- DEVELOPMENT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 76eb3759..1d2f6f45 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -6,7 +6,7 @@ To create a new release, clone the [scijava-scripts][gh_scijava-scripts] repo (e.g. in `/opt/imagej/`) and run the `release-version.sh` helper: ```bash -BASE_DIR=/opt/imagej +BASE_DIR=/opt mkdir -pv "$BASE_DIR" cd "$BASE_DIR" git clone https://github.com/scijava/scijava-scripts From a8aa1de41c3cc78a3900228045152c6669b78b53 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 13:15:28 +0200 Subject: [PATCH 629/678] Fix variable for the path --- tests/bdv/test_define_dataset_auto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index 08c53882..0cf83e30 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -35,7 +35,7 @@ def set_default_values( + ".xml" + "] " + "path=[" - + file_info["path"] + + file_info["full"] + "] " + "exclude=10 " + "bioformats_series_are?=" From d7723814b22bef2c125eff3ab7b9066b9db47106 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 13:15:34 +0200 Subject: [PATCH 630/678] Ruff formatting --- tests/bdv/test_define_dataset_auto.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index 0cf83e30..deab43d1 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -6,9 +6,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 @@ -106,9 +104,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_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] @@ -146,9 +142,7 @@ 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 = ( @@ -175,8 +169,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_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] From e79d452cf4161223af1ecc8851f0240a2da81a72 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 8 Apr 2025 13:27:53 +0200 Subject: [PATCH 631/678] Remove string representation method --- src/imcflibs/imagej/bioformats.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/imcflibs/imagej/bioformats.py b/src/imcflibs/imagej/bioformats.py index c766f331..5ef19a6b 100644 --- a/src/imcflibs/imagej/bioformats.py +++ b/src/imcflibs/imagej/bioformats.py @@ -103,12 +103,6 @@ def to_dict(self): """ return self.__dict__ - def __repr__(self): - """Return a string representation of the object.""" - return "".format( - ", ".join("{}={}".format(k, v) for k, v in self.__dict__.items()) - ) - class StageMetadata(object): """A class to store stage coordinates and calibration metadata for a set of images. From ebb5e966df0cd2e14a0fca251a6636c301354b83 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 13:31:21 +0200 Subject: [PATCH 632/678] Fix path to work with testing --- 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 deab43d1..05f48304 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -78,7 +78,7 @@ def test_define_dataset_auto_tile(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_info["path"]) # Construct the options for dataset definitions options = ( @@ -142,7 +142,7 @@ 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_info["path"], bf_series_type) # Construct the options for dataset definitions options = ( From de365e7dcc83f6873fe32fe9901b96d7bf942ba5 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 13:35:00 +0200 Subject: [PATCH 633/678] Fix updated path of interactive tests --- TESTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TESTING.md b/TESTING.md index 3a663c05..46e2b792 100644 --- a/TESTING.md +++ b/TESTING.md @@ -69,10 +69,10 @@ running exclusively in a ImageJ2 / Fiji context. So in order to provide at least some basic, semi-interactive tests the following conventions are being used: * Each _**function**_ in any of the `imcflibs.imagej` submodules should have its - own directory underneath `/tests/imagej/`, using their fully qualified name - as the path (only skipping the `imcflibs.` prefix). For example test scripts - for `imcflibs.imagej.bioformats.import_image()` will be placed in the - directory `/tests/imagej/bioformats/import_image/`. + own directory underneath `/tests/interactive-imagej/`, using their fully + qualified name as the path (only skipping the `imcflibs.` prefix). For example + test scripts for `imcflibs.imagej.bioformats.import_image()` will be placed in + the directory `/tests/interactive-imagej/bioformats/import_image/`. * The scripts inside those directories are intended to be run interactively / manually in a (freshly started) Fiji instance. Yes, really. Any other suggestions are highly welcome! From c42af0a987651ab9819c859f90b802cfb47996f7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 13:35:30 +0200 Subject: [PATCH 634/678] Some minor updates to the TESTING document --- TESTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TESTING.md b/TESTING.md index 46e2b792..356d6462 100644 --- a/TESTING.md +++ b/TESTING.md @@ -62,7 +62,7 @@ parameters specified, e.g. bash scripts/py2-pytest.sh -rv --cov --cov-report html ``` -## Common testing with ImageJ2 / Fiji +## Common (interactive) testing with ImageJ2 / Fiji Unfortunately there is nothing like `pytest` available for the parts that are running exclusively in a ImageJ2 / Fiji context. So in order to provide at least @@ -77,16 +77,16 @@ some basic, semi-interactive tests the following conventions are being used: manually in a (freshly started) Fiji instance. Yes, really. Any other suggestions are highly welcome! * To facilitate this, a collection of _test images_ (and possibly other input - data) should be cloned to the local file system. Currently this `sample_data` + data) should be cloned to the local file system. Currently this `sample-data` repository is _NOT_ publicly available due to legal โš– uncertainties. A repo containing test data ๐Ÿ—ž that can be published should be assembled over time though! * Any _interactive_ test script should start with a header similar to the one described below. Paths to input data _inside_ the test scripts **has** to be - relative to the location of the `sample_data` repository mentioned above. This + relative to the location of the `sample-data` repository mentioned above. This will allow for a fairly okayish testing workflow like this: * Make your changes in VS Code, then trigger a build by pressing `Shift` + - `Ctrl` + `B`. If things are configured as described in the *DEVELOPMENT* + `Ctrl` + `B`. If things are configured as described in the _DEVELOPMENT_ document, the resulting `.jar` file will be automatically placed in Fiji's `jars/` folder. * Next, start a fresh instance of the Fiji that received the newly built JAR. @@ -94,7 +94,7 @@ some basic, semi-interactive tests the following conventions are being used: the main window. This will open the _Script Editor_, then press `Ctrl` + `R` to launch the script. * Only on the first run on the machine being used you will have to select the - base location of the `sample_data` repository. + base location of the `sample-data` repository. * All subsequent runs of _**any**_ test script using the defined _Script Parameter_ `IMCF_TESTDATA` will remember this selection, so it will be sufficient to just confirm the dialog by pressing `Enter`. From 7048e168ed18c37805bd0e6ebb2028b0b099f0bd Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 8 Apr 2025 13:46:49 +0200 Subject: [PATCH 635/678] Add test file for bioformats metadata retrieval --- .../bioformats/metadata/test_metadata.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/interactive-imagej/bioformats/metadata/test_metadata.md diff --git a/tests/interactive-imagej/bioformats/metadata/test_metadata.md b/tests/interactive-imagej/bioformats/metadata/test_metadata.md new file mode 100644 index 00000000..e0a55d87 --- /dev/null +++ b/tests/interactive-imagej/bioformats/metadata/test_metadata.md @@ -0,0 +1,34 @@ +Following is a testing script for the retrieval of metadata methods in imcflibs.imagej.bioformats. + +Copy the following code to a Fiji that has the release `python-imcflibs-1.5.0.jar` in the /jars directory. + +Add the source folder and the names of the files under the corresponding lines, and run the script. If the metadata is printed in Fiji output, the methods are working as intended + +``` +import os +from imcflibs.imagej import bioformats +from ij import IJ + +# Testing for the metadata retrieval through Bioformats + +# Add directory path here that contains the files you wish to test for +path = r"U:\\VAMP\\rohang0000\\stage_test" +file_path_1 = os.path.join(path, "DON_25922_20250201_25922_2_01.vsi") +file_path_2 = os.path.join(path, "DON_25922_20250201_25922_2_02.vsi") +file_path_3 = os.path.join(path, "DON_25922_20250201_25922_2_03.vsi") + +metadata = bioformats.get_metadata_from_file(file_path_1) +print(metadata.unit_width) +print(metadata.unit) +print(metadata.channel_count) + +# Stage metadata and coordinates test for a list of vsi files +fnames = [file_path_1, file_path_2, file_path_3] + +metadata_stage = bioformats.get_stage_coords(fnames) + +print(metadata_stage.image_calibration) +print(metadata_stage.stage_coordinates_x) +print(metadata_stage.stage_coordinates_y) +print(metadata_stage.stage_coordinates_z) +``` \ No newline at end of file From 4d2b8f8264e1167c320191f9763331970801b7fd Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 14:04:13 +0200 Subject: [PATCH 636/678] Line length fix --- tests/bdv/test_define_dataset_auto.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/bdv/test_define_dataset_auto.py b/tests/bdv/test_define_dataset_auto.py index 05f48304..c10b973c 100644 --- a/tests/bdv/test_define_dataset_auto.py +++ b/tests/bdv/test_define_dataset_auto.py @@ -39,7 +39,8 @@ def set_default_values(project_filename, file_path, series_type="Tiles"): + "bioformats_series_are?=" + series_type + " " - + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid (use Metadata if available)] " + + "move_tiles_to_grid_(per_angle)?=[Do not move Tiles to Grid " + + "(use Metadata if available)] " ) return options From d465b4bedf27d7cd640ca12e01dfcba67fb1d3f9 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 14:18:05 +0200 Subject: [PATCH 637/678] Fix call to create LAP tracker --- src/imcflibs/imagej/trackmate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index 9fe8ce99..b17a13f7 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -347,7 +347,7 @@ def run_trackmate( if not settings.trackerFactory: # Create a Sparse LAP Tracker if no Tracker has been created - settings = sparseLAP_tracker(settings) + settings = sparse_lap_tracker(settings) ok = trackmate.checkInput() if not ok: From 4f89cbefe054cd8fc970359b2cee5b0f9dc8aeec Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 14:18:42 +0200 Subject: [PATCH 638/678] Change to more recent call for creating label image --- src/imcflibs/imagej/trackmate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/trackmate.py b/src/imcflibs/imagej/trackmate.py index b17a13f7..4433a8b7 100644 --- a/src/imcflibs/imagej/trackmate.py +++ b/src/imcflibs/imagej/trackmate.py @@ -8,15 +8,14 @@ from fiji.plugin.trackmate import Logger, Model, SelectionModel, Settings, TrackMate from fiji.plugin.trackmate.action import LabelImgExporter +from fiji.plugin.trackmate.action.LabelImgExporter import LabelIdPainting from fiji.plugin.trackmate.cellpose import CellposeDetectorFactory from fiji.plugin.trackmate.cellpose.CellposeSettings import PretrainedModel from fiji.plugin.trackmate.detection import LogDetectorFactory from fiji.plugin.trackmate.features import FeatureFilter from fiji.plugin.trackmate.stardist import StarDistDetectorFactory from fiji.plugin.trackmate.tracking.jaqaman import SparseLAPTrackerFactory - from ij import IJ - from java.lang import Double from .. import pathtools @@ -376,9 +375,10 @@ def run_trackmate( exportSpotsAsDots = False exportTracksOnly = False + labelIdPainting = LabelIdPainting.LABEL_IS_TRACK_ID # implus2.close() label_imp = LabelImgExporter.createLabelImagePlus( - trackmate, exportSpotsAsDots, exportTracksOnly, False + trackmate, exportSpotsAsDots, exportTracksOnly, labelIdPainting ) label_imp.setCalibration(cal) label_imp.setDimensions(dims[2], dims[3], dims[4]) From b3670cb12630de432d5ffecdafdcee45b55ac504 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Tue, 8 Apr 2025 14:39:33 +0200 Subject: [PATCH 639/678] Add usage instructions --- tests/interactive-imagej/send-notification-email.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/interactive-imagej/send-notification-email.md b/tests/interactive-imagej/send-notification-email.md index 0ad05ccb..eb81e6e9 100644 --- a/tests/interactive-imagej/send-notification-email.md +++ b/tests/interactive-imagej/send-notification-email.md @@ -8,6 +8,18 @@ from imcflibs.log import enable_console_logging from imcflibs.log import set_loglevel +""" +Usage: +BEFORE starting Fiji, add to the IJ_Prefs.txt: + +.imcf.sender_email=imcf@unibas.ch +.imcf.smtpserver=smtp.unibas.ch + +Linux/Mac: ~/.imagej/IJ_Prefs.txt +Windows: C:\Users\\.imagej\IJ_Prefs.txt +""" + + enable_console_logging() set_loglevel(2) From 9fba2b640e33513aef60ba55717fa56401081b5d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 15:06:09 +0200 Subject: [PATCH 640/678] Attempt to configure codecov reports With the lack of possible unit-testing for the Java-only parts, we unfortunately need to have ultra-relaxed coverage reports, otherwise they will quickly become annoying and disliked. --- .github/codecov.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 00000000..40446366 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,11 @@ +coverage: + precision: 0 + round: nearest + range: "0...75" + # notification blocks + # https://docs.codecov.io/docs/codecovyml-reference#section-coverage-notify + notify: + status: + project: + patch: + changes: From 43b2352f60fdfbfe82d0f8061093608471935d00 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 15:06:09 +0200 Subject: [PATCH 641/678] Attempt to configure codecov reports With the lack of possible unit-testing for the Java-only parts, we unfortunately need to have ultra-relaxed coverage reports, otherwise they will quickly become annoying and disliked. --- .github/codecov.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 00000000..40446366 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,11 @@ +coverage: + precision: 0 + round: nearest + range: "0...75" + # notification blocks + # https://docs.codecov.io/docs/codecovyml-reference#section-coverage-notify + notify: + status: + project: + patch: + changes: From adf0e9d4f7685da2f710d9d0ddab79635fb3e938 Mon Sep 17 00:00:00 2001 From: "kai.schleicher@unibas.ch" Date: Tue, 8 Apr 2025 15:17:19 +0200 Subject: [PATCH 642/678] Update Changelog.md for `project_stack` function --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 802e9c22..5babc921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,9 @@ detailed information, please refer to the updated API documentation. * `imcflibs.pathtools.create_directory` to create a new directory at the specified path. +* New function in `imcflibs.imagej.projections`: + * `project_stack` to project along a defined axis using the given projection type. + ## 1.4.0 ### Added From 10c5517b41848954140e2f31f74240da535731cc Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 15:58:03 +0200 Subject: [PATCH 643/678] Add missing methods to changelog and reformatting --- CHANGELOG.md | 112 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 802e9c22..b0de339c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ detailed information, please refer to the updated API documentation. ### Added +* `imcflibs.strtools.pad_number` to pad a number with leading zeros. +* `imcflibs.pathtools.create_directory` to create a new directory at the + specified path if it does not exist (needed with python 2.7) * Various additions to `imcflibs.imagej.misc`: * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon completion of long-running scripts. @@ -23,45 +26,56 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and various suffixes from an ImagePlus. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. - * `imcflibs.imagej.misc.close_images` for closing selected image windows. + * `imcflibs.imagej.misc.close_images` for closing all ImagePluses from a list. * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. - * `imcflibs.imagej.misc.write_orderddict_to_csv` to write data from an ordered - dictionary to a CSV file. -* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to - get a label image (2D/3D). -* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus image in a - specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. -* `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris - format using the utility ImarisConvert. Method uses - `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris - installation. - + * `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an ordered + dictionary (or list of ordered dictionaries) to a CSV file. + * `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus in a + specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. + * `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris + format using the utility ImarisConvert. Method uses + `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris + installation. +* New functions in `imcflibs.imagej.labelimage`: + * `imcflibs.imagej.labelimage.cookie_cut_labels` to use a label image as a mask + for another label image. Objects might get split or merged depending on the + mask. + * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to + get a label image (2D/3D). + * `imcflibs.imagej.labelimage.relate_label_images` to relate two label images + (2D/3D) using the 3D Association plugin from the 3DImageJSuite. + * `imcflibs.imagej.labelimage.dilate_labels_2d` to dilate a label image slice by + slice. Works for 2D or 3D images. * New `imcflibs.imagej.objects3d` submodule, providing: - * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn - an Objects3DPopulation into an ImagePlus (2D/3D). + * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn an + Objects3DPopulation into an ImagePlus (2D/3D). * `imcflibs.imagej.objects3d.imgplus_to_population3d` to get the Objects3DPopulation from an ImagePlus (2D/3D). * `imcflibs.imagej.objects3d.segment_3d_image` to threshold an image into a labeled stack. * `imcflibs.imagej.objects3d.get_objects_within_intensity` to filter a population of 3D objects by intensity. + * `imcflibs.imagej.objects3d.maxima_finder_3d` to find local maxima in a + 3D image. + * `imcflibs.imagej.objects3d.seeded_watershed` to perform a seeded watershed + segmentation on a binary image using seeds points. * New `imcflibs.imagej.bdv` submodule, providing BigDataViewer related functions: * New classes: * `ProcessingOptions` to store all options on how to process the dataset. * `DefinitionOptions` to store all options on how to define the dataset. - * `imcflibs.imagej.bdv.check_processing_input` to sanitize and clarify - the acitt input selection. - * `imcflibs.imagej.bdv.get_processing_settings` to generate the strings - needed for the processing. + * `imcflibs.imagej.bdv.check_processing_input` to sanitize and clarify the + acitt input selection. + * `imcflibs.imagej.bdv.get_processing_settings` to generate the strings needed + for the processing. * `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files. * `imcflibs.imagej.bdv.define_dataset_auto` to run "Define Multi-View Dataset" using the "Auto-Loader" option. * `imcflibs.imagej.bdv.define_dataset_manual` to run "Define Multi-View Dataset" using the "Manual Loader" option. - * `imcflibs.imagej.bdv.resave_as_h5` to resave the dataset in H5 to - make it compatible with BigDataViewer/BigStitcher. + * `imcflibs.imagej.bdv.resave_as_h5` to resave the dataset in H5 to make it + compatible with BigDataViewer/BigStitcher. * `imcflibs.imagej.bdv.flip_axes` tocall BigStitcher's "Flip Axes" command. * `imcflibs.imagej.bdv.phase_correlation_pairwise_shifts_calculation` to calculate pairwise shifts using Phase Correlation. @@ -77,14 +91,56 @@ detailed information, please refer to the updated API documentation. propagating transformation parameters to other channels. * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Multi-View Dataset" command. -* New `imcflibs.imagej.trackmate` submodule to provide helper functions to interface with Trackmate: - * Multiple functions to set up Trackmate settings with different detectors, such as `cellpose`, `StarDist` or a `sparseLAP tracker`. - * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. - * `imcflibs.imagej.trackmate.track_filtering` to create settings to filter detected tracks based upon optional distances, such as maximum linking, gap closing, track splitting & merging and maximum frame gap. - * `imcflibs.imagej.trackmate.run_trackmate` to run Fiji's Trackmate plugin on an open ImagePlus with given settings, which can be set up with available methods in the `imcflibs.imagej.trackmate` submodule. The method then returns a label image. -* New `imcflibs.imagej.omerotools` submodule, providing helper functions to connect to OMERO using user credentials, fetch and upload an image, retrieve a dataset, or save ROIs to OMERO. - -* `imcflibs.pathtools.create_directory` to create a new directory at the specified path. +* New `imcflibs.imagej.trackmate` submodule to provide helper functions to + interface with Trackmate: + * Multiple functions to set up Trackmate settings with different detectors, + such as `cellpose`, `StarDist` or a `log detector`. + * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter + detected spots based on optional thresholds for quality, area, circularity & + intensity. + * `imcflibs.imagej.trackmate.sparse_lap_tracker` to create default settings for the + sparse LAP tracker. + * `imcflibs.imagej.trackmate.track_filtering` to create settings to filter + detected tracks based upon optional distances, such as maximum linking, gap + closing, track splitting & merging and maximum frame gap. + * `imcflibs.imagej.trackmate.run_trackmate` to run Fiji's Trackmate plugin on + an open ImagePlus with given settings, which can be set up with available + methods in the `imcflibs.imagej.trackmate` submodule. The method then + returns a label image. +* New `imcflibs.imagej.omerotools` submodule, providing helper functions to + connect to OMERO using user credentials, fetch and upload an image, retrieve a + dataset, or save ROIs to OMERO. + * `imcflibs.imagej.omerotools.parse_url` to parse the OMERO URL and get a list + of `ImageWrappers` from multiple image or datasets IDs. + * `imcflibs.imagej.omerotools.connect` to connect to OMERO using user + credentials. + * `imcflibs.imagej.omerotools.fetch_image` to fetch an image from OMERO using + the image ID. + * `imcflibs.imagej.omerotools.upload_image_to_omero` to upload a local image to + OMERO and returning the new image ID. + * `imcflibs.imagej.omerotools.add_keyvalue_annotation` to add an annotation to an + OMERO object. + * `imcflibs.imagej.omerotools.delete_keyvalue_annotations` to delete + annotations from an OMERO object. + * `imcflibs.imagej.omerotools.find_dataset` to find a dataset in OMERO using + the dataset ID. + * `imcflibs.imagej.get_acquisition_metadata` to get the acquisition metadata + from an image in OMERO. + * `imcflibs.imagej.omerotools.get_info_from_original_metadata` to get the + original metadata from an image in OMERO. + * `imcflibs.imagej.omerotools.create_table_columns` to create OMERO table + headings from a list of column names. + * `imcflibs.imagej.omerotools.upload_array_as_omero_table` to upload a table + to OMERO. + * `imcflibs.imagej.omerotools.save_rois_to_omero` to save ROIs to OMERO. +* New `imcflibs.imagej.shading` module for everything background correction. + * `imcflibs.imagej.shading.simple_flatfield_correction` to perform a + simple flatfield correction to an ImagePlus. +* `imcflibs.imagej.projection.project_stack` to project a stack using + different projection methods, such as `max`, `min`, `mean`, `sum` or + `standard_deviation` using a defined axis. +* `imcflibs.imagej.prefs.set_default_ij_options` to configure ImageJ default + options for consistency. ## 1.4.0 From 570d1b6da51bbb96ea0f2a81bd053629fc0a8596 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 15:58:52 +0200 Subject: [PATCH 644/678] Rename methods and fix docstrings --- src/imcflibs/imagej/omerotools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/imcflibs/imagej/omerotools.py b/src/imcflibs/imagej/omerotools.py index c1abe3ab..31f4bdd4 100644 --- a/src/imcflibs/imagej/omerotools.py +++ b/src/imcflibs/imagej/omerotools.py @@ -178,7 +178,7 @@ def upload_image_to_omero(user_client, path, dataset_id): return user_client.getDataset(Long(dataset_id)).importImage(user_client, path)[0] -def add_annotation(client, repository_wpr, annotations, header): +def add_keyvalue_annotation(client, repository_wpr, annotations, header): """Add an annotation to an OMERO object. Parameters @@ -234,7 +234,7 @@ def find_dataset(client, dataset_id): def get_acquisition_metadata(user_client, image_wpr): - """Get acquisition metadata from OMERO based on an image ID. + """Get acquisition metadata from OMERO based on an image wrapper. Parameters ---------- @@ -324,7 +324,7 @@ def get_info_from_original_metadata(user_client, image_wpr, field): def create_table_columns(headings): - """Create OMERO table headings from an ImageJ results table. + """Create OMERO table headings from a list of column names. Parameters ---------- From af3de72dd4090a256205961bc316dcd301a5a4e4 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 8 Apr 2025 16:05:29 +0200 Subject: [PATCH 645/678] Update testing file to use test data repository --- .../bioformats/metadata/test_metadata.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/interactive-imagej/bioformats/metadata/test_metadata.md b/tests/interactive-imagej/bioformats/metadata/test_metadata.md index e0a55d87..ec1f72de 100644 --- a/tests/interactive-imagej/bioformats/metadata/test_metadata.md +++ b/tests/interactive-imagej/bioformats/metadata/test_metadata.md @@ -5,17 +5,20 @@ Copy the following code to a Fiji that has the release `python-imcflibs-1.5.0.ja Add the source folder and the names of the files under the corresponding lines, and run the script. If the metadata is printed in Fiji output, the methods are working as intended ``` +# @ File (label="IMCF testdata location", style="directory") IMCF_TESTDATA + import os +from imcflibs.pathtools import join2 from imcflibs.imagej import bioformats from ij import IJ # Testing for the metadata retrieval through Bioformats # Add directory path here that contains the files you wish to test for -path = r"U:\\VAMP\\rohang0000\\stage_test" -file_path_1 = os.path.join(path, "DON_25922_20250201_25922_2_01.vsi") -file_path_2 = os.path.join(path, "DON_25922_20250201_25922_2_02.vsi") -file_path_3 = os.path.join(path, "DON_25922_20250201_25922_2_03.vsi") + +file_path_1 = join2(IMCF_TESTDATA, "bioformats/DON_25922_20250201_25922_2_01.vsi") +file_path_2 = join2(IMCF_TESTDATA, "bioformats/DON_25922_20250201_25922_2_02.vsi") +file_path_3 = join2(IMCF_TESTDATA, "bioformats/DON_25922_20250201_25922_2_03.vsi") metadata = bioformats.get_metadata_from_file(file_path_1) print(metadata.unit_width) From a41ff42f14386469e709fb1d98b46d7d15cb2c41 Mon Sep 17 00:00:00 2001 From: Rohan Girish Date: Tue, 8 Apr 2025 16:11:15 +0200 Subject: [PATCH 646/678] Fix path to test data --- .../interactive-imagej/bioformats/metadata/test_metadata.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/interactive-imagej/bioformats/metadata/test_metadata.md b/tests/interactive-imagej/bioformats/metadata/test_metadata.md index ec1f72de..a6328955 100644 --- a/tests/interactive-imagej/bioformats/metadata/test_metadata.md +++ b/tests/interactive-imagej/bioformats/metadata/test_metadata.md @@ -16,9 +16,9 @@ from ij import IJ # Add directory path here that contains the files you wish to test for -file_path_1 = join2(IMCF_TESTDATA, "bioformats/DON_25922_20250201_25922_2_01.vsi") -file_path_2 = join2(IMCF_TESTDATA, "bioformats/DON_25922_20250201_25922_2_02.vsi") -file_path_3 = join2(IMCF_TESTDATA, "bioformats/DON_25922_20250201_25922_2_03.vsi") +file_path_1 = join2(IMCF_TESTDATA, "bioformats-multiposition/DON_25922_20250201_25922_2_01.vsi") +file_path_2 = join2(IMCF_TESTDATA, "bioformats-multiposition/DON_25922_20250201_25922_2_02.vsi") +file_path_3 = join2(IMCF_TESTDATA, "bioformats-multiposition/DON_25922_20250201_25922_2_03.vsi") metadata = bioformats.get_metadata_from_file(file_path_1) print(metadata.unit_width) From 3d08ca3022d9cf5a033b0aabae42dead69e7c520 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 16:12:35 +0200 Subject: [PATCH 647/678] Validate and fix codecov.yml --- .github/codecov.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index 40446366..eba40389 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,11 +1,13 @@ coverage: precision: 0 round: nearest - range: "0...75" + range: + - 0 + - 75 # notification blocks # https://docs.codecov.io/docs/codecovyml-reference#section-coverage-notify - notify: - status: - project: - patch: - changes: + # notify: + # status: + # project: + # patch: + # changes: off From fcb2bc6a1c3892e76d0a65bfb0ecbd4a249ab93d Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 15:06:09 +0200 Subject: [PATCH 648/678] Attempt to configure codecov reports With the lack of possible unit-testing for the Java-only parts, we unfortunately need to have ultra-relaxed coverage reports, otherwise they will quickly become annoying and disliked. --- .github/codecov.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 00000000..40446366 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,11 @@ +coverage: + precision: 0 + round: nearest + range: "0...75" + # notification blocks + # https://docs.codecov.io/docs/codecovyml-reference#section-coverage-notify + notify: + status: + project: + patch: + changes: From dde74d9e2283ba0f1e764563033340ef51d57ea7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 16:12:35 +0200 Subject: [PATCH 649/678] Validate and fix codecov.yml --- .github/codecov.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/codecov.yml b/.github/codecov.yml index 40446366..eba40389 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,11 +1,13 @@ coverage: precision: 0 round: nearest - range: "0...75" + range: + - 0 + - 75 # notification blocks # https://docs.codecov.io/docs/codecovyml-reference#section-coverage-notify - notify: - status: - project: - patch: - changes: + # notify: + # status: + # project: + # patch: + # changes: off From 6eabd3ee8f26400a94cc29591e20c888d2f1fd1f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 16:21:29 +0200 Subject: [PATCH 650/678] Rewraps (line length) --- CHANGELOG.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0de339c..d0829349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,8 +29,8 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.misc.close_images` for closing all ImagePluses from a list. * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a selected AutoThreshold method would be using. - * `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an ordered - dictionary (or list of ordered dictionaries) to a CSV file. + * `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an + ordered dictionary (or list of ordered dictionaries) to a CSV file. * `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus in a specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. * `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris @@ -38,15 +38,15 @@ detailed information, please refer to the updated API documentation. `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris installation. * New functions in `imcflibs.imagej.labelimage`: - * `imcflibs.imagej.labelimage.cookie_cut_labels` to use a label image as a mask - for another label image. Objects might get split or merged depending on the - mask. - * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to - get a label image (2D/3D). + * `imcflibs.imagej.labelimage.cookie_cut_labels` to use a label image as a + mask for another label image. Objects might get split or merged depending on + the mask. + * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image + to get a label image (2D/3D). * `imcflibs.imagej.labelimage.relate_label_images` to relate two label images (2D/3D) using the 3D Association plugin from the 3DImageJSuite. - * `imcflibs.imagej.labelimage.dilate_labels_2d` to dilate a label image slice by - slice. Works for 2D or 3D images. + * `imcflibs.imagej.labelimage.dilate_labels_2d` to dilate a label image slice + by slice. Works for 2D or 3D images. * New `imcflibs.imagej.objects3d` submodule, providing: * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn an Objects3DPopulation into an ImagePlus (2D/3D). @@ -98,8 +98,8 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. - * `imcflibs.imagej.trackmate.sparse_lap_tracker` to create default settings for the - sparse LAP tracker. + * `imcflibs.imagej.trackmate.sparse_lap_tracker` to create default settings + for the sparse LAP tracker. * `imcflibs.imagej.trackmate.track_filtering` to create settings to filter detected tracks based upon optional distances, such as maximum linking, gap closing, track splitting & merging and maximum frame gap. @@ -116,10 +116,10 @@ detailed information, please refer to the updated API documentation. credentials. * `imcflibs.imagej.omerotools.fetch_image` to fetch an image from OMERO using the image ID. - * `imcflibs.imagej.omerotools.upload_image_to_omero` to upload a local image to - OMERO and returning the new image ID. - * `imcflibs.imagej.omerotools.add_keyvalue_annotation` to add an annotation to an - OMERO object. + * `imcflibs.imagej.omerotools.upload_image_to_omero` to upload a local image + to OMERO and returning the new image ID. + * `imcflibs.imagej.omerotools.add_keyvalue_annotation` to add an annotation to + an OMERO object. * `imcflibs.imagej.omerotools.delete_keyvalue_annotations` to delete annotations from an OMERO object. * `imcflibs.imagej.omerotools.find_dataset` to find a dataset in OMERO using From 64a1a09a8690009caf66f8889b9f4591a82369ab Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 16:33:03 +0200 Subject: [PATCH 651/678] Line length fixes --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6df28b5d..36b42e5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,8 +79,10 @@ As this is a major release, not all changes and functions are listed below. For * Additions to `imcflibs.imagej.bioformats`: * `imcflibs.imagej.bioformats.export` to export an image to a given file. - * `imcflibs.imagej.bioformats.get_metadata_from_file` to extract various metadata from a given file using BioFormats. - * `imcflibs.imagej.bioformats.get_stage_coords`to get stage coordinates and calibration for one or more given images. + * `imcflibs.imagej.bioformats.get_metadata_from_file` to extract various + metadata from a given file using BioFormats. + * `imcflibs.imagej.bioformats.get_stage_coords`to get stage coordinates and + calibration for one or more given images. ## 1.4.0 From d6a600d8a10a9a52fa4af6518bd82c89a2b0f5f8 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 16:40:26 +0200 Subject: [PATCH 652/678] Use fully-qualified name so pdoc renders a link --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5babc921..7fcedbbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,8 +86,8 @@ detailed information, please refer to the updated API documentation. * `imcflibs.pathtools.create_directory` to create a new directory at the specified path. -* New function in `imcflibs.imagej.projections`: - * `project_stack` to project along a defined axis using the given projection type. +* `imcflibs.imagej.projections.project_stack` to project along a defined axis + using the given projection type. ## 1.4.0 From 341567e7f753540a97735c0a3ce84e06bb75b467 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 16:56:25 +0200 Subject: [PATCH 653/678] Add methods for `imcflibs.imagej.processing` --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0829349..21b0a604 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,6 +141,14 @@ detailed information, please refer to the updated API documentation. `standard_deviation` using a defined axis. * `imcflibs.imagej.prefs.set_default_ij_options` to configure ImageJ default options for consistency. +* New module `imcflibs.imagej.processing` containing utilities for filtering and thresholding: + * `imcflibs.imagej.processing.apply_filter` to apply a filter to an + ImagePlus. + * `imcflibs.imagej.processing.apply_rollingball_bg_subtraction` to apply a + rolling ball background subtraction to an ImagePlus. + * `imcflibs.imagej.processing.apply_threshold` to apply a threshold method to + an ImagePlus. + * ## 1.4.0 From e5db84cd7491d610e3b4400f89b6cd127636eb11 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 17:13:49 +0200 Subject: [PATCH 654/678] Minor description changes for send_notification_email --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d19111..3c561e99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,10 +19,10 @@ detailed information, please refer to the updated API documentation. time & an optional message. * To enable email notifications, the following preferences must be set in `~/.imagej/IJ_Prefs.txt`: - * .imcf.sender_email: sender's email address. - * .imcf.smtpserver: the SMTP server used for sending emails. - * If the sender email or SMTP server is not configured, method logs a - message and exits. + * `.imcf.sender_email`: sender's email address. + * `.imcf.smtpserver`: the SMTP server used for sending emails. + * If the sender email or SMTP server is not configured, the method logs a + message and returns. * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and various suffixes from an ImagePlus. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. From 866f1bacb6b58ec7ecbc3ecdfb7ba7ad5928a685 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 17:14:58 +0200 Subject: [PATCH 655/678] Remove leftover line --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c561e99..a4711642 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -148,7 +148,6 @@ detailed information, please refer to the updated API documentation. rolling ball background subtraction to an ImagePlus. * `imcflibs.imagej.processing.apply_threshold` to apply a threshold method to an ImagePlus. - * * Additions to `imcflibs.imagej.bioformats`: * `imcflibs.imagej.bioformats.export` to export an image to a given file. From f699d547f8d56b42991019d51c06092a6753130a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 17:43:37 +0200 Subject: [PATCH 656/678] =?UTF-8?q?Update=20imcf-fiji-mocks=20dependency?= =?UTF-8?q?=20and=20lock=20=F0=9F=8E=AD=F0=9F=94=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3005414a..833588fd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -105,13 +105,13 @@ test = ["pytest (>=6)"] [[package]] name = "imcf-fiji-mocks" -version = "0.9.0" +version = "0.10.0" description = "Mocks collection for Fiji-Python. Zero functional code." optional = false python-versions = ">=2.7" files = [ - {file = "imcf_fiji_mocks-0.9.0-py2.py3-none-any.whl", hash = "sha256:e911436cb922ee3296f6ea6ccb515d9712e6456ea887670575cd8a53416444b1"}, - {file = "imcf_fiji_mocks-0.9.0.tar.gz", hash = "sha256:c21ebb51128d315d7bfe56bfd09c8b51590772f9f0afeceba7b1272a92365a48"}, + {file = "imcf_fiji_mocks-0.10.0-py2.py3-none-any.whl", hash = "sha256:476927d82fa0e93b0b0b738f82cab60e180cf0da5b3dd09dc6a5336b08e18d2d"}, + {file = "imcf_fiji_mocks-0.10.0.tar.gz", hash = "sha256:d1f3302031cad5f1d15388bf337025bbfb59037a04e79a102de59093e643a5f5"}, ] [[package]] @@ -271,4 +271,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "a7683e17cd18448063dda6b81b8a75c6873a4be3f3c0326e53bfb887a2763dbb" +content-hash = "6b4a1828157bbddc15f61de0427a3d7970a61da1750f2cbf9e160f1b7546d7c9" diff --git a/pyproject.toml b/pyproject.toml index 32de1840..31da3a7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ version = "0.0.0" # - or: python = ">=3.10" [tool.poetry.dependencies] -imcf-fiji-mocks = ">=0.9.0" +imcf-fiji-mocks = ">=0.10.0" python = ">=2.7" python-micrometa = "^15.2.2" sjlogging = ">=0.5.2" From 6f8ca9654ccbc5b62655b59584c2fb5048fac649 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 17:13:42 +0200 Subject: [PATCH 657/678] Add full path to BDV classes and trackmate methods --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4711642..21469493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,8 +63,8 @@ detailed information, please refer to the updated API documentation. * New `imcflibs.imagej.bdv` submodule, providing BigDataViewer related functions: * New classes: - * `ProcessingOptions` to store all options on how to process the dataset. - * `DefinitionOptions` to store all options on how to define the dataset. + * `imcflibs.imagej.bdv.ProcessingOptions` to store all options on how to process the dataset. + * `imcflibs.imagej.bdv.DefinitionOptions` to store all options on how to define the dataset. * `imcflibs.imagej.bdv.check_processing_input` to sanitize and clarify the acitt input selection. * `imcflibs.imagej.bdv.get_processing_settings` to generate the strings needed @@ -94,7 +94,7 @@ detailed information, please refer to the updated API documentation. * New `imcflibs.imagej.trackmate` submodule to provide helper functions to interface with Trackmate: * Multiple functions to set up Trackmate settings with different detectors, - such as `cellpose`, `StarDist` or a `log detector`. + such as `imcflibs.imagej.trackmate.cellpose_detector`, `imcflibs.imagej.trackmate.stardist_detector` or a `imcflibs.imagej.trackmate.log_detector`. * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. From 758d7e205a89075013aa600a0bd186a616af1b7e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 17:13:57 +0200 Subject: [PATCH 658/678] Add modification to `listdir_matching` --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21469493..c84004e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,7 +76,7 @@ detailed information, please refer to the updated API documentation. Dataset" using the "Manual Loader" option. * `imcflibs.imagej.bdv.resave_as_h5` to resave the dataset in H5 to make it compatible with BigDataViewer/BigStitcher. - * `imcflibs.imagej.bdv.flip_axes` tocall BigStitcher's "Flip Axes" command. + * `imcflibs.imagej.bdv.flip_axes` to call BigStitcher's "Flip Axes" command. * `imcflibs.imagej.bdv.phase_correlation_pairwise_shifts_calculation` to calculate pairwise shifts using Phase Correlation. * `imcflibs.imagej.bdv.filter_pairwise_shifts` for filtering pairwise shifts @@ -158,6 +158,12 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.projections.project_stack` to project along a defined axis using the given projection type. + +### Changed + +* `imcflibs.listdir_matching` changed to be able to use regex patterns for + matching. The regex pattern is passed as a string and the function will + return a list of all files matching the regex pattern. ## 1.4.0 From 3634ffe42eca1dee8b9a23162aaea33f92306ca5 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 8 Apr 2025 17:18:39 +0200 Subject: [PATCH 659/678] Add changes to `calculate_mean_and_stdv` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c84004e5..a459cdf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -164,6 +164,7 @@ detailed information, please refer to the updated API documentation. * `imcflibs.listdir_matching` changed to be able to use regex patterns for matching. The regex pattern is passed as a string and the function will return a list of all files matching the regex pattern. +* `imcflibs.imagej.misc.calculate_mean_and_stdv` to allow for rounding the results. ## 1.4.0 From d55e777176ea44a4ee642e5a1283986a429312e4 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 18:41:56 +0200 Subject: [PATCH 660/678] Fix function path --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a459cdf1..07ddeb09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -161,8 +161,8 @@ detailed information, please refer to the updated API documentation. ### Changed -* `imcflibs.listdir_matching` changed to be able to use regex patterns for - matching. The regex pattern is passed as a string and the function will +* `imcflibs.pathtools.listdir_matching` changed to be able to use regex patterns + for matching. The regex pattern is passed as a string and the function will return a list of all files matching the regex pattern. * `imcflibs.imagej.misc.calculate_mean_and_stdv` to allow for rounding the results. From c99e01fbe6bb4701fdd1764d8633d20b7903c6ca Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 18:42:05 +0200 Subject: [PATCH 661/678] Newline --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07ddeb09..9f5fe532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,7 +158,7 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.projections.project_stack` to project along a defined axis using the given projection type. - + ### Changed * `imcflibs.pathtools.listdir_matching` changed to be able to use regex patterns From df74f8995dcbdae30d987e141392b90e8e6436df Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 18:47:21 +0200 Subject: [PATCH 662/678] Modify description of the `regex` parameter --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f5fe532..b7763476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -161,9 +161,9 @@ detailed information, please refer to the updated API documentation. ### Changed -* `imcflibs.pathtools.listdir_matching` changed to be able to use regex patterns - for matching. The regex pattern is passed as a string and the function will - return a list of all files matching the regex pattern. +* `imcflibs.pathtools.listdir_matching` now has an additional optional boolean + parameter `regex` to request the parameter `suffix` being interpreted as a + regular expression for filtering. * `imcflibs.imagej.misc.calculate_mean_and_stdv` to allow for rounding the results. ## 1.4.0 From 77760acbc29bfb255fdd54cf6c921f9f9c183259 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 18:53:31 +0200 Subject: [PATCH 663/678] Line length fixes --- CHANGELOG.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7763476..23294b79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,8 +63,10 @@ detailed information, please refer to the updated API documentation. * New `imcflibs.imagej.bdv` submodule, providing BigDataViewer related functions: * New classes: - * `imcflibs.imagej.bdv.ProcessingOptions` to store all options on how to process the dataset. - * `imcflibs.imagej.bdv.DefinitionOptions` to store all options on how to define the dataset. + * `imcflibs.imagej.bdv.ProcessingOptions` to store all options on how to + process the dataset. + * `imcflibs.imagej.bdv.DefinitionOptions` to store all options on how to + define the dataset. * `imcflibs.imagej.bdv.check_processing_input` to sanitize and clarify the acitt input selection. * `imcflibs.imagej.bdv.get_processing_settings` to generate the strings needed @@ -94,7 +96,9 @@ detailed information, please refer to the updated API documentation. * New `imcflibs.imagej.trackmate` submodule to provide helper functions to interface with Trackmate: * Multiple functions to set up Trackmate settings with different detectors, - such as `imcflibs.imagej.trackmate.cellpose_detector`, `imcflibs.imagej.trackmate.stardist_detector` or a `imcflibs.imagej.trackmate.log_detector`. + such as `imcflibs.imagej.trackmate.cellpose_detector`, + `imcflibs.imagej.trackmate.stardist_detector` or a + `imcflibs.imagej.trackmate.log_detector`. * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. From 61d3916ec33dda55184dde591c1f7b78f7f630b7 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 18:53:52 +0200 Subject: [PATCH 664/678] Explain new parameter for `round_decimals` --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23294b79..87844b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -168,7 +168,8 @@ detailed information, please refer to the updated API documentation. * `imcflibs.pathtools.listdir_matching` now has an additional optional boolean parameter `regex` to request the parameter `suffix` being interpreted as a regular expression for filtering. -* `imcflibs.imagej.misc.calculate_mean_and_stdv` to allow for rounding the results. +* `imcflibs.imagej.misc.calculate_mean_and_stdv` now has an optional parameter + `round_decimals` to allow for rounding the results, defaulting to `0`. ## 1.4.0 From 4db8566e6046f4ece01b18f41aa16ed5809da607 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 20:37:12 +0200 Subject: [PATCH 665/678] Remove TODO-BDV document MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With closing #35, this file is now obsolete and can be removed ๐Ÿ”ซ. --- TODO-BDV.md | 243 ---------------------------------------------------- 1 file changed, 243 deletions(-) delete mode 100644 TODO-BDV.md diff --git a/TODO-BDV.md b/TODO-BDV.md deleted file mode 100644 index da374ed4..00000000 --- a/TODO-BDV.md +++ /dev/null @@ -1,243 +0,0 @@ -# BDV processing-options-class TODO - -/!\ WARNING /!\ - -When the ***auto-loader*** is used, view enumeration in BigStitcher is based on -*metadata*, for example the first channel in a ligthsheet dataset might then be called -*Channel 488* instead of *Channel 1* or *Channel 0*. - --> It's actually very difficult to forsee this properly, upfront image metadata -inspection would be required to do this! - -In case the ***manual loader*** is used, view enumeration follows the file naming -pattern, e.g. the file name for the first tile and first channel would read -`myimage_tile0_channel1`. - --> In that case, tile enumeration starts at **zero** while channel enumeration starts at -**one**! - -## Options - -### First options block: "*what to process*" - -The *first options block* is selecting which parts of the h5/xml dataset will be -processed. - -This block is relevant for *most* of our BDV functions: - -- `resave_as_h5` -- `flip_axes` -- `phase_correlation_pairwise_shifts_calculation` -- `filter_pairwise_shifts` -- `optimize_and_apply_shifts` -- `detect_interest_points` -- `interest_points_registration` -- `duplicate_transformations` -- `fuse_dataset` - -Valid choices are: - -- `[All Xs]` -- `[Single X (Select from List)]` -- `[Multiple Xs (Select from List)]` -- `[Range of Xs (Specify by Name)]` - -With `X` being one of `angle`, `channel`, `illumination`, `tile`, `timepoint`. - -### Second options block: details for "*what to process*" - -The *second options block* is **required** for each component having selected anything -other than `All Xs` in the in the first options block. - -- For `Single X (Select from List)` the required option is `processing_X=[X n]`, e.g. - `processing_channel=[channel 1]` for `[Single channel (Select from List)]`. -- For `Multiple Xs (Select from List)` the required options are `X_n X_m`, e.g. - `channel_1 channel_3` for `[Multiple channels (Select from List)]`. -- For `Range of Xs (Specify by Name)` the required option is `process_following_X=n-m`, - e.g. `process_following_channels=1-3` for `[Range of channels (Specify by Name)]`. - -### Third options block: "*how to treat*" - -The *third options block* selects how the components will be processed. - -This options block is relevant for *some* of our BDV functions, for example: - -- `phase_correlation_pairwise_shifts_calculation` -- `optimize_and_apply_shifts` - -Valid choices are: - -- `[treat individually]` -- `group` -- `compare` - -### Fourth options block: details for "*how to treat*" - -The *fourth option block* is ***required*** for each component that has `group` as their -setting in the *third options block*. Exceptions are when e.g. the dataset only has a -single *illumination* (then nothing illumination-related needs to be put into the fourth -block, even if `group` was selected in the third block), or if the first block already -restricted the used data to a single item (for example `Single channel (Select from -List)` was selected in block 1, then nothing channel-related is required in block 4). - -Valid choices are: - -- `Average Ys` -- `Use Y n` - -With `Y` being one of `Angle`, `Channel`, `Illumination`, `Tile`, `Timepoint`. Note the -difference in uppercase / lowercase compared to the `X` from blocks 1 and 2! - -## Examples - -### Example 1 - -Selected options: - -- First block: - - Process angle: all angles - - Process channel: all channels - - Process illumination: all illuminations - - Process tile: all tiles - - Process timepoint: all Timepoints -- Second block: - - (N/A) -- Third block: - - How to treat Angles: treat individually - - How to treat Channels: group - - How to treat Illuminations: group - - How to treat Tiles: compare - - How to treat Timepoints: treat individually -- Fourth block: - - [use Channel 1] - -Resulting macro parameters: - -```text -process_angle=[All angles] -process_channel=[All channels] -process_illumination=[All illuminations] -process_tile=[All tiles] -process_timepoint=[All Timepoints] -method=[Phase Correlation] -show_expert_grouping_options -how_to_treat_angles=[treat individually] -how_to_treat_channels=group -how_to_treat_illuminations=group -how_to_treat_tiles=compare -how_to_treat_timepoints=[treat individually] -channels=[use Channel 1] -``` - -## Example 2 - -Selected options: - -- First block: - - Process angle: All angles - - Process channel: Single channel (Select from List) - - Process illumination: all illuminations - - Process tile: all tiles - - Process timepoint: all Timepoints -- Second block: - - Processing channel: channel 1 -- Third block: - - How to treat Timepoints: treat individually - - How to treat Channels: group - - How to treat Illuminations: group - - How to treat Angles: treat individually - - How to treat Tiles: group -- Fourth block: - - [use Tile 3] - -```text -process_angle=[All angles] -process_channel=[Single channel (Select from List)] -process_illumination=[All illuminations] -process_tile=[All tiles] -process_timepoint=[All Timepoints] -processing_channel=[channel 1] -method=[Phase Correlation] -show_expert_grouping_options -how_to_treat_timepoints=[treat individually] -how_to_treat_channels=group -how_to_treat_illuminations=group -how_to_treat_angles=[treat individually] -how_to_treat_tiles=group -tiles=[use Tile 3] -``` - -## Example 3 - -Selected options: - -- First block: - - Process angle: All angles - - Process channel: Multiple channels (Select from List) - - Process illumination: all illuminations - - Process tile: all tiles - - Process timepoint: all Timepoints -- Second block: - - Processing channel: channel_n channel_m etc -- Third block: - - How to treat Timepoints: treat individually - - How to treat Channels: group - - How to treat Illuminations: group - - How to treat Angles: treat individually - - How to treat Tiles: compare -- Fourth block: - - Average Channels - -```text -process_angle=[All angles] -process_channel=[Multiple channels (Select from List)] -process_illumination=[All illuminations] -process_tile=[All tiles] -process_timepoint=[All Timepoints] -channel_1 -channel_2 -method=[Phase Correlation] -show_expert_grouping_options -how_to_treat_timepoints=[treat individually] -how_to_treat_channels=group -how_to_treat_illuminations=group -how_to_treat_angles=[treat individually] -how_to_treat_tiles=compare -channels=[Average Channels] -``` - -## Example 4 - -- First block: - - Process angle: All angles - - Process channel: Range of channels (Specify by Name) - - Process illumination: all illuminations - - Process tile: all tiles - - Process timepoint: all Timepoints -- Second block: - - Processing following channels: n-m, e.g. 1-3 -- Third block: - - How to treat Timepoints: treat individually - - How to treat Channels: group - - How to treat Illuminations: group - - How to treat Angles: treat individually - - How to treat Tiles: compare -- Fourth block: - - Average Channels - -```text -process_angle=[All angles] -process_channel=[Range of channels (Specify by Name)] -process_illumination=[All illuminations] -process_tile=[All tiles] -process_timepoint=[All Timepoints] -process_following_channels=1-3 -method=[Phase Correlation] -show_expert_grouping_options -how_to_treat_timepoints=[treat individually] -how_to_treat_channels=group -how_to_treat_illuminations=group -how_to_treat_angles=[treat individually] -how_to_treat_tiles=compare -channels=[Average Channels] -``` From 085003928ea3fd2179e272de978c0642ae325ff3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 22:21:47 +0200 Subject: [PATCH 666/678] Merge (and fix) duplicate entries Refers to #34 --- CHANGELOG.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87844b13..683e3640 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,9 +140,6 @@ detailed information, please refer to the updated API documentation. * New `imcflibs.imagej.shading` module for everything background correction. * `imcflibs.imagej.shading.simple_flatfield_correction` to perform a simple flatfield correction to an ImagePlus. -* `imcflibs.imagej.projection.project_stack` to project a stack using - different projection methods, such as `max`, `min`, `mean`, `sum` or - `standard_deviation` using a defined axis. * `imcflibs.imagej.prefs.set_default_ij_options` to configure ImageJ default options for consistency. * New module `imcflibs.imagej.processing` containing utilities for filtering and thresholding: @@ -160,8 +157,9 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.bioformats.get_stage_coords`to get stage coordinates and calibration for one or more given images. -* `imcflibs.imagej.projections.project_stack` to project along a defined axis - using the given projection type. +* `imcflibs.imagej.projections.project_stack` to project a stack along a defined + axis using one of the available projection methods, such as `max`, `min`, + `mean`, `sum` or `standard_deviation`. ### Changed From 9ec87c9908f2239dcb5c42121dc9030b5a980385 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 22:46:01 +0200 Subject: [PATCH 667/678] Minor changes to improve readability Refers to #34 --- CHANGELOG.md | 115 +++++++++++++++++++++++++++------------------------ 1 file changed, 62 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 683e3640..ed5c0f7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,9 @@ detailed information, please refer to the updated API documentation. * `imcflibs.strtools.pad_number` to pad a number with leading zeros. * `imcflibs.pathtools.create_directory` to create a new directory at the - specified path if it does not exist (needed with python 2.7) -* Various additions to `imcflibs.imagej.misc`: + specified path if it does not exist (needed for Python 2.7). + +* **New functions** in `imcflibs.imagej.misc`: * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon completion of long-running scripts. * Sends a mail with job details, such as recipient, file name, execution @@ -24,34 +25,36 @@ detailed information, please refer to the updated API documentation. * If the sender email or SMTP server is not configured, the method logs a message and returns. * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and - various suffixes from an ImagePlus. + various suffixes from an ImagePlus' window title. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. * `imcflibs.imagej.misc.close_images` for closing all ImagePluses from a list. * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that - a selected AutoThreshold method would be using. + a selected _AutoThreshold_ method would be using. * `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an ordered dictionary (or list of ordered dictionaries) to a CSV file. * `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus in a - specified format, such as ImageJ-TIF or OME-TIFF etc., to a given directory. + specified format, such as `ImageJ-TIF` or `OME-TIFF` etc. * `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris - format using the utility ImarisConvert. Method uses - `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the Imaris - installation. -* New functions in `imcflibs.imagej.labelimage`: + format using the utility _ImarisConvert_. The function uses + `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the most + recent Imaris installation. + +* **New functions** in `imcflibs.imagej.labelimage`: * `imcflibs.imagej.labelimage.cookie_cut_labels` to use a label image as a mask for another label image. Objects might get split or merged depending on the mask. * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to get a label image (2D/3D). * `imcflibs.imagej.labelimage.relate_label_images` to relate two label images - (2D/3D) using the 3D Association plugin from the 3DImageJSuite. + (2D/3D) using the _3D Association_ plugin from the 3DImageJSuite. * `imcflibs.imagej.labelimage.dilate_labels_2d` to dilate a label image slice by slice. Works for 2D or 3D images. -* New `imcflibs.imagej.objects3d` submodule, providing: + +* **New submodule** `imcflibs.imagej.objects3d`, providing: * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn an - Objects3DPopulation into an ImagePlus (2D/3D). + _Objects3DPopulation_ into an ImagePlus (2D/3D). * `imcflibs.imagej.objects3d.imgplus_to_population3d` to get the - Objects3DPopulation from an ImagePlus (2D/3D). + _Objects3DPopulation_ from an ImagePlus (2D/3D). * `imcflibs.imagej.objects3d.segment_3d_image` to threshold an image into a labeled stack. * `imcflibs.imagej.objects3d.get_objects_within_intensity` to filter a @@ -60,60 +63,62 @@ detailed information, please refer to the updated API documentation. 3D image. * `imcflibs.imagej.objects3d.seeded_watershed` to perform a seeded watershed segmentation on a binary image using seeds points. -* New `imcflibs.imagej.bdv` submodule, providing BigDataViewer related - functions: - * New classes: - * `imcflibs.imagej.bdv.ProcessingOptions` to store all options on how to - process the dataset. - * `imcflibs.imagej.bdv.DefinitionOptions` to store all options on how to - define the dataset. + +* **New submodule** `imcflibs.imagej.bdv`, providing _BigDataViewer_ related + functionality: + * Option configuration classes: + * `imcflibs.imagej.bdv.ProcessingOptions` to configure the options on how + the dataset should be processed + * `imcflibs.imagej.bdv.DefinitionOptions` to hold the options on how a + dataset is defined. * `imcflibs.imagej.bdv.check_processing_input` to sanitize and clarify the - acitt input selection. + `acitt` input selection. * `imcflibs.imagej.bdv.get_processing_settings` to generate the strings needed for the processing. * `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files. - * `imcflibs.imagej.bdv.define_dataset_auto` to run "Define Multi-View Dataset" - using the "Auto-Loader" option. - * `imcflibs.imagej.bdv.define_dataset_manual` to run "Define Multi-View - Dataset" using the "Manual Loader" option. + * `imcflibs.imagej.bdv.define_dataset_auto` to run "_Define Multi-View + Dataset_" using the "_Auto-Loader_" option. + * `imcflibs.imagej.bdv.define_dataset_manual` to run "_Define Multi-View + Dataset_" using the "_Manual Loader_" option. * `imcflibs.imagej.bdv.resave_as_h5` to resave the dataset in H5 to make it compatible with BigDataViewer/BigStitcher. - * `imcflibs.imagej.bdv.flip_axes` to call BigStitcher's "Flip Axes" command. + * `imcflibs.imagej.bdv.flip_axes` to call BigStitcher's "_Flip Axes_" command. * `imcflibs.imagej.bdv.phase_correlation_pairwise_shifts_calculation` to calculate pairwise shifts using Phase Correlation. * `imcflibs.imagej.bdv.filter_pairwise_shifts` for filtering pairwise shifts based on different thresholds. * `imcflibs.imagej.bdv.optimize_and_apply_shifts` to optimize shifts and apply them to a dataset. - * `imcflibs.imagej.bdv.detect_interest_points` for running the "Detect - Interest Points" command for registration. - * `imcflibs.imagej.bdv.interest_points_registration` to run the "Register - Dataset based on Interest Points" command. + * `imcflibs.imagej.bdv.detect_interest_points` for running the "_Detect + Interest Points_" command for registration. + * `imcflibs.imagej.bdv.interest_points_registration` to run the "_Register + Dataset based on Interest Points_" command. * `imcflibs.imagej.bdv.duplicate_transformations` for duplicating / propagating transformation parameters to other channels. - * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "Fuse Multi-View - Dataset" command. -* New `imcflibs.imagej.trackmate` submodule to provide helper functions to - interface with Trackmate: - * Multiple functions to set up Trackmate settings with different detectors, - such as `imcflibs.imagej.trackmate.cellpose_detector`, - `imcflibs.imagej.trackmate.stardist_detector` or a - `imcflibs.imagej.trackmate.log_detector`. + * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "_Fuse Multi-View + Dataset_" command. + +* **New submodule** `imcflibs.imagej.trackmate` to provide helper functions to + interface with _Trackmate_: + * Multiple functions to set up Trackmate settings with different detectors: + * `imcflibs.imagej.trackmate.cellpose_detector` + * `imcflibs.imagej.trackmate.stardist_detector` + * `imcflibs.imagej.trackmate.log_detector` * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. * `imcflibs.imagej.trackmate.sparse_lap_tracker` to create default settings for the sparse LAP tracker. * `imcflibs.imagej.trackmate.track_filtering` to create settings to filter - detected tracks based upon optional distances, such as maximum linking, gap - closing, track splitting & merging and maximum frame gap. - * `imcflibs.imagej.trackmate.run_trackmate` to run Fiji's Trackmate plugin on - an open ImagePlus with given settings, which can be set up with available - methods in the `imcflibs.imagej.trackmate` submodule. The method then - returns a label image. -* New `imcflibs.imagej.omerotools` submodule, providing helper functions to - connect to OMERO using user credentials, fetch and upload an image, retrieve a - dataset, or save ROIs to OMERO. + detected tracks based upon optional distances, such as `maximum linking`, + `gap closing`, `track splitting & merging` and `maximum frame gap`. + * `imcflibs.imagej.trackmate.run_trackmate` to run Trackmate with given + settings (which can be set up with the methods in the + `imcflibs.imagej.trackmate` submodule) to create a label image. + +* **New submodule** `imcflibs.imagej.omerotools`, providing helper functions to + connect to _OMERO_ using user credentials, fetch and upload an image, retrieve + a dataset or save ROIs to OMERO. * `imcflibs.imagej.omerotools.parse_url` to parse the OMERO URL and get a list of `ImageWrappers` from multiple image or datasets IDs. * `imcflibs.imagej.omerotools.connect` to connect to OMERO using user @@ -124,7 +129,7 @@ detailed information, please refer to the updated API documentation. to OMERO and returning the new image ID. * `imcflibs.imagej.omerotools.add_keyvalue_annotation` to add an annotation to an OMERO object. - * `imcflibs.imagej.omerotools.delete_keyvalue_annotations` to delete + * `imcflibs.imagej.omerotools.delete_keyvalue_annotations` to delete key/value annotations from an OMERO object. * `imcflibs.imagej.omerotools.find_dataset` to find a dataset in OMERO using the dataset ID. @@ -137,12 +142,16 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.omerotools.upload_array_as_omero_table` to upload a table to OMERO. * `imcflibs.imagej.omerotools.save_rois_to_omero` to save ROIs to OMERO. -* New `imcflibs.imagej.shading` module for everything background correction. + +* **New submodule** `imcflibs.imagej.shading` for illumination correction: * `imcflibs.imagej.shading.simple_flatfield_correction` to perform a simple flatfield correction to an ImagePlus. + * `imcflibs.imagej.prefs.set_default_ij_options` to configure ImageJ default - options for consistency. -* New module `imcflibs.imagej.processing` containing utilities for filtering and thresholding: + options to ensure consistent behavior. + +* **New submodule** `imcflibs.imagej.processing` containing utilities for + filtering and thresholding: * `imcflibs.imagej.processing.apply_filter` to apply a filter to an ImagePlus. * `imcflibs.imagej.processing.apply_rollingball_bg_subtraction` to apply a @@ -150,7 +159,7 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.processing.apply_threshold` to apply a threshold method to an ImagePlus. -* Additions to `imcflibs.imagej.bioformats`: +* **New functions** in `imcflibs.imagej.bioformats`: * `imcflibs.imagej.bioformats.export` to export an image to a given file. * `imcflibs.imagej.bioformats.get_metadata_from_file` to extract various metadata from a given file using BioFormats. @@ -235,7 +244,7 @@ detailed information, please refer to the updated API documentation. * The functions below now also accept parameters of type `java.io.File` (instead of `str`), making them safe for being used directly with variables retrieved - via ImageJ2's *Script Parameter* `@# File`: + via ImageJ2's _Script Parameter_ `@# File`: * `imcflibs.pathtools.parse_path` * `imcflibs.strtools.filename` * Several changes in `imcflibs.pathtools.parse_path`: From 7e579b4e4aafe417f639f0a7682e1747774d0edb Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 22:50:32 +0200 Subject: [PATCH 668/678] Group smaller change-collections at the bottom Refers to #34 --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5c0f7c..56c96329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,6 @@ detailed information, please refer to the updated API documentation. ### Added -* `imcflibs.strtools.pad_number` to pad a number with leading zeros. -* `imcflibs.pathtools.create_directory` to create a new directory at the - specified path if it does not exist (needed for Python 2.7). - * **New functions** in `imcflibs.imagej.misc`: * `imcflibs.imagej.misc.send_notification_email` to send email notifications upon completion of long-running scripts. @@ -147,9 +143,6 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.shading.simple_flatfield_correction` to perform a simple flatfield correction to an ImagePlus. -* `imcflibs.imagej.prefs.set_default_ij_options` to configure ImageJ default - options to ensure consistent behavior. - * **New submodule** `imcflibs.imagej.processing` containing utilities for filtering and thresholding: * `imcflibs.imagej.processing.apply_filter` to apply a filter to an @@ -166,6 +159,13 @@ detailed information, please refer to the updated API documentation. * `imcflibs.imagej.bioformats.get_stage_coords`to get stage coordinates and calibration for one or more given images. +* `imcflibs.strtools.pad_number` to pad a number with leading zeros. +* `imcflibs.pathtools.create_directory` to create a new directory at the + specified path if it does not exist (needed for Python 2.7). + +* `imcflibs.imagej.prefs.set_default_ij_options` to configure ImageJ default + options to ensure consistent behavior. + * `imcflibs.imagej.projections.project_stack` to project a stack along a defined axis using one of the available projection methods, such as `max`, `min`, `mean`, `sum` or `standard_deviation`. From 835956ba10b51cbf0ee42cef77241c58e5ab2832 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 23:06:49 +0200 Subject: [PATCH 669/678] Another attempt to improve changelog readability Refers to #34 --- CHANGELOG.md | 224 +++++++++++++++++++++++++++------------------------ 1 file changed, 118 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56c96329..7f9fbc78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,163 +9,175 @@ detailed information, please refer to the updated API documentation. ### Added -* **New functions** in `imcflibs.imagej.misc`: - * `imcflibs.imagej.misc.send_notification_email` to send email notifications +#### New functions in `imcflibs.imagej.misc` + +* `imcflibs.imagej.misc.send_notification_email` to send email notifications upon completion of long-running scripts. - * Sends a mail with job details, such as recipient, file name, execution - time & an optional message. - * To enable email notifications, the following preferences must be set in + * Sends a mail with job details, such as recipient, file name, execution time + & an optional message. + * To enable email notifications, the following preferences must be set in `~/.imagej/IJ_Prefs.txt`: - * `.imcf.sender_email`: sender's email address. - * `.imcf.smtpserver`: the SMTP server used for sending emails. - * If the sender email or SMTP server is not configured, the method logs a + * `.imcf.sender_email`: sender's email address. + * `.imcf.smtpserver`: the SMTP server used for sending emails. + * If the sender email or SMTP server is not configured, the method logs a message and returns. - * `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and +* `imcflibs.imagej.misc.sanitize_image_title` to remove special chars and various suffixes from an ImagePlus' window title. - * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. - * `imcflibs.imagej.misc.close_images` for closing all ImagePluses from a list. - * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that - a selected _AutoThreshold_ method would be using. - * `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an - ordered dictionary (or list of ordered dictionaries) to a CSV file. - * `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus in a +* `imcflibs.imagej.misc.subtract_images` to subtract an image from another. +* `imcflibs.imagej.misc.close_images` for closing all ImagePluses from a list. +* `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a + selected _AutoThreshold_ method would be using. +* `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an ordered + dictionary (or list of ordered dictionaries) to a CSV file. +* `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus in a specified format, such as `ImageJ-TIF` or `OME-TIFF` etc. - * `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris +* `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris format using the utility _ImarisConvert_. The function uses `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the most recent Imaris installation. -* **New functions** in `imcflibs.imagej.labelimage`: - * `imcflibs.imagej.labelimage.cookie_cut_labels` to use a label image as a - mask for another label image. Objects might get split or merged depending on - the mask. - * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image - to get a label image (2D/3D). - * `imcflibs.imagej.labelimage.relate_label_images` to relate two label images +#### New functions in `imcflibs.imagej.labelimage` + +* `imcflibs.imagej.labelimage.cookie_cut_labels` to use a label image as a mask + for another label image. Objects might get split or merged depending on the + mask. +* `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to + get a label image (2D/3D). +* `imcflibs.imagej.labelimage.relate_label_images` to relate two label images (2D/3D) using the _3D Association_ plugin from the 3DImageJSuite. - * `imcflibs.imagej.labelimage.dilate_labels_2d` to dilate a label image slice - by slice. Works for 2D or 3D images. +* `imcflibs.imagej.labelimage.dilate_labels_2d` to dilate a label image slice by + slice. Works for 2D or 3D images. + +#### New submodule `imcflibs.imagej.objects3d` -* **New submodule** `imcflibs.imagej.objects3d`, providing: - * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn an +* `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn an _Objects3DPopulation_ into an ImagePlus (2D/3D). - * `imcflibs.imagej.objects3d.imgplus_to_population3d` to get the +* `imcflibs.imagej.objects3d.imgplus_to_population3d` to get the _Objects3DPopulation_ from an ImagePlus (2D/3D). - * `imcflibs.imagej.objects3d.segment_3d_image` to threshold an image into a +* `imcflibs.imagej.objects3d.segment_3d_image` to threshold an image into a labeled stack. - * `imcflibs.imagej.objects3d.get_objects_within_intensity` to filter a +* `imcflibs.imagej.objects3d.get_objects_within_intensity` to filter a population of 3D objects by intensity. - * `imcflibs.imagej.objects3d.maxima_finder_3d` to find local maxima in a - 3D image. - * `imcflibs.imagej.objects3d.seeded_watershed` to perform a seeded watershed +* `imcflibs.imagej.objects3d.maxima_finder_3d` to find local maxima in a 3D + image. +* `imcflibs.imagej.objects3d.seeded_watershed` to perform a seeded watershed segmentation on a binary image using seeds points. -* **New submodule** `imcflibs.imagej.bdv`, providing _BigDataViewer_ related - functionality: - * Option configuration classes: - * `imcflibs.imagej.bdv.ProcessingOptions` to configure the options on how - the dataset should be processed - * `imcflibs.imagej.bdv.DefinitionOptions` to hold the options on how a - dataset is defined. - * `imcflibs.imagej.bdv.check_processing_input` to sanitize and clarify the +#### New submodule `imcflibs.imagej.bdv` + +Providing _BigDataViewer_ related functionality. + +* Option configuration classes: + * `imcflibs.imagej.bdv.ProcessingOptions` to configure the options on how the + dataset should be processed + * `imcflibs.imagej.bdv.DefinitionOptions` to hold the options on how a dataset + is defined. +* `imcflibs.imagej.bdv.check_processing_input` to sanitize and clarify the `acitt` input selection. - * `imcflibs.imagej.bdv.get_processing_settings` to generate the strings needed +* `imcflibs.imagej.bdv.get_processing_settings` to generate the strings needed for the processing. - * `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files. - * `imcflibs.imagej.bdv.define_dataset_auto` to run "_Define Multi-View - Dataset_" using the "_Auto-Loader_" option. - * `imcflibs.imagej.bdv.define_dataset_manual` to run "_Define Multi-View +* `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files. +* `imcflibs.imagej.bdv.define_dataset_auto` to run "_Define Multi-View Dataset_" + using the "_Auto-Loader_" option. +* `imcflibs.imagej.bdv.define_dataset_manual` to run "_Define Multi-View Dataset_" using the "_Manual Loader_" option. - * `imcflibs.imagej.bdv.resave_as_h5` to resave the dataset in H5 to make it +* `imcflibs.imagej.bdv.resave_as_h5` to resave the dataset in H5 to make it compatible with BigDataViewer/BigStitcher. - * `imcflibs.imagej.bdv.flip_axes` to call BigStitcher's "_Flip Axes_" command. - * `imcflibs.imagej.bdv.phase_correlation_pairwise_shifts_calculation` to +* `imcflibs.imagej.bdv.flip_axes` to call BigStitcher's "_Flip Axes_" command. +* `imcflibs.imagej.bdv.phase_correlation_pairwise_shifts_calculation` to calculate pairwise shifts using Phase Correlation. - * `imcflibs.imagej.bdv.filter_pairwise_shifts` for filtering pairwise shifts +* `imcflibs.imagej.bdv.filter_pairwise_shifts` for filtering pairwise shifts based on different thresholds. - * `imcflibs.imagej.bdv.optimize_and_apply_shifts` to optimize shifts and apply +* `imcflibs.imagej.bdv.optimize_and_apply_shifts` to optimize shifts and apply them to a dataset. - * `imcflibs.imagej.bdv.detect_interest_points` for running the "_Detect - Interest Points_" command for registration. - * `imcflibs.imagej.bdv.interest_points_registration` to run the "_Register +* `imcflibs.imagej.bdv.detect_interest_points` for running the "_Detect Interest + Points_" command for registration. +* `imcflibs.imagej.bdv.interest_points_registration` to run the "_Register Dataset based on Interest Points_" command. - * `imcflibs.imagej.bdv.duplicate_transformations` for duplicating / - propagating transformation parameters to other channels. - * `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "_Fuse Multi-View +* `imcflibs.imagej.bdv.duplicate_transformations` for duplicating / propagating + transformation parameters to other channels. +* `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "_Fuse Multi-View Dataset_" command. -* **New submodule** `imcflibs.imagej.trackmate` to provide helper functions to - interface with _Trackmate_: - * Multiple functions to set up Trackmate settings with different detectors: - * `imcflibs.imagej.trackmate.cellpose_detector` - * `imcflibs.imagej.trackmate.stardist_detector` - * `imcflibs.imagej.trackmate.log_detector` - * `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter +#### New submodule `imcflibs.imagej.trackmate` + +Providing helper functions to interface with _Trackmate_. + +* Multiple functions to set up Trackmate settings with different detectors: + * `imcflibs.imagej.trackmate.cellpose_detector` + * `imcflibs.imagej.trackmate.stardist_detector` + * `imcflibs.imagej.trackmate.log_detector` +* `imcflibs.imagej.trackmate.spot_filtering` to create settings to filter detected spots based on optional thresholds for quality, area, circularity & intensity. - * `imcflibs.imagej.trackmate.sparse_lap_tracker` to create default settings - for the sparse LAP tracker. - * `imcflibs.imagej.trackmate.track_filtering` to create settings to filter +* `imcflibs.imagej.trackmate.sparse_lap_tracker` to create default settings for + the sparse LAP tracker. +* `imcflibs.imagej.trackmate.track_filtering` to create settings to filter detected tracks based upon optional distances, such as `maximum linking`, `gap closing`, `track splitting & merging` and `maximum frame gap`. - * `imcflibs.imagej.trackmate.run_trackmate` to run Trackmate with given - settings (which can be set up with the methods in the - `imcflibs.imagej.trackmate` submodule) to create a label image. - -* **New submodule** `imcflibs.imagej.omerotools`, providing helper functions to - connect to _OMERO_ using user credentials, fetch and upload an image, retrieve - a dataset or save ROIs to OMERO. - * `imcflibs.imagej.omerotools.parse_url` to parse the OMERO URL and get a list +* `imcflibs.imagej.trackmate.run_trackmate` to run Trackmate with given settings + (which can be set up with the methods in the `imcflibs.imagej.trackmate` + submodule) to create a label image. + +#### New submodule `imcflibs.imagej.omerotools` + +Providing helper functions to connect to _OMERO_ using user credentials, fetch +and upload images, retrieve datasets or save ROIs to OMERO. + +* `imcflibs.imagej.omerotools.parse_url` to parse the OMERO URL and get a list of `ImageWrappers` from multiple image or datasets IDs. - * `imcflibs.imagej.omerotools.connect` to connect to OMERO using user +* `imcflibs.imagej.omerotools.connect` to connect to OMERO using user credentials. - * `imcflibs.imagej.omerotools.fetch_image` to fetch an image from OMERO using +* `imcflibs.imagej.omerotools.fetch_image` to fetch an image from OMERO using the image ID. - * `imcflibs.imagej.omerotools.upload_image_to_omero` to upload a local image - to OMERO and returning the new image ID. - * `imcflibs.imagej.omerotools.add_keyvalue_annotation` to add an annotation to +* `imcflibs.imagej.omerotools.upload_image_to_omero` to upload a local image to + OMERO and returning the new image ID. +* `imcflibs.imagej.omerotools.add_keyvalue_annotation` to add an annotation to an OMERO object. - * `imcflibs.imagej.omerotools.delete_keyvalue_annotations` to delete key/value +* `imcflibs.imagej.omerotools.delete_keyvalue_annotations` to delete key/value annotations from an OMERO object. - * `imcflibs.imagej.omerotools.find_dataset` to find a dataset in OMERO using - the dataset ID. - * `imcflibs.imagej.get_acquisition_metadata` to get the acquisition metadata +* `imcflibs.imagej.omerotools.find_dataset` to find a dataset in OMERO using the + dataset ID. +* `imcflibs.imagej.get_acquisition_metadata` to get the acquisition metadata from an image in OMERO. - * `imcflibs.imagej.omerotools.get_info_from_original_metadata` to get the +* `imcflibs.imagej.omerotools.get_info_from_original_metadata` to get the original metadata from an image in OMERO. - * `imcflibs.imagej.omerotools.create_table_columns` to create OMERO table +* `imcflibs.imagej.omerotools.create_table_columns` to create OMERO table headings from a list of column names. - * `imcflibs.imagej.omerotools.upload_array_as_omero_table` to upload a table - to OMERO. - * `imcflibs.imagej.omerotools.save_rois_to_omero` to save ROIs to OMERO. +* `imcflibs.imagej.omerotools.upload_array_as_omero_table` to upload a table to + OMERO. +* `imcflibs.imagej.omerotools.save_rois_to_omero` to save ROIs to OMERO. -* **New submodule** `imcflibs.imagej.shading` for illumination correction: - * `imcflibs.imagej.shading.simple_flatfield_correction` to perform a - simple flatfield correction to an ImagePlus. +#### New submodule `imcflibs.imagej.shading` -* **New submodule** `imcflibs.imagej.processing` containing utilities for - filtering and thresholding: - * `imcflibs.imagej.processing.apply_filter` to apply a filter to an - ImagePlus. - * `imcflibs.imagej.processing.apply_rollingball_bg_subtraction` to apply a +* `imcflibs.imagej.shading.simple_flatfield_correction` to perform a simple + flatfield correction to an ImagePlus. + +#### New submodule `imcflibs.imagej.processing` + +Utilities for filtering and thresholding. + +* `imcflibs.imagej.processing.apply_filter` to apply a filter to an ImagePlus. +* `imcflibs.imagej.processing.apply_rollingball_bg_subtraction` to apply a rolling ball background subtraction to an ImagePlus. - * `imcflibs.imagej.processing.apply_threshold` to apply a threshold method to - an ImagePlus. +* `imcflibs.imagej.processing.apply_threshold` to apply a threshold method to an + ImagePlus. -* **New functions** in `imcflibs.imagej.bioformats`: - * `imcflibs.imagej.bioformats.export` to export an image to a given file. - * `imcflibs.imagej.bioformats.get_metadata_from_file` to extract various +#### New functions in `imcflibs.imagej.bioformats` + +* `imcflibs.imagej.bioformats.export` to export an image to a given file. +* `imcflibs.imagej.bioformats.get_metadata_from_file` to extract various metadata from a given file using BioFormats. - * `imcflibs.imagej.bioformats.get_stage_coords`to get stage coordinates and +* `imcflibs.imagej.bioformats.get_stage_coords`to get stage coordinates and calibration for one or more given images. +#### Other new functions + * `imcflibs.strtools.pad_number` to pad a number with leading zeros. * `imcflibs.pathtools.create_directory` to create a new directory at the specified path if it does not exist (needed for Python 2.7). - * `imcflibs.imagej.prefs.set_default_ij_options` to configure ImageJ default options to ensure consistent behavior. - * `imcflibs.imagej.projections.project_stack` to project a stack along a defined axis using one of the available projection methods, such as `max`, `min`, `mean`, `sum` or `standard_deviation`. From 438a8be41c9b4c1dabb5e557cde1c7620d52c68a Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 8 Apr 2025 23:11:48 +0200 Subject: [PATCH 670/678] Update intro section Refers to #34 --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9fbc78..5d74c949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,11 @@ ## 1.5.0 -As this is a major release, not all changes and functions are listed below. For -detailed information, please refer to the updated API documentation. +This release brings a lot of additions, not all changes and functions are +explained in depth below. For detailed information, please refer to the [updated +API documentation][apidocs]. + +[apidocs]: https://imcf.one/apidocs/imcflibs/imcflibs.html ### Added From 059db6a7dfd381c6e247bdcf14e021e8f130f153 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Wed, 9 Apr 2025 09:15:46 +0200 Subject: [PATCH 671/678] Fix changelog broken link --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d74c949..d5d29524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,7 +141,7 @@ and upload images, retrieve datasets or save ROIs to OMERO. annotations from an OMERO object. * `imcflibs.imagej.omerotools.find_dataset` to find a dataset in OMERO using the dataset ID. -* `imcflibs.imagej.get_acquisition_metadata` to get the acquisition metadata +* `imcflibs.imagej.omerotools.get_acquisition_metadata` to get the acquisition metadata from an image in OMERO. * `imcflibs.imagej.omerotools.get_info_from_original_metadata` to get the original metadata from an image in OMERO. From f46db33fff2fdfa38d59c9c68eaba2ad0bcb29cf Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 9 Apr 2025 09:18:35 +0200 Subject: [PATCH 672/678] Switch .md emphasis style to asterisks As pdoc is using the "code-friendly" markdown syntax, using underscores will not render properly. This pragma tells markdownlint to use asterisks instead. References: https://pdoc.dev/docs/pdoc.html#markdown-support https://github.com/trentm/python-markdown2/wiki/code-friendly Refers to #34 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d74c949..48782013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ๐Ÿงพ + ## 1.5.0 From 6148f752d7e0f2ca914ea2df76d5236db11e9659 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 9 Apr 2025 09:18:49 +0200 Subject: [PATCH 673/678] Move markdownlint pragmas to start of file --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48782013..684c75f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ -# Changelog ๐Ÿงพ - +# Changelog ๐Ÿงพ + ## 1.5.0 This release brings a lot of additions, not all changes and functions are From cee57bb803a6147fcb7a63c2badb4a3519a8f184 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 9 Apr 2025 09:19:09 +0200 Subject: [PATCH 674/678] Reformat document for new emphasis syntax Refers to #34 --- CHANGELOG.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 684c75f6..bcb4872f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,13 +30,13 @@ API documentation][apidocs]. * `imcflibs.imagej.misc.subtract_images` to subtract an image from another. * `imcflibs.imagej.misc.close_images` for closing all ImagePluses from a list. * `imcflibs.imagej.misc.get_threshold_value_from_method` to get the value that a - selected _AutoThreshold_ method would be using. + selected *AutoThreshold* method would be using. * `imcflibs.imagej.misc.write_ordereddict_to_csv` to write data from an ordered dictionary (or list of ordered dictionaries) to a CSV file. * `imcflibs.imagej.misc.save_image_in_format` to save an ImagePlus in a specified format, such as `ImageJ-TIF` or `OME-TIFF` etc. * `imcflibs.imagej.misc.run_imarisconvert` to convert a given file to Imaris - format using the utility _ImarisConvert_. The function uses + format using the utility *ImarisConvert*. The function uses `imcflibs.imagej.misc.locate_latest_imaris` to find the path to the most recent Imaris installation. @@ -48,16 +48,16 @@ API documentation][apidocs]. * `imcflibs.imagej.labelimage.binary_to_label` for segmenting a binary image to get a label image (2D/3D). * `imcflibs.imagej.labelimage.relate_label_images` to relate two label images - (2D/3D) using the _3D Association_ plugin from the 3DImageJSuite. + (2D/3D) using the *3D Association* plugin from the 3DImageJSuite. * `imcflibs.imagej.labelimage.dilate_labels_2d` to dilate a label image slice by slice. Works for 2D or 3D images. #### New submodule `imcflibs.imagej.objects3d` * `imcflibs.imagej.objects3d.population3d_to_imgplus` to turn an - _Objects3DPopulation_ into an ImagePlus (2D/3D). + *Objects3DPopulation* into an ImagePlus (2D/3D). * `imcflibs.imagej.objects3d.imgplus_to_population3d` to get the - _Objects3DPopulation_ from an ImagePlus (2D/3D). + *Objects3DPopulation* from an ImagePlus (2D/3D). * `imcflibs.imagej.objects3d.segment_3d_image` to threshold an image into a labeled stack. * `imcflibs.imagej.objects3d.get_objects_within_intensity` to filter a @@ -69,7 +69,7 @@ API documentation][apidocs]. #### New submodule `imcflibs.imagej.bdv` -Providing _BigDataViewer_ related functionality. +Providing *BigDataViewer* related functionality. * Option configuration classes: * `imcflibs.imagej.bdv.ProcessingOptions` to configure the options on how the @@ -81,31 +81,31 @@ Providing _BigDataViewer_ related functionality. * `imcflibs.imagej.bdv.get_processing_settings` to generate the strings needed for the processing. * `imcflibs.imagej.bdv.backup_xml_files` to create a backup of BDV-XML files. -* `imcflibs.imagej.bdv.define_dataset_auto` to run "_Define Multi-View Dataset_" - using the "_Auto-Loader_" option. -* `imcflibs.imagej.bdv.define_dataset_manual` to run "_Define Multi-View - Dataset_" using the "_Manual Loader_" option. +* `imcflibs.imagej.bdv.define_dataset_auto` to run "*Define Multi-View Dataset*" + using the "*Auto-Loader*" option. +* `imcflibs.imagej.bdv.define_dataset_manual` to run "*Define Multi-View + Dataset*" using the "*Manual Loader*" option. * `imcflibs.imagej.bdv.resave_as_h5` to resave the dataset in H5 to make it compatible with BigDataViewer/BigStitcher. -* `imcflibs.imagej.bdv.flip_axes` to call BigStitcher's "_Flip Axes_" command. +* `imcflibs.imagej.bdv.flip_axes` to call BigStitcher's "*Flip Axes*" command. * `imcflibs.imagej.bdv.phase_correlation_pairwise_shifts_calculation` to calculate pairwise shifts using Phase Correlation. * `imcflibs.imagej.bdv.filter_pairwise_shifts` for filtering pairwise shifts based on different thresholds. * `imcflibs.imagej.bdv.optimize_and_apply_shifts` to optimize shifts and apply them to a dataset. -* `imcflibs.imagej.bdv.detect_interest_points` for running the "_Detect Interest - Points_" command for registration. -* `imcflibs.imagej.bdv.interest_points_registration` to run the "_Register - Dataset based on Interest Points_" command. +* `imcflibs.imagej.bdv.detect_interest_points` for running the "*Detect Interest + Points*" command for registration. +* `imcflibs.imagej.bdv.interest_points_registration` to run the "*Register + Dataset based on Interest Points*" command. * `imcflibs.imagej.bdv.duplicate_transformations` for duplicating / propagating transformation parameters to other channels. -* `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "_Fuse Multi-View - Dataset_" command. +* `imcflibs.imagej.bdv.fuse_dataset` to call BigStitcher's "*Fuse Multi-View + Dataset*" command. #### New submodule `imcflibs.imagej.trackmate` -Providing helper functions to interface with _Trackmate_. +Providing helper functions to interface with *Trackmate*. * Multiple functions to set up Trackmate settings with different detectors: * `imcflibs.imagej.trackmate.cellpose_detector` @@ -125,7 +125,7 @@ Providing helper functions to interface with _Trackmate_. #### New submodule `imcflibs.imagej.omerotools` -Providing helper functions to connect to _OMERO_ using user credentials, fetch +Providing helper functions to connect to *OMERO* using user credentials, fetch and upload images, retrieve datasets or save ROIs to OMERO. * `imcflibs.imagej.omerotools.parse_url` to parse the OMERO URL and get a list @@ -260,7 +260,7 @@ Utilities for filtering and thresholding. * The functions below now also accept parameters of type `java.io.File` (instead of `str`), making them safe for being used directly with variables retrieved - via ImageJ2's _Script Parameter_ `@# File`: + via ImageJ2's *Script Parameter* `@# File`: * `imcflibs.pathtools.parse_path` * `imcflibs.strtools.filename` * Several changes in `imcflibs.pathtools.parse_path`: From 1bc20c0a9e6b37bca81c30b97be20a654a5105c3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 9 Apr 2025 09:19:44 +0200 Subject: [PATCH 675/678] Bump to next development cycle Signed-off-by: Niko Ehrenfeuchter --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 71e8518e..50e2d94e 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ ch.unibas.biozentrum.imcf python-imcflibs - 1.5.0-SNAPSHOT + 1.5.1.a26-SNAPSHOT python-imcflibs From 301f022e78fc7df42f8d454042e8566d8aa10d4f Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 9 Apr 2025 09:30:14 +0200 Subject: [PATCH 676/678] Fix fully-qualified function reference Refers to #34 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcb4872f..7503eb67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -142,8 +142,8 @@ and upload images, retrieve datasets or save ROIs to OMERO. annotations from an OMERO object. * `imcflibs.imagej.omerotools.find_dataset` to find a dataset in OMERO using the dataset ID. -* `imcflibs.imagej.get_acquisition_metadata` to get the acquisition metadata - from an image in OMERO. +* `imcflibs.imagej.omerotools.get_acquisition_metadata` to get the acquisition + metadata from an image in OMERO. * `imcflibs.imagej.omerotools.get_info_from_original_metadata` to get the original metadata from an image in OMERO. * `imcflibs.imagej.omerotools.create_table_columns` to create OMERO table From de087cd9505b4011cf1459015d85a1fe6047ba46 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 9 Apr 2025 10:29:56 +0200 Subject: [PATCH 677/678] Explain check-if-release.sh --- .github/workflows/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 96357707..cfb7b5c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,6 +65,11 @@ jobs: - name: ๐Ÿ•ต Inspect if this is a proper "scijava-scripts" release run: scripts/check-if-release.sh + # This will make sure the file 'release.properties' exists, meaning + # `release-version.sh` from the 'scijava-scripts' repo has been run to + # prepare the release and modify 'pom.xml' (which is in turn + # required by the local 'scripts/run-poetry.sh' script for building the + # Python package through Poetry. - name: ๐Ÿ Set up Python uses: actions/setup-python@v5 From caf69f797a722d15ee82233d90b798f283f81f77 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Wed, 9 Apr 2025 10:32:05 +0200 Subject: [PATCH 678/678] Only run "publish" workflow on (pre-) releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running it on pushes and / or PR's will make the Poetry ๐ŸŽญ build fail as that one needs changes in 'pom.xml' that are expected to be done by the SciJava release scripts. Hence this workflow only makes sense when actually cutting a release or pre-release. --- .github/workflows/build.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cfb7b5c8..de54ef54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,16 +1,18 @@ name: ๐Ÿšš๐ŸŒ Publish to ๐Ÿ”ฌโ˜• SciJava and ๐ŸŽช PyPI on: + + release: + types: + - published # A release, pre-release, or draft of a release was published. + workflow_dispatch: - push: - branches: - - master - tags: - - "python-imcflibs-[0-9]+.*" - pull_request: - branches: - - master + ## Do NOT run on pushes or PR's, as they won't have a 'release.properties', + ## which is required by the build-tooling (see the comment at the "Inspect" / + ## "check-if-release.sh" section below for details). + # push: + # pull_request: jobs: