siibra 1.0a19__py3-none-any.whl → 1.0.1a1__py3-none-any.whl

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.

Potentially problematic release.


This version of siibra might be problematic. Click here for more details.

Files changed (82) hide show
  1. siibra/VERSION +1 -1
  2. siibra/__init__.py +7 -7
  3. siibra/commons.py +8 -53
  4. siibra/configuration/__init__.py +1 -1
  5. siibra/configuration/configuration.py +1 -1
  6. siibra/configuration/factory.py +11 -21
  7. siibra/core/__init__.py +1 -1
  8. siibra/core/assignment.py +1 -1
  9. siibra/core/atlas.py +21 -15
  10. siibra/core/concept.py +3 -3
  11. siibra/core/parcellation.py +69 -54
  12. siibra/core/region.py +178 -158
  13. siibra/core/space.py +1 -1
  14. siibra/core/structure.py +2 -2
  15. siibra/exceptions.py +13 -1
  16. siibra/experimental/__init__.py +1 -1
  17. siibra/experimental/contour.py +8 -8
  18. siibra/experimental/cortical_profile_sampler.py +1 -1
  19. siibra/experimental/patch.py +3 -3
  20. siibra/experimental/plane3d.py +12 -12
  21. siibra/explorer/__init__.py +1 -1
  22. siibra/explorer/url.py +2 -2
  23. siibra/explorer/util.py +1 -1
  24. siibra/features/__init__.py +1 -1
  25. siibra/features/anchor.py +14 -15
  26. siibra/features/connectivity/__init__.py +1 -1
  27. siibra/features/connectivity/functional_connectivity.py +1 -1
  28. siibra/features/connectivity/regional_connectivity.py +4 -4
  29. siibra/features/connectivity/streamline_counts.py +1 -1
  30. siibra/features/connectivity/streamline_lengths.py +1 -1
  31. siibra/features/connectivity/tracing_connectivity.py +1 -1
  32. siibra/features/dataset/__init__.py +1 -1
  33. siibra/features/dataset/ebrains.py +1 -1
  34. siibra/features/feature.py +24 -26
  35. siibra/features/image/__init__.py +1 -1
  36. siibra/features/image/image.py +2 -2
  37. siibra/features/image/sections.py +1 -1
  38. siibra/features/image/volume_of_interest.py +1 -1
  39. siibra/features/tabular/__init__.py +1 -1
  40. siibra/features/tabular/bigbrain_intensity_profile.py +2 -2
  41. siibra/features/tabular/cell_density_profile.py +98 -64
  42. siibra/features/tabular/cortical_profile.py +3 -3
  43. siibra/features/tabular/gene_expression.py +1 -1
  44. siibra/features/tabular/layerwise_bigbrain_intensities.py +1 -1
  45. siibra/features/tabular/layerwise_cell_density.py +4 -23
  46. siibra/features/tabular/receptor_density_fingerprint.py +13 -10
  47. siibra/features/tabular/receptor_density_profile.py +1 -1
  48. siibra/features/tabular/regional_timeseries_activity.py +4 -4
  49. siibra/features/tabular/tabular.py +7 -5
  50. siibra/livequeries/__init__.py +1 -1
  51. siibra/livequeries/allen.py +42 -19
  52. siibra/livequeries/bigbrain.py +21 -12
  53. siibra/livequeries/ebrains.py +1 -1
  54. siibra/livequeries/query.py +2 -3
  55. siibra/locations/__init__.py +11 -11
  56. siibra/locations/boundingbox.py +30 -29
  57. siibra/locations/location.py +1 -1
  58. siibra/locations/point.py +7 -7
  59. siibra/locations/{pointset.py → pointcloud.py} +36 -33
  60. siibra/retrieval/__init__.py +1 -1
  61. siibra/retrieval/cache.py +1 -1
  62. siibra/retrieval/datasets.py +4 -4
  63. siibra/retrieval/exceptions/__init__.py +1 -1
  64. siibra/retrieval/repositories.py +13 -30
  65. siibra/retrieval/requests.py +25 -8
  66. siibra/vocabularies/__init__.py +1 -1
  67. siibra/volumes/__init__.py +2 -2
  68. siibra/volumes/parcellationmap.py +119 -91
  69. siibra/volumes/providers/__init__.py +1 -1
  70. siibra/volumes/providers/freesurfer.py +3 -3
  71. siibra/volumes/providers/gifti.py +1 -1
  72. siibra/volumes/providers/neuroglancer.py +67 -41
  73. siibra/volumes/providers/nifti.py +12 -26
  74. siibra/volumes/providers/provider.py +1 -1
  75. siibra/volumes/sparsemap.py +125 -246
  76. siibra/volumes/volume.py +150 -61
  77. {siibra-1.0a19.dist-info → siibra-1.0.1a1.dist-info}/METADATA +26 -4
  78. siibra-1.0.1a1.dist-info/RECORD +84 -0
  79. {siibra-1.0a19.dist-info → siibra-1.0.1a1.dist-info}/WHEEL +1 -1
  80. siibra-1.0a19.dist-info/RECORD +0 -84
  81. {siibra-1.0a19.dist-info → siibra-1.0.1a1.dist-info}/LICENSE +0 -0
  82. {siibra-1.0a19.dist-info → siibra-1.0.1a1.dist-info}/top_level.txt +0 -0
siibra/core/region.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2024
1
+ # Copyright 2018-2025
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,7 +18,7 @@ from . import concept, structure, space as _space, parcellation as _parcellation
18
18
  from .assignment import Qualification, AnatomicalAssignment
19
19
 
20
20
  from ..retrieval.cache import cache_user_fn
21
- from ..locations import location, pointset, boundingbox as _boundingbox
21
+ from ..locations import location, pointcloud, boundingbox as _boundingbox
22
22
  from ..volumes import parcellationmap, volume
23
23
  from ..commons import (
24
24
  logger,
@@ -26,20 +26,18 @@ from ..commons import (
26
26
  create_key,
27
27
  clear_name,
28
28
  InstanceTable,
29
- SIIBRA_DEFAULT_MAPTYPE,
30
- SIIBRA_DEFAULT_MAP_THRESHOLD
31
29
  )
32
30
  from ..exceptions import NoMapAvailableError, SpaceWarpingFailedError
33
31
 
34
- import numpy as np
35
32
  import re
36
33
  import anytree
37
- from typing import List, Union, Iterable, Dict, Callable, Tuple
34
+ from typing import List, Union, Iterable, Dict, Callable, Tuple, Set
38
35
  from difflib import SequenceMatcher
39
36
  from ebrains_drive import BucketApiClient
40
37
  import json
41
38
  from functools import wraps, reduce
42
39
  from concurrent.futures import ThreadPoolExecutor
40
+ from functools import lru_cache
43
41
 
44
42
 
45
43
  REGEX_TYPE = type(re.compile("test"))
@@ -55,8 +53,8 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
55
53
  _regex_re = re.compile(r'^\/(?P<expression>.+)\/(?P<flags>[a-zA-Z]*)$')
56
54
  _accepted_flags = "aiLmsux"
57
55
 
58
- _GETMAP_CACHE = {}
59
- _GETMAP_CACHE_MAX_ENTRIES = 1
56
+ _GETMASK_CACHE = {}
57
+ _GETMASK_CACHE_MAX_ENTRIES = 1
60
58
 
61
59
  def __init__(
62
60
  self,
@@ -113,20 +111,20 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
113
111
  )
114
112
 
115
113
  # anytree node will take care to use this appropriately
116
- self.parent = parent
117
- self.children = children
114
+ self.parent: "Region" = parent
115
+ self.children: List["Region"] = children
118
116
  # convert hex to int tuple if rgb is given
119
117
  self.rgb = (
120
118
  None if rgb is None
121
119
  else tuple(int(rgb[p:p + 2], 16) for p in [1, 3, 5])
122
120
  )
123
- self._supported_spaces = None # computed on 1st call of self.supported_spaces
121
+ self._supported_spaces: Set[_space.Space] = None # computed on 1st call of self.supported_spaces
124
122
  self._str_aliases = None
125
- self._CACHED_REGION_SEARCHES = {}
123
+ self.find = lru_cache(maxsize=3)(self.find)
126
124
 
127
125
  def get_related_regions(self) -> Iterable["RegionRelationAssessments"]:
128
126
  """
129
- Get assements on relations of this region to others defined on EBRAINS.
127
+ Get assessments on relations of this region to others defined on EBRAINS.
130
128
 
131
129
  Yields
132
130
  ------
@@ -135,8 +133,8 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
135
133
  Example
136
134
  -------
137
135
  >>> region = siibra.get_region("monkey", "PG")
138
- >>> for assesment in region.get_related_regions():
139
- >>> print(assesment)
136
+ >>> for assessment in region.get_related_regions():
137
+ >>> print(assessment)
140
138
  'PG' is homologous to 'Area PGa (IPL)'
141
139
  'PG' is homologous to 'Area PGa (IPL) left'
142
140
  'PG' is homologous to 'Area PGa (IPL) right'
@@ -265,7 +263,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
265
263
 
266
264
  Parameters
267
265
  ----------
268
- regionspec: str, regex, int, Region
266
+ regionspec: str, regex, Region
269
267
  - a string with a possibly inexact name (matched both against the name and the identifier key)
270
268
  - a string in '/pattern/flags' format to use regex search (acceptable flags: aiLmsux, see at https://docs.python.org/3/library/re.html#flags)
271
269
  - a regex applied to region names
@@ -286,11 +284,6 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
286
284
  ---
287
285
  See example 01-003, find regions.
288
286
  """
289
- key = (regionspec, filter_children, find_topmost)
290
- MEM = self._CACHED_REGION_SEARCHES
291
- if key in MEM:
292
- return MEM[key]
293
-
294
287
  if isinstance(regionspec, str):
295
288
  # convert the specified string into a regex for matching
296
289
  regex_match = self._regex_re.match(regionspec)
@@ -352,7 +345,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
352
345
  found_regions = sorted(set(candidates), key=lambda r: r.depth)
353
346
 
354
347
  # reverse is set to True, since SequenceMatcher().ratio(), higher == better
355
- MEM[key] = (
348
+ return (
356
349
  sorted(
357
350
  found_regions,
358
351
  reverse=True,
@@ -361,8 +354,6 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
361
354
  if isinstance(regionspec, str) else found_regions
362
355
  )
363
356
 
364
- return MEM[key]
365
-
366
357
  def matches(self, regionspec):
367
358
  """
368
359
  Checks whether this region matches the given region specification.
@@ -422,15 +413,14 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
422
413
 
423
414
  return self._CACHED_MATCHES[regionspec]
424
415
 
425
- def get_regional_map(
416
+ def get_regional_mask(
426
417
  self,
427
418
  space: Union[str, _space.Space],
428
- maptype: MapType = SIIBRA_DEFAULT_MAPTYPE,
429
- threshold: float = SIIBRA_DEFAULT_MAP_THRESHOLD,
430
- via_space: Union[str, _space.Space] = None
431
- ) -> volume.Volume:
419
+ maptype: MapType = MapType.LABELLED,
420
+ threshold: float = 0.0,
421
+ ) -> volume.FilteredVolume:
432
422
  """
433
- Attempts to build a binary mask of this region in the given space,
423
+ Get a binary mask of this region in the given space,
434
424
  using the specified MapTypes.
435
425
 
436
426
  Parameters
@@ -439,128 +429,135 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
439
429
  The requested reference space
440
430
  maptype: MapType, default: SIIBRA_DEFAULT_MAPTYPE
441
431
  The type of map to be used ('labelled' or 'statistical')
442
- threshold: float, optional
432
+ threshold: float, default: 0.0
443
433
  When fetching a statistical map, use this threshold to convert
444
- it to a binary mask
445
- via_space: Space or str
446
- If specified, fetch the map in this space first, and then perform
447
- a linear warping from there to the requested space.
448
-
449
- Tip
450
- ---
451
- You might want to use this if a map in the requested space
452
- is not available.
453
-
454
- Note
455
- ----
456
- This linear warping is an affine approximation of the
457
- nonlinear deformation, computed from the warped corner points
458
- of the bounding box (see siibra.locations.BoundingBox.estimate_affine()).
459
- It does not require voxel resampling, just replaces the affine
460
- matrix, but is less accurate than a full nonlinear warping,
461
- which is currently not supported in siibra-python for images.
434
+ it to a binary mask.
435
+
462
436
  Returns
463
437
  -------
464
438
  Volume (use fetch() to get a NiftiImage)
465
439
  """
466
- # check for a cached object
440
+ if isinstance(maptype, str):
441
+ maptype = MapType[maptype.upper()]
467
442
 
468
- getmap_hash = hash(f"{self.id}{space}{maptype}{threshold}{via_space}")
469
- if getmap_hash in self._GETMAP_CACHE:
470
- return self._GETMAP_CACHE[getmap_hash]
443
+ threshold_info = "" if maptype == MapType.LABELLED else f"(threshold: {threshold}) "
444
+ # check cache
445
+ getmask_hash = hash(f"{self.id} - {space} - {maptype}{threshold_info}")
446
+ if getmask_hash in self._GETMASK_CACHE:
447
+ return self._GETMASK_CACHE[getmask_hash]
471
448
 
449
+ name = f"Mask {threshold_info}of '{self.name} ({self.parcellation})' in "
450
+ try:
451
+ regional_map = self.get_regional_map(space=space, maptype=maptype)
452
+ if maptype == MapType.LABELLED:
453
+ assert threshold == 0.0, f"threshold can only be set for {MapType.STATISTICAL} maps."
454
+ result = volume.FilteredVolume(
455
+ parent_volume=regional_map,
456
+ label=regional_map.label,
457
+ fragment=regional_map.fragment
458
+ )
459
+ result._boundingbox = None
460
+ if maptype == MapType.STATISTICAL:
461
+ result = volume.FilteredVolume(
462
+ parent_volume=regional_map,
463
+ threshold=threshold
464
+ )
465
+ if threshold == 0.0:
466
+ result._boundingbox = regional_map._boundingbox
467
+ name += f"'{result.space}'"
468
+ except NoMapAvailableError as e:
469
+ # This region is not mapped directly in any map in the registry.
470
+ # Try building a map from the child regions
471
+ if (len(self.children) > 0) and self.mapped_in_space(space, recurse=True):
472
+ mapped_descendants: List[Region] = [
473
+ d for d in self.descendants if d.mapped_in_space(space, recurse=False)
474
+ ]
475
+ logger.info(f"{self.name} is not mapped in {space}. Merging the masks of its {len(mapped_descendants)} map descendants.")
476
+ descendant_volumes = [
477
+ descendant.get_regional_mask(space=space, maptype=maptype, threshold=threshold)
478
+ for descendant in mapped_descendants
479
+ ]
480
+ result = volume.FilteredVolume(
481
+ volume.merge(descendant_volumes),
482
+ label=1
483
+ )
484
+ name += f"'{result.space}' (built by merging the mask {threshold_info} of its descendants)"
485
+ else:
486
+ raise e
487
+ result._name = name
488
+
489
+ while len(self._GETMASK_CACHE) > self._GETMASK_CACHE_MAX_ENTRIES:
490
+ self._GETMASK_CACHE.pop(next(iter(self._GETMASK_CACHE)))
491
+ self._GETMASK_CACHE[getmask_hash] = result
492
+ return result
493
+
494
+ def get_regional_map(
495
+ self,
496
+ space: Union[str, _space.Space],
497
+ maptype: MapType = MapType.LABELLED,
498
+ ) -> Union[volume.FilteredVolume, volume.Volume, volume.Subvolume]:
499
+ """
500
+ Get a volume representing this region in the given space and MapType.
501
+
502
+ Note
503
+ ----
504
+ If a region is not mapped in any of the `Map`s in the registry, then
505
+ siibra will get the maps of its children recursively and merge them.
506
+ If no map is available this way as well, an exception is raised.
507
+
508
+ Parameters
509
+ ----------
510
+ space: Space or str
511
+ The requested reference space
512
+ maptype: MapType, default: SIIBRA_DEFAULT_MAPTYPE
513
+ The type of map to be used ('labelled' or 'statistical')
514
+
515
+ Returns
516
+ -------
517
+ Volume (use fetch() to get a NiftiImage)
518
+ """
472
519
  if isinstance(maptype, str):
473
520
  maptype = MapType[maptype.upper()]
474
521
 
475
- # prepare space instances
522
+ # prepare space instance
476
523
  if isinstance(space, str):
477
524
  space = _space.Space.get_instance(space)
478
- fetch_space = space if via_space is None else via_space
479
- if isinstance(fetch_space, str):
480
- fetch_space = _space.Space.get_instance(fetch_space)
481
-
482
- result = None # try to replace this with the actual regionmap volume
483
525
 
484
526
  # see if we find a map supporting the requested region
485
527
  for m in parcellationmap.Map.registry():
486
528
  if (
487
- m.space.matches(fetch_space)
529
+ m.space.matches(space)
488
530
  and m.parcellation == self.parcellation
489
531
  and m.provides_image
490
532
  and m.maptype == maptype
491
533
  and self.name in m.regions
492
534
  ):
493
- region_img = m.fetch(region=self, format='image')
494
- imgdata = np.asanyarray(region_img.dataobj)
495
- if maptype == MapType.STATISTICAL: # compute thresholded statistical map, default is 0.0
496
- logger.info(f"Thresholding statistical map at {threshold}")
497
- imgdata = (imgdata > threshold).astype('uint8')
498
- name = f"Statistical mask of {self} on {fetch_space}{f' thresholded by {threshold}' if threshold else ''}"
499
- else: # compute region mask from labelled parcellation map
500
- name = f"Mask of {self} in {m.parcellation} on {fetch_space}"
501
- result = volume.from_array(
502
- data=imgdata,
503
- affine=region_img.affine,
504
- space=fetch_space,
505
- name=name,
506
- )
507
- if result is not None:
508
- break
509
-
510
- if result is None:
511
- # No region map available. Then see if we can build a map from the child regions
512
- if (len(self.children) > 0) and all(c.mapped_in_space(fetch_space) for c in self.children):
513
- logger.debug(f"Building regional map of {self.name} in {self.parcellation} from {len(self.children)} child regions.")
514
- child_volumes = [
515
- child.get_regional_map(fetch_space, maptype, threshold, via_space)
516
- for child in self.children
517
- ]
518
- result = volume.merge(child_volumes)
519
- result._name = f"Subtree {'mask' if maptype == MapType.LABELLED else 'statistical map of'} built from {self.name}"
520
-
521
- if result is None:
522
- raise NoMapAvailableError(f"Cannot build region map for {self.name} from {str(maptype)} maps in {fetch_space}")
523
-
524
- if via_space is not None:
525
- # the map volume is taken from an intermediary reference space
526
- # provided by 'via_space'. Now transform the affine to match the
527
- # desired target space.
528
- intermediary_result = result
529
- transform = intermediary_result.get_boundingbox(clip=True, background=0.0).estimate_affine(space)
530
- result = volume.from_array(
531
- imgdata,
532
- np.dot(transform, region_img.affine),
533
- space,
534
- f"{result.name} fetched from {fetch_space} and linearly corrected to match {space}"
535
- )
536
-
537
- while len(self._GETMAP_CACHE) > self._GETMAP_CACHE_MAX_ENTRIES:
538
- self._GETMAP_CACHE.pop(next(iter(self._GETMAP_CACHE)))
539
- self._GETMAP_CACHE[getmap_hash] = result
540
- return result
535
+ return m.get_volume(region=self)
536
+ raise NoMapAvailableError(
537
+ f"{self.name} is not mapped in {space} as a {str(maptype)} map."
538
+ " Please try getting the children or getting the mask."
539
+ )
541
540
 
542
- def mapped_in_space(self, space, recurse: bool = True) -> bool:
541
+ def mapped_in_space(self, space, recurse: bool = False) -> bool:
543
542
  """
544
- Verifies wether this region is defined by an explicit map in the given space.
543
+ Verifies whether this region is defined by an explicit map in the given space.
545
544
 
546
545
  Parameters
547
546
  ----------
548
547
  space: Space or str
549
548
  reference space
550
- recurse: bool, default: True
551
- If True, check if all child regions are mapped instead
549
+ recurse: bool, default: False
550
+ If True, check if itself or all child regions are mapped instead recursively.
552
551
  Returns
553
552
  -------
554
553
  bool
555
554
  """
556
555
  from ..volumes.parcellationmap import Map
557
556
  for m in Map.registry():
558
- # Use and operant for efficiency (short circuiting logic)
559
- # Put the most inexpensive logic first
560
557
  if (
561
- self.name in m.regions
562
- and m.space.matches(space)
558
+ m.space.matches(space)
563
559
  and m.parcellation.matches(self.parcellation)
560
+ and self.name in m.regions
564
561
  ):
565
562
  return True
566
563
  if recurse and not self.is_leaf:
@@ -571,21 +568,16 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
571
568
  @property
572
569
  def supported_spaces(self) -> List[_space.Space]:
573
570
  """
574
- The set of spaces for which a mask could be extracted.
575
- Overwrites the corresponding method of AtlasConcept.
571
+ The list of spaces for which a mask could be extracted from an existing
572
+ map or combination of masks of its children.
576
573
  """
577
574
  if self._supported_spaces is None:
578
- self._supported_spaces = sorted(
579
- {s for s in _space.Space.registry() if self.mapped_in_space(s)}
580
- )
575
+ self._supported_spaces = sorted({
576
+ s for s in _space.Space.registry()
577
+ if self.mapped_in_space(s, recurse=True)
578
+ })
581
579
  return self._supported_spaces
582
580
 
583
- def supports_space(self, space: _space.Space):
584
- """
585
- Return true if this region supports the given space, else False.
586
- """
587
- return any(s.matches(space) for s in self.supported_spaces)
588
-
589
581
  @property
590
582
  def spaces(self):
591
583
  return InstanceTable(
@@ -598,7 +590,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
598
590
  return len(self.find(other)) > 0
599
591
  else:
600
592
  try:
601
- regionmap = self.get_regional_map(space=other.space)
593
+ regionmap = self.get_regional_mask(space=other.space)
602
594
  return regionmap.__contains__(other)
603
595
  except NoMapAvailableError:
604
596
  return False
@@ -627,9 +619,9 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
627
619
  return None
628
620
  return self._ASSIGNMENT_CACHE[other, self].invert()
629
621
 
630
- if isinstance(other, location.Location):
631
- if self.mapped_in_space(other.space):
632
- regionmap = self.get_regional_map(other.space)
622
+ if isinstance(other, (location.Location, volume.Volume)):
623
+ if self.mapped_in_space(other.space, recurse=True):
624
+ regionmap = self.get_regional_mask(other.space)
633
625
  self._ASSIGNMENT_CACHE[self, other] = regionmap.assign(other)
634
626
  return self._ASSIGNMENT_CACHE[self, other]
635
627
 
@@ -646,7 +638,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
646
638
  for targetspace in self.supported_spaces:
647
639
  try:
648
640
  other_warped = other.warp(targetspace)
649
- regionmap = self.get_regional_map(targetspace)
641
+ regionmap = self.get_regional_mask(targetspace)
650
642
  assignment_result = regionmap.assign(other_warped)
651
643
  except SpaceWarpingFailedError:
652
644
  try:
@@ -696,10 +688,10 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
696
688
  self,
697
689
  space: _space.Space,
698
690
  maptype: MapType = MapType.LABELLED,
699
- threshold_statistical=None,
700
- restrict_space=True,
691
+ threshold_statistical: float = 0.0,
692
+ restrict_space: bool = True,
701
693
  **fetch_kwargs
702
- ):
694
+ ) -> Union[_boundingbox.BoundingBox, None]:
703
695
  """
704
696
  Compute the bounding box of this region in the given space.
705
697
 
@@ -711,9 +703,9 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
711
703
  Type of map to build ('labelled' will result in a binary mask,
712
704
  'statistical' attempts to build a statistical mask, possibly by
713
705
  elementwise maximum of statistical maps of children)
714
- threshold_statistical: float, or None
715
- if not None, masks will be preferably constructed by thresholding
716
- statistical maps with the given value.
706
+ threshold_statistical: float, default: 0.0
707
+ When masking a statistical map, use this threshold to convert
708
+ it to a binary mask before finding its bounding box.
717
709
  restrict_space: bool, default: False
718
710
  If True, it will not try to fetch maps from other spaces and warp
719
711
  its boundingbox to requested space.
@@ -724,16 +716,20 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
724
716
  """
725
717
  spaceobj = _space.Space.get_instance(space)
726
718
  try:
727
- mask = self.get_regional_map(
719
+ mask = self.get_regional_mask(
728
720
  spaceobj, maptype=maptype, threshold=threshold_statistical
729
721
  )
730
- return mask.get_boundingbox(clip=True, background=0.0, **fetch_kwargs)
722
+ return mask.get_boundingbox(
723
+ clip=True,
724
+ background=0.0,
725
+ **fetch_kwargs
726
+ )
731
727
  except (RuntimeError, ValueError):
732
728
  if restrict_space:
733
729
  return None
734
730
  for other_space in self.parcellation.spaces - spaceobj:
735
731
  try:
736
- mask = self.get_regional_map(
732
+ mask = self.get_regional_mask(
737
733
  other_space,
738
734
  maptype=maptype,
739
735
  threshold=threshold_statistical,
@@ -754,7 +750,14 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
754
750
  logger.error(f"Could not compute bounding box for {self.name}.")
755
751
  return None
756
752
 
757
- def compute_centroids(self, space: _space.Space) -> pointset.PointSet:
753
+ def compute_centroids(
754
+ self,
755
+ space: _space.Space,
756
+ maptype: MapType = MapType.LABELLED,
757
+ threshold_statistical: float = 0.0,
758
+ split_components: bool = True,
759
+ **fetch_kwargs,
760
+ ) -> pointcloud.PointCloud:
758
761
  """
759
762
  Compute the centroids of the region in the given space.
760
763
 
@@ -762,19 +765,32 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
762
765
  ----------
763
766
  space: Space
764
767
  reference space in which the computation will be performed
768
+ maptype: MapType, default: MapType.LABELLED
769
+ Type of map to build ('labelled' will result in a binary mask,
770
+ 'statistical' attempts to build a statistical mask, possibly by
771
+ elementwise maximum of statistical maps of children)
772
+ threshold_statistical: float, default: 0.0
773
+ When masking a statistical map, use this threshold to convert
774
+ it to a binary mask before finding its centroids.
765
775
 
766
776
  Returns
767
777
  -------
768
- PointSet
769
- Found centroids (as Point objects) in a PointSet
778
+ PointCloud
779
+ Found centroids (as Point objects) in a PointCloud
770
780
 
771
781
  Note
772
782
  ----
773
783
  A region can generally have multiple centroids if it has multiple
774
784
  connected components in the map.
775
785
  """
776
- props = self.spatial_props(space)
777
- return pointset.PointSet(
786
+ props = self.spatial_props(
787
+ space=space,
788
+ maptype=maptype,
789
+ threshold_statistical=threshold_statistical,
790
+ split_components=split_components,
791
+ **fetch_kwargs,
792
+ )
793
+ return pointcloud.PointCloud(
778
794
  [c.centroid for c in props],
779
795
  space=space
780
796
  )
@@ -783,7 +799,9 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
783
799
  self,
784
800
  space: _space.Space,
785
801
  maptype: MapType = MapType.LABELLED,
786
- threshold_statistical=None,
802
+ threshold_statistical: float = 0.0,
803
+ split_components: bool = True,
804
+ **fetch_kwargs,
787
805
  ):
788
806
  """
789
807
  Compute spatial properties for connected components of this region in the given space.
@@ -796,21 +814,21 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
796
814
  Type of map to build ('labelled' will result in a binary mask,
797
815
  'statistical' attempts to build a statistical mask, possibly by
798
816
  elementwise maximum of statistical maps of children)
799
- threshold_statistical: float, or None
817
+ threshold_statistical: float, default: 0.0
800
818
  if not None, masks will be preferably constructed by thresholding
801
819
  statistical maps with the given value.
802
820
 
803
821
  Returns
804
822
  -------
805
- Dict
806
- Dictionary of region's spatial properties
823
+ List
824
+ List of region's component spatial properties
807
825
  """
808
826
  if not isinstance(space, _space.Space):
809
827
  space = _space.Space.get_instance(space)
810
828
 
811
829
  # build binary mask of the image
812
830
  try:
813
- region_vol = self.get_regional_map(
831
+ region_vol = self.get_regional_mask(
814
832
  space, maptype=maptype, threshold=threshold_statistical
815
833
  )
816
834
  except NoMapAvailableError:
@@ -820,7 +838,9 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
820
838
  f"{', '.join(s.name for s in self.supported_spaces)}"
821
839
  )
822
840
 
823
- return region_vol.compute_spatial_props()
841
+ return region_vol.compute_spatial_props(
842
+ split_components=split_components, **fetch_kwargs
843
+ )
824
844
 
825
845
  def __iter__(self):
826
846
  """
@@ -832,9 +852,9 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
832
852
  def intersection(self, other: "location.Location") -> "location.Location":
833
853
  """Use this region for filtering a location object."""
834
854
 
835
- if self.supports_space(other.space):
855
+ if self.mapped_in_space(other.space, recurse=True):
836
856
  try:
837
- volume = self.get_regional_map(other.space)
857
+ volume = self.get_regional_mask(other.space)
838
858
  if volume is not None:
839
859
  return volume.intersection(other)
840
860
  except NotImplementedError:
@@ -844,7 +864,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
844
864
  for space in self.supported_spaces:
845
865
  if space.provides_image:
846
866
  try:
847
- volume = self.get_regional_map(space)
867
+ volume = self.get_regional_mask(space)
848
868
  if volume is not None:
849
869
  intersection = volume.intersection(other)
850
870
  logger.info(f"Warped {other} to {space} to find the intersection.")
@@ -898,7 +918,7 @@ def get_peid_from_region(region: Region) -> str:
898
918
 
899
919
  def get_related_regions(region: Region) -> Iterable["RegionRelationAssessments"]:
900
920
  """
901
- Get assements on relations of a region to others defined on EBRAINS.
921
+ Get assessments on relations of a region to others defined on EBRAINS.
902
922
 
903
923
  Parameters
904
924
  ----------
@@ -911,8 +931,8 @@ def get_related_regions(region: Region) -> Iterable["RegionRelationAssessments"]
911
931
  Example
912
932
  -------
913
933
  >>> region = siibra.get_region("monkey", "PG")
914
- >>> for assesment in siibra.core.region.get_related_regions(region):
915
- >>> print(assesment)
934
+ >>> for assessment in siibra.core.region.get_related_regions(region):
935
+ >>> print(assessment)
916
936
  'PG' is homologous to 'Area PGa (IPL)'
917
937
  'PG' is homologous to 'Area PGa (IPL) left'
918
938
  'PG' is homologous to 'Area PGa (IPL) right'
siibra/core/space.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2024
1
+ # Copyright 2018-2025
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
siibra/core/structure.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2024
1
+ # Copyright 2018-2025
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -76,7 +76,7 @@ class BrainStructure(ABC):
76
76
  # Two cases:
77
77
  # 1) self is location, other is location -> look at spatial intersection/relationship, do it here
78
78
  # 2) self is location, other is region -> get region map, then call again. do it here
79
- # If self is region -> Region overwrite this method, adressed there
79
+ # If self is region -> Region overwrite this method, addressed there
80
80
 
81
81
  assert not isinstance(self, _region.Region) # method is overwritten by Region!
82
82
  if (self, other) in self._ASSIGNMENT_CACHE:
siibra/exceptions.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2024
1
+ # Copyright 2018-2025
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -53,3 +53,15 @@ class ZeroVolumeBoundingBox(Exception):
53
53
 
54
54
  class NoneCoordinateSuppliedError(ValueError):
55
55
  pass
56
+
57
+
58
+ class MapNotFound(ValueError):
59
+ pass
60
+
61
+
62
+ class EmptyPointCloudError(ValueError):
63
+ pass
64
+
65
+
66
+ class NoPredifinedColormapException(ValueError):
67
+ pass
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2024
1
+ # Copyright 2018-2025
2
2
  # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
3
 
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");