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");
@@ -16,16 +16,15 @@
16
16
 
17
17
  from .query import LiveQuery
18
18
 
19
- from ..core import space as _space
19
+ from ..core import space as _space, structure
20
20
  from ..features import anchor as _anchor
21
21
  from ..features.tabular.gene_expression import GeneExpressions
22
- from ..commons import logger, Species, MapType
23
- from ..locations import Point, PointSet
24
- from ..core.region import Region
22
+ from ..commons import logger, Species
23
+ from ..locations import point, pointcloud
25
24
  from ..retrieval import HttpRequest
26
25
  from ..vocabularies import GENE_NAMES
27
26
 
28
- from typing import Iterable, List
27
+ from typing import List
29
28
  from xml.etree import ElementTree
30
29
  import numpy as np
31
30
  import json
@@ -33,6 +32,24 @@ import json
33
32
 
34
33
  BASE_URL = "http://api.brain-map.org/api/v2/data"
35
34
 
35
+ LOCATION_PRECISION_MM = 2. # the assumed spatial precision of the probe locations in MNI space
36
+
37
+
38
+ def is_allen_api_microarray_service_available():
39
+ import requests
40
+
41
+ # see https://community.brain-map.org/t/human-brain-atlas-api/2876
42
+ microarray_test_url = "http://api.brain-map.org/api/v2/data/query.json?criteria= service::human_microarray_expression[probes$eq1023146,1023147][donors$eq15496][structures$eq9148]"
43
+ try:
44
+ response = requests.get(microarray_test_url).json()
45
+ except requests.RequestException:
46
+ return False
47
+ return response["success"]
48
+
49
+
50
+ class InvalidAllenAPIResponseException(Exception):
51
+ pass
52
+
36
53
 
37
54
  class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions):
38
55
  """
@@ -61,7 +78,7 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
61
78
  "probe": BASE_URL
62
79
  + "/query.xml?criteria=model::Probe,rma::criteria,[probe_type$eq'DNA'],products[abbreviation$eq'HumanMA'],gene[acronym$eq'{gene}'],rma::options[only$eq'probes.id']",
63
80
  "multiple_gene_probe": BASE_URL
64
- + "/query.xml?criteria=model::Probe,rma::criteria,[probe_type$eq'DNA'],products[abbreviation$eq'HumanMA'],gene[acronym$in{genes}],rma::options[only$eq'probes.id']",
81
+ + "/query.xml?criteria=model::Probe,rma::criteria,[probe_type$eq'DNA'],products[abbreviation$eq'HumanMA'],gene[acronym$in{genes}],rma::options[only$eq'probes.id']&start_row={start_row}&num_rows={num_rows}",
65
82
  "specimen": BASE_URL
66
83
  + "/Specimen/query.json?criteria=[name$eq'{specimen_id}']&include=alignment3d",
67
84
  "microarray": BASE_URL
@@ -69,7 +86,7 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
69
86
  "gene": BASE_URL
70
87
  + "/Gene/query.json?criteria=products[abbreviation$eq'HumanMA']&num_rows=all",
71
88
  "factors": BASE_URL
72
- + "/query.json?criteria=model::Donor,rma::criteria,products[id$eq2],rma::include,age,rma::options[only$eq%27donors.id,dono rs.name,donors.race_only,donors.sex%27]",
89
+ + "/query.json?criteria=model::Donor,rma::criteria,products[id$eq2],rma::include,age,rma::options[only$eq%27donors.id,dono rs.name,donors.race_only,donors.sex%27]&start_row={start_row}&num_rows={num_rows}",
73
90
  }
74
91
 
75
92
  # there is a 1:1 mapping between donors and specimen for the 6 adult human brains
@@ -102,10 +119,6 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
102
119
  """
103
120
  LiveQuery.__init__(self, **kwargs)
104
121
  gene = kwargs.get('gene')
105
- self.maptype = kwargs.get("maptype", MapType.LABELLED)
106
- if isinstance(self.maptype, str):
107
- self.maptype = MapType[self.maptype.upper()]
108
- self.threshold_statistical = kwargs.get("threshold_statistical", 0)
109
122
 
110
123
  def parse_gene(spec):
111
124
  if isinstance(spec, str):
@@ -122,48 +135,62 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
122
135
 
123
136
  self.genes = parse_gene(gene)
124
137
 
125
- def query(self, region: Region) -> List[GeneExpressions]:
126
- assert isinstance(region, Region)
127
- space = _space.Space.registry().get('mni152')
128
- mask = region.fetch_regional_map(space, maptype=self.maptype, threshold=self.threshold_statistical)
138
+ def query(self, concept: structure.BrainStructure) -> List[GeneExpressions]:
139
+ if not is_allen_api_microarray_service_available():
140
+ raise InvalidAllenAPIResponseException(
141
+ 'The service "web API of the Allen Brain Atlas for the human microarray expression" '
142
+ 'is not available at the moment, therefore siibra is not able to fetch '
143
+ 'gene expression features. This is a known issue which we are investigating: '
144
+ 'https://github.com/FZJ-INM1-BDA/siibra-python/issues/636.'
145
+ )
146
+
147
+ mnispace = _space.Space.registry().get('mni152')
148
+
149
+ # Match the microarray probes to the query mask.
150
+ # Record matched instances and their locations.
151
+ measurements = []
152
+ coordinates = []
153
+ for measurement in self:
154
+ pt = point.Point(measurement['mni_xyz'], space=mnispace, sigma_mm=LOCATION_PRECISION_MM)
155
+ if pt in concept:
156
+ measurements.append(measurement)
157
+ coordinates.append(pt)
158
+
159
+ if len(coordinates) == 0:
160
+ logger.info(f"No probes found that lie within {concept}")
161
+ return []
162
+
163
+ # Build the anatomical anchor and assignment to the query concept.
164
+ # It will be attached to the returned feature, with the set of matched
165
+ # MNI coordinates as anchor's location.
129
166
  anchor = _anchor.AnatomicalAnchor(
130
- species=self.species, region=region.name
131
- )
132
- ass = _anchor.AnatomicalAssignment(
133
- query_structure=region,
134
- assigned_structure=region,
135
- qualification=_anchor.AssignmentQualification.CONTAINED,
136
- explanation=(f"MNI coordinates of tissue samples were compared with mask of '{region.name}' in {space}.")
167
+ location=pointcloud.from_points(coordinates),
168
+ species=self.species
137
169
  )
138
- anchor._assignments[region] = [ass]
139
- anchor._last_matched_concept = region
140
- anchor._location_cached = PointSet(coordinates=[], space=space)
141
-
142
- measures = []
143
- contained = {}
144
- for measure in self:
145
- location = Point(measure['mni_xyz'], space=space)
146
- if location not in contained: # cache redundant intersection tests
147
- contained[location] = location.intersects(mask)
148
- if contained[location]:
149
- measures.append(measure)
150
- anchor._location_cached.points.append(location)
151
-
152
- yield GeneExpressions(
170
+ explanation = f"MNI coordinates of tissue samples were filtered using {concept}"
171
+ anchor._assignments[concept] = [_anchor.AnatomicalAssignment(
172
+ query_structure=concept,
173
+ assigned_structure=concept,
174
+ qualification=_anchor.Qualification.CONTAINED,
175
+ explanation=explanation
176
+ )]
177
+ anchor._last_matched_concept = concept
178
+
179
+ return [GeneExpressions(
153
180
  anchor=anchor,
154
- genes=[m['gene'] for m in measures],
155
- levels=[m['expression_level'] for m in measures],
156
- z_scores=[m['z_score'] for m in measures],
181
+ genes=[m['gene'] for m in measurements],
182
+ levels=[m['expression_level'] for m in measurements],
183
+ z_scores=[m['z_score'] for m in measurements],
157
184
  additional_columns={
158
- "race": [m['race'] for m in measures],
159
- "gender": [m['gender'] for m in measures],
160
- "age": [m['age'] for m in measures],
161
- "mni_xyz": [tuple(m['mni_xyz']) for m in measures],
162
- "sample": [m['sample_index'] for m in measures],
163
- "probe_id": [m['probe_id'] for m in measures],
164
- "donor_name": [m['donor_name'] for m in measures],
185
+ "race": [m['race'] for m in measurements],
186
+ "gender": [m['gender'] for m in measurements],
187
+ "age": [m['age'] for m in measurements],
188
+ "mni_xyz": [tuple(m['mni_xyz']) for m in measurements],
189
+ "sample": [m['sample_index'] for m in measurements],
190
+ "probe_id": [m['probe_id'] for m in measurements],
191
+ "donor_name": [m['donor_name'] for m in measurements],
165
192
  }
166
- )
193
+ )]
167
194
 
168
195
  def __iter__(self):
169
196
 
@@ -178,21 +205,7 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
178
205
  print(GeneExpressions.ALLEN_ATLAS_NOTIFICATION)
179
206
  self.__class__._notification_shown = True
180
207
 
181
- assert isinstance(self.genes, list)
182
- if len(self.genes) == 1:
183
- logger.info(f"Retrieving probe ids for gene {self.genes[0]['symbol']}")
184
- else:
185
- logger.info(f"Retrieving probe ids for genes {', '.join(g['symbol'] for g in self.genes)}")
186
- url = self._QUERY["multiple_gene_probe"].format(genes=','.join([f"'{g['symbol']}'" for g in self.genes]))
187
- response = HttpRequest(url).get()
188
- if "site unavailable" in response.decode().lower():
189
- # When the Allen site is not available, they still send a status code 200.
190
- raise RuntimeError(
191
- "Allen institute site unavailable - please try again later."
192
- )
193
- root = ElementTree.fromstring(response)
194
- num_probes = int(root.attrib["total_rows"])
195
- probe_ids = [int(root[0][i][0].text) for i in range(num_probes)]
208
+ probe_ids = self._retrieve_probe_ids(self.genes)
196
209
 
197
210
  # get specimen information
198
211
  if AllenBrainAtlasQuery._specimen is None:
@@ -201,15 +214,7 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
201
214
  }
202
215
 
203
216
  if AllenBrainAtlasQuery.factors is None:
204
- response = HttpRequest(self._QUERY["factors"]).get()
205
- AllenBrainAtlasQuery.factors = {
206
- item["id"]: {
207
- "race": item["race_only"],
208
- "gender": item["sex"],
209
- "age": int(item["age"]["days"] / 365),
210
- }
211
- for item in response["msg"]
212
- }
217
+ self._retrieve_factors()
213
218
 
214
219
  # get expression levels and z_scores for the gene
215
220
  if len(probe_ids) > 0:
@@ -217,6 +222,60 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
217
222
  for item in self._retrieve_microarray(donor_id, probe_ids):
218
223
  yield item
219
224
 
225
+ @staticmethod
226
+ def _retrieve_probe_ids(genes: list):
227
+ assert isinstance(genes, list)
228
+ if len(genes) == 1:
229
+ logger.debug(f"Retrieving probe ids for gene {genes[0]['symbol']}")
230
+ else:
231
+ logger.debug(f"Retrieving probe ids for genes {', '.join(g['symbol'] for g in genes)}")
232
+ start_row = 0
233
+ num_rows = 50
234
+ probe_ids = []
235
+ while True:
236
+ url = AllenBrainAtlasQuery._QUERY["multiple_gene_probe"].format(
237
+ start_row=start_row, num_rows=num_rows, genes=','.join([f"'{g['symbol']}'" for g in genes])
238
+ )
239
+ response = HttpRequest(url).get()
240
+ if "site unavailable" in response.decode().lower():
241
+ # When the Allen site is not available, they still send a status code 200.
242
+ raise RuntimeError(
243
+ "Allen institute site unavailable - please try again later."
244
+ )
245
+ root = ElementTree.fromstring(response)
246
+ num_probes = int(root.attrib["num_rows"])
247
+ total_probes = int(root.attrib["total_rows"])
248
+ assert len(root) == 1
249
+ probe_ids.extend([int(root[0][i][0].text) for i in range(num_probes)])
250
+ if (start_row + num_rows) >= total_probes:
251
+ break
252
+ # retrieve another page
253
+ start_row += num_rows
254
+ return probe_ids
255
+
256
+ @staticmethod
257
+ def _retrieve_factors():
258
+ start_row = 0
259
+ num_rows = 50
260
+ if AllenBrainAtlasQuery.factors is None or len(AllenBrainAtlasQuery.factors) == 0:
261
+ AllenBrainAtlasQuery.factors = {}
262
+ while True:
263
+ factors_url = AllenBrainAtlasQuery._QUERY["factors"].format(start_row=start_row, num_rows=num_rows)
264
+ response = HttpRequest(factors_url).get()
265
+ AllenBrainAtlasQuery.factors.update({
266
+ item["id"]: {
267
+ "race": item["race_only"],
268
+ "gender": item["sex"],
269
+ "age": int(item["age"]["days"] / 365),
270
+ }
271
+ for item in response["msg"]
272
+ })
273
+ total_factors = int(response["total_rows"])
274
+ if (start_row + num_rows) >= total_factors:
275
+ break
276
+ # retrieve another page
277
+ start_row += num_rows
278
+
220
279
  @staticmethod
221
280
  def _retrieve_specimen(specimen_id: str):
222
281
  """
@@ -225,7 +284,7 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
225
284
  url = AllenBrainAtlasQuery._QUERY["specimen"].format(specimen_id=specimen_id)
226
285
  response = HttpRequest(url).get()
227
286
  if not response["success"]:
228
- raise Exception(
287
+ raise InvalidAllenAPIResponseException(
229
288
  "Invalid response when retrieving specimen information: {}".format(url)
230
289
  )
231
290
  # we ask for 1 specimen, so list should have length 1
@@ -242,7 +301,7 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
242
301
  return specimen
243
302
 
244
303
  @classmethod
245
- def _retrieve_microarray(cls, donor_id: str, probe_ids: str) -> Iterable[GeneExpressions]:
304
+ def _retrieve_microarray(cls, donor_id: str, probe_ids: str):
246
305
  """
247
306
  Retrieve microarray data for several probes of a given donor, and
248
307
  compute the MRI position of the corresponding tissue block in the ICBM
@@ -256,9 +315,12 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
256
315
  url = AllenBrainAtlasQuery._QUERY["microarray"].format(
257
316
  probe_ids=",".join([str(id) for id in probe_ids]), donor_id=donor_id
258
317
  )
259
- response = HttpRequest(url, json.loads).get()
318
+ try:
319
+ response = HttpRequest(url, json.loads).get()
320
+ except json.JSONDecodeError as e:
321
+ raise RuntimeError(f"Allen institute site produced an empty response - please try again later.\n{e}")
260
322
  if not response["success"]:
261
- raise Exception(
323
+ raise InvalidAllenAPIResponseException(
262
324
  "Invalid response when retrieving microarray data: {}".format(url)
263
325
  )
264
326
 
@@ -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");
@@ -17,10 +17,12 @@
17
17
  from . import query
18
18
 
19
19
  from ..features.tabular import bigbrain_intensity_profile, layerwise_bigbrain_intensities
20
+ from ..features import anchor as _anchor
20
21
  from ..commons import logger
21
- from ..locations import point, pointset
22
- from ..core import region
22
+ from ..locations import point, pointcloud
23
+ from ..core import structure
23
24
  from ..retrieval import requests, cache
25
+ from ..retrieval.datasets import GenericDataset
24
26
 
25
27
  import numpy as np
26
28
  from typing import List
@@ -36,6 +38,37 @@ class WagstylProfileLoader:
36
38
  _profiles = None
37
39
  _vertices = None
38
40
  _boundary_depths = None
41
+ DATASET = GenericDataset(
42
+ name="HIBALL workshop on cortical layers",
43
+ contributors=[
44
+ 'Konrad Wagstyl',
45
+ 'Stéphanie Larocque',
46
+ 'Guillem Cucurull',
47
+ 'Claude Lepage',
48
+ 'Joseph Paul Cohen',
49
+ 'Sebastian Bludau',
50
+ 'Nicola Palomero-Gallagher',
51
+ 'Lindsay B. Lewis',
52
+ 'Thomas Funck',
53
+ 'Hannah Spitzer',
54
+ 'Timo Dickscheid',
55
+ 'Paul C. Fletcher',
56
+ 'Adriana Romero',
57
+ 'Karl Zilles',
58
+ 'Katrin Amunts',
59
+ 'Yoshua Bengio',
60
+ 'Alan C. Evans'
61
+ ],
62
+ url="https://github.com/kwagstyl/cortical_layers_tutorial/",
63
+ description="Cortical profiles of BigBrain staining intensities computed by Konrad Wagstyl, "
64
+ "as described in the publication 'Wagstyl, K., et al (2020). BigBrain 3D atlas of "
65
+ "cortical layers: Cortical and laminar thickness gradients diverge in sensory and "
66
+ "motor cortices. PLoS Biology, 18(4), e3000678. "
67
+ "http://dx.doi.org/10.1371/journal.pbio.3000678."
68
+ "The data is taken from the tutorial at "
69
+ "https://github.com/kwagstyl/cortical_layers_tutorial. Each vertex is "
70
+ "assigned to the regional map when queried."
71
+ )
39
72
 
40
73
  def __init__(self):
41
74
  if self._profiles is None:
@@ -72,78 +105,11 @@ class WagstylProfileLoader:
72
105
  logger.debug(f"{cls._profiles.shape[0]} BigBrain intensity profiles.")
73
106
  assert cls._vertices.shape[0] == cls._profiles.shape[0]
74
107
 
75
- @staticmethod
76
- def _choose_space(regionobj: region.Region):
77
- """
78
- Helper function to obtain a suitable space to fetch a regional mask.
79
- BigBrain space has priorty.
80
-
81
- Parameters
82
- ----------
83
- regionobj : region.Region
84
-
85
- Returns
86
- -------
87
- Space
88
-
89
- Raises
90
- ------
91
- RuntimeError
92
- When there is no supported space that provides image for `regionobj`.
93
- """
94
- if regionobj.mapped_in_space('bigbrain'):
95
- return 'bigbrain'
96
- supported_spaces = [s for s in regionobj.supported_spaces if s.provides_image]
97
- if len(supported_spaces) == 0:
98
- raise RuntimeError(f"Could not find a supported space for {regionobj}")
99
- return supported_spaces[0]
100
-
101
108
  def __len__(self):
102
109
  return self._vertices.shape[0]
103
110
 
104
- def match(self, regionobj: region.Region, space: str = None):
105
- """
106
- Retrieve BigBrainIntesityProfiles .
107
-
108
- Parameters
109
- ----------
110
- regionobj : region.Region
111
- Region or parcellation
112
- space : str, optional
113
- The space in which the region masks will be calculated. By default,
114
- siibra tries to fetch in BigBrain first and then the other spaces.
115
-
116
- Returns
117
- -------
118
- tuple
119
- tuple of profiles, boundary depths, and vertices
120
- """
121
- assert isinstance(regionobj, region.Region)
122
- logger.debug(f"Matching locations of {len(self)} BigBrain profiles to {regionobj}")
123
-
124
- if space is None:
125
- space = self._choose_space(regionobj)
126
-
127
- mask = regionobj.fetch_regional_map(space=space, maptype="labelled")
128
- logger.info(f"Assigning {len(self)} profile locations to {regionobj} in {space}...")
129
- voxels = (
130
- pointset.PointSet(self._vertices, space="bigbrain")
131
- .warp(space)
132
- .transform(np.linalg.inv(mask.affine), space=None)
133
- )
134
- arr = mask.get_fdata()
135
- XYZ = np.array(voxels.as_list()).astype('int')
136
- X, Y, Z = np.split(
137
- XYZ[np.all((XYZ < arr.shape) & (XYZ > 0), axis=1), :],
138
- 3, axis=1
139
- )
140
- inside = np.where(arr[X, Y, Z] > 0)[0]
141
111
 
142
- return (
143
- self._profiles[inside, :],
144
- self._boundary_depths[inside, :],
145
- self._vertices[inside, :]
146
- )
112
+ cache.Warmup.register_warmup_fn()(lambda: WagstylProfileLoader._load())
147
113
 
148
114
 
149
115
  class BigBrainProfileQuery(query.LiveQuery, args=[], FeatureType=bigbrain_intensity_profile.BigBrainIntensityProfile):
@@ -151,32 +117,38 @@ class BigBrainProfileQuery(query.LiveQuery, args=[], FeatureType=bigbrain_intens
151
117
  def __init__(self):
152
118
  query.LiveQuery.__init__(self)
153
119
 
154
- def query(self, regionobj: region.Region, **kwargs) -> List[bigbrain_intensity_profile.BigBrainIntensityProfile]:
155
- assert isinstance(regionobj, region.Region)
120
+ def query(self, concept: structure.BrainStructure, **kwargs) -> List[bigbrain_intensity_profile.BigBrainIntensityProfile]:
156
121
  loader = WagstylProfileLoader()
122
+ mesh_vertices = pointcloud.PointCloud(loader._vertices, space='bigbrain')
123
+ matched = concept.intersection(mesh_vertices) # returns a reduced PointCloud with og indices as labels
124
+ if matched is None:
125
+ return []
126
+ assert isinstance(matched, pointcloud.PointCloud)
127
+ indices = matched.labels
128
+ assert indices is not None
129
+ features = []
130
+ for i in matched.labels:
131
+ anchor = _anchor.AnatomicalAnchor(
132
+ location=point.Point(loader._vertices[i], space='bigbrain'),
133
+ region=str(concept),
134
+ species='Homo sapiens'
135
+ )
136
+ prof = bigbrain_intensity_profile.BigBrainIntensityProfile(
137
+ anchor=anchor,
138
+ depths=loader.profile_labels,
139
+ values=loader._profiles[i],
140
+ boundaries=loader._boundary_depths[i]
141
+ )
142
+ prof.anchor._assignments[concept] = _anchor.AnatomicalAssignment(
143
+ query_structure=concept,
144
+ assigned_structure=concept,
145
+ qualification=_anchor.Qualification.CONTAINED,
146
+ explanation=f"Surface vertex of BigBrain cortical profile was filtered using {concept}"
147
+ )
148
+ prof.datasets = [WagstylProfileLoader.DATASET]
149
+ features.append(prof)
157
150
 
158
- space = WagstylProfileLoader._choose_space(regionobj)
159
- if not regionobj.is_leaf:
160
- leaves_defined_on_space = [
161
- r for r in regionobj.leaves if r.mapped_in_space(space)
162
- ]
163
- else:
164
- leaves_defined_on_space = [regionobj]
165
-
166
- result = []
167
- for subregion in leaves_defined_on_space:
168
- matched_profiles, boundary_depths, coords = loader.match(subregion, space)
169
- for i, profile in enumerate(matched_profiles):
170
- prof = bigbrain_intensity_profile.BigBrainIntensityProfile(
171
- regionname=subregion.name,
172
- depths=loader.profile_labels,
173
- values=profile,
174
- boundaries=boundary_depths[i, :],
175
- location=point.Point(coords[i, :], 'bigbrain') # points are warped into BigBrain
176
- )
177
- result.append(prof)
178
-
179
- return result
151
+ return features
180
152
 
181
153
 
182
154
  class LayerwiseBigBrainIntensityQuery(query.LiveQuery, args=[], FeatureType=layerwise_bigbrain_intensities.LayerwiseBigBrainIntensities):
@@ -184,38 +156,42 @@ class LayerwiseBigBrainIntensityQuery(query.LiveQuery, args=[], FeatureType=laye
184
156
  def __init__(self):
185
157
  query.LiveQuery.__init__(self)
186
158
 
187
- def query(self, regionobj: region.Region, **kwargs) -> List[layerwise_bigbrain_intensities.LayerwiseBigBrainIntensities]:
188
- assert isinstance(regionobj, region.Region)
189
- loader = WagstylProfileLoader()
159
+ def query(self, concept: structure.BrainStructure, **kwargs) -> List[layerwise_bigbrain_intensities.LayerwiseBigBrainIntensities]:
190
160
 
191
- space = WagstylProfileLoader._choose_space(regionobj)
192
- if not regionobj.is_leaf:
193
- leaves_defined_on_space = [
194
- r for r in regionobj.leaves if r.mapped_in_space(space)
195
- ]
196
- else:
197
- leaves_defined_on_space = [regionobj]
198
-
199
- result = []
200
- for subregion in leaves_defined_on_space:
201
- matched_profiles, boundary_depths, coords = loader.match(subregion, space)
202
- if matched_profiles.shape[0] == 0:
203
- continue
204
-
205
- # compute array of layer labels for all coefficients in profiles_left
206
- N = matched_profiles.shape[1]
207
- prange = np.arange(N)
208
- layer_labels = 7 - np.array([
209
- [np.array([[(prange < T) * 1] for i, T in enumerate((b * N).astype('int'))]).squeeze().sum(0)]
210
- for b in boundary_depths
211
- ]).reshape((-1, 200))
212
-
213
- fp = layerwise_bigbrain_intensities.LayerwiseBigBrainIntensities(
214
- regionname=subregion.name,
215
- means=[matched_profiles[layer_labels == layer].mean() for layer in range(1, 7)],
216
- stds=[matched_profiles[layer_labels == layer].std() for layer in range(1, 7)],
217
- )
218
- assert fp.matches(subregion) # to create an assignment result
219
- result.append(fp)
161
+ loader = WagstylProfileLoader()
162
+ mesh_vertices = pointcloud.PointCloud(loader._vertices, space='bigbrain')
163
+ matched = concept.intersection(mesh_vertices) # returns a reduced PointCloud with og indices as labels
164
+ if matched is None:
165
+ return []
166
+ assert isinstance(matched, pointcloud.PointCloud)
167
+ indices = matched.labels
168
+ assert indices is not None
169
+ matched_profiles = loader._profiles[indices, :]
170
+ boundary_depths = loader._boundary_depths[indices, :]
171
+ # compute array of layer labels for all coefficients in profiles_left
172
+ N = matched_profiles.shape[1]
173
+ prange = np.arange(N)
174
+ layer_labels = 7 - np.array([
175
+ [np.array([[(prange < T) * 1] for i, T in enumerate((b * N).astype('int'))]).squeeze().sum(0)]
176
+ for b in boundary_depths
177
+ ]).reshape((-1, 200))
178
+
179
+ anchor = _anchor.AnatomicalAnchor(
180
+ location=pointcloud.PointCloud(loader._vertices[indices, :], space='bigbrain'),
181
+ region=str(concept),
182
+ species='Homo sapiens'
183
+ )
184
+ result = layerwise_bigbrain_intensities.LayerwiseBigBrainIntensities(
185
+ anchor=anchor,
186
+ means=[matched_profiles[layer_labels == layer].mean() for layer in range(1, 7)],
187
+ stds=[matched_profiles[layer_labels == layer].std() for layer in range(1, 7)],
188
+ )
189
+ result.anchor._assignments[concept] = _anchor.AnatomicalAssignment(
190
+ query_structure=concept,
191
+ assigned_structure=concept,
192
+ qualification=_anchor.Qualification.CONTAINED,
193
+ explanation=f"Surface vertices of BigBrain cortical profiles were filtered using {concept}"
194
+ )
195
+ result.datasets = [WagstylProfileLoader.DATASET]
220
196
 
221
- return result
197
+ return [result]
@@ -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");
@@ -19,12 +19,12 @@ from . import query
19
19
 
20
20
  from ..commons import logger, siibra_tqdm
21
21
  from ..features import anchor as _anchor
22
- from ..retrieval import requests, datasets
22
+ from ..retrieval import requests, datasets, cache
23
23
  from ..core import parcellation, region
24
24
 
25
25
  from collections import defaultdict
26
26
  import re
27
- from distutils.version import LooseVersion
27
+ from packaging.version import Version
28
28
  from tempfile import NamedTemporaryFile
29
29
 
30
30
 
@@ -121,7 +121,7 @@ class EbrainsFeatureQuery(query.LiveQuery, args=[], FeatureType=_ebrains.Ebrains
121
121
  # the newest one with older ones linked as a version history.
122
122
  for name, dsets in versioned_datasets.items():
123
123
  try: # if possible, sort by version tag
124
- sorted_versions = sorted(dsets.keys(), key=LooseVersion)
124
+ sorted_versions = sorted(dsets.keys(), key=Version)
125
125
  except TypeError: # else sort lexicographically
126
126
  sorted_versions = sorted(dsets.keys())
127
127
 
@@ -140,3 +140,6 @@ class EbrainsFeatureQuery(query.LiveQuery, args=[], FeatureType=_ebrains.Ebrains
140
140
  f"Its version history is: {curr.version_history}"
141
141
  )
142
142
  yield curr
143
+
144
+
145
+ cache.Warmup.register_warmup_fn(cache.WarmupLevel.DATA)(lambda: EbrainsFeatureQuery.loader.data)
@@ -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");
@@ -47,4 +47,3 @@ class LiveQuery(ABC):
47
47
  @abstractmethod
48
48
  def query(self, concept: AtlasConcept, **kwargs) -> List[Feature]:
49
49
  raise NotImplementedError(f"Dervied class {self.__class__} needs to implement query()")
50
- pass