voidspace 0.1.4__tar.gz → 0.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {voidspace-0.1.4/src/voidspace.egg-info → voidspace-0.1.6}/PKG-INFO +1 -1
- {voidspace-0.1.4 → voidspace-0.1.6}/pyproject.toml +1 -1
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/__init__.py +1 -1
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/workflows.py +8 -2
- {voidspace-0.1.4 → voidspace-0.1.6/src/voidspace.egg-info}/PKG-INFO +1 -1
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace.egg-info/SOURCES.txt +1 -0
- voidspace-0.1.6/tests/test_version.py +11 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/tests/test_workflows.py +41 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/LICENSE +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/README.md +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/setup.cfg +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/change.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/cli.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/io.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/metrics.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/models.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/morphology.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace/segment.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace.egg-info/dependency_links.txt +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace.egg-info/entry_points.txt +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace.egg-info/requires.txt +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/src/voidspace.egg-info/top_level.txt +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/tests/test_aim_io.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/tests/test_change.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/tests/test_cli.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/tests/test_metrics.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/tests/test_models.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/tests/test_morphology.py +0 -0
- {voidspace-0.1.4 → voidspace-0.1.6}/tests/test_segment.py +0 -0
|
@@ -12,7 +12,7 @@ from voidspace.models import (
|
|
|
12
12
|
from voidspace.segment import segment_voidspace
|
|
13
13
|
from voidspace.workflows import analyze_maps, compare, intersect_masks, run_case
|
|
14
14
|
|
|
15
|
-
__version__ = "0.1.
|
|
15
|
+
__version__ = "0.1.6"
|
|
16
16
|
|
|
17
17
|
__all__ = [
|
|
18
18
|
"VoidspaceChangeMasks",
|
|
@@ -69,8 +69,14 @@ def intersect_masks(
|
|
|
69
69
|
intersection, spacing, reference = read_mask(paths[0])
|
|
70
70
|
for path in paths[1:]:
|
|
71
71
|
mask, mask_spacing, mask_reference = read_mask(path)
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
# AIM header quantization and NIfTI float storage can differ below 1 nm.
|
|
73
|
+
# Tolerate that rounding, but do not silently mix scan resolutions.
|
|
74
|
+
if not np.allclose(mask_spacing, spacing, rtol=0, atol=1e-6):
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"mask spacing mismatch (z, y, x; mm): {paths[0]} has {spacing}; "
|
|
77
|
+
f"{path} has {mask_spacing}. Expected the matching native-space "
|
|
78
|
+
"common region at the same scan resolution."
|
|
79
|
+
)
|
|
74
80
|
mask = _array_on_reference_grid(mask, mask_reference, reference)
|
|
75
81
|
if mask.shape != intersection.shape:
|
|
76
82
|
raise ValueError("all masks must overlap the reference image space")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import tomllib
|
|
3
|
+
|
|
4
|
+
import voidspace
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_public_version_matches_package_version():
|
|
8
|
+
project = tomllib.loads(
|
|
9
|
+
(Path(__file__).resolve().parents[1] / "pyproject.toml").read_text()
|
|
10
|
+
)["project"]
|
|
11
|
+
assert voidspace.__version__ == project["version"]
|
|
@@ -116,6 +116,47 @@ def test_intersect_masks_writes_boolean_intersection(tmp_path):
|
|
|
116
116
|
assert np.array_equal(result, full & common)
|
|
117
117
|
|
|
118
118
|
|
|
119
|
+
@pytest.mark.parametrize("shift", [0.0, 0.0607])
|
|
120
|
+
@pytest.mark.parametrize("source_spacing, common_spacing", [
|
|
121
|
+
((0.0607,) * 3, (float(np.float32(0.0607)),) * 3),
|
|
122
|
+
((0.06069900095462799, 0.06069900095462799, 0.06069599837064743),
|
|
123
|
+
(0.06069965288043022, 0.06069965288043022, 0.06069643050432205)),
|
|
124
|
+
])
|
|
125
|
+
def test_intersect_masks_accepts_header_rounding_and_preserves_physical_placement(
|
|
126
|
+
tmp_path, shift, source_spacing, common_spacing,
|
|
127
|
+
):
|
|
128
|
+
full = sitk.GetImageFromArray(np.ones((5, 5, 5), dtype=np.uint8))
|
|
129
|
+
full.SetSpacing(source_spacing)
|
|
130
|
+
sitk.WriteImage(full, str(tmp_path / "full.mha"))
|
|
131
|
+
common = sitk.GetImageFromArray(np.ones((3, 3, 3), dtype=np.uint8))
|
|
132
|
+
common.SetSpacing(common_spacing)
|
|
133
|
+
common.SetOrigin((shift,) * 3)
|
|
134
|
+
sitk.WriteImage(common, str(tmp_path / "common.nii.gz"))
|
|
135
|
+
|
|
136
|
+
output = intersect_masks(
|
|
137
|
+
[tmp_path / "full.mha", tmp_path / "common.nii.gz"],
|
|
138
|
+
output_path=tmp_path / "intersection.nii.gz",
|
|
139
|
+
)
|
|
140
|
+
image = sitk.ReadImage(str(output))
|
|
141
|
+
expected = np.zeros((5, 5, 5), dtype=bool)
|
|
142
|
+
start = 0 if shift == 0 else 1
|
|
143
|
+
expected[start:start + 3, start:start + 3, start:start + 3] = True
|
|
144
|
+
assert np.array_equal(sitk.GetArrayFromImage(image).astype(bool), expected)
|
|
145
|
+
assert np.allclose(image.GetSpacing(), source_spacing, rtol=0, atol=1e-8)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_intersect_masks_reports_genuinely_different_resolution(tmp_path):
|
|
149
|
+
for name, spacing in [("full", 0.0607), ("common", 0.082)]:
|
|
150
|
+
image = sitk.GetImageFromArray(np.ones((3, 3, 3), dtype=np.uint8))
|
|
151
|
+
image.SetSpacing((spacing,) * 3)
|
|
152
|
+
sitk.WriteImage(image, str(tmp_path / f"{name}.mha"))
|
|
153
|
+
with pytest.raises(ValueError, match="spacing.*full.*common"):
|
|
154
|
+
intersect_masks(
|
|
155
|
+
[tmp_path / "full.mha", tmp_path / "common.mha"],
|
|
156
|
+
output_path=tmp_path / "intersection.nii.gz",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
119
160
|
def test_intersect_masks_resamples_cropped_mask_to_reference_grid(tmp_path):
|
|
120
161
|
full = np.ones((5, 5, 5), dtype=bool)
|
|
121
162
|
common = np.ones((3, 3, 3), dtype=bool)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|