From 3ca94fa31848bbf617ce66bbf3940acfe89c3b0b Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 31 Mar 2026 15:03:52 +0200 Subject: [PATCH 01/10] feat: Enhance rolling ball background subtraction with additional options --- src/imcflibs/imagej/processing.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index 41a6b9ae..767e2a63 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -66,7 +66,14 @@ def apply_filter(imp, filter_method, filter_radius, do_3d=False): return imageplus -def apply_rollingball_bg_subtraction(imp, rolling_ball_radius, do_3d=False): +def apply_rollingball_bg_subtraction( + imp, + rolling_ball_radius, + light_background=False, + sliding=False, + disable=False, + do_3d=False, +): """Perform background subtraction using a rolling ball method. Parameters @@ -75,6 +82,12 @@ def apply_rollingball_bg_subtraction(imp, rolling_ball_radius, do_3d=False): Input ImagePlus to filter and threshold rolling_ball_radius : int Radius of the rolling ball filter to use + light_background : bool, optional + If set to True, will treat the background as light, by default False + sliding : bool, optional + If set to True, will do a sliding window approach, by default False + disable : bool, optional + If set to True, will disable the smoothing, by default False do_3d : bool, optional If set to True, will do a 3D filtering, by default False @@ -85,7 +98,18 @@ def apply_rollingball_bg_subtraction(imp, rolling_ball_radius, do_3d=False): """ log.info("Applying rolling ball with radius %d" % rolling_ball_radius) - options = "rolling=" + str(rolling_ball_radius) + " stack" if do_3d else "" + option_parts = ["rolling=" + str(rolling_ball_radius)] + + if light_background: + option_parts.append("light") + if sliding: + option_parts.append("sliding") + if disable: + option_parts.append("disable") + if do_3d: + option_parts.append("stack") + + options = " ".join(option_parts) log.debug("Background subtraction options: %s" % options) From 6659e1188eeada0879dc5c9545d90603e769443f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 31 Mar 2026 15:09:03 +0200 Subject: [PATCH 02/10] fix: Correct spelling of 'Subtract Background' in rolling ball background subtraction function --- 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 767e2a63..910e865c 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -114,7 +114,7 @@ def apply_rollingball_bg_subtraction( log.debug("Background subtraction options: %s" % options) imageplus = imp.duplicate() - IJ.run(imageplus, "Substract Background...", options) + IJ.run(imageplus, "Subtract Background...", options) return imageplus From cd48fac5743c3b748e96fde4b68d8cf2aa018791 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 31 Mar 2026 15:19:15 +0200 Subject: [PATCH 03/10] refactor: Extract rolling ball options into a separate function for clarity --- src/imcflibs/imagej/processing.py | 39 +++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index 910e865c..0aa223c1 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -98,18 +98,13 @@ def apply_rollingball_bg_subtraction( """ log.info("Applying rolling ball with radius %d" % rolling_ball_radius) - option_parts = ["rolling=" + str(rolling_ball_radius)] - - if light_background: - option_parts.append("light") - if sliding: - option_parts.append("sliding") - if disable: - option_parts.append("disable") - if do_3d: - option_parts.append("stack") - - options = " ".join(option_parts) + options = rolling_ball_options( + rolling_ball_radius, + light_background=light_background, + sliding=sliding, + disable=disable, + do_3d=do_3d, + ) log.debug("Background subtraction options: %s" % options) @@ -119,6 +114,26 @@ def apply_rollingball_bg_subtraction( return imageplus +def rolling_ball_options( + rolling_ball_radius, + light_background=False, + sliding=False, + disable=False, + do_3d=False, +): + """Return the option string for rolling ball background subtraction.""" + parts = ["rolling=" + str(rolling_ball_radius)] + if light_background: + parts.append("light") + if sliding: + parts.append("sliding") + if disable: + parts.append("disable") + if do_3d: + parts.append("stack") + return " ".join(parts) + + def apply_threshold(imp, threshold_method, do_3d=True): """Apply a threshold method to the input ImagePlus. From edaa79e262a37afecdc86be0155a0b058c2045e3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Apr 2026 17:29:35 +0200 Subject: [PATCH 04/10] Update docstring --- 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 0aa223c1..2a7abf9e 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -121,7 +121,7 @@ def rolling_ball_options( disable=False, do_3d=False, ): - """Return the option string for rolling ball background subtraction.""" + """Generate the options for the "Subtract Background..." macro command.""" parts = ["rolling=" + str(rolling_ball_radius)] if light_background: parts.append("light") From 373a05c51a3f2b4e173fbac77aa4d29b63dbb0b3 Mon Sep 17 00:00:00 2001 From: Niko Ehrenfeuchter Date: Tue, 7 Apr 2026 17:32:11 +0200 Subject: [PATCH 05/10] Add test for the rolling_ball_options function --- tests/test_processing.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/test_processing.py diff --git a/tests/test_processing.py b/tests/test_processing.py new file mode 100644 index 00000000..5614826a --- /dev/null +++ b/tests/test_processing.py @@ -0,0 +1,9 @@ +"""Tests for the imcflibs.imagej.processing module.""" + +from imcflibs.imagej.processing import rolling_ball_options + + +def test_rolling_ball_options(): + """Test the rolling_ball_options function.""" + options = rolling_ball_options(42.23) + assert options == "rolling=42.23" From 4a4a10bba2b523fc794fc15ef77a56141cd5eeaa Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 14 Apr 2026 11:08:57 +0200 Subject: [PATCH 06/10] Add pure helpers for processing options --- src/imcflibs/imagej/processing.py | 50 +++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index 2a7abf9e..084ff48c 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -9,6 +9,37 @@ from ..log import LOG as log +def filter_options(filter_method, filter_radius, do_3d=False): + """Build the ImageJ filter command and options strings.""" + + if do_3d: + filter_name = filter_method + " 3D..." + else: + filter_name = filter_method + "..." + + options = ( + "sigma=" + if filter_method == "Gaussian Blur" + else "radius=" + str(filter_radius) + " stack" + ) + + return filter_name, options + + +def threshold_options(threshold_method, do_3d=True): + """Build the ImageJ threshold option strings.""" + + auto_threshold_options = ( + threshold_method + " " + "dark" + " " + "stack" if do_3d else "" + ) + + convert_to_binary_options = ( + "method=" + threshold_method + " " + "background=Dark" + " " + "black" + ) + + return auto_threshold_options, convert_to_binary_options + + def apply_filter(imp, filter_method, filter_radius, do_3d=False): """Make a specific filter followed by a threshold method of choice. @@ -47,16 +78,7 @@ def apply_filter(imp, filter_method, filter_radius, do_3d=False): "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" - ) + filter, options = filter_options(filter_method, filter_radius, do_3d=do_3d) log.debug("Filter: <%s> with options <%s>" % (filter, options)) @@ -156,18 +178,14 @@ def apply_threshold(imp, threshold_method, do_3d=True): imageplus = imp.duplicate() - auto_threshold_options = ( - threshold_method + " " + "dark" + " " + "stack" if do_3d else "" + auto_threshold_options, convert_to_binary_options = threshold_options( + threshold_method, do_3d=do_3d ) 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) From c76580d1d2d832206e988b7770d9f8b7b318f35e Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 14 Apr 2026 11:09:24 +0200 Subject: [PATCH 07/10] Test processing option strings --- tests/test_processing.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/test_processing.py b/tests/test_processing.py index 5614826a..1844b545 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -1,9 +1,43 @@ """Tests for the imcflibs.imagej.processing module.""" -from imcflibs.imagej.processing import rolling_ball_options +from imcflibs.imagej.processing import ( + filter_options, + rolling_ball_options, + threshold_options, +) def test_rolling_ball_options(): """Test the rolling_ball_options function.""" + options = rolling_ball_options(42.23) assert options == "rolling=42.23" + + +def test_rolling_ball_options_with_flags(): + """Test `rolling_ball_options()` string concatenation with all flags.""" + + options = rolling_ball_options( + 12, + light_background=True, + sliding=True, + disable=True, + do_3d=True, + ) + assert options == "rolling=12 light sliding disable stack" + + +def test_filter_options(): + """Test `filter_options()` string concatenation.""" + + command, options = filter_options("Mean", 5, do_3d=True) + assert command == "Mean 3D..." + assert options == "radius=5 stack" + + +def test_threshold_options(): + """Test `threshold_options()` string concatenation.""" + + auto_threshold, convert_to_binary = threshold_options("Otsu", do_3d=True) + assert auto_threshold == "Otsu dark stack" + assert convert_to_binary == "method=Otsu background=Dark black" From 9fe0a640f6d74af106b13df7381af402380e1000 Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Tue, 14 Apr 2026 11:26:49 +0200 Subject: [PATCH 08/10] Add processing helper coverage --- tests/test_processing.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_processing.py b/tests/test_processing.py index 1844b545..be2eb0d4 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -35,9 +35,25 @@ def test_filter_options(): assert options == "radius=5 stack" +def test_filter_options_gaussian_blur(): + """Test `filter_options()` with the Gaussian Blur branch.""" + + command, options = filter_options("Gaussian Blur", 5) + assert command == "Gaussian Blur..." + assert options == "sigma=" + + def test_threshold_options(): """Test `threshold_options()` string concatenation.""" auto_threshold, convert_to_binary = threshold_options("Otsu", do_3d=True) assert auto_threshold == "Otsu dark stack" assert convert_to_binary == "method=Otsu background=Dark black" + + +def test_threshold_options_without_stack(): + """Test `threshold_options()` when 3D stacking is disabled.""" + + auto_threshold, convert_to_binary = threshold_options("Otsu", do_3d=False) + assert auto_threshold == "" + assert convert_to_binary == "method=Otsu background=Dark black" From 27f4165af3368aefb30e2cf226cbdf557669204f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 20 Apr 2026 12:20:05 +0200 Subject: [PATCH 09/10] Enhance docstrings for filter and threshold options functions Update parameter names for clarity --- src/imcflibs/imagej/processing.py | 78 +++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index 084ff48c..7a30573b 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -10,24 +10,53 @@ def filter_options(filter_method, filter_radius, do_3d=False): - """Build the ImageJ filter command and options strings.""" + """Build the ImageJ filter command and options strings. + + Parameters + ---------- + filter_method : str + Name of the filter method to use + filter_radius : int + Radius of the filter to use + do_3d : bool, optional + If set to True, will do a 3D filtering, by default False + + Returns + ------- + tuple[str, str] + The filter name and options strings + """ if do_3d: filter_name = filter_method + " 3D..." else: filter_name = filter_method + "..." - options = ( - "sigma=" - if filter_method == "Gaussian Blur" - else "radius=" + str(filter_radius) + " stack" - ) + if filter_method == "Gaussian Blur": + options = "sigma=" + str(filter_radius) + " stack" + else: + options = "radius=" + str(filter_radius) + " stack" return filter_name, options def threshold_options(threshold_method, do_3d=True): - """Build the ImageJ threshold option strings.""" + """Build the ImageJ threshold option strings. + + Parameters + ---------- + 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 + ------- + tuple[str, str] + The auto threshold options and the convert to binary options strings + + """ auto_threshold_options = ( threshold_method + " " + "dark" + " " + "stack" if do_3d else "" @@ -93,7 +122,7 @@ def apply_rollingball_bg_subtraction( rolling_ball_radius, light_background=False, sliding=False, - disable=False, + disable_smooth=False, do_3d=False, ): """Perform background subtraction using a rolling ball method. @@ -108,7 +137,7 @@ def apply_rollingball_bg_subtraction( If set to True, will treat the background as light, by default False sliding : bool, optional If set to True, will do a sliding window approach, by default False - disable : bool, optional + disable_smooth : bool, optional If set to True, will disable the smoothing, by default False do_3d : bool, optional If set to True, will do a 3D filtering, by default False @@ -124,7 +153,7 @@ def apply_rollingball_bg_subtraction( rolling_ball_radius, light_background=light_background, sliding=sliding, - disable=disable, + disable_smooth=disable_smooth, do_3d=do_3d, ) @@ -140,16 +169,36 @@ def rolling_ball_options( rolling_ball_radius, light_background=False, sliding=False, - disable=False, + disable_smooth=False, do_3d=False, ): - """Generate the options for the "Subtract Background..." macro command.""" + """Generate the options for the "Subtract Background..." macro command. + + Parameters + ---------- + rolling_ball_radius : int + Radius of the rolling ball filter to use + light_background : bool, optional + If set to True, will treat the background as light, by default False + sliding : bool, optional + If set to True, will do a sliding window approach, by default False + disable_smooth : bool, optional + If set to True, will disable the smoothing, by default False + do_3d : bool, optional + If set to True, will do a 3D filtering, by default False + + Returns + ------- + str + The options string for the "Subtract Background..." macro command + + """ parts = ["rolling=" + str(rolling_ball_radius)] if light_background: parts.append("light") if sliding: parts.append("sliding") - if disable: + if disable_smooth: parts.append("disable") if do_3d: parts.append("stack") @@ -166,7 +215,8 @@ def apply_threshold(imp, threshold_method, do_3d=True): 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 + If set to True, the automatic threshold will be done on a 3D stack, + by default True Returns ------- From 56d003b9c7197695bc88904c05d95bda225c122f Mon Sep 17 00:00:00 2001 From: Laurent Guerard Date: Mon, 20 Apr 2026 12:25:30 +0200 Subject: [PATCH 10/10] Fix parameter name for disable smoothing in rolling ball functions and tests --- src/imcflibs/imagej/processing.py | 12 ++++++------ tests/test_processing.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/imcflibs/imagej/processing.py b/src/imcflibs/imagej/processing.py index 7a30573b..1d902828 100644 --- a/src/imcflibs/imagej/processing.py +++ b/src/imcflibs/imagej/processing.py @@ -122,7 +122,7 @@ def apply_rollingball_bg_subtraction( rolling_ball_radius, light_background=False, sliding=False, - disable_smooth=False, + disable_smoothing=False, do_3d=False, ): """Perform background subtraction using a rolling ball method. @@ -137,7 +137,7 @@ def apply_rollingball_bg_subtraction( If set to True, will treat the background as light, by default False sliding : bool, optional If set to True, will do a sliding window approach, by default False - disable_smooth : bool, optional + disable_smoothing : bool, optional If set to True, will disable the smoothing, by default False do_3d : bool, optional If set to True, will do a 3D filtering, by default False @@ -153,7 +153,7 @@ def apply_rollingball_bg_subtraction( rolling_ball_radius, light_background=light_background, sliding=sliding, - disable_smooth=disable_smooth, + disable_smoothing=disable_smoothing, do_3d=do_3d, ) @@ -169,7 +169,7 @@ def rolling_ball_options( rolling_ball_radius, light_background=False, sliding=False, - disable_smooth=False, + disable_smoothing=False, do_3d=False, ): """Generate the options for the "Subtract Background..." macro command. @@ -182,7 +182,7 @@ def rolling_ball_options( If set to True, will treat the background as light, by default False sliding : bool, optional If set to True, will do a sliding window approach, by default False - disable_smooth : bool, optional + disable_smoothing : bool, optional If set to True, will disable the smoothing, by default False do_3d : bool, optional If set to True, will do a 3D filtering, by default False @@ -198,7 +198,7 @@ def rolling_ball_options( parts.append("light") if sliding: parts.append("sliding") - if disable_smooth: + if disable_smoothing: parts.append("disable") if do_3d: parts.append("stack") diff --git a/tests/test_processing.py b/tests/test_processing.py index be2eb0d4..0a4ee2bf 100644 --- a/tests/test_processing.py +++ b/tests/test_processing.py @@ -21,7 +21,7 @@ def test_rolling_ball_options_with_flags(): 12, light_background=True, sliding=True, - disable=True, + disable_smoothing=True, do_3d=True, ) assert options == "rolling=12 light sliding disable stack" @@ -40,7 +40,7 @@ def test_filter_options_gaussian_blur(): command, options = filter_options("Gaussian Blur", 5) assert command == "Gaussian Blur..." - assert options == "sigma=" + assert options == "sigma=5 stack" def test_threshold_options():