siibra 1.0a1__1-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 (84) hide show
  1. siibra/VERSION +1 -0
  2. siibra/__init__.py +164 -0
  3. siibra/commons.py +823 -0
  4. siibra/configuration/__init__.py +17 -0
  5. siibra/configuration/configuration.py +189 -0
  6. siibra/configuration/factory.py +589 -0
  7. siibra/core/__init__.py +16 -0
  8. siibra/core/assignment.py +110 -0
  9. siibra/core/atlas.py +239 -0
  10. siibra/core/concept.py +308 -0
  11. siibra/core/parcellation.py +387 -0
  12. siibra/core/region.py +1223 -0
  13. siibra/core/space.py +131 -0
  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 +17 -0
  22. siibra/explorer/url.py +222 -0
  23. siibra/explorer/util.py +87 -0
  24. siibra/features/__init__.py +117 -0
  25. siibra/features/anchor.py +224 -0
  26. siibra/features/connectivity/__init__.py +33 -0
  27. siibra/features/connectivity/functional_connectivity.py +57 -0
  28. siibra/features/connectivity/regional_connectivity.py +494 -0
  29. siibra/features/connectivity/streamline_counts.py +27 -0
  30. siibra/features/connectivity/streamline_lengths.py +27 -0
  31. siibra/features/connectivity/tracing_connectivity.py +30 -0
  32. siibra/features/dataset/__init__.py +17 -0
  33. siibra/features/dataset/ebrains.py +90 -0
  34. siibra/features/feature.py +970 -0
  35. siibra/features/image/__init__.py +27 -0
  36. siibra/features/image/image.py +115 -0
  37. siibra/features/image/sections.py +26 -0
  38. siibra/features/image/volume_of_interest.py +88 -0
  39. siibra/features/tabular/__init__.py +24 -0
  40. siibra/features/tabular/bigbrain_intensity_profile.py +77 -0
  41. siibra/features/tabular/cell_density_profile.py +298 -0
  42. siibra/features/tabular/cortical_profile.py +322 -0
  43. siibra/features/tabular/gene_expression.py +257 -0
  44. siibra/features/tabular/layerwise_bigbrain_intensities.py +62 -0
  45. siibra/features/tabular/layerwise_cell_density.py +95 -0
  46. siibra/features/tabular/receptor_density_fingerprint.py +192 -0
  47. siibra/features/tabular/receptor_density_profile.py +110 -0
  48. siibra/features/tabular/regional_timeseries_activity.py +294 -0
  49. siibra/features/tabular/tabular.py +139 -0
  50. siibra/livequeries/__init__.py +19 -0
  51. siibra/livequeries/allen.py +352 -0
  52. siibra/livequeries/bigbrain.py +197 -0
  53. siibra/livequeries/ebrains.py +145 -0
  54. siibra/livequeries/query.py +49 -0
  55. siibra/locations/__init__.py +91 -0
  56. siibra/locations/boundingbox.py +454 -0
  57. siibra/locations/location.py +115 -0
  58. siibra/locations/point.py +344 -0
  59. siibra/locations/pointcloud.py +349 -0
  60. siibra/retrieval/__init__.py +27 -0
  61. siibra/retrieval/cache.py +233 -0
  62. siibra/retrieval/datasets.py +389 -0
  63. siibra/retrieval/exceptions/__init__.py +27 -0
  64. siibra/retrieval/repositories.py +769 -0
  65. siibra/retrieval/requests.py +659 -0
  66. siibra/vocabularies/__init__.py +45 -0
  67. siibra/vocabularies/gene_names.json +29176 -0
  68. siibra/vocabularies/receptor_symbols.json +210 -0
  69. siibra/vocabularies/region_aliases.json +460 -0
  70. siibra/volumes/__init__.py +23 -0
  71. siibra/volumes/parcellationmap.py +1279 -0
  72. siibra/volumes/providers/__init__.py +20 -0
  73. siibra/volumes/providers/freesurfer.py +113 -0
  74. siibra/volumes/providers/gifti.py +165 -0
  75. siibra/volumes/providers/neuroglancer.py +736 -0
  76. siibra/volumes/providers/nifti.py +266 -0
  77. siibra/volumes/providers/provider.py +107 -0
  78. siibra/volumes/sparsemap.py +468 -0
  79. siibra/volumes/volume.py +892 -0
  80. siibra-1.0.0a1.dist-info/LICENSE +201 -0
  81. siibra-1.0.0a1.dist-info/METADATA +160 -0
  82. siibra-1.0.0a1.dist-info/RECORD +84 -0
  83. siibra-1.0.0a1.dist-info/WHEEL +5 -0
  84. siibra-1.0.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,387 @@
1
+ # Copyright 2018-2024
2
+ # Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
3
+
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """Hierarchal brain regions and metadata."""
16
+ from . import region
17
+
18
+ from ..commons import logger, MapType, Species
19
+ from ..volumes import parcellationmap
20
+ from ..exceptions import NoMapMatchingValues
21
+
22
+ from functools import lru_cache
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
30
+
31
+
32
+ if TYPE_CHECKING:
33
+ from .space import Space
34
+
35
+ # NOTE : such code could be used to automatically resolve
36
+ # multiple matching parcellations for a short spec to the newset version:
37
+ # try:
38
+ # collections = {m.version.collection for m in matches}
39
+ # if len(collections)==1:
40
+ # return sorted(matches,key=lambda m:m.version,reverse=True)[0]
41
+ # except Exception as e:
42
+ # pass
43
+
44
+
45
+ class ParcellationVersion:
46
+ def __init__(
47
+ self, name, parcellation, collection=None, prev_id=None, next_id=None, deprecated=False
48
+ ):
49
+ self.name = name
50
+ self.collection = collection
51
+ self.parcellation = parcellation
52
+ self.next_id = next_id
53
+ self.prev_id = prev_id
54
+ self.deprecated = deprecated
55
+
56
+ def __eq__(self, other):
57
+ return all([self.name == other.name, self.collection == other.collection])
58
+
59
+ def __str__(self):
60
+ return self.name
61
+
62
+ def __repr__(self):
63
+ return self.name
64
+
65
+ def __iter__(self):
66
+ yield "name", self.name
67
+ yield "prev", self.prev_id
68
+ yield "next", self.next_id
69
+ yield "deprecated", self.deprecated
70
+
71
+ def __lt__(self, other: 'ParcellationVersion'):
72
+ """
73
+ < operator, useful for sorting by version
74
+ FIXME: this is only by name, not recursing into parcellations, to avoid importing the registry here.
75
+ """
76
+ return self.name < other.name
77
+
78
+
79
+ class Parcellation(region.Region, configuration_folder="parcellations"):
80
+
81
+ def __init__(
82
+ self,
83
+ identifier: str,
84
+ name: str,
85
+ species: Union[Species, str],
86
+ regions: Union[List[region.Region], region.Region] = (),
87
+ shortname: str = "",
88
+ description: str = "",
89
+ version: ParcellationVersion = None,
90
+ modality: str = None,
91
+ publications: list = [],
92
+ datasets: list = [],
93
+ prerelease: bool = False,
94
+ ):
95
+ """
96
+ Constructs a new parcellation object.
97
+
98
+ Parameters
99
+ ----------
100
+ identifier : str
101
+ Unique identifier of the parcellation
102
+ name : str
103
+ Human-readable name of the parcellation
104
+ species: str or Species
105
+ Specification of the species
106
+ regions: list or Region
107
+ shortname: str
108
+ Short form of human-readable name (optional)
109
+ description: str
110
+ Textual description of the parcellation
111
+ version : str or None
112
+ Version specification, optional
113
+ modality : str or None
114
+ Specification of the modality used for creating the parcellation
115
+ publications: list
116
+ List of associated publications, each a dictionary with "doi"
117
+ and/or "citation" fields
118
+ datasets : list
119
+ datasets associated with this region
120
+ """
121
+ region.Region.__init__(
122
+ self,
123
+ name=name,
124
+ children=regions,
125
+ parent=None,
126
+ shortname=shortname,
127
+ description=description,
128
+ publications=publications,
129
+ datasets=datasets,
130
+ modality=modality,
131
+ prerelease=prerelease,
132
+ )
133
+ self._species_cached = Species.decode(species)
134
+ self._id = identifier
135
+ self.version = version
136
+
137
+ @property
138
+ def id(self):
139
+ return self._id
140
+
141
+ def matches(self, spec):
142
+ if spec not in self._CACHED_MATCHES:
143
+ if isinstance(spec, str):
144
+ if all(
145
+ w in self.shortname.lower()
146
+ for w in re.split(r'\s+', spec.lower())
147
+ ):
148
+ self._CACHED_MATCHES[spec] = True
149
+ return super().matches(spec)
150
+
151
+ def get_map(
152
+ self,
153
+ space: Union[str, "Space"],
154
+ maptype: Union[Literal['labelled', 'statistical'], MapType] = MapType.LABELLED,
155
+ spec: str = ""
156
+ ):
157
+ """
158
+ Get the maps for the parcellation in the requested template space.
159
+
160
+ This might in general include multiple 3D volumes. For example,
161
+ the Julich-Brain atlas provides two separate maps, one per hemisphere.
162
+ Per default, multiple maps are concatenated into a 4D array, but you
163
+ can choose to retrieve a dict of 3D volumes instead using
164
+ `return_dict=True`.
165
+
166
+ Parameters
167
+ ----------
168
+ space: Space or str
169
+ reference space specification such as name, id, or a `Space` instance.
170
+ maptype: MapType or str
171
+ Type of map requested (e.g., statistical or labelled).
172
+ Use MapType.STATISTICAL to request probability maps.
173
+ Defaults to MapType.LABELLED.
174
+ spec: str, optional
175
+ In case of multiple matching maps for the given parcellation, space
176
+ and type, use this field to specify keywords matching the desired
177
+ parcellation map name. Otherwise, siibra will default to the first
178
+ in the list of matches (and inform with a log message)
179
+ Returns
180
+ -------
181
+ parcellationmap.Map or SparseMap
182
+ A ParcellationMap representing the volumetric map or
183
+ a SparseMap representing the list of statistical maps.
184
+ """
185
+ if isinstance(maptype, str):
186
+ maptype = MapType[maptype.upper()]
187
+ assert isinstance(maptype, MapType), "Possible values of `maptype` are `MapType`s, 'labelled', 'statistical'."
188
+
189
+ candidates = [
190
+ m for m in parcellationmap.Map.registry()
191
+ if m.space.matches(space)
192
+ and m.maptype == maptype
193
+ and m.parcellation.matches(self)
194
+ ]
195
+ if len(candidates) == 0:
196
+ raise NoMapMatchingValues(f"No '{maptype}' map in '{space}' available for {str(self)}")
197
+ if len(candidates) > 1:
198
+ spec_candidates = [
199
+ c for c in candidates if all(w.lower() in c.id.lower() for w in spec.split())
200
+ ]
201
+ if len(spec_candidates) == 0:
202
+ raise NoMapMatchingValues(f"'{spec}' does not match any options from {[c.name for c in candidates]}.")
203
+ if len(spec_candidates) > 1:
204
+ logger.warning(
205
+ f"Multiple maps are available in this specification of space, parcellation, and map type.\n"
206
+ f"Choosing the first map from {[c.name for c in spec_candidates]}."
207
+ )
208
+ return spec_candidates[0]
209
+ return candidates[0]
210
+
211
+ @property
212
+ def is_newest_version(self):
213
+ return (self.version is None) or (self.version.next_id is None)
214
+
215
+ def _split_group_spec(self, spec: str):
216
+ """
217
+ Split a group region specification as produced in older siibra versions
218
+ into the subregion specs.
219
+ This is used when decoding datasets that still include region name
220
+ specifications from old siibra versions, such as some connectivity matrices.
221
+ """
222
+ spec = re.sub(r'Group: *', '', spec)
223
+ for substr in re.findall(r'\(.*?\)', spec):
224
+ # temporarilty replace commas inside brackets with a placeholder
225
+ # because these are not region spec delimiters
226
+ spec = spec.replace(substr, re.sub(r', *', '##', substr))
227
+ # process the comma separated substrings
228
+ candidates = list({
229
+ self.get_region(re.sub(r'##', ', ', s))
230
+ for s in re.split(r', *', spec)
231
+ })
232
+ if len(candidates) > 0:
233
+ return candidates
234
+ else:
235
+ return [spec]
236
+
237
+ def get_region(
238
+ self,
239
+ regionspec: Union[str, region.Region],
240
+ find_topmost: bool = False,
241
+ allow_tuple: bool = False
242
+ ):
243
+ """
244
+ Given a unique specification, return the corresponding region.
245
+
246
+ The spec could be a (possibly incomplete) name, or a region object.
247
+ This method is meant to definitely determine a valid region. Therefore,
248
+ if no match is found, it raises a ValueError. If multiple matches are
249
+ found, the method tries to return only the common parent node. If there
250
+ is no common parent, an exception is raised, except when
251
+ allow_tuple=True - then a tuple of matched regions is returned.
252
+
253
+ Parameters
254
+ ----------
255
+ regionspec: str, Region
256
+ - a string with a possibly inexact name (matched both against the name and the identifier key)
257
+ - a Region object
258
+ find_topmost: bool, default: False
259
+ If True, will automatically return the parent of a decoded region
260
+ the decoded region is its only child.
261
+ allow_tuple: bool, default: False
262
+ If multiple candidates without a common parent are found,
263
+ return a tuple of matches instead of raising an exception.
264
+
265
+ Returns
266
+ -------
267
+ Region
268
+ A region object defined in the parcellation.
269
+
270
+ Note
271
+ ----
272
+ If the spec exactly matched with more than one region, the first
273
+ will be returned.
274
+
275
+ Raises
276
+ ------
277
+ RuntimeError
278
+ If the spec matches multiple regions
279
+ ValueError
280
+ If the spec cannot be matched against any region.
281
+ """
282
+ assert isinstance(regionspec, (str, region.Region)), f"get_region takes str or Region but you provided {type(regionspec)}"
283
+ if isinstance(regionspec, region.Region) and (regionspec.parcellation == self):
284
+ return regionspec
285
+
286
+ # if there exist an exact match of region spec to region name, return
287
+ if isinstance(regionspec, str):
288
+ exact_match = [
289
+ region
290
+ for region in self
291
+ if region.name == regionspec or region.key == regionspec
292
+ ]
293
+ if len(exact_match) == 1:
294
+ return exact_match[0]
295
+ if len(exact_match) > 1:
296
+ logger.info(f"Found multiple region with exact match to {regionspec}. Returning the first one.")
297
+ return exact_match[0]
298
+
299
+ if regionspec.startswith("Group"): # backwards compatibility with old "Group: <region a>, <region b>" specs
300
+ candidates = {
301
+ r
302
+ for spec in self._split_group_spec(regionspec)
303
+ for r in self.find(spec, filter_children=True, find_topmost=find_topmost)
304
+ }
305
+ else:
306
+ candidates = self.find(regionspec, filter_children=True, find_topmost=find_topmost)
307
+
308
+ if len(candidates) > 1 and isinstance(regionspec, str):
309
+ # if we have an exact match of words in one region, discard other candidates.
310
+ querywords = {w.replace(',', '').lower() for w in regionspec.split()}
311
+ full_matches = []
312
+ for c in candidates:
313
+ targetwords = {w.lower() for w in c.name.split()}
314
+ if len(querywords & targetwords) == len(targetwords):
315
+ full_matches.append(c)
316
+ if len(full_matches) == 1:
317
+ candidates = full_matches
318
+
319
+ if not candidates:
320
+ raise ValueError(f"'{regionspec}' could not be decoded under '{self.name}'")
321
+ elif len(candidates) == 1:
322
+ return candidates[0]
323
+ else:
324
+ if allow_tuple:
325
+ return tuple(candidates)
326
+ raise RuntimeError(
327
+ f"Spec {regionspec!r} resulted in multiple matches: {', '.join(r.name for r in candidates)}."
328
+ )
329
+
330
+ def __getitem__(self, regionspec: Union[str, int]):
331
+ """
332
+ Retrieve a region object from the parcellation by labelindex or partial name.
333
+ """
334
+ return self.get_region(regionspec)
335
+
336
+ def __lt__(self, other):
337
+ """
338
+ We sort parcellations by their version
339
+ """
340
+ if (self.version is None) or (other.version is None):
341
+ logger.warning(
342
+ f"Sorting non-versioned instances of {self.__class__.__name__} "
343
+ f"by name: {self.name}, {other.name}"
344
+ )
345
+ return self.name < other.name
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 registery.
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