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
|
@@ -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");
|
|
@@ -18,6 +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, EmptyPointCloudError
|
|
21
22
|
|
|
22
23
|
from typing import List, Union, Tuple
|
|
23
24
|
import numbers
|
|
@@ -31,13 +32,13 @@ except ImportError:
|
|
|
31
32
|
_HAS_HDBSCAN = False
|
|
32
33
|
logger.warning(
|
|
33
34
|
f"HDBSCAN is not available with your version {sklearn.__version__} of sckit-learn."
|
|
34
|
-
"`
|
|
35
|
+
"`PointCloud.find_clusters()` will not be avaiable."
|
|
35
36
|
)
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
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":
|
|
39
40
|
"""
|
|
40
|
-
Create a
|
|
41
|
+
Create a PointCloud from an iterable of Points.
|
|
41
42
|
|
|
42
43
|
Parameters
|
|
43
44
|
----------
|
|
@@ -47,16 +48,17 @@ def from_points(points: List["point.Point"], newlabels: List[Union[int, float, t
|
|
|
47
48
|
|
|
48
49
|
Returns
|
|
49
50
|
-------
|
|
50
|
-
|
|
51
|
+
PointCloud
|
|
51
52
|
"""
|
|
52
53
|
if len(points) == 0:
|
|
53
|
-
|
|
54
|
+
raise EmptyPointCloudError("Cannot create a PointCloud without any points.")
|
|
55
|
+
|
|
54
56
|
spaces = {p.space for p in points}
|
|
55
|
-
assert len(spaces) == 1, f"
|
|
57
|
+
assert len(spaces) == 1, f"PointCloud can only be constructed with points from the same space.\n{spaces}"
|
|
56
58
|
coords, sigmas, labels = zip(*((p.coordinate, p.sigma, p.label) for p in points))
|
|
57
59
|
if all(lb is None for lb in set(labels)):
|
|
58
60
|
labels = None
|
|
59
|
-
return
|
|
61
|
+
return PointCloud(
|
|
60
62
|
coordinates=coords,
|
|
61
63
|
space=next(iter(spaces)),
|
|
62
64
|
sigma_mm=sigmas,
|
|
@@ -64,7 +66,7 @@ def from_points(points: List["point.Point"], newlabels: List[Union[int, float, t
|
|
|
64
66
|
)
|
|
65
67
|
|
|
66
68
|
|
|
67
|
-
class
|
|
69
|
+
class PointCloud(location.Location):
|
|
68
70
|
"""A set of 3D points in the same reference space,
|
|
69
71
|
defined by a list of coordinates."""
|
|
70
72
|
|
|
@@ -90,6 +92,9 @@ class PointSet(location.Location):
|
|
|
90
92
|
"""
|
|
91
93
|
location.Location.__init__(self, space)
|
|
92
94
|
|
|
95
|
+
if len(coordinates) == 0:
|
|
96
|
+
raise EmptyPointCloudError(f"Cannot create a {self.__class__.__name__} without any coordinates.")
|
|
97
|
+
|
|
93
98
|
self._coordinates = coordinates
|
|
94
99
|
if not isinstance(coordinates, np.ndarray):
|
|
95
100
|
self._coordinates = np.array(self._coordinates).reshape((-1, 3))
|
|
@@ -112,7 +117,7 @@ class PointSet(location.Location):
|
|
|
112
117
|
NOTE: The affine matrix of the image must be set to warp voxels
|
|
113
118
|
coordinates into the reference space of this Bounding Box.
|
|
114
119
|
"""
|
|
115
|
-
if not isinstance(other, (point.Point,
|
|
120
|
+
if not isinstance(other, (point.Point, PointCloud, _boundingbox.BoundingBox)):
|
|
116
121
|
return other.intersection(self)
|
|
117
122
|
|
|
118
123
|
intersections = [(i, p) for i, p in enumerate(self) if p.intersects(other)]
|
|
@@ -121,7 +126,7 @@ class PointSet(location.Location):
|
|
|
121
126
|
ids, points = zip(*intersections)
|
|
122
127
|
labels = None if self.labels is None else [self.labels[i] for i in ids]
|
|
123
128
|
sigma = [p.sigma for p in points]
|
|
124
|
-
intersection =
|
|
129
|
+
intersection = PointCloud(
|
|
125
130
|
points,
|
|
126
131
|
space=self.space,
|
|
127
132
|
sigma_mm=sigma,
|
|
@@ -149,7 +154,7 @@ class PointSet(location.Location):
|
|
|
149
154
|
if spaceobj == self.space:
|
|
150
155
|
return self
|
|
151
156
|
if any(_ not in location.Location.SPACEWARP_IDS for _ in [self.space.id, spaceobj.id]):
|
|
152
|
-
raise
|
|
157
|
+
raise SpaceWarpingFailedError(
|
|
153
158
|
f"Cannot convert coordinates between {self.space.id} and {spaceobj.id}"
|
|
154
159
|
)
|
|
155
160
|
|
|
@@ -178,10 +183,14 @@ class PointSet(location.Location):
|
|
|
178
183
|
).data
|
|
179
184
|
tgt_points.extend(list(response["target_points"]))
|
|
180
185
|
|
|
186
|
+
# TODO: consider using np.isnan(np.dot(arr, arr)). see https://stackoverflow.com/a/45011547
|
|
187
|
+
if np.any(np.isnan(response['target_points'])):
|
|
188
|
+
raise SpaceWarpingFailedError(f'Warping {str(self)} to {spaceobj.name} resulted in NaN')
|
|
189
|
+
|
|
181
190
|
return self.__class__(coordinates=tuple(tgt_points), space=spaceobj, labels=self.labels)
|
|
182
191
|
|
|
183
192
|
def transform(self, affine: np.ndarray, space=None):
|
|
184
|
-
"""Returns a new
|
|
193
|
+
"""Returns a new PointCloud obtained by transforming the
|
|
185
194
|
coordinates of this one with the given affine matrix.
|
|
186
195
|
|
|
187
196
|
Parameters
|
|
@@ -202,7 +211,7 @@ class PointSet(location.Location):
|
|
|
202
211
|
def __getitem__(self, index: int):
|
|
203
212
|
if (abs(index) >= self.__len__()):
|
|
204
213
|
raise IndexError(
|
|
205
|
-
f"
|
|
214
|
+
f"pointcloud with {self.__len__()} points "
|
|
206
215
|
f"cannot be accessed with index {index}."
|
|
207
216
|
)
|
|
208
217
|
return point.Point(
|
|
@@ -224,10 +233,10 @@ class PointSet(location.Location):
|
|
|
224
233
|
for i in range(len(self))
|
|
225
234
|
)
|
|
226
235
|
|
|
227
|
-
def __eq__(self, other: '
|
|
236
|
+
def __eq__(self, other: 'PointCloud'):
|
|
228
237
|
if isinstance(other, point.Point):
|
|
229
238
|
return len(self) == 1 and self[0] == other
|
|
230
|
-
if not isinstance(other,
|
|
239
|
+
if not isinstance(other, PointCloud):
|
|
231
240
|
return False
|
|
232
241
|
return list(self) == list(other)
|
|
233
242
|
|
|
@@ -235,7 +244,7 @@ class PointSet(location.Location):
|
|
|
235
244
|
return super().__hash__()
|
|
236
245
|
|
|
237
246
|
def __len__(self):
|
|
238
|
-
"""The number of points in this
|
|
247
|
+
"""The number of points in this PointCloud."""
|
|
239
248
|
return self.coordinates.shape[0]
|
|
240
249
|
|
|
241
250
|
def __str__(self):
|
|
@@ -243,15 +252,19 @@ class PointSet(location.Location):
|
|
|
243
252
|
|
|
244
253
|
@property
|
|
245
254
|
def boundingbox(self):
|
|
246
|
-
"""Return the bounding box of these points.
|
|
247
|
-
TODO revisit the numerical margin of 1e-6, should not be necessary.
|
|
248
255
|
"""
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
256
|
+
Return the bounding box of these points, or None in the
|
|
257
|
+
special case of an empty PointCloud.
|
|
258
|
+
"""
|
|
259
|
+
if len(self.coordinates) == 0:
|
|
260
|
+
return None
|
|
261
|
+
coords = self.coordinates
|
|
262
|
+
# TODO this needs a more precise treatment of the sigmas
|
|
263
|
+
sigma_min = max(self.sigma[i] for i in coords.argmin(0))
|
|
264
|
+
sigma_max = max(self.sigma[i] for i in coords.argmax(0))
|
|
252
265
|
return _boundingbox.BoundingBox(
|
|
253
|
-
point1=
|
|
254
|
-
point2=
|
|
266
|
+
point1=coords.min(0),
|
|
267
|
+
point2=coords.max(0),
|
|
255
268
|
space=self.space,
|
|
256
269
|
sigma_mm=[sigma_min, sigma_max]
|
|
257
270
|
)
|
|
@@ -276,11 +289,38 @@ class PointSet(location.Location):
|
|
|
276
289
|
"""Access the list of 3D point as an Nx4 array of homogeneous coordinates."""
|
|
277
290
|
return np.c_[self.coordinates, np.ones(len(self))]
|
|
278
291
|
|
|
279
|
-
def find_clusters(
|
|
292
|
+
def find_clusters(
|
|
293
|
+
self,
|
|
294
|
+
min_fraction: float = 1 / 200,
|
|
295
|
+
max_fraction: float = 1 / 8
|
|
296
|
+
) -> List[int]:
|
|
297
|
+
"""
|
|
298
|
+
Find clusters using HDBSCAN (https://dl.acm.org/doi/10.1145/2733381)
|
|
299
|
+
implementation of scikit-learn (https://dl.acm.org/doi/10.5555/1953048.2078195).
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
min_fraction: min cluster size as a fraction of total points in the PointCloud
|
|
304
|
+
max_fraction: max cluster size as a fraction of total points in the PointCloud
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
List[int]
|
|
309
|
+
Returns the cluster labels found by skilearn.cluster.HDBSCAN.
|
|
310
|
+
|
|
311
|
+
Note
|
|
312
|
+
----
|
|
313
|
+
Replaces the labels of the PointCloud instance with these labels.
|
|
314
|
+
|
|
315
|
+
Raises
|
|
316
|
+
------
|
|
317
|
+
RuntimeError
|
|
318
|
+
If a sklearn version without HDBSCAN is installed.
|
|
319
|
+
"""
|
|
280
320
|
if not _HAS_HDBSCAN:
|
|
281
321
|
raise RuntimeError(
|
|
282
322
|
f"HDBSCAN is not available with your version {sklearn.__version__} "
|
|
283
|
-
"of sckit-learn. `
|
|
323
|
+
"of sckit-learn. `PointCloud.find_clusters()` will not be avaiable."
|
|
284
324
|
)
|
|
285
325
|
points = np.array(self.as_list())
|
|
286
326
|
N = points.shape[0]
|
|
@@ -289,7 +329,9 @@ class PointSet(location.Location):
|
|
|
289
329
|
max_cluster_size=int(N * max_fraction),
|
|
290
330
|
)
|
|
291
331
|
if self.labels is not None:
|
|
292
|
-
logger.
|
|
332
|
+
logger.warning(
|
|
333
|
+
"Existing labels of PointCloud will be overwritten with cluster labels."
|
|
334
|
+
)
|
|
293
335
|
self.labels = clustering.fit_predict(points)
|
|
294
336
|
return self.labels
|
|
295
337
|
|
siibra/retrieval/__init__.py
CHANGED
siibra/retrieval/cache.py
CHANGED
siibra/retrieval/datasets.py
CHANGED
siibra/retrieval/repositories.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,8 @@ from .requests import (
|
|
|
19
19
|
EbrainsRequest,
|
|
20
20
|
SiibraHttpRequestError,
|
|
21
21
|
find_suitiable_decoder,
|
|
22
|
-
DECODERS
|
|
22
|
+
DECODERS,
|
|
23
|
+
FileLoader
|
|
23
24
|
)
|
|
24
25
|
from .cache import CACHE
|
|
25
26
|
|
|
@@ -124,34 +125,16 @@ class LocalFileRepository(RepositoryConnector):
|
|
|
124
125
|
self._folder = pathlib.Path(folder)
|
|
125
126
|
assert pathlib.Path.is_dir(self._folder)
|
|
126
127
|
|
|
127
|
-
def _build_url(self, folder: str, filename: str):
|
|
128
|
-
return pathlib.Path.joinpath(self._folder, folder, filename)
|
|
129
|
-
|
|
130
|
-
class FileLoader:
|
|
131
|
-
"""
|
|
132
|
-
Just a loads a local file, but mimics the behaviour
|
|
133
|
-
of cached http requests used in other connectors.
|
|
134
|
-
"""
|
|
135
|
-
def __init__(self, file_url, decode_func):
|
|
136
|
-
self.url = file_url
|
|
137
|
-
self.func = decode_func
|
|
138
|
-
self.cached = True
|
|
139
|
-
|
|
140
|
-
@property
|
|
141
|
-
def data(self):
|
|
142
|
-
with open(self.url, 'rb') as f:
|
|
143
|
-
return self.func(f.read())
|
|
128
|
+
def _build_url(self, folder: str, filename: str) -> str:
|
|
129
|
+
return pathlib.Path.joinpath(self._folder, folder, filename).as_posix()
|
|
144
130
|
|
|
145
131
|
def get_loader(self, filename, folder="", decode_func=None):
|
|
146
132
|
"""Get a lazy loader for a file, for loading data
|
|
147
133
|
only once loader.data is accessed."""
|
|
148
|
-
|
|
149
|
-
if
|
|
150
|
-
raise RuntimeError(f"
|
|
151
|
-
|
|
152
|
-
return self.FileLoader(url, lambda b: self._decode_response(b, filename))
|
|
153
|
-
else:
|
|
154
|
-
return self.FileLoader(url, decode_func)
|
|
134
|
+
filepath = self._build_url(folder, filename)
|
|
135
|
+
if not pathlib.Path(filepath).is_file():
|
|
136
|
+
raise RuntimeError(f"No file is found in {filepath}")
|
|
137
|
+
return FileLoader(filepath, decode_func)
|
|
155
138
|
|
|
156
139
|
def search_files(self, folder="", suffix=None, recursive=False):
|
|
157
140
|
results = []
|
|
@@ -165,7 +148,7 @@ class LocalFileRepository(RepositoryConnector):
|
|
|
165
148
|
def __str__(self):
|
|
166
149
|
return f"{self.__class__.__name__} at {self._folder}"
|
|
167
150
|
|
|
168
|
-
def __eq__(self, other):
|
|
151
|
+
def __eq__(self, other: "LocalFileRepository"):
|
|
169
152
|
return self._folder == other._folder
|
|
170
153
|
|
|
171
154
|
|
siibra/retrieval/requests.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");
|
|
@@ -117,8 +117,7 @@ def find_suitiable_decoder(url: str) -> Callable:
|
|
|
117
117
|
suitable_decoders = [
|
|
118
118
|
dec for sfx, dec in DECODERS.items() if urlpath.endswith(sfx)
|
|
119
119
|
]
|
|
120
|
-
if len(suitable_decoders)
|
|
121
|
-
assert len(suitable_decoders) == 1
|
|
120
|
+
if len(suitable_decoders) == 1:
|
|
122
121
|
return suitable_decoders[0]
|
|
123
122
|
else:
|
|
124
123
|
return None
|
|
@@ -270,6 +269,24 @@ class HttpRequest:
|
|
|
270
269
|
return self.get()
|
|
271
270
|
|
|
272
271
|
|
|
272
|
+
class FileLoader(HttpRequest):
|
|
273
|
+
"""
|
|
274
|
+
Just a loads a local file, but mimics the behaviour
|
|
275
|
+
of cached http requests used in other connectors.
|
|
276
|
+
"""
|
|
277
|
+
def __init__(self, filepath, func=None):
|
|
278
|
+
HttpRequest.__init__(
|
|
279
|
+
self, filepath, refresh=False,
|
|
280
|
+
func=func or find_suitiable_decoder(filepath)
|
|
281
|
+
)
|
|
282
|
+
self.cachefile = filepath
|
|
283
|
+
|
|
284
|
+
def _retrieve(self, **kwargs):
|
|
285
|
+
if kwargs:
|
|
286
|
+
logger.info(f"Keywords {list(kwargs.keys())} are supplied but won't be used.")
|
|
287
|
+
assert os.path.isfile(self.cachefile)
|
|
288
|
+
|
|
289
|
+
|
|
273
290
|
class ZipfileRequest(HttpRequest):
|
|
274
291
|
def __init__(self, url, filename, func=None, refresh=False):
|
|
275
292
|
HttpRequest.__init__(
|
siibra/vocabularies/__init__.py
CHANGED
siibra/volumes/__init__.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,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
from .parcellationmap import Map
|
|
18
18
|
from .providers import provider
|
|
19
|
-
from .volume import from_array, from_file,
|
|
19
|
+
from .volume import from_array, from_file, from_pointcloud, from_nifti, Volume
|
|
20
20
|
|
|
21
21
|
from ..commons import logger
|
|
22
22
|
from typing import List, Union
|