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/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0a19
1
+ 1.0.1-alpha.1
siibra/__init__.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");
@@ -39,7 +39,7 @@ from . import configuration
39
39
  from . import experimental
40
40
  from .configuration import factory
41
41
  from . import features, livequeries
42
- from siibra.locations import Point, PointSet
42
+ from siibra.locations import Point, PointCloud
43
43
 
44
44
  import os as _os
45
45
  logger.info(f"Version: {__version__}")
@@ -51,7 +51,7 @@ logger.info(
51
51
  # forward access to some functions
52
52
  set_ebrains_token = _EbrainsRequest.set_token
53
53
  fetch_ebrains_token = _EbrainsRequest.fetch_token
54
- find_regions = _parcellation.Parcellation.find_regions
54
+ find_regions = _parcellation.find_regions
55
55
  from_json = factory.Factory.from_json
56
56
 
57
57
 
@@ -108,7 +108,7 @@ def set_feasible_download_size(maxsize_gbyte):
108
108
 
109
109
  def set_cache_size(maxsize_gbyte: int):
110
110
  """
111
- siibra runs maintainance on its local cache to keep it under a predetermined
111
+ siibra runs maintenance on its local cache to keep it under a predetermined
112
112
  size of 2 gigabytes. This method changes the cache size.
113
113
 
114
114
  Parameters
@@ -129,9 +129,9 @@ def warm_cache(level=WarmupLevel.INSTANCE):
129
129
  Preload preconfigured siibra concepts.
130
130
 
131
131
  Siibra relies on preconfigurations that simplify integrating various
132
- concepts such as parcellations, refernce spaces, and multimodal data
132
+ concepts such as parcellations, reference spaces, and multimodal data
133
133
  features. By preloading the instances, siibra commits all preconfigurations
134
- to the memory at once instead of commiting them when required.
134
+ to the memory at once instead of committing them when required.
135
135
  """
136
136
  Warmup.warmup(level)
137
137
 
@@ -150,7 +150,7 @@ def __dir__():
150
150
  "get_template",
151
151
  "MapType",
152
152
  "Point",
153
- "PointSet",
153
+ "PointCloud",
154
154
  "QUIET",
155
155
  "VERBOSE",
156
156
  "fetch_ebrains_token",
siibra/commons.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");
@@ -139,7 +139,7 @@ class InstanceTable(Generic[T], Iterable):
139
139
  return (w for w in self._elements.values())
140
140
 
141
141
  def __contains__(self, key: Union[str, T]) -> bool:
142
- """Test wether the given key or element is defined by the registry."""
142
+ """Test whether the given key or element is defined by the registry."""
143
143
  if isinstance(key, str):
144
144
  return key in self._elements
145
145
  return key in [item for _, item in self._elements.values()]
@@ -366,9 +366,6 @@ class MapType(Enum):
366
366
  STATISTICAL = 2
367
367
 
368
368
 
369
- SIIBRA_DEFAULT_MAPTYPE = MapType.LABELLED
370
- SIIBRA_DEFAULT_MAP_THRESHOLD = 0.0
371
-
372
369
  REMOVE_FROM_NAME = [
373
370
  "hemisphere",
374
371
  " -",
@@ -539,7 +536,8 @@ def resample_img_to_img(
539
536
  resampled_img = resample_to_img(
540
537
  source_img=source_img,
541
538
  target_img=target_img,
542
- interpolation=interpolation
539
+ interpolation=interpolation,
540
+ force_resample=True, # False is intended for testing. see nilearn docs
543
541
  )
544
542
  return resampled_img
545
543
 
@@ -557,7 +555,7 @@ def connected_components(
557
555
 
558
556
  Note
559
557
  ----
560
- `Uses skimage.measure.label()` to determine foreground compenents.
558
+ `Uses skimage.measure.label()` to determine foreground components.
561
559
 
562
560
  Parameters
563
561
  ----------
@@ -571,7 +569,7 @@ def connected_components(
571
569
  Yields
572
570
  ------
573
571
  Generator[Tuple[int, np.ndarray], None, None]
574
- tuple of integer label of the component and component as an nd.array in
572
+ tuple of integer label of the component and component as an ndarray in
575
573
  the shape of the original image.
576
574
  """
577
575
  from skimage import measure
@@ -586,49 +584,6 @@ def connected_components(
586
584
  )
587
585
 
588
586
 
589
- class PolyLine:
590
- """Simple polyline representation which allows equidistant sampling.."""
591
-
592
- def __init__(self, pts):
593
- self.pts = pts
594
- self.lengths = [
595
- np.sqrt(np.sum((pts[i, :] - pts[i - 1, :]) ** 2))
596
- for i in range(1, pts.shape[0])
597
- ]
598
-
599
- def length(self):
600
- return sum(self.lengths)
601
-
602
- def sample(self, d):
603
-
604
- # if d is interable, we assume a list of sample positions
605
- try:
606
- iter(d)
607
- except TypeError:
608
- positions = [d]
609
- else:
610
- positions = d
611
-
612
- samples = []
613
- for s_ in positions:
614
- s = min(max(s_, 0), 1)
615
- target_distance = s * self.length()
616
- current_distance = 0
617
- for i, length in enumerate(self.lengths):
618
- current_distance += length
619
- if current_distance >= target_distance:
620
- p1 = self.pts[i, :]
621
- p2 = self.pts[i + 1, :]
622
- r = (target_distance - current_distance + length) / length
623
- samples.append(p1 + (p2 - p1) * r)
624
- break
625
-
626
- if len(samples) == 1:
627
- return samples[0]
628
- else:
629
- return np.array(samples)
630
-
631
-
632
587
  def unify_stringlist(L: list):
633
588
  """Adds asterisks to strings that appear multiple times, so the resulting
634
589
  list has only unique strings but still the same length, order, and meaning.
@@ -690,11 +645,11 @@ def MI(arr1, arr2, nbins=100, normalized=True):
690
645
  assert (all(arr.size > 0) for arr in [arr1, arr2])
691
646
 
692
647
  # compute the normalized joint 2D histogram as an
693
- # empirical measure of the joint probabily of arr1 and arr2
648
+ # empirical measure of the joint probability of arr1 and arr2
694
649
  pxy, _, _ = np.histogram2d(arr1.ravel(), arr2.ravel(), bins=nbins)
695
650
  pxy /= pxy.sum()
696
651
 
697
- # extract the empirical propabilities of intensities
652
+ # extract the empirical probabilities of intensities
698
653
  # from the joint histogram
699
654
  px = np.sum(pxy, axis=1) # marginal for x over y
700
655
  py = np.sum(pxy, axis=0) # marginal for y over x
@@ -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");
@@ -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");
@@ -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");
@@ -24,7 +24,7 @@ from ..features.tabular import (
24
24
  )
25
25
  from ..features.image import sections, volume_of_interest
26
26
  from ..core import atlas, parcellation, space, region
27
- from ..locations import point, pointset, boundingbox
27
+ from ..locations import point, pointcloud, boundingbox
28
28
  from ..retrieval import datasets, repositories
29
29
  from ..volumes import volume, sparsemap, parcellationmap
30
30
  from ..volumes.providers.provider import VolumeProvider
@@ -302,25 +302,18 @@ class Factory:
302
302
  @build_type("siibra/map/v0.0.1")
303
303
  def build_map(cls, spec):
304
304
  # maps have no configured identifier - we require the spec filename to build one
305
- assert "filename" in spec
306
- basename = path.splitext(path.basename(spec["filename"]))[0]
307
- name = (
308
- basename.replace("-", " ")
309
- .replace("_", " ")
310
- .replace("continuous", "statistical")
311
- )
312
- identifier = f"{spec['@type'].replace('/', '-')}_{basename}"
305
+ identifier = spec.get("@id")
313
306
  volumes = cls.extract_volumes(
314
- spec, space_id=spec["space"].get("@id"), name_prefix=basename
307
+ spec, space_id=spec["space"].get("@id"), name_prefix=identifier
315
308
  )
316
309
 
317
- if spec.get("sparsemap", {}).get("is_sparsemap"):
310
+ if spec.get("represented_as_sparsemap", False):
318
311
  Maptype = sparsemap.SparseMap
319
312
  else:
320
313
  Maptype = parcellationmap.Map
321
314
  return Maptype(
322
- identifier=spec.get("@id", identifier),
323
- name=spec.get("name", name),
315
+ identifier=identifier,
316
+ name=spec.get("name"),
324
317
  space_spec=spec.get("space", {}),
325
318
  parcellation_spec=spec.get("parcellation", {}),
326
319
  indices=spec.get("indices", {}),
@@ -363,18 +356,18 @@ class Factory:
363
356
 
364
357
  @classmethod
365
358
  @build_type("tmp/poly")
366
- @build_type("siibra/location/pointset/v0.1")
367
- def build_pointset(cls, spec):
359
+ @build_type("siibra/location/pointcloud/v0.1")
360
+ def build_pointcloud(cls, spec):
368
361
  if spec.get("@type") == "tmp/poly":
369
362
  space_id = spec["coordinateSpace"]["@id"]
370
363
  coords = []
371
364
  for coord in spec["coordinates"]:
372
365
  assert all(c["unit"]["@id"] == "id.link/mm" for c in coord)
373
366
  coords.append(list(np.float16(c["value"]) for c in coord))
374
- elif spec.get("@type") == "siibra/location/pointset/v0.1":
367
+ elif spec.get("@type") == "siibra/location/pointcloud/v0.1":
375
368
  space_id = spec.get("space").get("@id")
376
369
  coords = [tuple(c) for c in spec.get("coordinates")]
377
- return pointset.PointSet(coords, space=space_id)
370
+ return pointcloud.PointCloud(coords, space=space_id)
378
371
 
379
372
  @classmethod
380
373
  @build_type("siibra/location/boundingbox/v0.1")
@@ -584,11 +577,8 @@ class Factory:
584
577
 
585
578
  if isinstance(spec, str):
586
579
  if path.isfile(spec):
587
- fname = spec
588
580
  with open(spec, "r") as f:
589
581
  spec = json.load(f)
590
- assert "filename" not in spec
591
- spec["filename"] = fname
592
582
  else:
593
583
  spec = json.loads(spec)
594
584
 
siibra/core/__init__.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/assignment.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/atlas.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");
@@ -197,27 +197,29 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
197
197
 
198
198
  def find_regions(
199
199
  self,
200
- regionspec,
201
- all_versions=False,
202
- filter_children=True,
203
- **kwargs
200
+ regionspec: str,
201
+ all_versions: bool = False,
202
+ filter_children: bool = True,
203
+ find_topmost: bool = False
204
204
  ):
205
205
  """
206
- Find regions with the given specification in all
207
- parcellations offered by the atlas. Additional kwargs
208
- are passed on to Parcellation.find().
206
+ Find regions with the given specification in all parcellations offered
207
+ by the atlas.
209
208
 
210
209
  Parameters
211
210
  ----------
212
- regionspec: str, regex, int, Region, MapIndex
211
+ regionspec: str, regex
213
212
  - a string with a possibly inexact name (matched both against the name and the identifier key)
214
213
  - a string in '/pattern/flags' format to use regex search (acceptable flags: aiLmsux, see at https://docs.python.org/3/library/re.html#flags)
215
214
  - a regex applied to region names
216
- - a Region object
217
215
  all_versions : Bool, default: False
218
216
  If True, matched regions for all versions of a parcellation are returned.
219
217
  filter_children : bool, default: True
220
218
  If False, children of matched parents will be returned.
219
+ find_topmost : bool, default: False
220
+ If True (requires `filter_children=True`), will return parent
221
+ structures if all children are matched, even though the parent
222
+ itself might not match the specification.
221
223
 
222
224
  Returns
223
225
  -------
@@ -225,9 +227,13 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
225
227
  list of regions matching to the regionspec
226
228
  """
227
229
  result = []
228
- for p in self._parcellation_ids:
229
- parcobj = _parcellation.Parcellation.get_instance(p)
230
- if parcobj.is_newest_version or all_versions:
231
- match = parcobj.find(regionspec, filter_children=filter_children, **kwargs)
232
- result.extend(match)
230
+ for p in self.parcellations:
231
+ if p.is_newest_version or all_versions:
232
+ result.extend(
233
+ p.find(
234
+ regionspec=regionspec,
235
+ filter_children=filter_children,
236
+ find_topmost=find_topmost
237
+ )
238
+ )
233
239
  return result
siibra/core/concept.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");
@@ -145,9 +145,9 @@ class AtlasConcept:
145
145
  @property
146
146
  def authors(self):
147
147
  return [
148
- contributer['name']
148
+ contributor['name']
149
149
  for ds in self.datasets
150
- for contributer in ds.contributors
150
+ for contributor in ds.contributors
151
151
  ]
152
152
 
153
153
  @property
@@ -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");
@@ -17,11 +17,21 @@ from . import region
17
17
 
18
18
  from ..commons import logger, MapType, Species
19
19
  from ..volumes import parcellationmap
20
+ from ..exceptions import MapNotFound
20
21
 
21
- from typing import Union, List, Dict
22
+ from functools import lru_cache
22
23
  import re
24
+ from typing import Union, List, TYPE_CHECKING
25
+ try:
26
+ from typing import Literal
27
+ except ImportError:
28
+ # support python 3.7
29
+ from typing_extensions import Literal
23
30
 
24
31
 
32
+ if TYPE_CHECKING:
33
+ from .space import Space
34
+
25
35
  # NOTE : such code could be used to automatically resolve
26
36
  # multiple matching parcellations for a short spec to the newset version:
27
37
  # try:
@@ -68,8 +78,6 @@ class ParcellationVersion:
68
78
 
69
79
  class Parcellation(region.Region, configuration_folder="parcellations"):
70
80
 
71
- _CACHED_REGION_SEARCHES: Dict[str, List[region.Region]] = {}
72
-
73
81
  def __init__(
74
82
  self,
75
83
  identifier: str,
@@ -140,7 +148,12 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
140
148
  self._CACHED_MATCHES[spec] = True
141
149
  return super().matches(spec)
142
150
 
143
- def get_map(self, space=None, maptype: Union[str, MapType] = MapType.LABELLED, spec: str = ""):
151
+ def get_map(
152
+ self,
153
+ space: Union[str, "Space"],
154
+ maptype: Union[Literal['labelled', 'statistical'], MapType] = MapType.LABELLED,
155
+ spec: str = ""
156
+ ):
144
157
  """
145
158
  Get the maps for the parcellation in the requested template space.
146
159
 
@@ -153,8 +166,8 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
153
166
  Parameters
154
167
  ----------
155
168
  space: Space or str
156
- template space specification
157
- maptype: MapType
169
+ reference space specification such as name, id, or a `Space` instance.
170
+ maptype: MapType or str
158
171
  Type of map requested (e.g., statistical or labelled).
159
172
  Use MapType.STATISTICAL to request probability maps.
160
173
  Defaults to MapType.LABELLED.
@@ -169,26 +182,24 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
169
182
  A ParcellationMap representing the volumetric map or
170
183
  a SparseMap representing the list of statistical maps.
171
184
  """
172
- if not isinstance(maptype, MapType):
185
+ if isinstance(maptype, str):
173
186
  maptype = MapType[maptype.upper()]
187
+ assert isinstance(maptype, MapType), "Possible values of `maptype` are `MapType`s, 'labelled', 'statistical'."
174
188
 
175
189
  candidates = [
176
190
  m for m in parcellationmap.Map.registry()
177
191
  if m.space.matches(space)
178
192
  and m.maptype == maptype
179
- and m.parcellation
180
193
  and m.parcellation.matches(self)
181
194
  ]
182
195
  if len(candidates) == 0:
183
- logger.error(f"No {maptype} map in {space} available for {str(self)}")
184
- return None
196
+ raise MapNotFound(f"No '{maptype}' map in '{space}' available for {str(self)}")
185
197
  if len(candidates) > 1:
186
198
  spec_candidates = [
187
- c for c in candidates if all(w.lower() in c.name.lower() for w in spec.split())
199
+ c for c in candidates if all(w.lower() in c.id.lower() for w in spec.split())
188
200
  ]
189
201
  if len(spec_candidates) == 0:
190
- logger.warning(f"'{spec}' does not match any options from {[c.name for c in candidates]}.")
191
- return None
202
+ raise MapNotFound(f"'{spec}' does not match any options from {[c.name for c in candidates]}.")
192
203
  if len(spec_candidates) > 1:
193
204
  logger.warning(
194
205
  f"Multiple maps are available in this specification of space, parcellation, and map type.\n"
@@ -197,43 +208,6 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
197
208
  return spec_candidates[0]
198
209
  return candidates[0]
199
210
 
200
- @staticmethod
201
- def find_regions(region_spec: str, parents_only=True):
202
- """
203
- Find regions that match the given region specification in the subtree
204
- headed by each parcellation in the registry.
205
- Note
206
- ----
207
- Use Region.find() to search for a region in an instance of a
208
- parcellation.
209
-
210
- Parameters
211
- ----------
212
- regionspec: str
213
- a string with a possibly inexact name, which is matched both
214
- against the name and the identifier key,
215
- parents_only: bool
216
- If true, children of matched parents will not be returned
217
- Returns
218
- -------
219
- List[Region]
220
- list of matching regions
221
- """
222
- MEM = Parcellation._CACHED_REGION_SEARCHES
223
- if region_spec not in MEM:
224
- MEM[region_spec] = [
225
- r
226
- for p in Parcellation.registry()
227
- for r in p.find(regionspec=region_spec)
228
- ]
229
- if parents_only:
230
- return [
231
- r for r in MEM[region_spec]
232
- if (r.parent is None) or (r.parent not in MEM[region_spec])
233
- ]
234
- else:
235
- return MEM[region_spec]
236
-
237
211
  @property
238
212
  def is_newest_version(self):
239
213
  return (self.version is None) or (self.version.next_id is None)
@@ -247,7 +221,7 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
247
221
  """
248
222
  spec = re.sub(r'Group: *', '', spec)
249
223
  for substr in re.findall(r'\(.*?\)', spec):
250
- # temporarilty replace commas inside brackets with a placeholder
224
+ # temporarily replace commas inside brackets with a placeholder
251
225
  # because these are not region spec delimiters
252
226
  spec = spec.replace(substr, re.sub(r', *', '##', substr))
253
227
  # process the comma separated substrings
@@ -263,7 +237,7 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
263
237
  def get_region(
264
238
  self,
265
239
  regionspec: Union[str, region.Region],
266
- find_topmost: bool = True,
240
+ find_topmost: bool = False,
267
241
  allow_tuple: bool = False
268
242
  ):
269
243
  """
@@ -281,7 +255,7 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
281
255
  regionspec: str, Region
282
256
  - a string with a possibly inexact name (matched both against the name and the identifier key)
283
257
  - a Region object
284
- find_topmost: bool, default: True
258
+ find_topmost: bool, default: False
285
259
  If True, will automatically return the parent of a decoded region
286
260
  the decoded region is its only child.
287
261
  allow_tuple: bool, default: False
@@ -370,3 +344,44 @@ class Parcellation(region.Region, configuration_folder="parcellations"):
370
344
  )
371
345
  return self.name < other.name
372
346
  return self.version.__lt__(other.version)
347
+
348
+
349
+ @lru_cache(maxsize=128)
350
+ def find_regions(
351
+ regionspec: str,
352
+ filter_children=True,
353
+ find_topmost=False
354
+ ):
355
+ """
356
+ Find regions matching the given region specification across all parcellation
357
+ instances in the registry.
358
+
359
+ Parameters
360
+ ----------
361
+ regionspec: str, regex, Region
362
+ - a string with a possibly inexact name (matched both against the name and the identifier key)
363
+ - a string in '/pattern/flags' format to use regex search (acceptable flags: aiLmsux) (see https://docs.python.org/3/library/re.html#flags)
364
+ - a regex applied to region names
365
+ - a Region object
366
+ filter_children : bool, default: True
367
+ If True, children of matched parents will not be returned
368
+ find_topmost : bool, default: False
369
+ If True (requires `filter_children=True`), will return parent
370
+ structures if all children are matched, even though the parent
371
+ itself might not match the specification.
372
+
373
+ Returns
374
+ -------
375
+ list[Region]
376
+ list of regions matching to the regionspec
377
+ """
378
+ result = []
379
+ for p in Parcellation.registry():
380
+ result.extend(
381
+ p.find(
382
+ regionspec=regionspec,
383
+ filter_children=filter_children,
384
+ find_topmost=find_topmost
385
+ )
386
+ )
387
+ return result