Skip to content

Commit 7741f5e

Browse files
committed
Merge branch 'devel' into pr/143
2 parents cd48fac + 70bd38d commit 7741f5e

6 files changed

Lines changed: 275 additions & 17 deletions

File tree

poetry.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ version = "0.0.0"
2020
# - or: python = ">=3.10"
2121

2222
[tool.poetry.dependencies]
23-
imcf-fiji-mocks = ">=0.13.0"
23+
# IMPORTANT: see the "poetry.lock.md" file when changing dependencies!!!
24+
imcf-fiji-mocks = ">=0.14.0"
2425
python = ">=2.7"
2526
python-micrometa = "^15.2.3"
2627
sjlogging = ">=0.5.2"
@@ -54,3 +55,9 @@ ignore = [
5455

5556
[tool.ruff.lint.pydocstyle]
5657
convention = "numpy"
58+
59+
[tool.coverage.report]
60+
exclude_also = [
61+
'no-cover:jython-only',
62+
'cover:jython',
63+
]

src/imcflibs/imagej/objects3d.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from de.mpicbg.scf.imgtools.image.create.image import ImageCreationUtilities
99
from de.mpicbg.scf.imgtools.image.create.labelmap import WatershedLabeling
1010
from ij import IJ
11+
from inra.ijpb.plugins import RemoveBorderLabelsPlugin
1112
from mcib3d.geom import Objects3DPopulation
1213
from mcib3d.image3d import ImageHandler, ImageLabeller
1314
from mcib3d.image3d.processing import MaximaFinder
@@ -71,7 +72,15 @@ def imgplus_to_population3d(imp):
7172
return Objects3DPopulation(img)
7273

7374

74-
def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None):
75+
def segment_3d_image(
76+
imp,
77+
title=None,
78+
min_thresh=1,
79+
min_vol=None,
80+
max_vol=None,
81+
remove_touching_borders=False,
82+
remove_touching_borders_z=False,
83+
): # cover:jython
7584
"""Segment a 3D binary image to get a labelled stack.
7685
7786
Parameters
@@ -90,6 +99,11 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None):
9099
max_vol : int, optional
91100
Maximum volume (in voxels) above which objects get filtered.
92101
Defaults to None.
102+
remove_touching_borders : bool, optional
103+
Whether to remove objects that touch the borders in X and Y. Defaults to False.
104+
remove_touching_borders_z : bool, optional
105+
Whether to remove objects that touch the z-axis borders. Defaults to False.
106+
93107
94108
Returns
95109
-------
@@ -107,14 +121,24 @@ def segment_3d_image(imp, title=None, min_thresh=1, min_vol=None, max_vol=None):
107121
labeler.setMinSizeCalibrated(min_vol, img)
108122
if max_vol:
109123
labeler.setMaxSizeCalibrated(max_vol, img)
110-
111124
# Generate labelled segmentation
112125
seg = labeler.getLabels(img)
113126
seg.setScale(cal.pixelWidth, cal.pixelDepth, cal.getUnits())
127+
128+
seg = RemoveBorderLabelsPlugin().remove(
129+
seg.getImagePlus(),
130+
remove_touching_borders,
131+
remove_touching_borders,
132+
remove_touching_borders,
133+
remove_touching_borders,
134+
remove_touching_borders_z,
135+
remove_touching_borders_z,
136+
)
137+
114138
if title:
115139
seg.setTitle(title)
116140

117-
return seg.getImagePlus()
141+
return seg
118142

119143

120144
def maxima_finder_3d(imp, min_threshold=0, noise=100, rxy=1.5, rz=1.5):

src/imcflibs/pathtools.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@ def listdir_matching(
231231
if not regex_compiled:
232232
if candidate.lower().endswith(suffix.lower()):
233233
if fullpath:
234-
matching_files.append(os.path.join(dirpath, candidate))
234+
matching_files.append(
235+
os.path.abspath(os.path.join(dirpath, candidate))
236+
)
235237
else:
236238
rel = os.path.relpath(
237239
os.path.join(dirpath, candidate), path
@@ -240,7 +242,9 @@ def listdir_matching(
240242
else:
241243
if regex_compiled.match(candidate):
242244
if fullpath:
243-
matching_files.append(os.path.join(dirpath, candidate))
245+
matching_files.append(
246+
os.path.abspath(os.path.join(dirpath, candidate))
247+
)
244248
else:
245249
rel = os.path.relpath(
246250
os.path.join(dirpath, candidate), path
@@ -252,13 +256,17 @@ def listdir_matching(
252256
if not regex_compiled:
253257
if candidate.lower().endswith(suffix.lower()):
254258
if fullpath:
255-
matching_files.append(os.path.join(path, candidate))
259+
matching_files.append(
260+
os.path.abspath(os.path.join(path, candidate))
261+
)
256262
else:
257263
matching_files.append(candidate)
258264
else:
259265
if regex_compiled.match(candidate):
260266
if fullpath:
261-
matching_files.append(os.path.join(path, candidate))
267+
matching_files.append(
268+
os.path.abspath(os.path.join(path, candidate))
269+
)
262270
else:
263271
matching_files.append(candidate)
264272

tests/test_objects3d.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Tests for the imcflibs.imagej.objects3d module."""
2+
3+
from imcflibs.imagej.objects3d import imgplus_to_population3d
4+
from imcflibs.imagej.objects3d import maxima_finder_3d
5+
from imcflibs.imagej.objects3d import population3d_to_imgplus
6+
from imcflibs.imagej.objects3d import seeded_watershed
7+
from imcflibs.imagej.objects3d import segment_3d_image
8+
9+
10+
def test_mock_imports():
11+
"""Test if the mock imports work fine."""
12+
assert True

tests/test_pathtools.py

Lines changed: 212 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
"""Tests for `imcflibs.pathtools`."""
22
# -*- coding: utf-8 -*-
33

4-
from imcflibs.pathtools import parse_path
5-
from imcflibs.pathtools import jython_fiji_exists
6-
from imcflibs.pathtools import image_basename
7-
from imcflibs.pathtools import gen_name_from_orig
8-
from imcflibs.pathtools import derive_out_dir
4+
import os
5+
6+
from imcflibs.pathtools import (
7+
create_directory,
8+
derive_out_dir,
9+
find_dirs_containing_filetype,
10+
folder_size,
11+
gen_name_from_orig,
12+
image_basename,
13+
join2,
14+
join_files_with_channel_suffix,
15+
jython_fiji_exists,
16+
listdir_matching,
17+
parse_path,
18+
)
919

1020

1121
def test_parse_path():
@@ -114,3 +124,200 @@ def test_derive_out_dir():
114124
assert derive_out_dir("/foo", "none") == "/foo"
115125
assert derive_out_dir("/foo", "NONE") == "/foo"
116126
assert derive_out_dir("/foo", "/bar") == "/bar"
127+
128+
129+
def test_listdir_matching_various(tmpdir):
130+
"""Test non-recursive, recursive, fullpath, regex and sorting behaviour."""
131+
base = tmpdir.mkdir("base")
132+
133+
# create mixed files
134+
base.join("a.TIF").write("x")
135+
base.join("b.tif").write("x")
136+
base.join("c.png").write("x")
137+
138+
# non-recursive, suffix match (case-insensitive)
139+
res = listdir_matching(str(base), ".tif")
140+
assert set(res) == {"a.TIF", "b.tif"}
141+
142+
# fullpath returns absolute paths
143+
res_full = listdir_matching(str(base), ".tif", fullpath=True)
144+
assert all(os.path.isabs(x) for x in res_full)
145+
assert os.path.join(str(base), "a.TIF") in res_full
146+
assert os.path.join(str(base), "b.tif") in res_full
147+
148+
# recursive with relative paths
149+
sub = base.mkdir("sub")
150+
sub.join("s.TIF").write("x")
151+
res_rec = listdir_matching(str(base), ".tif", recursive=True)
152+
# should include the file from subdir as a relative path
153+
assert "sub/s.TIF" in [p.replace(os.sep, "/") for p in res_rec]
154+
155+
# recursive with fullpath
156+
res_rec_full = listdir_matching(str(base), ".tif", recursive=True, fullpath=True)
157+
assert all(os.path.isabs(x) for x in res_rec_full)
158+
assert os.path.join(str(sub), "s.TIF") in res_rec_full
159+
160+
# regex matching
161+
res_regex = listdir_matching(str(base), r".*\.tif$", regex=True)
162+
assert set(res_regex) >= {"a.TIF", "b.tif"}
163+
164+
# sorting: create names that sort differently lexicographically
165+
base.join("img2.tif").write("x")
166+
base.join("img10.tif").write("x")
167+
base.join("img1.tif").write("x")
168+
res_sorted = listdir_matching(str(base), ".tif", sort=True)
169+
# expected alphanumeric order
170+
assert res_sorted.index("img1.tif") < res_sorted.index("img2.tif")
171+
assert res_sorted.index("img2.tif") < res_sorted.index("img10.tif")
172+
173+
174+
def test_listdir_matching_invalid_regex(tmpdir):
175+
"""Invalid regular expressions should result in an empty list."""
176+
base = tmpdir.mkdir("base_invalid_regex")
177+
base.join("a.tif").write("x")
178+
179+
# invalid regex should not raise but simply return an empty list
180+
res = listdir_matching(str(base), "([", regex=True)
181+
assert res == []
182+
183+
184+
def test_listdir_matching_recursive_regex_fullpath(tmpdir):
185+
"""Recursive search with regex and fullpath should return absolute paths."""
186+
base = tmpdir.mkdir("base_recursive")
187+
sub = base.mkdir("subdir")
188+
sub.join("s.tif").write("x")
189+
190+
# recursive + regex + fullpath should return absolute path including subdir
191+
res = listdir_matching(
192+
str(base), r".*\.tif$", regex=True, recursive=True, fullpath=True
193+
)
194+
assert any(os.path.isabs(x) for x in res)
195+
expected = os.path.abspath(os.path.join(str(sub), "s.tif"))
196+
assert expected in res
197+
198+
199+
def test_find_dirs_containing_filetype(tmpdir):
200+
"""Test find_dirs_containing_filetype function."""
201+
base = tmpdir.mkdir("find_dirs")
202+
sub1 = base.mkdir("sub1")
203+
sub2 = base.mkdir("sub2")
204+
sub1.join("file1.tif").write("x")
205+
sub2.join("file2.png").write("x")
206+
sub2.join("file3.tif").write("x")
207+
208+
res = find_dirs_containing_filetype(str(base), ".tif")
209+
# find_dirs_containing_filetype appends a "/" to the dirname
210+
expected_sub1 = str(sub1) + "/"
211+
expected_sub2 = str(sub2) + "/"
212+
assert expected_sub1 in res
213+
assert expected_sub2 in res
214+
assert len(res) == 2
215+
216+
217+
def test_folder_size(tmpdir):
218+
"""Test folder_size function."""
219+
base = tmpdir.mkdir("folder_size")
220+
base.join("file1.txt").write("123") # 3 bytes
221+
sub = base.mkdir("sub")
222+
sub.join("file2.txt").write("12345") # 5 bytes
223+
# Total should be 8 bytes
224+
225+
assert folder_size(str(base)) == 8
226+
227+
228+
def test_join_files_with_channel_suffix():
229+
"""Test join_files_with_channel_suffix function."""
230+
files = ["file1.tif", "file2.tif"]
231+
232+
# nchannels = 1 (no suffixed copies added)
233+
assert join_files_with_channel_suffix(files, 1) == files
234+
235+
# nchannels = 3 (original then _0 and _1 copies)
236+
res = join_files_with_channel_suffix(files, 3)
237+
expected = [
238+
"file1.tif",
239+
"file2.tif",
240+
"file1_0.tif",
241+
"file2_0.tif",
242+
"file1_1.tif",
243+
"file2_1.tif",
244+
]
245+
assert res == expected
246+
247+
# Empty files list
248+
assert join_files_with_channel_suffix([], 3) == ""
249+
250+
# nchannels as string
251+
assert join_files_with_channel_suffix(["a.tif"], "2") == ["a.tif", "a_0.tif"]
252+
253+
# nchannels as invalid string (fall back to [0])
254+
assert join_files_with_channel_suffix(["a.tif"], "foo") == ["a.tif", "a_0.tif"]
255+
256+
257+
def test_create_directory(tmpdir):
258+
"""Test create_directory function."""
259+
new_dir = tmpdir.join("new_dir")
260+
assert not os.path.exists(str(new_dir))
261+
create_directory(str(new_dir))
262+
assert os.path.exists(str(new_dir))
263+
# Test creating existing directory (should not fail)
264+
create_directory(str(new_dir))
265+
assert os.path.exists(str(new_dir))
266+
267+
268+
def test_join2():
269+
"""Test join2 function."""
270+
assert join2("/foo", "bar") == "/foo/bar"
271+
assert join2("/foo/", "bar") == "/foo/bar"
272+
assert join2("/foo", "/bar") == "/foo/bar"
273+
# test with double backslashes which should be sanitized
274+
assert join2("C:\\Temp", "file.txt") == "C:/Temp/file.txt"
275+
276+
277+
def test_listdir_matching_recursive_with_subfolders(tmpdir):
278+
"""Test recursive listdir_matching ensures paths are correctly combined."""
279+
base = tmpdir.mkdir("base_rec_sf")
280+
sub = base.mkdir("subfolder")
281+
sub.join("test.tif").write("x")
282+
283+
# non-recursive path join (uses path + candidate)
284+
res = listdir_matching(str(base), ".tif", fullpath=True, recursive=False)
285+
assert res == []
286+
287+
# recursive path join (uses dirpath + candidate)
288+
res_rec = listdir_matching(str(base), ".tif", fullpath=True, recursive=True)
289+
expected = os.path.abspath(os.path.join(str(sub), "test.tif"))
290+
assert expected in res_rec
291+
292+
# recursive path join with regex and fullpath
293+
res_rec_regex = listdir_matching(
294+
str(base), r".*\.tif$", fullpath=True, recursive=True, regex=True
295+
)
296+
assert expected in res_rec_regex
297+
298+
# recursive path join with regex and NOT fullpath
299+
res_rec_regex_rel = listdir_matching(
300+
str(base), r".*\.tif$", fullpath=False, recursive=True, regex=True
301+
)
302+
assert "subfolder/test.tif" in [p.replace(os.sep, "/") for p in res_rec_regex_rel]
303+
304+
# non-recursive path join with regex and fullpath
305+
sub_file = sub.join("test2.tif").write("x")
306+
res_nonrec_regex = listdir_matching(
307+
str(sub), r".*\.tif$", fullpath=True, recursive=False, regex=True
308+
)
309+
expected_nonrec = os.path.abspath(os.path.join(str(sub), "test2.tif"))
310+
assert expected_nonrec in res_nonrec_regex
311+
312+
313+
def test_create_directory(tmpdir):
314+
"""Test create_directory function."""
315+
new_dir = os.path.join(str(tmpdir), "new_dir")
316+
assert not os.path.exists(new_dir)
317+
create_directory(new_dir)
318+
assert os.path.exists(new_dir)
319+
assert os.path.isdir(new_dir)
320+
321+
# Calling again should not raise (exist_ok behavior)
322+
create_directory(new_dir)
323+
assert os.path.exists(new_dir)

0 commit comments

Comments
 (0)