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/retrieval/repositories.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");
|
|
@@ -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
|
|
|
@@ -213,7 +196,7 @@ class GithubConnector(RepositoryConnector):
|
|
|
213
196
|
if len(matched_reftags) == 1:
|
|
214
197
|
self._want_commit_cached = matched_reftags[0]["commit"]
|
|
215
198
|
else:
|
|
216
|
-
raise RuntimeError(f"Found {len(matched_reftags)}
|
|
199
|
+
raise RuntimeError(f"Found {len(matched_reftags)} matches to {reftag}")
|
|
217
200
|
self._tag_checked = True
|
|
218
201
|
except Exception:
|
|
219
202
|
logger.warning("Could not connect to GitHub repository.", exc_info=1)
|
|
@@ -275,7 +258,7 @@ class GitlabConnector(RepositoryConnector):
|
|
|
275
258
|
n.b. only archive_mode should only be set for trusted domains. Extraction of archive can result in files created outside the path
|
|
276
259
|
see https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall
|
|
277
260
|
"""
|
|
278
|
-
# TODO: the query builder needs to check
|
|
261
|
+
# TODO: the query builder needs to check whether the reftag is a branch, and then not cache.
|
|
279
262
|
assert server.startswith("http")
|
|
280
263
|
RepositoryConnector.__init__(
|
|
281
264
|
self, base_url=f"{server}/api/v4/projects/{project}/repository"
|
|
@@ -601,7 +584,7 @@ class EbrainsPublicDatasetConnector(RepositoryConnector):
|
|
|
601
584
|
Part of dataset title as an alternative dataset specification (will ignore dataset_id then)
|
|
602
585
|
in_progress: bool (default:False)
|
|
603
586
|
If true, will request datasets that are still under curation.
|
|
604
|
-
Will only work when
|
|
587
|
+
Will only work when authenticated with an appropriately privileged
|
|
605
588
|
user account.
|
|
606
589
|
"""
|
|
607
590
|
self.dataset_id = dataset_id
|
siibra/retrieval/requests.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");
|
|
@@ -94,8 +94,8 @@ DECODERS = {
|
|
|
94
94
|
def find_suitiable_decoder(url: str) -> Callable:
|
|
95
95
|
"""
|
|
96
96
|
By supplying a url or a filename, obtain a suitable decoder function
|
|
97
|
-
for siibra to digest based on
|
|
98
|
-
gzip
|
|
97
|
+
for siibra to digest based on predefined DECODERS. An extra layer of
|
|
98
|
+
gzip decompressor automatically added for gzipped files.
|
|
99
99
|
|
|
100
100
|
Parameters
|
|
101
101
|
----------
|
|
@@ -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
|
|
@@ -194,7 +193,7 @@ class HttpRequest:
|
|
|
194
193
|
"""
|
|
195
194
|
Populates the file cache with the data from http if required.
|
|
196
195
|
noop if 1/ data is already cached and 2/ refresh flag not set
|
|
197
|
-
The caller should load the cachefile after _retrieve
|
|
196
|
+
The caller should load the cachefile after _retrieve successfully executes
|
|
198
197
|
"""
|
|
199
198
|
if self.cached and not self.refresh:
|
|
200
199
|
return
|
|
@@ -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__(
|
|
@@ -440,9 +457,9 @@ class EbrainsRequest(HttpRequest):
|
|
|
440
457
|
|
|
441
458
|
if resp.status_code == 200:
|
|
442
459
|
json_resp = resp.json()
|
|
443
|
-
logger.debug("Device flow
|
|
460
|
+
logger.debug("Device flow successful:", json_resp)
|
|
444
461
|
cls._KG_API_TOKEN = json_resp.get("access_token")
|
|
445
|
-
print("ebrains token
|
|
462
|
+
print("ebrains token successfully set.")
|
|
446
463
|
break
|
|
447
464
|
|
|
448
465
|
if resp.status_code == 400:
|
siibra/vocabularies/__init__.py
CHANGED
siibra/volumes/__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,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
|
|
@@ -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");
|
|
@@ -32,8 +32,7 @@ from ..commons import (
|
|
|
32
32
|
generate_uuid
|
|
33
33
|
)
|
|
34
34
|
from ..core import concept, space, parcellation, region as _region
|
|
35
|
-
from ..locations import location, point,
|
|
36
|
-
from ..retrieval import requests
|
|
35
|
+
from ..locations import location, point, pointcloud
|
|
37
36
|
|
|
38
37
|
import numpy as np
|
|
39
38
|
from typing import Union, Dict, List, TYPE_CHECKING, Iterable, Tuple
|
|
@@ -160,6 +159,11 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
160
159
|
logger.warning(f"Non unique indices encountered in {self}: {duplicates}")
|
|
161
160
|
self._affine_cached = None
|
|
162
161
|
|
|
162
|
+
@property
|
|
163
|
+
def key(self):
|
|
164
|
+
_id = self.id
|
|
165
|
+
return create_key(_id[len("siibra-map-v0.0.1"):])
|
|
166
|
+
|
|
163
167
|
@property
|
|
164
168
|
def species(self) -> Species:
|
|
165
169
|
# lazy implementation
|
|
@@ -319,69 +323,27 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
319
323
|
def regions(self):
|
|
320
324
|
return list(self._indices)
|
|
321
325
|
|
|
322
|
-
def
|
|
326
|
+
def get_volume(
|
|
323
327
|
self,
|
|
324
|
-
|
|
328
|
+
region: Union[str, "Region"] = None,
|
|
325
329
|
*,
|
|
326
330
|
index: MapIndex = None,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
):
|
|
330
|
-
"""
|
|
331
|
-
Fetches one particular volume of this parcellation map.
|
|
332
|
-
|
|
333
|
-
If there's only one volume, this is the default, otherwise further
|
|
334
|
-
specification is requested:
|
|
335
|
-
- the volume index,
|
|
336
|
-
- the MapIndex (which results in a regional map being returned)
|
|
337
|
-
|
|
338
|
-
You might also consider fetch_iter() to iterate the volumes, or
|
|
339
|
-
compress() to produce a single-volume parcellation map.
|
|
340
|
-
|
|
341
|
-
Parameters
|
|
342
|
-
----------
|
|
343
|
-
region_or_index: str, Region, MapIndex
|
|
344
|
-
Lazy match the specification.
|
|
345
|
-
index: MapIndex
|
|
346
|
-
Explicit specification of the map index, typically resulting
|
|
347
|
-
in a regional map (mask or statistical map) to be returned.
|
|
348
|
-
Note that supplying 'region' will result in retrieving the map index of that region
|
|
349
|
-
automatically.
|
|
350
|
-
region: str, Region
|
|
351
|
-
Specification of a region name, resulting in a regional map
|
|
352
|
-
(mask or statistical map) to be returned.
|
|
353
|
-
**kwargs
|
|
354
|
-
- resolution_mm: resolution in millimeters
|
|
355
|
-
- format: the format of the volume, like "mesh" or "nii"
|
|
356
|
-
- voi: a BoundingBox of interest
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
Note
|
|
360
|
-
----
|
|
361
|
-
Not all keyword arguments are supported for volume formats. Format
|
|
362
|
-
is restricted by available formats (check formats property).
|
|
363
|
-
|
|
364
|
-
Returns
|
|
365
|
-
-------
|
|
366
|
-
An image or mesh
|
|
367
|
-
"""
|
|
331
|
+
**kwargs,
|
|
332
|
+
) -> Union[_volume.Volume, _volume.FilteredVolume, _volume.Subvolume]:
|
|
368
333
|
try:
|
|
369
|
-
length = len([arg for arg in [
|
|
334
|
+
length = len([arg for arg in [region, index] if arg is not None])
|
|
370
335
|
assert length == 1
|
|
371
336
|
except AssertionError:
|
|
372
337
|
if length > 1:
|
|
373
|
-
raise exceptions.ExcessiveArgumentException(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if isinstance(region_or_index, MapIndex):
|
|
377
|
-
index = region_or_index
|
|
378
|
-
|
|
379
|
-
if isinstance(region_or_index, (str, _region.Region)):
|
|
380
|
-
region = region_or_index
|
|
381
|
-
|
|
338
|
+
raise exceptions.ExcessiveArgumentException(
|
|
339
|
+
"One and only one of region or index can be defined for `get_volume`."
|
|
340
|
+
)
|
|
382
341
|
mapindex = None
|
|
383
342
|
if region is not None:
|
|
384
|
-
|
|
343
|
+
try:
|
|
344
|
+
assert isinstance(region, (str, _region.Region))
|
|
345
|
+
except AssertionError:
|
|
346
|
+
raise TypeError(f"Please provide a region name or region instance, not a {type(region)}")
|
|
385
347
|
mapindex = self.get_index(region)
|
|
386
348
|
if index is not None:
|
|
387
349
|
assert isinstance(index, MapIndex)
|
|
@@ -390,19 +352,17 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
390
352
|
if len(self) == 1:
|
|
391
353
|
mapindex = MapIndex(volume=0, label=None)
|
|
392
354
|
elif len(self) > 1:
|
|
355
|
+
assert self.maptype == MapType.LABELLED, f"Cannot merge multiple volumes of map type {self.maptype}. Please specify a region or index."
|
|
393
356
|
logger.info(
|
|
394
357
|
"Map provides multiple volumes and no specification is"
|
|
395
358
|
" provided. Resampling all volumes to the space."
|
|
396
359
|
)
|
|
397
360
|
labels = list(range(len(self.volumes)))
|
|
398
361
|
merged_volume = _volume.merge(self.volumes, labels, **kwargs)
|
|
399
|
-
return merged_volume
|
|
362
|
+
return merged_volume
|
|
400
363
|
else:
|
|
401
364
|
raise exceptions.NoVolumeFound("Map provides no volumes.")
|
|
402
365
|
|
|
403
|
-
if "resolution_mm" in kwargs and kwargs.get("format") is None:
|
|
404
|
-
kwargs["format"] = 'neuroglancer/precomputed'
|
|
405
|
-
|
|
406
366
|
kwargs_fragment = kwargs.pop("fragment", None)
|
|
407
367
|
if kwargs_fragment is not None:
|
|
408
368
|
if (mapindex.fragment is not None) and (kwargs_fragment != mapindex.fragment):
|
|
@@ -418,17 +378,60 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
418
378
|
raise IndexError(
|
|
419
379
|
f"{self} provides {len(self)} mapped volumes, but #{mapindex.volume} was requested."
|
|
420
380
|
)
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
381
|
+
if mapindex.label is None and mapindex.fragment is None:
|
|
382
|
+
return self.volumes[mapindex.volume]
|
|
383
|
+
|
|
384
|
+
return _volume.FilteredVolume(
|
|
385
|
+
parent_volume=self.volumes[mapindex.volume],
|
|
386
|
+
label=mapindex.label,
|
|
387
|
+
fragment=mapindex.fragment,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
def fetch(
|
|
391
|
+
self,
|
|
392
|
+
region: Union[str, "Region"] = None,
|
|
393
|
+
*,
|
|
394
|
+
index: MapIndex = None,
|
|
395
|
+
**fetch_kwargs
|
|
396
|
+
):
|
|
397
|
+
"""
|
|
398
|
+
Fetches one particular volume of this parcellation map.
|
|
399
|
+
|
|
400
|
+
If there's only one volume, this is the default, otherwise further
|
|
401
|
+
specification is requested:
|
|
402
|
+
- the volume index,
|
|
403
|
+
- the MapIndex (which results in a regional map being returned)
|
|
404
|
+
|
|
405
|
+
You might also consider fetch_iter() to iterate the volumes, or
|
|
406
|
+
compress() to produce a single-volume parcellation map.
|
|
407
|
+
|
|
408
|
+
Parameters
|
|
409
|
+
----------
|
|
410
|
+
region: str, Region
|
|
411
|
+
Specification of a region name, resulting in a regional map
|
|
412
|
+
(mask or statistical map) to be returned.
|
|
413
|
+
index: MapIndex
|
|
414
|
+
Explicit specification of the map index, typically resulting
|
|
415
|
+
in a regional map (mask or statistical map) to be returned.
|
|
416
|
+
Note that supplying 'region' will result in retrieving the map index of that region
|
|
417
|
+
automatically.
|
|
418
|
+
**fetch_kwargs
|
|
419
|
+
- resolution_mm: resolution in millimeters
|
|
420
|
+
- format: the format of the volume, like "mesh" or "nii"
|
|
421
|
+
- voi: a BoundingBox of interest
|
|
422
|
+
|
|
427
423
|
|
|
428
|
-
|
|
429
|
-
|
|
424
|
+
Note
|
|
425
|
+
----
|
|
426
|
+
Not all keyword arguments are supported for volume formats. Format
|
|
427
|
+
is restricted by available formats (check formats property).
|
|
430
428
|
|
|
431
|
-
|
|
429
|
+
Returns
|
|
430
|
+
-------
|
|
431
|
+
An image or mesh
|
|
432
|
+
"""
|
|
433
|
+
vol = self.get_volume(region=region, index=index, **fetch_kwargs)
|
|
434
|
+
return vol.fetch(**fetch_kwargs)
|
|
432
435
|
|
|
433
436
|
def fetch_iter(self, **kwargs):
|
|
434
437
|
"""
|
|
@@ -500,7 +503,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
500
503
|
taking the voxelwise maximum across the mapped volumes and fragments,
|
|
501
504
|
and re-labelling regions sequentially.
|
|
502
505
|
|
|
503
|
-
|
|
506
|
+
Parameters
|
|
504
507
|
----------
|
|
505
508
|
**kwargs: Takes the fetch arguments of its space's template.
|
|
506
509
|
|
|
@@ -531,7 +534,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
531
534
|
disable=(len(self.fragments) == 1 or self.fragments is None)
|
|
532
535
|
):
|
|
533
536
|
mapindex = MapIndex(volume=volidx, fragment=frag)
|
|
534
|
-
img = self.fetch(mapindex)
|
|
537
|
+
img = self.fetch(index=mapindex)
|
|
535
538
|
if np.allclose(img.affine, result_affine):
|
|
536
539
|
img_data = np.asanyarray(img.dataobj)
|
|
537
540
|
else:
|
|
@@ -572,11 +575,11 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
572
575
|
)]
|
|
573
576
|
)
|
|
574
577
|
|
|
575
|
-
def compute_centroids(self, split_components: bool = True) -> Dict[str,
|
|
578
|
+
def compute_centroids(self, split_components: bool = True, **fetch_kwargs) -> Dict[str, pointcloud.PointCloud]:
|
|
576
579
|
"""
|
|
577
580
|
Compute a dictionary of all regions in this map to their centroids.
|
|
578
581
|
By default, the regional masks will be split to connected components
|
|
579
|
-
and each point in the
|
|
582
|
+
and each point in the PointCloud corresponds to a region component.
|
|
580
583
|
|
|
581
584
|
Parameters
|
|
582
585
|
----------
|
|
@@ -589,6 +592,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
589
592
|
Dict[str, point.Point]
|
|
590
593
|
Region names as keys and computed centroids as items.
|
|
591
594
|
"""
|
|
595
|
+
assert self.provides_image, "Centroid computation for meshes is not supported yet."
|
|
592
596
|
centroids = dict()
|
|
593
597
|
for regionname, indexlist in siibra_tqdm(
|
|
594
598
|
self._indices.items(), unit="regions", desc="Computing centroids"
|
|
@@ -600,7 +604,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
600
604
|
merged_volume = _volume.merge(
|
|
601
605
|
[
|
|
602
606
|
_volume.from_nifti(
|
|
603
|
-
self.fetch(index=index),
|
|
607
|
+
self.fetch(index=index, **fetch_kwargs),
|
|
604
608
|
self.space,
|
|
605
609
|
f"{self.name} - {index}"
|
|
606
610
|
)
|
|
@@ -611,13 +615,16 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
611
615
|
mapimg = merged_volume.fetch()
|
|
612
616
|
elif len(indexlist) == 1:
|
|
613
617
|
index = indexlist[0]
|
|
614
|
-
mapimg = self.fetch(index=index) # returns a mask of the region
|
|
618
|
+
mapimg = self.fetch(index=index, **fetch_kwargs) # returns a mask of the region
|
|
615
619
|
props = _volume.ComponentSpatialProperties.compute_from_image(
|
|
616
620
|
img=mapimg,
|
|
617
621
|
space=self.space,
|
|
618
622
|
split_components=split_components,
|
|
619
623
|
)
|
|
620
|
-
|
|
624
|
+
try:
|
|
625
|
+
centroids[regionname] = pointcloud.from_points([c.centroid for c in props])
|
|
626
|
+
except exceptions.EmptyPointCloudError:
|
|
627
|
+
centroids[regionname] = None
|
|
621
628
|
return centroids
|
|
622
629
|
|
|
623
630
|
def get_resampled_template(self, **fetch_kwargs) -> _volume.Volume:
|
|
@@ -683,7 +690,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
683
690
|
name=f"Custom colorization of {self}"
|
|
684
691
|
)
|
|
685
692
|
|
|
686
|
-
def get_colormap(self, region_specs: Iterable = None):
|
|
693
|
+
def get_colormap(self, region_specs: Iterable = None, *, allow_random_colors: bool = False):
|
|
687
694
|
"""
|
|
688
695
|
Generate a matplotlib colormap from known rgb values of label indices.
|
|
689
696
|
|
|
@@ -691,13 +698,23 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
691
698
|
----------
|
|
692
699
|
region_specs: iterable(regions), optional
|
|
693
700
|
Optional parameter to only color the desired regions.
|
|
701
|
+
allow_random_colors: bool , optional
|
|
694
702
|
|
|
695
703
|
Returns
|
|
696
704
|
-------
|
|
697
705
|
ListedColormap
|
|
698
706
|
"""
|
|
699
|
-
|
|
700
|
-
|
|
707
|
+
try:
|
|
708
|
+
from matplotlib.colors import ListedColormap
|
|
709
|
+
except ImportError as e:
|
|
710
|
+
logger.error(
|
|
711
|
+
"matplotlib not available. Please install matplotlib to create a matplotlib colormap."
|
|
712
|
+
)
|
|
713
|
+
raise e
|
|
714
|
+
if allow_random_colors:
|
|
715
|
+
seed = len(self.regions)
|
|
716
|
+
np.random.seed(seed)
|
|
717
|
+
logger.info(f"Random colors are allowed for regions without preconfgirued colors. Random seee: {seed}.")
|
|
701
718
|
|
|
702
719
|
colors = {}
|
|
703
720
|
if region_specs is not None:
|
|
@@ -718,14 +735,25 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
718
735
|
region = self.get_region(index=index)
|
|
719
736
|
if region.rgb is not None:
|
|
720
737
|
colors[index.label] = region.rgb
|
|
738
|
+
elif allow_random_colors:
|
|
739
|
+
random_clr = [np.random.randint(0, 255) for r in range(3)]
|
|
740
|
+
while random_clr in list(colors.values()):
|
|
741
|
+
random_clr = [np.random.randint(0, 255) for r in range(3)]
|
|
742
|
+
colors[index.label] = random_clr
|
|
743
|
+
|
|
744
|
+
if len(colors) == 0:
|
|
745
|
+
raise exceptions.NoPredifinedColormapException(
|
|
746
|
+
f"There is no predefined/preconfigured colormap for '{self}'."
|
|
747
|
+
"Set `allow_random_colors=True` to a colormap with random values"
|
|
748
|
+
)
|
|
721
749
|
|
|
722
|
-
|
|
750
|
+
palette = np.array(
|
|
723
751
|
[
|
|
724
752
|
list(colors[i]) + [1] if i in colors else [0, 0, 0, 0]
|
|
725
753
|
for i in range(max(colors.keys()) + 1)
|
|
726
754
|
]
|
|
727
755
|
) / [255, 255, 255, 1]
|
|
728
|
-
return ListedColormap(
|
|
756
|
+
return ListedColormap(palette)
|
|
729
757
|
|
|
730
758
|
def sample_locations(self, regionspec, numpoints: int):
|
|
731
759
|
""" Sample 3D locations inside a given region.
|
|
@@ -742,8 +770,8 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
742
770
|
|
|
743
771
|
Returns
|
|
744
772
|
-------
|
|
745
|
-
|
|
746
|
-
Sample points in
|
|
773
|
+
PointCloud
|
|
774
|
+
Sample points in physical coordinates corresponding to this
|
|
747
775
|
parcellationmap
|
|
748
776
|
"""
|
|
749
777
|
index = self.get_index(regionspec)
|
|
@@ -760,7 +788,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
760
788
|
np.unravel_index(np.random.choice(len(p), numpoints, p=p), W.shape)
|
|
761
789
|
).T
|
|
762
790
|
XYZ = np.dot(mask.affine, np.c_[XYZ_, np.ones(numpoints)].T)[:3, :].T
|
|
763
|
-
return
|
|
791
|
+
return pointcloud.PointCloud(XYZ, space=self.space)
|
|
764
792
|
|
|
765
793
|
def to_sparse(self):
|
|
766
794
|
"""
|
|
@@ -831,10 +859,10 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
831
859
|
|
|
832
860
|
if isinstance(item, point.Point):
|
|
833
861
|
return self._assign_points(
|
|
834
|
-
|
|
862
|
+
pointcloud.PointCloud([item], item.space, sigma_mm=item.sigma),
|
|
835
863
|
lower_threshold
|
|
836
864
|
)
|
|
837
|
-
if isinstance(item,
|
|
865
|
+
if isinstance(item, pointcloud.PointCloud):
|
|
838
866
|
return self._assign_points(item, lower_threshold)
|
|
839
867
|
if isinstance(item, _volume.Volume):
|
|
840
868
|
return self._assign_volume(
|
|
@@ -916,7 +944,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
916
944
|
if len(assignments) == 0:
|
|
917
945
|
return pd.DataFrame(columns=columns)
|
|
918
946
|
# determine the unique set of observed indices in order to do region lookups
|
|
919
|
-
# only once for each map index
|
|
947
|
+
# only once for each map index occurring in the point list
|
|
920
948
|
labelled = self.is_labelled # avoid calling this in a loop
|
|
921
949
|
observed_indices = { # unique set of observed map indices. NOTE: len(observed_indices) << len(assignments)
|
|
922
950
|
(
|
|
@@ -989,9 +1017,9 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
989
1017
|
.dropna(axis='columns', how='all')
|
|
990
1018
|
)
|
|
991
1019
|
|
|
992
|
-
def _assign_points(self, points:
|
|
1020
|
+
def _assign_points(self, points: pointcloud.PointCloud, lower_threshold: float) -> List[MapAssignment]:
|
|
993
1021
|
"""
|
|
994
|
-
assign a
|
|
1022
|
+
assign a PointCloud to this parcellation map.
|
|
995
1023
|
|
|
996
1024
|
Parameters
|
|
997
1025
|
-----------
|
|
@@ -1025,7 +1053,7 @@ class Map(concept.AtlasConcept, configuration_folder="maps"):
|
|
|
1025
1053
|
assignments.append(
|
|
1026
1054
|
MapAssignment(
|
|
1027
1055
|
input_structure=pointindex,
|
|
1028
|
-
centroid=tuple(
|
|
1056
|
+
centroid=tuple(position),
|
|
1029
1057
|
volume=vol,
|
|
1030
1058
|
fragment=frag,
|
|
1031
1059
|
map_value=value
|
|
@@ -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");
|
|
@@ -54,7 +54,7 @@ class FreesurferAnnot(_provider.VolumeProvider, srctype="freesurfer-annot"):
|
|
|
54
54
|
frag_labels[selected_label] = 1
|
|
55
55
|
frag_labels[~selected_label] = 0
|
|
56
56
|
else:
|
|
57
|
-
frag_labels[frag_labels == -1] = 0 # annot files store
|
|
57
|
+
frag_labels[frag_labels == -1] = 0 # annot files store background as -1 while siibra uses 0
|
|
58
58
|
vertex_labels.append(frag_labels)
|
|
59
59
|
|
|
60
60
|
return {"labels": np.hstack(vertex_labels)}
|
|
@@ -98,7 +98,7 @@ class ZippedFreesurferAnnot(_provider.VolumeProvider, srctype="zip/freesurfer-an
|
|
|
98
98
|
frag_labels[selected_label] = 1
|
|
99
99
|
frag_labels[~selected_label] = 0
|
|
100
100
|
else:
|
|
101
|
-
frag_labels[frag_labels == -1] = 0 # annot files store
|
|
101
|
+
frag_labels[frag_labels == -1] = 0 # annot files store background as -1 while siibra uses 0
|
|
102
102
|
vertex_labels.append(frag_labels)
|
|
103
103
|
|
|
104
104
|
return {"labels": np.hstack(vertex_labels)}
|