Skip to content

Commit d33a0a6

Browse files
committed
test: Add unit tests for rolling ball options and background subtraction
1 parent cd48fac commit d33a0a6

1 file changed

Lines changed: 160 additions & 0 deletions

File tree

tests/test_processing.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""Unit tests for imcflibs.imagej.processing."""
2+
3+
import importlib
4+
import sys
5+
import types
6+
7+
import pytest
8+
9+
10+
def _import_processing():
11+
"""Import processing module in a runtime-independent way."""
12+
"""Load imcflibs.imagej.processing with a fake ij.IJ module for plain pytest."""
13+
if "ij" not in sys.modules:
14+
fake_ij = types.ModuleType("ij")
15+
fake_ij.IJ = types.SimpleNamespace(run=lambda *args, **kwargs: None)
16+
sys.modules["ij"] = fake_ij
17+
18+
import os
19+
from importlib.util import module_from_spec, spec_from_file_location
20+
21+
pkg_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
22+
processing_path = os.path.join(
23+
pkg_root, "src", "imcflibs", "imagej", "processing.py"
24+
)
25+
26+
spec = spec_from_file_location("imcflibs.imagej.processing", processing_path)
27+
module = module_from_spec(spec)
28+
sys.modules[spec.name] = module
29+
spec.loader.exec_module(module)
30+
return module
31+
32+
33+
def test_rolling_ball_options_combinations():
34+
"""rolling_ball_options should produce correct option strings."""
35+
processing = _import_processing()
36+
37+
assert processing.rolling_ball_options(10) == "rolling=10"
38+
assert processing.rolling_ball_options(10, sliding=True) == "rolling=10 sliding"
39+
assert processing.rolling_ball_options(10, do_3d=True) == "rolling=10 stack"
40+
assert (
41+
processing.rolling_ball_options(10, sliding=True, do_3d=True)
42+
== "rolling=10 sliding stack"
43+
)
44+
assert (
45+
processing.rolling_ball_options(10, light_background=True, disable=True)
46+
== "rolling=10 light disable"
47+
)
48+
assert (
49+
processing.rolling_ball_options(
50+
10, sliding=True, light_background=True, do_3d=True, disable=True
51+
)
52+
== "rolling=10 light sliding disable stack"
53+
)
54+
55+
56+
def test_apply_rollingball_bg_subtraction_calls_IJ_run_with_expected_options():
57+
"""apply_rollingball_bg_subtraction should invoke ImageJ run with composed options."""
58+
processing = _import_processing()
59+
60+
class DummyImagePlus:
61+
def duplicate(self):
62+
return self
63+
64+
image = DummyImagePlus()
65+
66+
executed = {}
67+
68+
def fake_run(arg_image, arg_command, arg_options):
69+
executed["image"] = arg_image
70+
executed["command"] = arg_command
71+
executed["options"] = arg_options
72+
73+
processing.IJ = types.SimpleNamespace(run=fake_run)
74+
75+
output = processing.apply_rollingball_bg_subtraction(
76+
image,
77+
rolling_ball_radius=5,
78+
light_background=True,
79+
sliding=True,
80+
disable=True,
81+
do_3d=True,
82+
)
83+
84+
assert output is image
85+
assert executed["command"] == "Subtract Background..."
86+
assert executed["options"] == "rolling=5 light sliding disable stack"
87+
88+
89+
def test_apply_filter_calls_IJ_run_with_expected_options():
90+
"""apply_filter should invoke ImageJ run with expected filter command and options."""
91+
processing = _import_processing()
92+
93+
class DummyImagePlus:
94+
def duplicate(self):
95+
return self
96+
97+
image = DummyImagePlus()
98+
99+
executed = {}
100+
101+
def fake_run(arg_image, arg_command, arg_options):
102+
executed["image"] = arg_image
103+
executed["command"] = arg_command
104+
executed["options"] = arg_options
105+
106+
processing.IJ = types.SimpleNamespace(run=fake_run)
107+
108+
output = processing.apply_filter(image, "Median", 3, do_3d=True)
109+
110+
assert output is image
111+
assert executed["command"] == "Median 3D..."
112+
assert executed["options"] == "radius=3 stack"
113+
114+
115+
def test_apply_filter_invalid_method_raises_value_error():
116+
"""apply_filter with unknown method raises ValueError."""
117+
processing = _import_processing()
118+
119+
class DummyImagePlus:
120+
def duplicate(self):
121+
return self
122+
123+
image = DummyImagePlus()
124+
125+
with pytest.raises(ValueError):
126+
processing.apply_filter(image, "BadFilter", 1)
127+
128+
129+
def test_apply_threshold_uses_IJ_setAutoThreshold_and_IJ_run():
130+
"""apply_threshold should call IJ.setAutoThreshold and IJ.run appropriately."""
131+
processing = _import_processing()
132+
133+
class DummyImagePlus:
134+
def duplicate(self):
135+
return self
136+
137+
image = DummyImagePlus()
138+
139+
auto_threshold_called = {}
140+
run_called = {}
141+
142+
def fake_set_auto_threshold(arg_image, arg_options):
143+
auto_threshold_called["image"] = arg_image
144+
auto_threshold_called["options"] = arg_options
145+
146+
def fake_run(arg_image, arg_command, arg_options):
147+
run_called["image"] = arg_image
148+
run_called["command"] = arg_command
149+
run_called["options"] = arg_options
150+
151+
processing.IJ = types.SimpleNamespace(
152+
setAutoThreshold=fake_set_auto_threshold, run=fake_run
153+
)
154+
155+
output = processing.apply_threshold(image, "Default", do_3d=False)
156+
157+
assert output is image
158+
assert auto_threshold_called["options"] == ""
159+
assert run_called["command"] == "Convert to Mask"
160+
assert run_called["options"] == "method=Default background=Dark black"

0 commit comments

Comments
 (0)