siibra 0.5a2__py3-none-any.whl → 1.0.0a1__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 (83) hide show
  1. siibra/VERSION +1 -1
  2. siibra/__init__.py +20 -12
  3. siibra/commons.py +145 -90
  4. siibra/configuration/__init__.py +1 -1
  5. siibra/configuration/configuration.py +22 -17
  6. siibra/configuration/factory.py +177 -128
  7. siibra/core/__init__.py +1 -8
  8. siibra/core/{relation_qualification.py → assignment.py} +17 -14
  9. siibra/core/atlas.py +66 -35
  10. siibra/core/concept.py +81 -39
  11. siibra/core/parcellation.py +83 -67
  12. siibra/core/region.py +569 -263
  13. siibra/core/space.py +7 -39
  14. siibra/core/structure.py +111 -0
  15. siibra/exceptions.py +63 -0
  16. siibra/experimental/__init__.py +19 -0
  17. siibra/experimental/contour.py +61 -0
  18. siibra/experimental/cortical_profile_sampler.py +57 -0
  19. siibra/experimental/patch.py +98 -0
  20. siibra/experimental/plane3d.py +256 -0
  21. siibra/explorer/__init__.py +16 -0
  22. siibra/explorer/url.py +112 -52
  23. siibra/explorer/util.py +31 -9
  24. siibra/features/__init__.py +73 -8
  25. siibra/features/anchor.py +75 -196
  26. siibra/features/connectivity/__init__.py +1 -1
  27. siibra/features/connectivity/functional_connectivity.py +2 -2
  28. siibra/features/connectivity/regional_connectivity.py +99 -10
  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 +3 -3
  34. siibra/features/feature.py +219 -110
  35. siibra/features/image/__init__.py +1 -1
  36. siibra/features/image/image.py +21 -13
  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 +24 -13
  41. siibra/features/tabular/cell_density_profile.py +111 -69
  42. siibra/features/tabular/cortical_profile.py +82 -16
  43. siibra/features/tabular/gene_expression.py +117 -6
  44. siibra/features/tabular/layerwise_bigbrain_intensities.py +7 -9
  45. siibra/features/tabular/layerwise_cell_density.py +9 -24
  46. siibra/features/tabular/receptor_density_fingerprint.py +11 -6
  47. siibra/features/tabular/receptor_density_profile.py +12 -15
  48. siibra/features/tabular/regional_timeseries_activity.py +74 -18
  49. siibra/features/tabular/tabular.py +17 -8
  50. siibra/livequeries/__init__.py +1 -7
  51. siibra/livequeries/allen.py +139 -77
  52. siibra/livequeries/bigbrain.py +104 -128
  53. siibra/livequeries/ebrains.py +7 -4
  54. siibra/livequeries/query.py +1 -2
  55. siibra/locations/__init__.py +32 -25
  56. siibra/locations/boundingbox.py +153 -127
  57. siibra/locations/location.py +45 -80
  58. siibra/locations/point.py +97 -83
  59. siibra/locations/pointcloud.py +349 -0
  60. siibra/retrieval/__init__.py +1 -1
  61. siibra/retrieval/cache.py +107 -13
  62. siibra/retrieval/datasets.py +9 -14
  63. siibra/retrieval/exceptions/__init__.py +2 -1
  64. siibra/retrieval/repositories.py +147 -53
  65. siibra/retrieval/requests.py +64 -29
  66. siibra/vocabularies/__init__.py +2 -2
  67. siibra/volumes/__init__.py +7 -9
  68. siibra/volumes/parcellationmap.py +396 -253
  69. siibra/volumes/providers/__init__.py +20 -0
  70. siibra/volumes/providers/freesurfer.py +113 -0
  71. siibra/volumes/{gifti.py → providers/gifti.py} +29 -18
  72. siibra/volumes/{neuroglancer.py → providers/neuroglancer.py} +204 -92
  73. siibra/volumes/{nifti.py → providers/nifti.py} +64 -44
  74. siibra/volumes/providers/provider.py +107 -0
  75. siibra/volumes/sparsemap.py +159 -260
  76. siibra/volumes/volume.py +720 -152
  77. {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/METADATA +25 -28
  78. siibra-1.0.0a1.dist-info/RECORD +84 -0
  79. {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/WHEEL +1 -1
  80. siibra/locations/pointset.py +0 -198
  81. siibra-0.5a2.dist-info/RECORD +0 -74
  82. {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/LICENSE +0 -0
  83. {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
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");
@@ -14,6 +14,9 @@
14
14
  # limitations under the License.
15
15
  """Multimodal data features types and query mechanisms."""
16
16
 
17
+ from typing import Union
18
+ from functools import partial
19
+
17
20
  from . import (
18
21
  connectivity,
19
22
  tabular,
@@ -21,21 +24,24 @@ from . import (
21
24
  dataset,
22
25
  )
23
26
 
27
+ from ..commons import logger
24
28
 
25
29
  from .feature import Feature
26
- get = Feature.match
30
+ from ..retrieval import cache
31
+ from ..commons import siibra_tqdm
27
32
 
33
+ get = Feature._match
28
34
 
29
- TYPES = Feature._get_subclasses()
35
+ TYPES = Feature._get_subclasses() # Feature types that can be used to query for features
30
36
 
31
37
 
32
38
  def __dir__():
33
- return list(Feature.CATEGORIZED.keys())
39
+ return list(Feature._CATEGORIZED.keys()) + ["get", "TYPES", "render_ascii_tree"]
34
40
 
35
41
 
36
42
  def __getattr__(attr: str):
37
- if attr in Feature.CATEGORIZED:
38
- return Feature.CATEGORIZED[attr]
43
+ if attr in Feature._CATEGORIZED:
44
+ return Feature._CATEGORIZED[attr]
39
45
  else:
40
46
  hint = ""
41
47
  if isinstance(attr, str):
@@ -46,7 +52,66 @@ def __getattr__(attr: str):
46
52
  raise AttributeError(f"No such attribute: {__name__}.{attr} " + hint)
47
53
 
48
54
 
49
- def warm_cache():
55
+ @cache.Warmup.register_warmup_fn()
56
+ def _warm_feature_cache_instances():
50
57
  """Preload preconfigured multimodal data features."""
51
58
  for ftype in TYPES.values():
52
- _ = ftype.get_instances()
59
+ _ = ftype._get_instances()
60
+
61
+
62
+ @cache.Warmup.register_warmup_fn(cache.WarmupLevel.DATA, is_factory=True)
63
+ def _warm_feature_cache_data():
64
+ return_callables = []
65
+ for ftype in TYPES.values():
66
+ instances = ftype._get_instances()
67
+
68
+ # the instances *must* be cleared, or it will impede the garbage collection, and results in memleak
69
+ ftype._clean_instances()
70
+ tally = siibra_tqdm(desc=f"Warming data {ftype.__name__}", total=len(instances))
71
+ for f in instances:
72
+ def get_data(arg):
73
+ tally = arg.pop("tally")
74
+ feature = arg.pop("feature")
75
+ # TODO
76
+ # the try catch is as a result of https://github.com/FZJ-INM1-BDA/siibra-python/issues/509
77
+ # sometimes f.data can fail
78
+ try:
79
+ _ = feature.data
80
+ except Exception as e:
81
+ logger.warn(f"Feature {feature.name} warmup failed: {str(e)}")
82
+ finally:
83
+ tally.update(1)
84
+ # append dictionary, so that popping the dictionary will mark the feature to be garbage collected
85
+ return_callables.append(partial(get_data, {"feature": f, "tally": tally}))
86
+ return return_callables
87
+
88
+
89
+ def render_ascii_tree(class_or_classname: Union[type, str]):
90
+ """
91
+ Print the ascii hierarchy representation of a feature type.
92
+
93
+ Parameters
94
+ ----------
95
+ class_or_classname: type, str
96
+ Any Feature class or string of the feature type name
97
+ """
98
+ from anytree.importer import DictImporter
99
+ from anytree import RenderTree
100
+ Cls = TYPES[class_or_classname] if isinstance(class_or_classname, str) else class_or_classname
101
+ assert issubclass(Cls, Feature)
102
+
103
+ def create_treenode(feature_type):
104
+ return {
105
+ 'name': feature_type.__name__,
106
+ 'children': [
107
+ create_treenode(c)
108
+ for c in feature_type.__subclasses__()
109
+ ]
110
+ }
111
+ D = create_treenode(Cls)
112
+ importer = DictImporter()
113
+ tree = importer.import_(D)
114
+ print("\n".join(
115
+ "%s%s" % (pre, node.name)
116
+ for pre, _, node in RenderTree(tree)
117
+ ))
siibra/features/anchor.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
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");
@@ -12,40 +12,41 @@
12
12
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
- """Handles the relation between study targets and AtlasConcepts."""
15
+ """Handles the relation between study targets and BrainStructures."""
16
16
 
17
- from ..commons import logger, Species
17
+ from ..commons import Species, logger
18
18
 
19
- from ..core.concept import AtlasConcept
19
+ from ..core.structure import BrainStructure
20
+ from ..core.assignment import AnatomicalAssignment, Qualification
20
21
  from ..locations.location import Location
21
- from ..locations.boundingbox import BoundingBox
22
- from ..core.parcellation import Parcellation
22
+ from ..core.parcellation import Parcellation, find_regions
23
23
  from ..core.region import Region
24
24
  from ..core.space import Space
25
- from ..core.relation_qualification import Qualification as AssignmentQualification, RelationAssignment
25
+ from ..exceptions import SpaceWarpingFailedError
26
26
 
27
27
  from ..vocabularies import REGION_ALIASES
28
28
 
29
- from typing import Union, List, Dict
30
-
31
- AnatomicalAssignment = RelationAssignment[Union[Region, Location]]
29
+ from typing import Union, List, Dict, Iterable
32
30
 
33
31
 
34
32
  class AnatomicalAnchor:
35
33
  """
36
- Anatomical anchor to an atlas region,
37
- a geometric primitive in an atlas reference space,
38
- or both.
34
+ Anatomical anchor to an atlas region, a geometric primitive in an atlas
35
+ reference space, or both.
39
36
  """
40
37
 
41
- _MATCH_MEMO: Dict[str, Dict[Region, AssignmentQualification]] = {}
42
- _MASK_MEMO = {}
38
+ _MATCH_MEMO: Dict[str, Dict[Region, Qualification]] = {}
43
39
 
44
- def __init__(self, species: Union[List[Species], Species, str], location: Location = None, region: Union[str, Region] = None):
40
+ def __init__(
41
+ self,
42
+ species: Union[List[Species], Species, str],
43
+ location: Location = None,
44
+ region: Union[str, Region] = None
45
+ ):
45
46
 
46
47
  if isinstance(species, (str, Species)):
47
48
  self.species = {Species.decode(species)}
48
- elif isinstance(species, list):
49
+ elif isinstance(species, Iterable):
49
50
  assert all(isinstance(_, Species) for _ in species)
50
51
  self.species = set(species)
51
52
  else:
@@ -55,7 +56,7 @@ class AnatomicalAnchor:
55
56
  else:
56
57
  self.species = {sp}
57
58
  self._location_cached = location
58
- self._assignments: Dict[Union[AtlasConcept, Location], List[AnatomicalAssignment]] = {}
59
+ self._assignments: Dict[BrainStructure, List[AnatomicalAssignment]] = {}
59
60
  self._last_matched_concept = None
60
61
  if isinstance(region, dict):
61
62
  self._regions_cached = region
@@ -64,8 +65,9 @@ class AnatomicalAnchor:
64
65
  self._regions_cached = None
65
66
  self._regionspec = None
66
67
  if isinstance(region, Region):
67
- self._regions_cached = {region: AssignmentQualification.EXACT}
68
+ self._regions_cached = {region: Qualification.EXACT}
68
69
  elif isinstance(region, str):
70
+ # we will decode regions only when needed, see self.regions property
69
71
  self._regionspec = region
70
72
  else:
71
73
  if region is not None:
@@ -73,24 +75,25 @@ class AnatomicalAnchor:
73
75
  self._aliases_cached = None
74
76
 
75
77
  @property
76
- def location(self):
78
+ def location(self) -> Location:
77
79
  # allow to overwrite in derived classes
78
80
  return self._location_cached
79
81
 
80
82
  @property
81
83
  def parcellations(self) -> List[Parcellation]:
84
+ """
85
+ Return any parcellation objects that regions of this anchor belong to.
86
+ """
82
87
  return list({region.root for region in self.regions})
83
88
 
84
89
  @property
85
90
  def space(self) -> Space:
86
91
  # may be overriden by derived classes, e.g. in features.VolumeOfInterest
87
- if self.location is None:
88
- return None
89
- else:
90
- return self.location.space
92
+ return None if self.location is None else self.location.space
91
93
 
92
94
  @property
93
95
  def region_aliases(self):
96
+ # return any predefined aliases for the region specified in this anchor.
94
97
  if self._aliases_cached is None:
95
98
  self._aliases_cached: Dict[str, Dict[str, str]] = {
96
99
  Species.decode(species_str): region_alias_mapping
@@ -100,43 +103,52 @@ class AnatomicalAnchor:
100
103
  return self._aliases_cached
101
104
 
102
105
  @property
103
- def has_region_aliases(self):
106
+ def has_region_aliases(self) -> bool:
104
107
  return len(self.region_aliases) > 0
105
108
 
106
109
  @property
107
- def regions(self) -> Dict[Region, AssignmentQualification]:
110
+ def regions(self) -> Dict[Region, Qualification]:
111
+ """
112
+ Return the list of regions associated with this anchor.
113
+ Decode the self._regionspec string into region objects now,
114
+ if applicable and called for the first time.
115
+ """
108
116
  # decoding region strings is quite compute intensive, so we cache this at the class level
109
117
  if self._regions_cached is not None:
110
118
  return self._regions_cached
111
119
 
112
120
  if self._regionspec is None:
113
- self._regions_cached = {}
121
+ self._regions_cached = dict()
114
122
  return self._regions_cached
115
123
 
116
- if self._regionspec not in self.__class__._MATCH_MEMO:
117
- self._regions_cached = {}
118
- # decode the region specification into a set of region objects
124
+ match_key = self._regionspec + '-' + str(self.species)
125
+ if match_key not in self.__class__._MATCH_MEMO:
126
+ # decode the region specification into a dict of region objects and assignment qualifications
119
127
  regions = {
120
- r: AssignmentQualification.EXACT
121
- for species in self.species
122
- for r in Parcellation.find_regions(self._regionspec)
123
- if r.species == species
128
+ region: Qualification.EXACT
129
+ for region in find_regions(self._regionspec, filter_children=True, find_topmost=False)
130
+ if region.species in self.species
124
131
  }
125
132
  # add more regions from possible aliases of the region spec
126
133
  for alt_species, aliases in self.region_aliases.items():
127
- for regionspec, qualificationspec in aliases.items():
128
- for r in Parcellation.find_regions(regionspec):
134
+ for alias_regionspec, qualificationspec in aliases.items():
135
+ for r in find_regions(alias_regionspec, filter_children=True, find_topmost=False):
129
136
  if r.species != alt_species:
130
137
  continue
131
- if r not in self._regions_cached:
132
- regions[r] = AssignmentQualification[qualificationspec.upper()]
133
- self.__class__._MATCH_MEMO[self._regionspec] = regions
134
- self._regions_cached = self.__class__._MATCH_MEMO[self._regionspec]
138
+ if r not in regions:
139
+ regions[r] = Qualification[qualificationspec.upper()]
140
+
141
+ self.__class__._MATCH_MEMO[match_key] = regions
142
+ self._regions_cached = self.__class__._MATCH_MEMO[match_key]
135
143
 
136
144
  return self._regions_cached
137
145
 
138
146
  def __str__(self):
139
- region = "" if self._regionspec is None else str(self._regionspec)
147
+ parcs = {p.id: p.name for p in self.represented_parcellations()}
148
+ if len(parcs) == 1 and self._regionspec in [pid for pid in parcs]:
149
+ region = parcs[self._regionspec] # if parcellation was anchored with the id instead of the name
150
+ else:
151
+ region = "" if self._regionspec is None else str(self._regionspec)
140
152
  location = "" if self.location is None else str(self.location)
141
153
  separator = " " if min(len(region), len(location)) > 0 else ""
142
154
  if region and location:
@@ -144,174 +156,41 @@ class AnatomicalAnchor:
144
156
  else:
145
157
  return region + separator + location
146
158
 
147
- def __repr__(self):
148
- return self.__str__()
149
-
150
- def assign(self, concept: AtlasConcept):
159
+ def assign(self, concept: Union[BrainStructure, Space]) -> AnatomicalAssignment:
151
160
  """
152
- Match this anchoring to an atlas concept.
161
+ Match this anchor to a query concept. Assignments are cached at runtime,
162
+ so repeated assignment with the same concept will be cheap.
153
163
  """
164
+ if isinstance(concept, Space):
165
+ if self.location is not None and self.location.space.matches(concept):
166
+ return [AnatomicalAssignment(concept, self.location, Qualification.CONTAINED)]
167
+ else:
168
+ return []
169
+
154
170
  if concept not in self._assignments:
155
- matches: List[AnatomicalAssignment] = []
156
- if isinstance(concept, Space):
157
- if self.space == concept:
158
- matches.append(
159
- AnatomicalAssignment(self.space, concept, AssignmentQualification.EXACT)
160
- )
161
- elif isinstance(concept, Region):
162
- if concept.species in self.species:
163
- if any(_.matches(self._regionspec) for _ in concept) \
164
- or self.has_region_aliases: # dramatic speedup, since decoding _regionspec is expensive
165
- for r in self.regions:
166
- matches.append(AnatomicalAnchor.match_regions(r, concept))
167
- if len(concept.root.find(self._regionspec)) == 0:
168
- # We perform the (quite expensive) location-to-region test
169
- # only if this anchor's regionspec is not known to the
170
- # parcellation of the query region. Otherwise we can rely
171
- # on the region-to-region test.
172
- if self.location is not None:
173
- matches.append(AnatomicalAnchor.match_location_to_region(self.location, concept))
174
- elif isinstance(concept, Location):
175
- if self.location is not None:
176
- matches.append(AnatomicalAnchor.match_locations(self.location, concept))
177
- for region in self.regions:
178
- match = AnatomicalAnchor.match_location_to_region(concept, region)
179
- matches.append(None if match is None else match.invert())
180
- self._assignments[concept] = sorted(m for m in matches if m is not None)
171
+ assignments: List[AnatomicalAssignment] = []
172
+ if self.location is not None:
173
+ try:
174
+ assignments.append(self.location.assign(concept))
175
+ except SpaceWarpingFailedError as e:
176
+ logger.debug(e)
177
+ for region in self.regions:
178
+ assignments.append(region.assign(concept))
179
+ self._assignments[concept] = sorted(a for a in assignments if a is not None)
181
180
 
182
181
  self._last_matched_concept = concept \
183
182
  if len(self._assignments[concept]) > 0 \
184
183
  else None
185
-
186
184
  return self._assignments[concept]
187
185
 
188
- def matches(self, concept: AtlasConcept):
186
+ def matches(self, concept: Union[BrainStructure, Space]) -> bool:
189
187
  return len(self.assign(concept)) > 0
190
188
 
191
- @classmethod
192
- def match_locations(cls, location1: Location, location2: Location):
193
- assert all(isinstance(loc, Location) for loc in [location1, location2])
194
- if (location1, location2) not in cls._MATCH_MEMO:
195
- if location1 == location2:
196
- res = AnatomicalAssignment(location1, location2, AssignmentQualification.EXACT)
197
- elif location1.contained_in(location2):
198
- res = AnatomicalAssignment(location1, location2, AssignmentQualification.CONTAINED)
199
- elif location1.contains(location2):
200
- res = AnatomicalAssignment(location1, location2, AssignmentQualification.CONTAINS)
201
- elif location1.intersects(location2):
202
- res = AnatomicalAssignment(location1, location2, AssignmentQualification.OVERLAPS)
203
- else:
204
- res = None
205
- cls._MATCH_MEMO[location1, location2] = res
206
- return cls._MATCH_MEMO[location1, location2]
207
-
208
- @classmethod
209
- def match_regions(cls, region1: Region, region2: Region):
210
- assert all(isinstance(r, Region) for r in [region1, region2])
211
- if (region1, region2) not in cls._MATCH_MEMO:
212
- if region1 == region2:
213
- res = AnatomicalAssignment(region1, region2, AssignmentQualification.EXACT)
214
- elif region1 in region2:
215
- res = AnatomicalAssignment(region1, region2, AssignmentQualification.CONTAINED)
216
- elif region2 in region1:
217
- res = AnatomicalAssignment(region1, region2, AssignmentQualification.CONTAINS)
218
- else:
219
- res = None
220
- cls._MATCH_MEMO[region1, region2] = res
221
- return cls._MATCH_MEMO[region1, region2]
222
-
223
- @classmethod
224
- def match_location_to_region(cls, location: Location, region: Region):
225
- assert isinstance(location, Location)
226
- assert isinstance(region, Region)
227
-
228
- for subregion in region.children:
229
- res = cls.match_location_to_region(location, subregion)
230
- if res is not None:
231
- return res
232
-
233
- if (location, region) not in cls._MATCH_MEMO:
234
- # compute mask of the region
235
- mask = None
236
- if region.mapped_in_space(location.space, recurse=False):
237
- if (region, location.space) not in cls._MASK_MEMO:
238
- cls._MASK_MEMO[region, location.space] = \
239
- region.fetch_regional_map(space=location.space, maptype='labelled')
240
- mask = cls._MASK_MEMO[region, location.space]
241
- mask_space = location.space
242
- expl = (
243
- f"{location} was compared with the mask of query region "
244
- f"'{region.name}' in {location.space.name}."
245
- )
246
- else:
247
- for space in region.supported_spaces:
248
- if not region.mapped_in_space(space, recurse=False):
249
- continue
250
- if not space.provides_image: # siibra does not yet match locations to surface spaces
251
- continue
252
- if (region, space) not in cls._MASK_MEMO:
253
- cls._MASK_MEMO[region, space] = region.fetch_regional_map(space=space, maptype='labelled')
254
- mask = cls._MASK_MEMO[region, space]
255
- mask_space = space
256
- if location.space == mask_space:
257
- expl = (
258
- f"{location} was compared with the mask of query region '{region.name}' "
259
- f"in {mask_space}."
260
- )
261
- else:
262
- expl = (
263
- f"{location} was warped from {location.space.name} and then compared "
264
- f"in this space with the mask of query region '{region.name}'."
265
- )
266
- if mask is not None:
267
- break
268
-
269
- if mask is None:
270
- logger.debug(
271
- f"'{region.name}' provides no mask in a space "
272
- f"to which {location} can be warped."
273
- )
274
- res = None
275
- else:
276
- # compare mask to location
277
- loc_warped = location.warp(mask_space)
278
- if loc_warped is None:
279
- # seems we cannot warp our location to the mask space
280
- # this typically happens when the location extends outside
281
- # the brain. We might still be able the warp the
282
- # bounding box of the mask to the location and check.
283
- # TODO in fact we should estimate an affine matrix from the warped bounding box,
284
- # and resample the mask for the inverse test to be more precise.
285
- bbox_mask = BoundingBox.from_image(mask, mask_space).warp(location.space)
286
- return cls.match_locations(location, bbox_mask)
287
- elif loc_warped.contained_in(mask):
288
- res = AnatomicalAssignment(location, region, AssignmentQualification.CONTAINED, expl)
289
- elif loc_warped.contains(mask):
290
- res = AnatomicalAssignment(location, region, AssignmentQualification.CONTAINS, expl)
291
- elif loc_warped.intersects(mask):
292
- res = AnatomicalAssignment(location, region, AssignmentQualification.OVERLAPS, expl)
293
- else:
294
- logger.debug(
295
- f"{location} does not match mask of '{region.name}' in {mask_space.name}."
296
- )
297
- res = None
298
- cls._MATCH_MEMO[location, region] = res
299
-
300
- # keep mask cache small
301
- if len(cls._MASK_MEMO) > 3:
302
- # from Python 3.6, this remove the *oldest* entry
303
- cls._MASK_MEMO.pop(next(iter(cls._MASK_MEMO)))
304
-
305
- return cls._MATCH_MEMO[location, region]
306
-
307
- def represented_parcellations(self):
189
+ def represented_parcellations(self) -> List[Parcellation]:
308
190
  """
309
191
  Return any parcellation objects that this anchor explicitly points to.
310
192
  """
311
- return [
312
- r for r in self.regions
313
- if isinstance(r, Parcellation)
314
- ]
193
+ return [r for r in self.regions if isinstance(r, Parcellation)]
315
194
 
316
195
  @property
317
196
  def last_match_result(self) -> List[AnatomicalAssignment]:
@@ -340,6 +219,6 @@ class AnatomicalAnchor:
340
219
 
341
220
  return AnatomicalAnchor(species, location, regions)
342
221
 
343
- def __radd__(self, other):
222
+ def __radd__(self, other) -> 'AnatomicalAnchor':
344
223
  # required to enable `sum`
345
224
  return self if other == 0 else self.__add__(other)
@@ -1,4 +1,4 @@
1
- # Copyright 2018-2021
1
+ # Copyright 2018-2024
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-2021
1
+ # Copyright 2018-2024
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");
@@ -41,7 +41,7 @@ class FunctionalConnectivity(
41
41
 
42
42
  @property
43
43
  def name(self):
44
- return f"{super().name}, {self.paradigm} paradigm"
44
+ return super().name + f", paradigm: {self.paradigm}"
45
45
 
46
46
 
47
47
  class AnatomoFunctionalConnectivity(