siibra 1.0a19__py3-none-any.whl → 1.0.1a1__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 +7 -7
- siibra/commons.py +8 -53
- siibra/configuration/__init__.py +1 -1
- siibra/configuration/configuration.py +1 -1
- siibra/configuration/factory.py +11 -21
- siibra/core/__init__.py +1 -1
- siibra/core/assignment.py +1 -1
- siibra/core/atlas.py +21 -15
- siibra/core/concept.py +3 -3
- siibra/core/parcellation.py +69 -54
- siibra/core/region.py +178 -158
- siibra/core/space.py +1 -1
- siibra/core/structure.py +2 -2
- siibra/exceptions.py +13 -1
- siibra/experimental/__init__.py +1 -1
- siibra/experimental/contour.py +8 -8
- siibra/experimental/cortical_profile_sampler.py +1 -1
- siibra/experimental/patch.py +3 -3
- siibra/experimental/plane3d.py +12 -12
- siibra/explorer/__init__.py +1 -1
- siibra/explorer/url.py +2 -2
- siibra/explorer/util.py +1 -1
- siibra/features/__init__.py +1 -1
- siibra/features/anchor.py +14 -15
- siibra/features/connectivity/__init__.py +1 -1
- siibra/features/connectivity/functional_connectivity.py +1 -1
- siibra/features/connectivity/regional_connectivity.py +4 -4
- 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 +24 -26
- siibra/features/image/__init__.py +1 -1
- siibra/features/image/image.py +2 -2
- 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 +98 -64
- siibra/features/tabular/cortical_profile.py +3 -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 +4 -23
- siibra/features/tabular/receptor_density_fingerprint.py +13 -10
- siibra/features/tabular/receptor_density_profile.py +1 -1
- siibra/features/tabular/regional_timeseries_activity.py +4 -4
- siibra/features/tabular/tabular.py +7 -5
- siibra/livequeries/__init__.py +1 -1
- siibra/livequeries/allen.py +42 -19
- siibra/livequeries/bigbrain.py +21 -12
- siibra/livequeries/ebrains.py +1 -1
- siibra/livequeries/query.py +2 -3
- siibra/locations/__init__.py +11 -11
- siibra/locations/boundingbox.py +30 -29
- siibra/locations/location.py +1 -1
- siibra/locations/point.py +7 -7
- siibra/locations/{pointset.py → pointcloud.py} +36 -33
- siibra/retrieval/__init__.py +1 -1
- siibra/retrieval/cache.py +1 -1
- siibra/retrieval/datasets.py +4 -4
- siibra/retrieval/exceptions/__init__.py +1 -1
- siibra/retrieval/repositories.py +13 -30
- siibra/retrieval/requests.py +25 -8
- siibra/vocabularies/__init__.py +1 -1
- siibra/volumes/__init__.py +2 -2
- siibra/volumes/parcellationmap.py +119 -91
- siibra/volumes/providers/__init__.py +1 -1
- siibra/volumes/providers/freesurfer.py +3 -3
- siibra/volumes/providers/gifti.py +1 -1
- siibra/volumes/providers/neuroglancer.py +67 -41
- siibra/volumes/providers/nifti.py +12 -26
- siibra/volumes/providers/provider.py +1 -1
- siibra/volumes/sparsemap.py +125 -246
- siibra/volumes/volume.py +150 -61
- {siibra-1.0a19.dist-info → siibra-1.0.1a1.dist-info}/METADATA +26 -4
- siibra-1.0.1a1.dist-info/RECORD +84 -0
- {siibra-1.0a19.dist-info → siibra-1.0.1a1.dist-info}/WHEEL +1 -1
- siibra-1.0a19.dist-info/RECORD +0 -84
- {siibra-1.0a19.dist-info → siibra-1.0.1a1.dist-info}/LICENSE +0 -0
- {siibra-1.0a19.dist-info → siibra-1.0.1a1.dist-info}/top_level.txt +0 -0
siibra/livequeries/bigbrain.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2025
|
|
2
2
|
# Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
|
|
3
3
|
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -12,14 +12,14 @@
|
|
|
12
12
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
|
-
"""Matches BigBrain
|
|
15
|
+
"""Matches BigBrain intensity profiles extracted by Wagstyl et al. to volumes."""
|
|
16
16
|
|
|
17
17
|
from . import query
|
|
18
18
|
|
|
19
19
|
from ..features.tabular import bigbrain_intensity_profile, layerwise_bigbrain_intensities
|
|
20
20
|
from ..features import anchor as _anchor
|
|
21
21
|
from ..commons import logger
|
|
22
|
-
from ..locations import point,
|
|
22
|
+
from ..locations import point, pointcloud, location
|
|
23
23
|
from ..core import structure
|
|
24
24
|
from ..retrieval import requests, cache
|
|
25
25
|
from ..retrieval.datasets import GenericDataset
|
|
@@ -119,15 +119,20 @@ class BigBrainProfileQuery(query.LiveQuery, args=[], FeatureType=bigbrain_intens
|
|
|
119
119
|
|
|
120
120
|
def query(self, concept: structure.BrainStructure, **kwargs) -> List[bigbrain_intensity_profile.BigBrainIntensityProfile]:
|
|
121
121
|
loader = WagstylProfileLoader()
|
|
122
|
-
mesh_vertices =
|
|
123
|
-
matched = concept.intersection(mesh_vertices) # returns a reduced
|
|
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
124
|
if matched is None:
|
|
125
125
|
return []
|
|
126
|
-
|
|
126
|
+
if isinstance(matched, point.Point):
|
|
127
|
+
matched = pointcloud.from_points([matched])
|
|
128
|
+
assert isinstance(matched, pointcloud.PointCloud)
|
|
129
|
+
if isinstance(concept, location.Location):
|
|
130
|
+
mesh_as_list = mesh_vertices.as_list()
|
|
131
|
+
matched.labels = [mesh_as_list.index(v.coordinate) for v in matched]
|
|
127
132
|
indices = matched.labels
|
|
128
133
|
assert indices is not None
|
|
129
134
|
features = []
|
|
130
|
-
for i in
|
|
135
|
+
for i in indices:
|
|
131
136
|
anchor = _anchor.AnatomicalAnchor(
|
|
132
137
|
location=point.Point(loader._vertices[i], space='bigbrain'),
|
|
133
138
|
region=str(concept),
|
|
@@ -159,13 +164,17 @@ class LayerwiseBigBrainIntensityQuery(query.LiveQuery, args=[], FeatureType=laye
|
|
|
159
164
|
def query(self, concept: structure.BrainStructure, **kwargs) -> List[layerwise_bigbrain_intensities.LayerwiseBigBrainIntensities]:
|
|
160
165
|
|
|
161
166
|
loader = WagstylProfileLoader()
|
|
162
|
-
mesh_vertices =
|
|
163
|
-
matched = concept.intersection(mesh_vertices) # returns a reduced
|
|
167
|
+
mesh_vertices = pointcloud.PointCloud(loader._vertices, space='bigbrain')
|
|
168
|
+
matched = concept.intersection(mesh_vertices) # returns a reduced PointCloud with og indices as labels if the concept is a region
|
|
164
169
|
if matched is None:
|
|
165
170
|
return []
|
|
166
|
-
|
|
171
|
+
if isinstance(matched, point.Point):
|
|
172
|
+
matched = pointcloud.from_points([matched])
|
|
173
|
+
assert isinstance(matched, pointcloud.PointCloud)
|
|
174
|
+
if isinstance(concept, location.Location):
|
|
175
|
+
mesh_as_list = mesh_vertices.as_list()
|
|
176
|
+
matched.labels = [mesh_as_list.index(v.coordinate) for v in matched]
|
|
167
177
|
indices = matched.labels
|
|
168
|
-
assert indices is not None
|
|
169
178
|
matched_profiles = loader._profiles[indices, :]
|
|
170
179
|
boundary_depths = loader._boundary_depths[indices, :]
|
|
171
180
|
# compute array of layer labels for all coefficients in profiles_left
|
|
@@ -177,7 +186,7 @@ class LayerwiseBigBrainIntensityQuery(query.LiveQuery, args=[], FeatureType=laye
|
|
|
177
186
|
]).reshape((-1, 200))
|
|
178
187
|
|
|
179
188
|
anchor = _anchor.AnatomicalAnchor(
|
|
180
|
-
location=
|
|
189
|
+
location=pointcloud.PointCloud(loader._vertices[indices, :], space='bigbrain'),
|
|
181
190
|
region=str(concept),
|
|
182
191
|
species='Homo sapiens'
|
|
183
192
|
)
|
siibra/livequeries/ebrains.py
CHANGED
siibra/livequeries/query.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2025
|
|
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");
|
|
@@ -46,5 +46,4 @@ class LiveQuery(ABC):
|
|
|
46
46
|
|
|
47
47
|
@abstractmethod
|
|
48
48
|
def query(self, concept: AtlasConcept, **kwargs) -> List[Feature]:
|
|
49
|
-
raise NotImplementedError(f"
|
|
50
|
-
pass
|
|
49
|
+
raise NotImplementedError(f"Derived class {self.__class__} needs to implement query()")
|
siibra/locations/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2025
|
|
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,13 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
from .location import Location
|
|
18
18
|
from .point import Point
|
|
19
|
-
from .
|
|
19
|
+
from .pointcloud import PointCloud, from_points
|
|
20
20
|
from .boundingbox import BoundingBox
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def reassign_union(loc0: 'Location', loc1: 'Location') -> 'Location':
|
|
24
24
|
"""
|
|
25
|
-
Add two locations of same or
|
|
25
|
+
Add two locations of same or different type to find their union as a
|
|
26
26
|
Location object.
|
|
27
27
|
Note
|
|
28
28
|
----
|
|
@@ -36,11 +36,11 @@ def reassign_union(loc0: 'Location', loc1: 'Location') -> 'Location':
|
|
|
36
36
|
Returns
|
|
37
37
|
-------
|
|
38
38
|
Location
|
|
39
|
-
- Point U Point =
|
|
40
|
-
- Point U
|
|
41
|
-
-
|
|
39
|
+
- Point U Point = PointCloud
|
|
40
|
+
- Point U PointCloud = PointCloud
|
|
41
|
+
- PointCloud U PointCloud = PointCloud
|
|
42
42
|
- BoundingBox U BoundingBox = BoundingBox
|
|
43
|
-
- BoundingBox U
|
|
43
|
+
- BoundingBox U PointCloud = BoundingBox
|
|
44
44
|
- BoundingBox U Point = BoundingBox
|
|
45
45
|
- WholeBrain U Location = NotImplementedError
|
|
46
46
|
(all operations are commutative)
|
|
@@ -53,14 +53,14 @@ def reassign_union(loc0: 'Location', loc1: 'Location') -> 'Location':
|
|
|
53
53
|
# location that has its own union method since it is not a part of locations
|
|
54
54
|
# module and to avoid importing Volume here.
|
|
55
55
|
if not all(
|
|
56
|
-
isinstance(loc, (Point,
|
|
56
|
+
isinstance(loc, (Point, PointCloud, BoundingBox)) for loc in [loc0, loc1]
|
|
57
57
|
):
|
|
58
58
|
try:
|
|
59
59
|
return loc1.union(loc0)
|
|
60
60
|
except Exception:
|
|
61
61
|
raise NotImplementedError(f"There are no union method for {(loc0.__class__.__name__, loc1.__class__.__name__)}")
|
|
62
62
|
|
|
63
|
-
# convert Points to
|
|
63
|
+
# convert Points to PointClouds
|
|
64
64
|
loc0, loc1 = [
|
|
65
65
|
from_points([loc]) if isinstance(loc, Point) else loc
|
|
66
66
|
for loc in [loc0, loc1]
|
|
@@ -69,8 +69,8 @@ def reassign_union(loc0: 'Location', loc1: 'Location') -> 'Location':
|
|
|
69
69
|
# adopt the space of the first location
|
|
70
70
|
loc1_w = loc1.warp(loc0.space)
|
|
71
71
|
|
|
72
|
-
if isinstance(loc0,
|
|
73
|
-
if isinstance(loc1_w,
|
|
72
|
+
if isinstance(loc0, PointCloud):
|
|
73
|
+
if isinstance(loc1_w, PointCloud):
|
|
74
74
|
points = list(dict.fromkeys([*loc0, *loc1_w]))
|
|
75
75
|
return from_points(points)
|
|
76
76
|
if isinstance(loc1_w, BoundingBox):
|
siibra/locations/boundingbox.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2025
|
|
2
2
|
# Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
|
|
3
3
|
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
"""A box defined by two farthest corner coordinates on a specific space."""
|
|
16
16
|
|
|
17
|
-
from . import point,
|
|
17
|
+
from . import point, pointcloud, location
|
|
18
18
|
|
|
19
19
|
from ..commons import logger
|
|
20
20
|
from ..exceptions import SpaceWarpingFailedError
|
|
@@ -85,7 +85,7 @@ class BoundingBox(location.Location):
|
|
|
85
85
|
self.maxpoint[d] = self.minpoint[d] + minsize
|
|
86
86
|
|
|
87
87
|
if self.volume == 0:
|
|
88
|
-
logger.warning("
|
|
88
|
+
logger.warning(f"Zero-volume bounding box from points {point1} and {point2} in {self.space} space.")
|
|
89
89
|
|
|
90
90
|
@property
|
|
91
91
|
def id(self) -> str:
|
|
@@ -144,16 +144,12 @@ class BoundingBox(location.Location):
|
|
|
144
144
|
if isinstance(other, point.Point):
|
|
145
145
|
warped = other.warp(self.space)
|
|
146
146
|
return other if self.minpoint <= warped <= self.maxpoint else None
|
|
147
|
-
if isinstance(other,
|
|
147
|
+
if isinstance(other, pointcloud.PointCloud):
|
|
148
148
|
points_inside = [p for p in other if self.intersects(p)]
|
|
149
|
-
|
|
150
|
-
points_inside,
|
|
151
|
-
space=other.space,
|
|
152
|
-
sigma_mm=[p.sigma for p in points_inside]
|
|
153
|
-
)
|
|
154
|
-
if len(result) == 0:
|
|
149
|
+
if len(points_inside) == 0:
|
|
155
150
|
return None
|
|
156
|
-
|
|
151
|
+
result = pointcloud.from_points(points_inside)
|
|
152
|
+
return result[0] if len(result) == 1 else result # if PointCloud has single point return as a Point
|
|
157
153
|
|
|
158
154
|
return other.intersection(self)
|
|
159
155
|
|
|
@@ -213,7 +209,7 @@ class BoundingBox(location.Location):
|
|
|
213
209
|
X, Y, Z = np.where(mask.get_fdata() > threshold)
|
|
214
210
|
h = np.ones(len(X))
|
|
215
211
|
|
|
216
|
-
# array of
|
|
212
|
+
# array of homogeneous physical nonzero voxel coordinates
|
|
217
213
|
coords = np.dot(mask.affine, np.vstack((X, Y, Z, h)))[:3, :].T
|
|
218
214
|
minpoint = [min(self.minpoint[i], self.maxpoint[i]) for i in range(3)]
|
|
219
215
|
maxpoint = [max(self.minpoint[i], self.maxpoint[i]) for i in range(3)]
|
|
@@ -240,8 +236,8 @@ class BoundingBox(location.Location):
|
|
|
240
236
|
)
|
|
241
237
|
else:
|
|
242
238
|
return self._intersect_bbox(
|
|
243
|
-
|
|
244
|
-
.
|
|
239
|
+
pointcloud
|
|
240
|
+
.PointCloud(XYZ, space=self.space, sigma_mm=voxel_size.max())
|
|
245
241
|
.boundingbox
|
|
246
242
|
)
|
|
247
243
|
|
|
@@ -259,7 +255,7 @@ class BoundingBox(location.Location):
|
|
|
259
255
|
@property
|
|
260
256
|
def corners(self):
|
|
261
257
|
"""
|
|
262
|
-
Returns all 8 corners of the box as a
|
|
258
|
+
Returns all 8 corners of the box as a pointcloud.
|
|
263
259
|
|
|
264
260
|
Note
|
|
265
261
|
----
|
|
@@ -279,7 +275,7 @@ class BoundingBox(location.Location):
|
|
|
279
275
|
TODO: deal with sigma. Currently, returns the mean of min and max point.
|
|
280
276
|
"""
|
|
281
277
|
xs, ys, zs = zip(self.minpoint, self.maxpoint)
|
|
282
|
-
return
|
|
278
|
+
return pointcloud.PointCloud(
|
|
283
279
|
coordinates=[[x, y, z] for x, y, z in product(xs, ys, zs)],
|
|
284
280
|
space=self.space,
|
|
285
281
|
sigma_mm=np.mean([self.minpoint.sigma, self.maxpoint.sigma])
|
|
@@ -360,7 +356,7 @@ class BoundingBox(location.Location):
|
|
|
360
356
|
x1, y1, z1 = self.maxpoint
|
|
361
357
|
|
|
362
358
|
# set of 8 corner points in source space
|
|
363
|
-
corners1 =
|
|
359
|
+
corners1 = pointcloud.PointCloud(
|
|
364
360
|
[
|
|
365
361
|
(x0, y0, z0),
|
|
366
362
|
(x0, y0, z1),
|
|
@@ -417,14 +413,15 @@ class BoundingBox(location.Location):
|
|
|
417
413
|
return super().__hash__()
|
|
418
414
|
|
|
419
415
|
|
|
420
|
-
def _determine_bounds(
|
|
416
|
+
def _determine_bounds(masked_array: np.ndarray, background: float = 0.0):
|
|
421
417
|
"""
|
|
422
|
-
Bounding box of nonzero values in a 3D array.
|
|
418
|
+
Bounding box of nonzero (background) values in a 3D array.
|
|
419
|
+
|
|
423
420
|
https://stackoverflow.com/questions/31400769/bounding-box-of-numpy-array
|
|
424
421
|
"""
|
|
425
|
-
x = np.any(
|
|
426
|
-
y = np.any(
|
|
427
|
-
z = np.any(
|
|
422
|
+
x = np.any(masked_array != background, axis=(1, 2))
|
|
423
|
+
y = np.any(masked_array != background, axis=(0, 2))
|
|
424
|
+
z = np.any(masked_array != background, axis=(0, 1))
|
|
428
425
|
nzx, nzy, nzz = [np.where(v) for v in (x, y, z)]
|
|
429
426
|
if any(len(nz[0]) == 0 for nz in [nzx, nzy, nzz]):
|
|
430
427
|
# empty array
|
|
@@ -435,15 +432,19 @@ def _determine_bounds(array: np.ndarray, threshold=0):
|
|
|
435
432
|
return np.array([[xmin, xmax + 1], [ymin, ymax + 1], [zmin, zmax + 1], [1, 1]])
|
|
436
433
|
|
|
437
434
|
|
|
438
|
-
def from_array(
|
|
435
|
+
def from_array(
|
|
436
|
+
array: np.ndarray,
|
|
437
|
+
background: Union[int, float] = 0.0,
|
|
438
|
+
space: "Space" = None
|
|
439
|
+
) -> BoundingBox:
|
|
439
440
|
"""
|
|
440
|
-
Find the bounding box of
|
|
441
|
+
Find the bounding box of non-background values for any 3D array.
|
|
441
442
|
|
|
442
443
|
Parameters
|
|
443
444
|
----------
|
|
444
|
-
array
|
|
445
|
-
|
|
446
|
-
space
|
|
445
|
+
array: np.ndarray
|
|
446
|
+
background: int or float, default: 0.0
|
|
447
|
+
space: Space, default: None
|
|
447
448
|
"""
|
|
448
|
-
bounds = _determine_bounds(array,
|
|
449
|
-
return BoundingBox(bounds[:3, 0], bounds[:3, 1], space=space)
|
|
449
|
+
bounds = _determine_bounds(array, background)
|
|
450
|
+
return BoundingBox(point1=bounds[:3, 0], point2=bounds[:3, 1], space=space)
|
siibra/locations/location.py
CHANGED
siibra/locations/point.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2025
|
|
2
2
|
# Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
|
|
3
3
|
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
"""Singular coordinate defined on a space, possibly with an uncertainty."""
|
|
16
16
|
|
|
17
|
-
from . import location, boundingbox,
|
|
17
|
+
from . import location, boundingbox, pointcloud
|
|
18
18
|
|
|
19
19
|
from ..commons import logger
|
|
20
20
|
from ..retrieval.requests import HttpRequest
|
|
@@ -108,14 +108,14 @@ class Point(location.Location):
|
|
|
108
108
|
|
|
109
109
|
@property
|
|
110
110
|
def homogeneous(self):
|
|
111
|
-
"""The
|
|
111
|
+
"""The homogeneous coordinate of this point as a 4-tuple,
|
|
112
112
|
obtained by appending '1' to the original 3-tuple."""
|
|
113
113
|
return np.atleast_2d(self.coordinate + (1,))
|
|
114
114
|
|
|
115
115
|
def intersection(self, other: location.Location) -> "Point":
|
|
116
116
|
if isinstance(other, Point):
|
|
117
117
|
return self if self == other else None
|
|
118
|
-
elif isinstance(other,
|
|
118
|
+
elif isinstance(other, pointcloud.PointCloud):
|
|
119
119
|
return self if self in other else None
|
|
120
120
|
else:
|
|
121
121
|
return self if other.intersection(self) else None
|
|
@@ -157,7 +157,7 @@ class Point(location.Location):
|
|
|
157
157
|
return self.sigma**3 * np.pi * 4. / 3.
|
|
158
158
|
|
|
159
159
|
def __sub__(self, other):
|
|
160
|
-
"""
|
|
160
|
+
"""Subtract the coordinates of two points to get
|
|
161
161
|
a new point representing the offset vector. Alternatively,
|
|
162
162
|
subtract an integer from the all coordinates of this point
|
|
163
163
|
to create a new one.
|
|
@@ -190,8 +190,8 @@ class Point(location.Location):
|
|
|
190
190
|
return super().__hash__()
|
|
191
191
|
|
|
192
192
|
def __eq__(self, other: 'Point'):
|
|
193
|
-
if isinstance(other,
|
|
194
|
-
return other == self # implemented at
|
|
193
|
+
if isinstance(other, pointcloud.PointCloud):
|
|
194
|
+
return other == self # implemented at pointcloud
|
|
195
195
|
if not isinstance(other, Point):
|
|
196
196
|
return False
|
|
197
197
|
o = other if self.space is None else other.warp(self.space)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2025
|
|
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");
|
|
@@ -18,7 +18,7 @@ from . import location, point, boundingbox as _boundingbox
|
|
|
18
18
|
|
|
19
19
|
from ..retrieval.requests import HttpRequest
|
|
20
20
|
from ..commons import logger
|
|
21
|
-
from ..exceptions import SpaceWarpingFailedError
|
|
21
|
+
from ..exceptions import SpaceWarpingFailedError, EmptyPointCloudError
|
|
22
22
|
|
|
23
23
|
from typing import List, Union, Tuple
|
|
24
24
|
import numbers
|
|
@@ -32,13 +32,13 @@ except ImportError:
|
|
|
32
32
|
_HAS_HDBSCAN = False
|
|
33
33
|
logger.warning(
|
|
34
34
|
f"HDBSCAN is not available with your version {sklearn.__version__} of sckit-learn."
|
|
35
|
-
"`
|
|
35
|
+
"`PointCloud.find_clusters()` will not be available."
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def from_points(points: List["point.Point"], newlabels: List[Union[int, float, tuple]] = None) -> "
|
|
39
|
+
def from_points(points: List["point.Point"], newlabels: List[Union[int, float, tuple]] = None) -> "PointCloud":
|
|
40
40
|
"""
|
|
41
|
-
Create a
|
|
41
|
+
Create a PointCloud from an iterable of Points.
|
|
42
42
|
|
|
43
43
|
Parameters
|
|
44
44
|
----------
|
|
@@ -48,16 +48,17 @@ def from_points(points: List["point.Point"], newlabels: List[Union[int, float, t
|
|
|
48
48
|
|
|
49
49
|
Returns
|
|
50
50
|
-------
|
|
51
|
-
|
|
51
|
+
PointCloud
|
|
52
52
|
"""
|
|
53
53
|
if len(points) == 0:
|
|
54
|
-
|
|
54
|
+
raise EmptyPointCloudError("Cannot create a PointCloud without any points.")
|
|
55
|
+
|
|
55
56
|
spaces = {p.space for p in points}
|
|
56
|
-
assert len(spaces) == 1, f"
|
|
57
|
+
assert len(spaces) == 1, f"PointCloud can only be constructed with points from the same space.\n{spaces}"
|
|
57
58
|
coords, sigmas, labels = zip(*((p.coordinate, p.sigma, p.label) for p in points))
|
|
58
59
|
if all(lb is None for lb in set(labels)):
|
|
59
60
|
labels = None
|
|
60
|
-
return
|
|
61
|
+
return PointCloud(
|
|
61
62
|
coordinates=coords,
|
|
62
63
|
space=next(iter(spaces)),
|
|
63
64
|
sigma_mm=sigmas,
|
|
@@ -65,7 +66,7 @@ def from_points(points: List["point.Point"], newlabels: List[Union[int, float, t
|
|
|
65
66
|
)
|
|
66
67
|
|
|
67
68
|
|
|
68
|
-
class
|
|
69
|
+
class PointCloud(location.Location):
|
|
69
70
|
"""A set of 3D points in the same reference space,
|
|
70
71
|
defined by a list of coordinates."""
|
|
71
72
|
|
|
@@ -91,6 +92,9 @@ class PointSet(location.Location):
|
|
|
91
92
|
"""
|
|
92
93
|
location.Location.__init__(self, space)
|
|
93
94
|
|
|
95
|
+
if len(coordinates) == 0:
|
|
96
|
+
raise EmptyPointCloudError(f"Cannot create a {self.__class__.__name__} without any coordinates.")
|
|
97
|
+
|
|
94
98
|
self._coordinates = coordinates
|
|
95
99
|
if not isinstance(coordinates, np.ndarray):
|
|
96
100
|
self._coordinates = np.array(self._coordinates).reshape((-1, 3))
|
|
@@ -113,21 +117,16 @@ class PointSet(location.Location):
|
|
|
113
117
|
NOTE: The affine matrix of the image must be set to warp voxels
|
|
114
118
|
coordinates into the reference space of this Bounding Box.
|
|
115
119
|
"""
|
|
116
|
-
if not isinstance(other, (point.Point,
|
|
120
|
+
if not isinstance(other, (point.Point, PointCloud, _boundingbox.BoundingBox)):
|
|
117
121
|
return other.intersection(self)
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
if isinstance(other, PointCloud):
|
|
124
|
+
intersecting_points = [p for p in self if p.coordinate in other.coordinates]
|
|
125
|
+
else:
|
|
126
|
+
intersecting_points = [p for p in self if p.intersects(other)]
|
|
127
|
+
if len(intersecting_points) == 0:
|
|
121
128
|
return None
|
|
122
|
-
|
|
123
|
-
labels = None if self.labels is None else [self.labels[i] for i in ids]
|
|
124
|
-
sigma = [p.sigma for p in points]
|
|
125
|
-
intersection = PointSet(
|
|
126
|
-
points,
|
|
127
|
-
space=self.space,
|
|
128
|
-
sigma_mm=sigma,
|
|
129
|
-
labels=labels
|
|
130
|
-
)
|
|
129
|
+
intersection = from_points(intersecting_points)
|
|
131
130
|
return intersection[0] if len(intersection) == 1 else intersection
|
|
132
131
|
|
|
133
132
|
@property
|
|
@@ -186,7 +185,7 @@ class PointSet(location.Location):
|
|
|
186
185
|
return self.__class__(coordinates=tuple(tgt_points), space=spaceobj, labels=self.labels)
|
|
187
186
|
|
|
188
187
|
def transform(self, affine: np.ndarray, space=None):
|
|
189
|
-
"""Returns a new
|
|
188
|
+
"""Returns a new PointCloud obtained by transforming the
|
|
190
189
|
coordinates of this one with the given affine matrix.
|
|
191
190
|
|
|
192
191
|
Parameters
|
|
@@ -207,7 +206,7 @@ class PointSet(location.Location):
|
|
|
207
206
|
def __getitem__(self, index: int):
|
|
208
207
|
if (abs(index) >= self.__len__()):
|
|
209
208
|
raise IndexError(
|
|
210
|
-
f"
|
|
209
|
+
f"pointcloud with {self.__len__()} points "
|
|
211
210
|
f"cannot be accessed with index {index}."
|
|
212
211
|
)
|
|
213
212
|
return point.Point(
|
|
@@ -229,10 +228,10 @@ class PointSet(location.Location):
|
|
|
229
228
|
for i in range(len(self))
|
|
230
229
|
)
|
|
231
230
|
|
|
232
|
-
def __eq__(self, other: '
|
|
231
|
+
def __eq__(self, other: 'PointCloud'):
|
|
233
232
|
if isinstance(other, point.Point):
|
|
234
233
|
return len(self) == 1 and self[0] == other
|
|
235
|
-
if not isinstance(other,
|
|
234
|
+
if not isinstance(other, PointCloud):
|
|
236
235
|
return False
|
|
237
236
|
return list(self) == list(other)
|
|
238
237
|
|
|
@@ -240,7 +239,7 @@ class PointSet(location.Location):
|
|
|
240
239
|
return super().__hash__()
|
|
241
240
|
|
|
242
241
|
def __len__(self):
|
|
243
|
-
"""The number of points in this
|
|
242
|
+
"""The number of points in this PointCloud."""
|
|
244
243
|
return self.coordinates.shape[0]
|
|
245
244
|
|
|
246
245
|
def __str__(self):
|
|
@@ -249,9 +248,13 @@ class PointSet(location.Location):
|
|
|
249
248
|
@property
|
|
250
249
|
def boundingbox(self):
|
|
251
250
|
"""
|
|
252
|
-
Return the bounding box of these points
|
|
251
|
+
Return the bounding box of these points, or None in the
|
|
252
|
+
special case of an empty PointCloud.
|
|
253
253
|
"""
|
|
254
|
+
if len(self.coordinates) == 0:
|
|
255
|
+
return None
|
|
254
256
|
coords = self.coordinates
|
|
257
|
+
# TODO this needs a more precise treatment of the sigmas
|
|
255
258
|
sigma_min = max(self.sigma[i] for i in coords.argmin(0))
|
|
256
259
|
sigma_max = max(self.sigma[i] for i in coords.argmax(0))
|
|
257
260
|
return _boundingbox.BoundingBox(
|
|
@@ -292,8 +295,8 @@ class PointSet(location.Location):
|
|
|
292
295
|
|
|
293
296
|
Parameters
|
|
294
297
|
----------
|
|
295
|
-
min_fraction: min cluster size as a fraction of total points in the
|
|
296
|
-
max_fraction: max cluster size as a fraction of total points in the
|
|
298
|
+
min_fraction: min cluster size as a fraction of total points in the PointCloud
|
|
299
|
+
max_fraction: max cluster size as a fraction of total points in the PointCloud
|
|
297
300
|
|
|
298
301
|
Returns
|
|
299
302
|
-------
|
|
@@ -302,7 +305,7 @@ class PointSet(location.Location):
|
|
|
302
305
|
|
|
303
306
|
Note
|
|
304
307
|
----
|
|
305
|
-
Replaces the labels of the
|
|
308
|
+
Replaces the labels of the PointCloud instance with these labels.
|
|
306
309
|
|
|
307
310
|
Raises
|
|
308
311
|
------
|
|
@@ -312,7 +315,7 @@ class PointSet(location.Location):
|
|
|
312
315
|
if not _HAS_HDBSCAN:
|
|
313
316
|
raise RuntimeError(
|
|
314
317
|
f"HDBSCAN is not available with your version {sklearn.__version__} "
|
|
315
|
-
"of sckit-learn. `
|
|
318
|
+
"of sckit-learn. `PointCloud.find_clusters()` will not be available."
|
|
316
319
|
)
|
|
317
320
|
points = np.array(self.as_list())
|
|
318
321
|
N = points.shape[0]
|
|
@@ -322,7 +325,7 @@ class PointSet(location.Location):
|
|
|
322
325
|
)
|
|
323
326
|
if self.labels is not None:
|
|
324
327
|
logger.warning(
|
|
325
|
-
"Existing labels of
|
|
328
|
+
"Existing labels of PointCloud will be overwritten with cluster labels."
|
|
326
329
|
)
|
|
327
330
|
self.labels = clustering.fit_predict(points)
|
|
328
331
|
return self.labels
|
siibra/retrieval/__init__.py
CHANGED
siibra/retrieval/cache.py
CHANGED
siibra/retrieval/datasets.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2025
|
|
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");
|
|
@@ -319,12 +319,12 @@ class EbrainsV3Dataset(EbrainsBaseDataset):
|
|
|
319
319
|
@property
|
|
320
320
|
def contributors(self):
|
|
321
321
|
if self._contributers is None:
|
|
322
|
-
|
|
322
|
+
contributors = {}
|
|
323
323
|
for version_id in self.version_ids:
|
|
324
|
-
|
|
324
|
+
contributors.update(
|
|
325
325
|
{c['@id']: c for c in EbrainsV3DatasetVersion(version_id).contributors}
|
|
326
326
|
)
|
|
327
|
-
self._contributers = list(
|
|
327
|
+
self._contributers = list(contributors.values())
|
|
328
328
|
return self._contributers
|
|
329
329
|
|
|
330
330
|
@property
|