|
14 | 14 | # limitations under the License.
|
15 | 15 |
|
16 | 16 | 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 | +) |
20 | 27 | from ..retrieval.repositories import GitlabConnector
|
21 | 28 |
|
22 | 29 | import numpy as np
|
@@ -335,7 +342,8 @@ def build_mask(
|
335 | 342 |
|
336 | 343 | logger.info(
|
337 | 344 | 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 | + ) |
339 | 347 | regionmap = self.get_regional_map(space, MapType.CONTINUOUS)
|
340 | 348 | pmap = None
|
341 | 349 | if regionmap is None:
|
@@ -418,7 +426,9 @@ def build_mask(
|
418 | 426 | continue
|
419 | 427 |
|
420 | 428 | 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 | + ) |
422 | 432 | else:
|
423 | 433 | return nib.Nifti1Image(dataobj=mask.squeeze(), affine=affine)
|
424 | 434 |
|
@@ -615,6 +625,45 @@ def get_bounding_box(
|
615 | 625 | logger.error(f"Could not compute bounding box for {self.name}.")
|
616 | 626 | return None
|
617 | 627 |
|
| 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 | + |
618 | 667 | @cached
|
619 | 668 | def spatial_props(
|
620 | 669 | self,
|
@@ -655,12 +704,7 @@ def spatial_props(
|
655 | 704 | pimg = self.build_mask(space)
|
656 | 705 |
|
657 | 706 | # 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) |
664 | 708 |
|
665 | 709 | # compute properties of labelled volume
|
666 | 710 | A = np.asarray(pimg.get_fdata(), dtype=np.int32).squeeze()
|
|
0 commit comments