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.
- siibra/VERSION +1 -1
- siibra/__init__.py +20 -12
- siibra/commons.py +145 -90
- siibra/configuration/__init__.py +1 -1
- siibra/configuration/configuration.py +22 -17
- siibra/configuration/factory.py +177 -128
- siibra/core/__init__.py +1 -8
- siibra/core/{relation_qualification.py → assignment.py} +17 -14
- siibra/core/atlas.py +66 -35
- siibra/core/concept.py +81 -39
- siibra/core/parcellation.py +83 -67
- siibra/core/region.py +569 -263
- siibra/core/space.py +7 -39
- siibra/core/structure.py +111 -0
- siibra/exceptions.py +63 -0
- siibra/experimental/__init__.py +19 -0
- siibra/experimental/contour.py +61 -0
- siibra/experimental/cortical_profile_sampler.py +57 -0
- siibra/experimental/patch.py +98 -0
- siibra/experimental/plane3d.py +256 -0
- siibra/explorer/__init__.py +16 -0
- siibra/explorer/url.py +112 -52
- siibra/explorer/util.py +31 -9
- siibra/features/__init__.py +73 -8
- siibra/features/anchor.py +75 -196
- siibra/features/connectivity/__init__.py +1 -1
- siibra/features/connectivity/functional_connectivity.py +2 -2
- siibra/features/connectivity/regional_connectivity.py +99 -10
- siibra/features/connectivity/streamline_counts.py +1 -1
- siibra/features/connectivity/streamline_lengths.py +1 -1
- siibra/features/connectivity/tracing_connectivity.py +1 -1
- siibra/features/dataset/__init__.py +1 -1
- siibra/features/dataset/ebrains.py +3 -3
- siibra/features/feature.py +219 -110
- siibra/features/image/__init__.py +1 -1
- siibra/features/image/image.py +21 -13
- siibra/features/image/sections.py +1 -1
- siibra/features/image/volume_of_interest.py +1 -1
- siibra/features/tabular/__init__.py +1 -1
- siibra/features/tabular/bigbrain_intensity_profile.py +24 -13
- siibra/features/tabular/cell_density_profile.py +111 -69
- siibra/features/tabular/cortical_profile.py +82 -16
- siibra/features/tabular/gene_expression.py +117 -6
- siibra/features/tabular/layerwise_bigbrain_intensities.py +7 -9
- siibra/features/tabular/layerwise_cell_density.py +9 -24
- siibra/features/tabular/receptor_density_fingerprint.py +11 -6
- siibra/features/tabular/receptor_density_profile.py +12 -15
- siibra/features/tabular/regional_timeseries_activity.py +74 -18
- siibra/features/tabular/tabular.py +17 -8
- siibra/livequeries/__init__.py +1 -7
- siibra/livequeries/allen.py +139 -77
- siibra/livequeries/bigbrain.py +104 -128
- siibra/livequeries/ebrains.py +7 -4
- siibra/livequeries/query.py +1 -2
- siibra/locations/__init__.py +32 -25
- siibra/locations/boundingbox.py +153 -127
- siibra/locations/location.py +45 -80
- siibra/locations/point.py +97 -83
- siibra/locations/pointcloud.py +349 -0
- siibra/retrieval/__init__.py +1 -1
- siibra/retrieval/cache.py +107 -13
- siibra/retrieval/datasets.py +9 -14
- siibra/retrieval/exceptions/__init__.py +2 -1
- siibra/retrieval/repositories.py +147 -53
- siibra/retrieval/requests.py +64 -29
- siibra/vocabularies/__init__.py +2 -2
- siibra/volumes/__init__.py +7 -9
- siibra/volumes/parcellationmap.py +396 -253
- siibra/volumes/providers/__init__.py +20 -0
- siibra/volumes/providers/freesurfer.py +113 -0
- siibra/volumes/{gifti.py → providers/gifti.py} +29 -18
- siibra/volumes/{neuroglancer.py → providers/neuroglancer.py} +204 -92
- siibra/volumes/{nifti.py → providers/nifti.py} +64 -44
- siibra/volumes/providers/provider.py +107 -0
- siibra/volumes/sparsemap.py +159 -260
- siibra/volumes/volume.py +720 -152
- {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/METADATA +25 -28
- siibra-1.0.0a1.dist-info/RECORD +84 -0
- {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/WHEEL +1 -1
- siibra/locations/pointset.py +0 -198
- siibra-0.5a2.dist-info/RECORD +0 -74
- {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/LICENSE +0 -0
- {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/top_level.txt +0 -0
siibra/livequeries/allen.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
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
|
|
23
|
-
from ..locations import
|
|
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
|
|
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,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
anchor.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
155
|
-
levels=[m['expression_level'] for m in
|
|
156
|
-
z_scores=[m['z_score'] for m in
|
|
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
|
|
159
|
-
"gender": [m['gender'] for m in
|
|
160
|
-
"age": [m['age'] for m in
|
|
161
|
-
"mni_xyz": [tuple(m['mni_xyz']) for m in
|
|
162
|
-
"sample": [m['sample_index'] for m in
|
|
163
|
-
"probe_id": [m['probe_id'] for m in
|
|
164
|
-
"donor_name": [m['donor_name'] for m in
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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)
|
|
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
|
-
|
|
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
|
|
323
|
+
raise InvalidAllenAPIResponseException(
|
|
262
324
|
"Invalid response when retrieving microarray data: {}".format(url)
|
|
263
325
|
)
|
|
264
326
|
|
siibra/livequeries/bigbrain.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
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,
|
|
22
|
-
from ..core import
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
]
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
|
|
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]
|
siibra/livequeries/ebrains.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
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
|
|
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=
|
|
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)
|
siibra/livequeries/query.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
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
|