siibra 1.0a14__py3-none-any.whl → 1.0.1a0__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 +15 -5
- siibra/commons.py +3 -48
- siibra/configuration/__init__.py +1 -1
- siibra/configuration/configuration.py +1 -1
- siibra/configuration/factory.py +164 -127
- siibra/core/__init__.py +1 -1
- siibra/core/assignment.py +1 -1
- siibra/core/atlas.py +24 -17
- siibra/core/concept.py +18 -9
- siibra/core/parcellation.py +76 -55
- siibra/core/region.py +163 -183
- siibra/core/space.py +3 -1
- siibra/core/structure.py +1 -2
- siibra/exceptions.py +17 -1
- siibra/experimental/contour.py +6 -6
- siibra/experimental/patch.py +2 -2
- siibra/experimental/plane3d.py +8 -8
- siibra/explorer/__init__.py +1 -1
- siibra/explorer/url.py +15 -0
- siibra/explorer/util.py +1 -1
- siibra/features/__init__.py +1 -1
- siibra/features/anchor.py +13 -14
- siibra/features/connectivity/__init__.py +1 -1
- siibra/features/connectivity/functional_connectivity.py +1 -1
- siibra/features/connectivity/regional_connectivity.py +7 -5
- 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 +1 -1
- siibra/features/feature.py +50 -28
- siibra/features/image/__init__.py +1 -1
- siibra/features/image/image.py +18 -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 +2 -2
- siibra/features/tabular/cell_density_profile.py +102 -66
- siibra/features/tabular/cortical_profile.py +5 -3
- siibra/features/tabular/gene_expression.py +1 -1
- siibra/features/tabular/layerwise_bigbrain_intensities.py +1 -1
- siibra/features/tabular/layerwise_cell_density.py +8 -25
- siibra/features/tabular/receptor_density_fingerprint.py +5 -3
- siibra/features/tabular/receptor_density_profile.py +5 -3
- siibra/features/tabular/regional_timeseries_activity.py +7 -5
- siibra/features/tabular/tabular.py +5 -3
- siibra/livequeries/__init__.py +1 -1
- siibra/livequeries/allen.py +46 -20
- siibra/livequeries/bigbrain.py +9 -9
- siibra/livequeries/ebrains.py +1 -1
- siibra/livequeries/query.py +1 -2
- siibra/locations/__init__.py +10 -10
- siibra/locations/boundingbox.py +77 -38
- siibra/locations/location.py +12 -4
- siibra/locations/point.py +14 -9
- siibra/locations/{pointset.py → pointcloud.py} +69 -27
- siibra/retrieval/__init__.py +1 -1
- siibra/retrieval/cache.py +1 -1
- siibra/retrieval/datasets.py +1 -1
- siibra/retrieval/exceptions/__init__.py +1 -1
- siibra/retrieval/repositories.py +10 -27
- siibra/retrieval/requests.py +20 -3
- siibra/vocabularies/__init__.py +1 -1
- siibra/volumes/__init__.py +2 -2
- siibra/volumes/parcellationmap.py +121 -94
- siibra/volumes/providers/__init__.py +1 -1
- siibra/volumes/providers/freesurfer.py +1 -1
- siibra/volumes/providers/gifti.py +1 -1
- siibra/volumes/providers/neuroglancer.py +68 -42
- siibra/volumes/providers/nifti.py +18 -28
- siibra/volumes/providers/provider.py +2 -2
- siibra/volumes/sparsemap.py +128 -247
- siibra/volumes/volume.py +252 -65
- {siibra-1.0a14.dist-info → siibra-1.0.1a0.dist-info}/METADATA +17 -4
- siibra-1.0.1a0.dist-info/RECORD +84 -0
- {siibra-1.0a14.dist-info → siibra-1.0.1a0.dist-info}/WHEEL +1 -1
- siibra-1.0a14.dist-info/RECORD +0 -84
- {siibra-1.0a14.dist-info → siibra-1.0.1a0.dist-info}/LICENSE +0 -0
- {siibra-1.0a14.dist-info → siibra-1.0.1a0.dist-info}/top_level.txt +0 -0
siibra/experimental/contour.py
CHANGED
|
@@ -13,22 +13,22 @@
|
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
|
|
16
|
-
from ..locations import point,
|
|
16
|
+
from ..locations import point, pointcloud, boundingbox
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class Contour(
|
|
21
|
+
class Contour(pointcloud.PointCloud):
|
|
22
22
|
"""
|
|
23
|
-
A
|
|
23
|
+
A PointCloud that represents a contour line.
|
|
24
24
|
The only difference is that the point order is relevant,
|
|
25
25
|
and consecutive points are thought as being connected by an edge.
|
|
26
26
|
|
|
27
|
-
In fact,
|
|
27
|
+
In fact, PointCloud assumes order as well, but no connections between points.
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
def __init__(self, coordinates, space=None, sigma_mm=0, labels: list = None):
|
|
31
|
-
|
|
31
|
+
pointcloud.PointCloud.__init__(self, coordinates, space, sigma_mm, labels)
|
|
32
32
|
|
|
33
33
|
def crop(self, voi: boundingbox.BoundingBox):
|
|
34
34
|
"""
|
|
@@ -45,7 +45,7 @@ class Contour(pointset.PointSet):
|
|
|
45
45
|
cropped = self.intersection(voi)
|
|
46
46
|
|
|
47
47
|
if cropped is not None and not isinstance(cropped, point.Point):
|
|
48
|
-
assert isinstance(cropped,
|
|
48
|
+
assert isinstance(cropped, pointcloud.PointCloud)
|
|
49
49
|
# Identifiy contour splits are by discontinuouities ("jumps")
|
|
50
50
|
# of their labels, which denote positions in the original contour
|
|
51
51
|
jumps = np.diff([self.labels.index(lb) for lb in cropped.labels])
|
siibra/experimental/patch.py
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
|
|
16
16
|
from ..volumes import volume
|
|
17
|
-
from ..locations import
|
|
17
|
+
from ..locations import pointcloud, boundingbox
|
|
18
18
|
from ..commons import translation_matrix, y_rotation_matrix
|
|
19
19
|
|
|
20
20
|
import numpy as np
|
|
@@ -24,7 +24,7 @@ from nilearn import image
|
|
|
24
24
|
|
|
25
25
|
class Patch:
|
|
26
26
|
|
|
27
|
-
def __init__(self, corners:
|
|
27
|
+
def __init__(self, corners: pointcloud.PointCloud):
|
|
28
28
|
"""Construct a patch in physical coordinates.
|
|
29
29
|
As of now, only patches aligned in the y plane of the physical space
|
|
30
30
|
are supported."""
|
siibra/experimental/plane3d.py
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
from . import contour
|
|
17
17
|
from . import patch
|
|
18
|
-
from ..locations import point,
|
|
18
|
+
from ..locations import point, pointcloud
|
|
19
19
|
from ..volumes import volume
|
|
20
20
|
|
|
21
21
|
import numpy as np
|
|
@@ -93,9 +93,9 @@ class Plane3D:
|
|
|
93
93
|
and an Mx3 array "faces" of face definitions.
|
|
94
94
|
Each row in the face array corresponds to the three indices of vertices making up the
|
|
95
95
|
triangle.
|
|
96
|
-
The result is a list of contour segments, each represented as a
|
|
96
|
+
The result is a list of contour segments, each represented as a PointCloud
|
|
97
97
|
holding the ordered list of contour points.
|
|
98
|
-
The point labels in each "contour"
|
|
98
|
+
The point labels in each "contour" PointCloud hold the index of the face in the
|
|
99
99
|
mesh which made up each contour point.
|
|
100
100
|
"""
|
|
101
101
|
|
|
@@ -185,17 +185,17 @@ class Plane3D:
|
|
|
185
185
|
|
|
186
186
|
return result
|
|
187
187
|
|
|
188
|
-
def project_points(self, points:
|
|
188
|
+
def project_points(self, points: pointcloud.PointCloud):
|
|
189
189
|
"""projects the given points onto the plane."""
|
|
190
190
|
assert self.space == points.space
|
|
191
191
|
XYZ = points.coordinates
|
|
192
192
|
N = XYZ.shape[0]
|
|
193
193
|
dists = np.dot(self._n, XYZ.T) - self._d
|
|
194
|
-
return
|
|
194
|
+
return pointcloud.PointCloud(
|
|
195
195
|
XYZ - np.tile(self._n, (N, 1)) * dists[:, np.newaxis], space=self.space
|
|
196
196
|
)
|
|
197
197
|
|
|
198
|
-
def get_enclosing_patch(self, points:
|
|
198
|
+
def get_enclosing_patch(self, points: pointcloud.PointCloud, margin=[0.5, 0.5]):
|
|
199
199
|
"""
|
|
200
200
|
Computes the enclosing patch in the given plane
|
|
201
201
|
which contains the projections of the given points.
|
|
@@ -225,7 +225,7 @@ class Plane3D:
|
|
|
225
225
|
|
|
226
226
|
m0, m1 = margin
|
|
227
227
|
w = np.linalg.norm(p3 - p2)
|
|
228
|
-
corners =
|
|
228
|
+
corners = pointcloud.PointCloud(
|
|
229
229
|
[
|
|
230
230
|
p1 + (w / 2 + m1) * v2 + m0 * v1,
|
|
231
231
|
p0 + (w / 2 + m1) * v2 - m0 * v1,
|
|
@@ -249,7 +249,7 @@ class Plane3D:
|
|
|
249
249
|
assert isinstance(image, volume.Volume)
|
|
250
250
|
im_lowres = image.fetch(resolution_mm=1)
|
|
251
251
|
plane_dims = np.where(np.argsort(im_lowres.shape) < 2)[0]
|
|
252
|
-
voxels =
|
|
252
|
+
voxels = pointcloud.PointCloud(
|
|
253
253
|
np.vstack(([0, 0, 0], np.identity(3)[plane_dims])), space=None
|
|
254
254
|
)
|
|
255
255
|
points = voxels.transform(im_lowres.affine, space=image.space)
|
siibra/explorer/__init__.py
CHANGED
siibra/explorer/url.py
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
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
|
+
|
|
1
16
|
from typing import Optional, TYPE_CHECKING
|
|
2
17
|
from urllib.parse import quote_plus
|
|
3
18
|
from numpy import int32
|
siibra/explorer/util.py
CHANGED
siibra/features/__init__.py
CHANGED
siibra/features/anchor.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,7 +19,7 @@ from ..commons import Species, logger
|
|
|
19
19
|
from ..core.structure import BrainStructure
|
|
20
20
|
from ..core.assignment import AnatomicalAssignment, Qualification
|
|
21
21
|
from ..locations.location import Location
|
|
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
25
|
from ..exceptions import SpaceWarpingFailedError
|
|
@@ -126,13 +126,13 @@ class AnatomicalAnchor:
|
|
|
126
126
|
# decode the region specification into a dict of region objects and assignment qualifications
|
|
127
127
|
regions = {
|
|
128
128
|
region: Qualification.EXACT
|
|
129
|
-
for region in
|
|
129
|
+
for region in find_regions(self._regionspec, filter_children=True, find_topmost=False)
|
|
130
130
|
if region.species in self.species
|
|
131
131
|
}
|
|
132
132
|
# add more regions from possible aliases of the region spec
|
|
133
133
|
for alt_species, aliases in self.region_aliases.items():
|
|
134
134
|
for alias_regionspec, qualificationspec in aliases.items():
|
|
135
|
-
for r in
|
|
135
|
+
for r in find_regions(alias_regionspec, filter_children=True, find_topmost=False):
|
|
136
136
|
if r.species != alt_species:
|
|
137
137
|
continue
|
|
138
138
|
if r not in regions:
|
|
@@ -156,18 +156,17 @@ class AnatomicalAnchor:
|
|
|
156
156
|
else:
|
|
157
157
|
return region + separator + location
|
|
158
158
|
|
|
159
|
-
def assign(self, concept: BrainStructure,
|
|
159
|
+
def assign(self, concept: Union[BrainStructure, Space]) -> AnatomicalAssignment:
|
|
160
160
|
"""
|
|
161
161
|
Match this anchor to a query concept. Assignments are cached at runtime,
|
|
162
162
|
so repeated assignment with the same concept will be cheap.
|
|
163
163
|
"""
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return []
|
|
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
|
+
|
|
171
170
|
if concept not in self._assignments:
|
|
172
171
|
assignments: List[AnatomicalAssignment] = []
|
|
173
172
|
if self.location is not None:
|
|
@@ -184,8 +183,8 @@ class AnatomicalAnchor:
|
|
|
184
183
|
else None
|
|
185
184
|
return self._assignments[concept]
|
|
186
185
|
|
|
187
|
-
def matches(self, concept: BrainStructure,
|
|
188
|
-
return len(self.assign(concept
|
|
186
|
+
def matches(self, concept: Union[BrainStructure, Space]) -> bool:
|
|
187
|
+
return len(self.assign(concept)) > 0
|
|
189
188
|
|
|
190
189
|
def represented_parcellations(self) -> List[Parcellation]:
|
|
191
190
|
"""
|
|
@@ -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");
|
|
@@ -21,7 +21,7 @@ from .. import anchor as _anchor
|
|
|
21
21
|
|
|
22
22
|
from ...commons import logger, QUIET, siibra_tqdm
|
|
23
23
|
from ...core import region as _region
|
|
24
|
-
from ...locations import
|
|
24
|
+
from ...locations import pointcloud
|
|
25
25
|
from ...retrieval.repositories import RepositoryConnector
|
|
26
26
|
from ...retrieval.requests import HttpRequest
|
|
27
27
|
|
|
@@ -58,7 +58,8 @@ class RegionalConnectivity(Feature, Compoundable):
|
|
|
58
58
|
datasets: list = [],
|
|
59
59
|
subject: str = "average",
|
|
60
60
|
feature: str = None,
|
|
61
|
-
id: str = None
|
|
61
|
+
id: str = None,
|
|
62
|
+
prerelease: bool = False,
|
|
62
63
|
):
|
|
63
64
|
"""
|
|
64
65
|
Construct a parcellation-averaged connectivity matrix.
|
|
@@ -92,7 +93,8 @@ class RegionalConnectivity(Feature, Compoundable):
|
|
|
92
93
|
description=description,
|
|
93
94
|
anchor=anchor,
|
|
94
95
|
datasets=datasets,
|
|
95
|
-
id=id
|
|
96
|
+
id=id,
|
|
97
|
+
prerelease=prerelease
|
|
96
98
|
)
|
|
97
99
|
self.cohort = cohort.upper()
|
|
98
100
|
if isinstance(connector, str) and connector:
|
|
@@ -438,7 +440,7 @@ class RegionalConnectivity(Feature, Compoundable):
|
|
|
438
440
|
found = [r for r in region if r.name in all_centroids]
|
|
439
441
|
assert len(found) > 0
|
|
440
442
|
result.append(
|
|
441
|
-
tuple(
|
|
443
|
+
tuple(pointcloud.PointCloud(
|
|
442
444
|
[all_centroids[r.name] for r in found], space=space
|
|
443
445
|
).centroid)
|
|
444
446
|
)
|
siibra/features/feature.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");
|
|
@@ -99,7 +99,8 @@ class Feature:
|
|
|
99
99
|
description: str,
|
|
100
100
|
anchor: _anchor.AnatomicalAnchor,
|
|
101
101
|
datasets: List['TypeDataset'] = [],
|
|
102
|
-
id: str = None
|
|
102
|
+
id: str = None,
|
|
103
|
+
prerelease: bool = False,
|
|
103
104
|
):
|
|
104
105
|
"""
|
|
105
106
|
Parameters
|
|
@@ -117,6 +118,7 @@ class Feature:
|
|
|
117
118
|
self._anchor_cached = anchor
|
|
118
119
|
self.datasets = datasets
|
|
119
120
|
self._id = id
|
|
121
|
+
self._prerelease = prerelease
|
|
120
122
|
|
|
121
123
|
@property
|
|
122
124
|
def modality(self):
|
|
@@ -170,12 +172,19 @@ class Feature:
|
|
|
170
172
|
|
|
171
173
|
@property
|
|
172
174
|
def LICENSE(self) -> str:
|
|
173
|
-
licenses =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
licenses = []
|
|
176
|
+
for ds in self.datasets:
|
|
177
|
+
if ds.LICENSE is None or ds.LICENSE == "No license information is found.":
|
|
178
|
+
continue
|
|
179
|
+
if isinstance(ds.LICENSE, str):
|
|
180
|
+
licenses.append(ds.LICENSE)
|
|
181
|
+
if isinstance(ds.LICENSE, list):
|
|
182
|
+
licenses.extend(ds.LICENSE)
|
|
183
|
+
if len(licenses) == 0:
|
|
184
|
+
logger.warning("No license information is found.")
|
|
185
|
+
return ""
|
|
186
|
+
if len(licenses) > 1:
|
|
187
|
+
logger.info("Found multiple licenses corresponding to datasets.")
|
|
179
188
|
return '\n'.join(licenses)
|
|
180
189
|
|
|
181
190
|
@property
|
|
@@ -199,7 +208,8 @@ class Feature:
|
|
|
199
208
|
def name(self):
|
|
200
209
|
"""Returns a short human-readable name of this feature."""
|
|
201
210
|
readable_class_name = sub("([a-z])([A-Z])", r"\g<1> \g<2>", self.__class__.__name__)
|
|
202
|
-
|
|
211
|
+
name_ = sub("([b,B]ig [b,B]rain)", "BigBrain", readable_class_name)
|
|
212
|
+
return name_ if not self._prerelease else f"[PRERELEASE] {name_}"
|
|
203
213
|
|
|
204
214
|
@classmethod
|
|
205
215
|
def _get_instances(cls, **kwargs) -> List['Feature']:
|
|
@@ -241,13 +251,16 @@ class Feature:
|
|
|
241
251
|
""" Removes all instantiated object instances"""
|
|
242
252
|
cls._preconfigured_instances = None
|
|
243
253
|
|
|
244
|
-
def matches(
|
|
254
|
+
def matches(
|
|
255
|
+
self,
|
|
256
|
+
concept: Union[structure.BrainStructure, space.Space],
|
|
257
|
+
) -> bool:
|
|
245
258
|
"""
|
|
246
259
|
Match the features anatomical anchor agains the given query concept.
|
|
247
260
|
Record the most recently matched concept for inspection by the caller.
|
|
248
261
|
"""
|
|
249
262
|
# TODO: storing the last matched concept. It is not ideal, might cause problems in multithreading
|
|
250
|
-
if self.anchor and self.anchor.matches(concept
|
|
263
|
+
if self.anchor and self.anchor.matches(concept):
|
|
251
264
|
self.anchor._last_matched_concept = concept
|
|
252
265
|
return True
|
|
253
266
|
self.anchor._last_matched_concept = None
|
|
@@ -268,13 +281,18 @@ class Feature:
|
|
|
268
281
|
if self._id:
|
|
269
282
|
return self._id
|
|
270
283
|
|
|
284
|
+
if self._prerelease:
|
|
285
|
+
name_ = self.name.replace("[PRERELEASE] ", "")
|
|
286
|
+
else:
|
|
287
|
+
name_ = self.name
|
|
288
|
+
|
|
271
289
|
prefix = ''
|
|
272
290
|
for ds in self.datasets:
|
|
273
291
|
if hasattr(ds, "id"):
|
|
274
292
|
prefix = ds.id + '--'
|
|
275
293
|
break
|
|
276
294
|
return prefix + md5(
|
|
277
|
-
f"{
|
|
295
|
+
f"{name_} - {self.anchor}".encode("utf-8")
|
|
278
296
|
).hexdigest()
|
|
279
297
|
|
|
280
298
|
def _to_zip(self, fh: ZipFile):
|
|
@@ -468,7 +486,10 @@ class Feature:
|
|
|
468
486
|
f"objects linked to {str(concept)}{argstr}"
|
|
469
487
|
)
|
|
470
488
|
q = QueryType(**kwargs)
|
|
471
|
-
|
|
489
|
+
if isinstance(concept, space.Space):
|
|
490
|
+
features = q.query(concept.get_template())
|
|
491
|
+
else:
|
|
492
|
+
features = q.query(concept)
|
|
472
493
|
live_instances.extend(
|
|
473
494
|
Feature._wrap_livequery_feature(f, Feature._serialize_query_context(f, concept))
|
|
474
495
|
for f in features
|
|
@@ -479,9 +500,8 @@ class Feature:
|
|
|
479
500
|
@classmethod
|
|
480
501
|
def _match(
|
|
481
502
|
cls,
|
|
482
|
-
concept: structure.BrainStructure,
|
|
503
|
+
concept: Union[structure.BrainStructure, space.Space],
|
|
483
504
|
feature_type: Union[str, Type['Feature'], list],
|
|
484
|
-
restrict_space: bool = False,
|
|
485
505
|
**kwargs
|
|
486
506
|
) -> List['Feature']:
|
|
487
507
|
"""
|
|
@@ -500,10 +520,6 @@ class Feature:
|
|
|
500
520
|
An anatomical concept, typically a brain region or parcellation.
|
|
501
521
|
feature_type: subclass of Feature, str
|
|
502
522
|
specififies the type of features ("modality")
|
|
503
|
-
restrict_space: bool: default: False
|
|
504
|
-
If true, will skip features anchored at spatial locations of
|
|
505
|
-
different spaces than the concept. Requires concept to be a
|
|
506
|
-
Location.
|
|
507
523
|
"""
|
|
508
524
|
if isinstance(feature_type, list):
|
|
509
525
|
# a list of feature types is given, collect match results on those
|
|
@@ -513,7 +529,7 @@ class Feature:
|
|
|
513
529
|
)
|
|
514
530
|
return list(dict.fromkeys(
|
|
515
531
|
sum((
|
|
516
|
-
cls._match(concept, t,
|
|
532
|
+
cls._match(concept, t, **kwargs) for t in feature_type
|
|
517
533
|
), [])
|
|
518
534
|
))
|
|
519
535
|
|
|
@@ -530,15 +546,15 @@ class Feature:
|
|
|
530
546
|
f"'{feature_type}' decoded as feature type/s: "
|
|
531
547
|
f"{[c.__name__ for c in ftype_candidates]}."
|
|
532
548
|
)
|
|
533
|
-
return cls._match(concept, ftype_candidates,
|
|
549
|
+
return cls._match(concept, ftype_candidates, **kwargs)
|
|
534
550
|
|
|
535
551
|
assert issubclass(feature_type, Feature)
|
|
536
552
|
|
|
537
553
|
# At this stage, no recursion is needed.
|
|
538
554
|
# We expect a specific supported feature type is to be matched now.
|
|
539
|
-
if not isinstance(concept, structure.BrainStructure):
|
|
555
|
+
if not isinstance(concept, (structure.BrainStructure, space.Space)):
|
|
540
556
|
raise ValueError(
|
|
541
|
-
f"{concept.__class__.__name__} cannot be used for feature queries as it is not a BrainStructure type."
|
|
557
|
+
f"{concept.__class__.__name__} cannot be used for feature queries as it is not a `BrainStructure` or a `Space` type."
|
|
542
558
|
)
|
|
543
559
|
|
|
544
560
|
# Collect any preconfigured instances of the requested feature type
|
|
@@ -556,7 +572,7 @@ class Feature:
|
|
|
556
572
|
total=len(instances),
|
|
557
573
|
disable=(not instances)
|
|
558
574
|
)
|
|
559
|
-
if f.matches(concept
|
|
575
|
+
if f.matches(concept)
|
|
560
576
|
]
|
|
561
577
|
|
|
562
578
|
# Then run any registered live queries for the requested feature type
|
|
@@ -721,7 +737,7 @@ class CompoundFeature(Feature):
|
|
|
721
737
|
def __init__(
|
|
722
738
|
self,
|
|
723
739
|
elements: List['Feature'],
|
|
724
|
-
queryconcept: Union[region.Region, parcellation.Parcellation, space.Space]
|
|
740
|
+
queryconcept: Union[region.Region, parcellation.Parcellation, space.Space],
|
|
725
741
|
):
|
|
726
742
|
"""
|
|
727
743
|
A compound of several features of the same type with an anchor created
|
|
@@ -754,7 +770,8 @@ class CompoundFeature(Feature):
|
|
|
754
770
|
modality=modality,
|
|
755
771
|
description="\n".join({f.description for f in elements}),
|
|
756
772
|
anchor=self._feature_type._merge_anchors([f.anchor for f in elements]),
|
|
757
|
-
datasets=list(dict.fromkeys([ds for f in elements for ds in f.datasets]))
|
|
773
|
+
datasets=list(dict.fromkeys([ds for f in elements for ds in f.datasets])),
|
|
774
|
+
prerelease=all(f._prerelease for f in elements),
|
|
758
775
|
)
|
|
759
776
|
self._queryconcept = queryconcept
|
|
760
777
|
self._merged_feature_cached = None
|
|
@@ -830,16 +847,21 @@ class CompoundFeature(Feature):
|
|
|
830
847
|
for k, v in self._compounding_attributes.items()
|
|
831
848
|
if k != 'modality'
|
|
832
849
|
])
|
|
833
|
-
|
|
850
|
+
cf_name = f"{len(self)} {readable_feature_type} features{f' {groupby}' if groupby else ''}"
|
|
851
|
+
return cf_name if not self._prerelease else f"[PRERELEASE] {cf_name}"
|
|
834
852
|
|
|
835
853
|
@property
|
|
836
854
|
def id(self) -> str:
|
|
855
|
+
if self._prerelease:
|
|
856
|
+
name_ = self.name.replace("[PRERELEASE] ", "")
|
|
857
|
+
else:
|
|
858
|
+
name_ = self.name
|
|
837
859
|
return "::".join((
|
|
838
860
|
"cf0",
|
|
839
861
|
f"{self._feature_type.__name__}",
|
|
840
862
|
self._encode_concept(self._queryconcept),
|
|
841
863
|
self.datasets[0].id if self.datasets else "nodsid",
|
|
842
|
-
md5(
|
|
864
|
+
md5(name_.encode("utf-8")).hexdigest()
|
|
843
865
|
))
|
|
844
866
|
|
|
845
867
|
def __iter__(self) -> Iterator['Feature']:
|
siibra/features/image/image.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");
|
|
@@ -20,19 +20,19 @@ from .. import feature
|
|
|
20
20
|
from .. import anchor as _anchor
|
|
21
21
|
|
|
22
22
|
from ...volumes import volume as _volume
|
|
23
|
-
from ...volumes.providers import provider
|
|
24
23
|
|
|
25
|
-
from typing import List
|
|
24
|
+
from typing import List, TYPE_CHECKING
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from ...locations.boundingbox import BoundingBox
|
|
28
|
+
from ...volumes.providers import provider
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
class ImageAnchor(_anchor.AnatomicalAnchor):
|
|
29
32
|
|
|
30
33
|
def __init__(self, volume: _volume.Volume, region: str = None):
|
|
31
34
|
_anchor.AnatomicalAnchor.__init__(
|
|
32
|
-
self,
|
|
33
|
-
species=volume.space.species,
|
|
34
|
-
location=None,
|
|
35
|
-
region=region
|
|
35
|
+
self, species=volume.space.species, location=None, region=region
|
|
36
36
|
)
|
|
37
37
|
self.volume = volume
|
|
38
38
|
|
|
@@ -42,7 +42,9 @@ class ImageAnchor(_anchor.AnatomicalAnchor):
|
|
|
42
42
|
Loads the bounding box only if required, since it demands image data access.
|
|
43
43
|
"""
|
|
44
44
|
if self._location_cached is None:
|
|
45
|
-
self._location_cached = self.volume.get_boundingbox(
|
|
45
|
+
self._location_cached = self.volume.get_boundingbox(
|
|
46
|
+
clip=False
|
|
47
|
+
) # use unclipped to preseve exisiting behaviour
|
|
46
48
|
return self._location_cached
|
|
47
49
|
|
|
48
50
|
@property
|
|
@@ -60,10 +62,12 @@ class Image(feature.Feature, _volume.Volume):
|
|
|
60
62
|
name: str,
|
|
61
63
|
modality: str,
|
|
62
64
|
space_spec: dict,
|
|
63
|
-
providers: List[provider.VolumeProvider],
|
|
65
|
+
providers: List["provider.VolumeProvider"],
|
|
64
66
|
region: str = None,
|
|
65
67
|
datasets: List = [],
|
|
66
|
-
|
|
68
|
+
bbox: "BoundingBox" = None,
|
|
69
|
+
id: str = None,
|
|
70
|
+
prerelease: bool = False,
|
|
67
71
|
):
|
|
68
72
|
feature.Feature.__init__(
|
|
69
73
|
self,
|
|
@@ -71,7 +75,8 @@ class Image(feature.Feature, _volume.Volume):
|
|
|
71
75
|
description=None, # lazy implementation below!
|
|
72
76
|
anchor=None, # lazy implementation below!
|
|
73
77
|
datasets=datasets,
|
|
74
|
-
id=id
|
|
78
|
+
id=id,
|
|
79
|
+
prerelease=prerelease,
|
|
75
80
|
)
|
|
76
81
|
|
|
77
82
|
_volume.Volume.__init__(
|
|
@@ -80,6 +85,7 @@ class Image(feature.Feature, _volume.Volume):
|
|
|
80
85
|
providers=providers,
|
|
81
86
|
name=name,
|
|
82
87
|
datasets=datasets,
|
|
88
|
+
bbox=bbox,
|
|
83
89
|
)
|
|
84
90
|
|
|
85
91
|
self._anchor_cached = ImageAnchor(self, region=region)
|
|
@@ -104,7 +110,6 @@ class Image(feature.Feature, _volume.Volume):
|
|
|
104
110
|
def description(self):
|
|
105
111
|
if self._description_cached is None:
|
|
106
112
|
self._description_cached = (
|
|
107
|
-
f"Image feature with modality {self.modality} "
|
|
108
|
-
f"at {self.anchor}"
|
|
113
|
+
f"Image feature with modality {self.modality} " f"at {self.anchor}"
|
|
109
114
|
)
|
|
110
115
|
return self._description_cached
|
|
@@ -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");
|
|
@@ -65,7 +65,7 @@ class BigBrainIntensityProfile(
|
|
|
65
65
|
|
|
66
66
|
@classmethod
|
|
67
67
|
def _merge_anchors(cls, anchors: List['AnatomicalAnchor']):
|
|
68
|
-
from ...locations.
|
|
68
|
+
from ...locations.pointcloud import from_points
|
|
69
69
|
from ...features.anchor import AnatomicalAnchor
|
|
70
70
|
|
|
71
71
|
location = from_points([anchor.location for anchor in anchors])
|