Skip to content
Open
95 changes: 76 additions & 19 deletions src/imcflibs/imagej/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Comment on lines +12 to +29
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring is missing parameters description (simply copy the relevant parts from the apply_filter() function).

Actually the ruff linter should complain about this but we need to wait until ruff-pr-21076 has been merged (cf. #148) ... ⏳

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"
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much harder to read compared to this (which is 3 lines vs. 5 lines):

options = "sigma"
if filter_method != "Gaussian Blur":
    options = "radius=" + str(filter_radius) + " stack"


return filter_name, options


def threshold_options(threshold_method, do_3d=True):
"""Build the ImageJ threshold option strings."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring is missing parameters description (simply copy the relevant parts from the apply_filter() function).

Actually the ruff linter should complain about this but we need to wait until ruff-pr-21076 has been merged (cf. #148) ... ⏳

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.

Expand Down Expand Up @@ -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))

Expand All @@ -66,7 +88,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
Expand All @@ -75,6 +104,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

Expand All @@ -85,16 +120,42 @@ 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 ""
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)

imageplus = imp.duplicate()
IJ.run(imageplus, "Substract Background...", options)
IJ.run(imageplus, "Subtract Background...", options)

return imageplus


def rolling_ball_options(
rolling_ball_radius,
light_background=False,
sliding=False,
disable=False,
do_3d=False,
):
"""Generate the options 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:
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.

Expand All @@ -117,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)
Expand Down
59 changes: 59 additions & 0 deletions tests/test_processing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Tests for the imcflibs.imagej.processing module."""

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_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"
Loading