From 28f6efb7519e12282004ef5935a5079ec002aa38 Mon Sep 17 00:00:00 2001 From: Ahmet Nihat Simsek Date: Mon, 14 Oct 2024 15:19:22 +0200 Subject: [PATCH 1/3] clip voi to template's to if voi lies partially outside of the template (#605) --- siibra/volumes/providers/neuroglancer.py | 19 ++++++++++++++++++- siibra/volumes/providers/nifti.py | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/siibra/volumes/providers/neuroglancer.py b/siibra/volumes/providers/neuroglancer.py index 2f44f2fe0..c1e124452 100644 --- a/siibra/volumes/providers/neuroglancer.py +++ b/siibra/volumes/providers/neuroglancer.py @@ -503,7 +503,24 @@ def fetch(self, voi: _boundingbox.BoundingBox = None, **kwargs): if voi is None: bbox_ = _boundingbox.BoundingBox((0, 0, 0), self.size, space=None) else: - bbox_ = voi.transform(np.linalg.inv(self.affine)) + if voi.space is not None: + # ensure the voi is inside the template + tmplt_bbox = voi.space.get_template().get_boundingbox(clip=False) + intersection_bbox = voi.intersection(tmplt_bbox) + if intersection_bbox is None: + raise RuntimeError(f"{voi=} provided lies out side the voxel space of the {voi.space.name} template.") + if intersection_bbox == voi: + bbox_ = voi.transform(np.linalg.inv(self.affine)) + else: + logger.warning( + ( + f"{voi=} provided was cliped to be in the voxel space of the {voi.space.name} template.\n", + f"Clipped boundingbox={intersection_bbox}" + ) + ) + bbox_ = intersection_bbox.transform(np.linalg.inv(self.affine)) + else: + bbox_ = voi.transform(np.linalg.inv(self.affine)) for dim in range(3): if bbox_.shape[dim] < 1: diff --git a/siibra/volumes/providers/nifti.py b/siibra/volumes/providers/nifti.py index 5d3a4aaf9..f9375e3fe 100644 --- a/siibra/volumes/providers/nifti.py +++ b/siibra/volumes/providers/nifti.py @@ -188,7 +188,24 @@ def fetch( result = loader() if voi is not None: - bb_vox = voi.transform(np.linalg.inv(result.affine)) + if voi.space is not None: + # ensure the voi is inside the template + tmplt_bbox = voi.space.get_template().get_boundingbox(clip=False) + intersection_bbox = voi.intersection(tmplt_bbox) + if intersection_bbox is None: + raise RuntimeError(f"{voi=} provided lies out side the voxel space of the {voi.space.name} template.") + if intersection_bbox == voi: + bb_vox = voi.transform(np.linalg.inv(result.affine)) + else: + logger.warning( + ( + f"{voi=} provided was cliped to be in the voxel space of the {voi.space.name} template.\n", + f"Clipped boundingbox={intersection_bbox}" + ) + ) + bb_vox = intersection_bbox.transform(np.linalg.inv(result.affine)) + else: + bb_vox = voi.transform(np.linalg.inv(result.affine)) (x0, y0, z0), (x1, y1, z1) = bb_vox.minpoint, bb_vox.maxpoint shift = np.identity(4) shift[:3, -1] = bb_vox.minpoint From 28e59b821e4262b950da33e8b6d5af6fcf946866 Mon Sep 17 00:00:00 2001 From: Ahmet Nihat Simsek Date: Tue, 15 Oct 2024 11:16:27 +0200 Subject: [PATCH 2/3] Make voi validity and cliping at `Volume.fetch` stage --- siibra/volumes/providers/neuroglancer.py | 19 +------------------ siibra/volumes/providers/nifti.py | 19 +------------------ siibra/volumes/volume.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 36 deletions(-) diff --git a/siibra/volumes/providers/neuroglancer.py b/siibra/volumes/providers/neuroglancer.py index c1e124452..2f44f2fe0 100644 --- a/siibra/volumes/providers/neuroglancer.py +++ b/siibra/volumes/providers/neuroglancer.py @@ -503,24 +503,7 @@ def fetch(self, voi: _boundingbox.BoundingBox = None, **kwargs): if voi is None: bbox_ = _boundingbox.BoundingBox((0, 0, 0), self.size, space=None) else: - if voi.space is not None: - # ensure the voi is inside the template - tmplt_bbox = voi.space.get_template().get_boundingbox(clip=False) - intersection_bbox = voi.intersection(tmplt_bbox) - if intersection_bbox is None: - raise RuntimeError(f"{voi=} provided lies out side the voxel space of the {voi.space.name} template.") - if intersection_bbox == voi: - bbox_ = voi.transform(np.linalg.inv(self.affine)) - else: - logger.warning( - ( - f"{voi=} provided was cliped to be in the voxel space of the {voi.space.name} template.\n", - f"Clipped boundingbox={intersection_bbox}" - ) - ) - bbox_ = intersection_bbox.transform(np.linalg.inv(self.affine)) - else: - bbox_ = voi.transform(np.linalg.inv(self.affine)) + bbox_ = voi.transform(np.linalg.inv(self.affine)) for dim in range(3): if bbox_.shape[dim] < 1: diff --git a/siibra/volumes/providers/nifti.py b/siibra/volumes/providers/nifti.py index f9375e3fe..8c1dc8c4e 100644 --- a/siibra/volumes/providers/nifti.py +++ b/siibra/volumes/providers/nifti.py @@ -188,24 +188,7 @@ def fetch( result = loader() if voi is not None: - if voi.space is not None: - # ensure the voi is inside the template - tmplt_bbox = voi.space.get_template().get_boundingbox(clip=False) - intersection_bbox = voi.intersection(tmplt_bbox) - if intersection_bbox is None: - raise RuntimeError(f"{voi=} provided lies out side the voxel space of the {voi.space.name} template.") - if intersection_bbox == voi: - bb_vox = voi.transform(np.linalg.inv(result.affine)) - else: - logger.warning( - ( - f"{voi=} provided was cliped to be in the voxel space of the {voi.space.name} template.\n", - f"Clipped boundingbox={intersection_bbox}" - ) - ) - bb_vox = intersection_bbox.transform(np.linalg.inv(result.affine)) - else: - bb_vox = voi.transform(np.linalg.inv(result.affine)) + bb_vox = voi.transform(np.linalg.inv(self.affine)) (x0, y0, z0), (x1, y1, z1) = bb_vox.minpoint, bb_vox.maxpoint shift = np.identity(4) shift[:3, -1] = bb_vox.minpoint diff --git a/siibra/volumes/volume.py b/siibra/volumes/volume.py index dc0bade29..83ae24b38 100644 --- a/siibra/volumes/volume.py +++ b/siibra/volumes/volume.py @@ -416,6 +416,20 @@ def fetch( f"volume are: {self.formats}" ) + # ensure the voi is inside the template + voi = kwargs.get("voi", None) + if voi is not None and voi.space is not None: + assert isinstance(voi, boundingbox.BoundingBox) + tmplt_bbox = voi.space.get_template().get_boundingbox(clip=False) + intersection_bbox = voi.intersection(tmplt_bbox) + if intersection_bbox is None: + raise RuntimeError(f"{voi=} provided lies out side the voxel space of the {voi.space.name} template.") + if intersection_bbox != voi: + logger.info( + f"Since provided voi lies outside the template ({voi.space}) it is clipped as: {intersection_bbox}" + ) + kwargs["voi"] = intersection_bbox + result = None # try each possible format for fmt in possible_formats: From 99e32a61b44f1350dc3abfc1f5c505848e9cef8a Mon Sep 17 00:00:00 2001 From: Ahmet Nihat Simsek Date: Tue, 15 Oct 2024 11:33:50 +0200 Subject: [PATCH 3/3] add test for extracting bbox from template --- .../test_extracting_bbox_from_template.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 e2e/volumes/test_extracting_bbox_from_template.py diff --git a/e2e/volumes/test_extracting_bbox_from_template.py b/e2e/volumes/test_extracting_bbox_from_template.py new file mode 100644 index 000000000..c45c99871 --- /dev/null +++ b/e2e/volumes/test_extracting_bbox_from_template.py @@ -0,0 +1,23 @@ +import pytest +import siibra +from nibabel import Nifti1Image + + +vois = [ + siibra.locations.BoundingBox( + (-54.90, -29.47, 12.66), (12.69, 35.12, 20.24), "bigbrain" + ), + siibra.locations.BoundingBox( + (-154.90, -29.47, 12.66), (112.69, 38.12, 80.24), "bigbrain" + ), + siibra.locations.BoundingBox( + (-54.90, -29.47, 12.66), (12.69, 38.12, 80.24), "bigbrain" + ), +] + + +@pytest.mark.parametrize("voi", vois) +def test_fetching_voi(voi): + assert isinstance( + voi.space.get_template().fetch(voi=voi, resolution_mm=0.52), Nifti1Image + )