Skip to content

Commit 1916cf9

Browse files
committed
construct bounding boxes around points
1 parent 5cace7a commit 1916cf9

File tree

3 files changed

+76
-11
lines changed

3 files changed

+76
-11
lines changed

siibra/commons.py

+10
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,16 @@ def nonzero_coordinates(arr):
245245
return NZCACHE[obj_id]
246246

247247

248+
def affine_scaling(affine):
249+
"""Estimate approximate isotropic scaling factor of an affine matrix. """
250+
orig = np.dot(affine, [0, 0, 0, 1])
251+
unit_lengths = []
252+
for vec in np.identity(3):
253+
vec_phys = np.dot(affine, np.r_[vec, 1])
254+
unit_lengths.append(np.linalg.norm(orig - vec_phys))
255+
return np.prod(unit_lengths)
256+
257+
248258
def compare_maps(map1: Nifti1Image, map2: Nifti1Image):
249259
"""
250260
Compare two maps, given as Nifti1Image objects.

siibra/core/region.py

+55-11
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,16 @@
1414
# limitations under the License.
1515

1616
from .concept import AtlasConcept
17-
from .space import Space, Point, BoundingBox
18-
19-
from ..commons import logger, Registry, ParcellationIndex, MapType, compare_maps
17+
from .space import PointSet, Space, Point, BoundingBox
18+
19+
from ..commons import (
20+
logger,
21+
Registry,
22+
ParcellationIndex,
23+
MapType,
24+
compare_maps,
25+
affine_scaling,
26+
)
2027
from ..retrieval.repositories import GitlabConnector
2128

2229
import numpy as np
@@ -335,7 +342,8 @@ def build_mask(
335342

336343
logger.info(
337344
f"Extracting mask for {self.name} in {spaceobj.name} by "
338-
f"thresholding continuous map at {threshold_continuous}.")
345+
f"thresholding continuous map at {threshold_continuous}."
346+
)
339347
regionmap = self.get_regional_map(space, MapType.CONTINUOUS)
340348
pmap = None
341349
if regionmap is None:
@@ -418,7 +426,9 @@ def build_mask(
418426
continue
419427

420428
if mask is None:
421-
raise RuntimeError(f"Could not compute mask for {self.name} in {spaceobj.name}.")
429+
raise RuntimeError(
430+
f"Could not compute mask for {self.name} in {spaceobj.name}."
431+
)
422432
else:
423433
return nib.Nifti1Image(dataobj=mask.squeeze(), affine=affine)
424434

@@ -615,6 +625,45 @@ def get_bounding_box(
615625
logger.error(f"Could not compute bounding box for {self.name}.")
616626
return None
617627

628+
def find_peaks(self, space: Space, min_distance_mm=5):
629+
"""
630+
Find peaks of the region's continuous map in the given space, if any.
631+
632+
Arguments:
633+
----------
634+
space : Space
635+
requested reference space
636+
min_distance_mm : float
637+
Minimum distance between peaks in mm
638+
639+
Returns:
640+
--------
641+
peaks: PointSet
642+
pmap: continuous map
643+
"""
644+
spaceobj = Space.REGISTRY[space]
645+
pmap = self.get_regional_map(spaceobj, MapType.CONTINUOUS)
646+
if pmap is None:
647+
logger.warn(
648+
f"No continuous map found for {self.name} in {spaceobj.name}, "
649+
"cannot compute peaks."
650+
)
651+
return PointSet([], space)
652+
653+
from skimage.feature.peak import peak_local_max
654+
655+
img = pmap.fetch()
656+
dist = int(min_distance_mm / affine_scaling(img.affine) + .5)
657+
voxels = peak_local_max(
658+
img.get_fdata(),
659+
exclude_border=False,
660+
min_distance=dist,
661+
)
662+
return PointSet(
663+
[np.dot(img.affine, [x, y, z, 1])[:3] for x, y, z in voxels],
664+
space=spaceobj,
665+
), img
666+
618667
@cached
619668
def spatial_props(
620669
self,
@@ -655,12 +704,7 @@ def spatial_props(
655704
pimg = self.build_mask(space)
656705

657706
# determine scaling factor from voxels to cube mm
658-
orig = np.dot(pimg.affine, [0, 0, 0, 1])
659-
unit_lengths = []
660-
for vec in np.identity(3):
661-
vec_phys = np.dot(pimg.affine, np.r_[vec, 1])
662-
unit_lengths.append(np.linalg.norm(orig - vec_phys))
663-
scale = np.prod(unit_lengths)
707+
scale = affine_scaling(pimg.affine)
664708

665709
# compute properties of labelled volume
666710
A = np.asarray(pimg.get_fdata(), dtype=np.int32).squeeze()

siibra/core/space.py

+11
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,17 @@ def transform(self, affine: np.ndarray, space: Space = None):
437437
logger.warning(f"Homogeneous coordinate is not one: {h}")
438438
return self.__class__((x / h, y / h, z / h), space)
439439

440+
def get_enclosing_cube(self, width_mm):
441+
"""
442+
Create a bounding box centered around this point with the given width.
443+
"""
444+
offset = width_mm / 2
445+
return BoundingBox(
446+
point1=self - offset,
447+
point2=self + offset,
448+
space=self.space,
449+
)
450+
440451
def __iter__(self):
441452
""" Return an iterator over the location,
442453
so the Point can be easily cast to list or tuple. """

0 commit comments

Comments
 (0)