siibra 1.0a14__py3-none-any.whl → 1.0a19__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 +12 -2
- siibra/commons.py +3 -2
- siibra/configuration/__init__.py +1 -1
- siibra/configuration/configuration.py +1 -1
- siibra/configuration/factory.py +164 -117
- siibra/core/__init__.py +1 -1
- siibra/core/assignment.py +1 -1
- siibra/core/atlas.py +4 -3
- siibra/core/concept.py +18 -9
- siibra/core/parcellation.py +9 -3
- siibra/core/region.py +35 -65
- siibra/core/space.py +3 -1
- siibra/core/structure.py +1 -2
- siibra/exceptions.py +9 -1
- 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 +1 -1
- siibra/features/connectivity/__init__.py +1 -1
- siibra/features/connectivity/functional_connectivity.py +1 -1
- siibra/features/connectivity/regional_connectivity.py +5 -3
- 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 +39 -15
- 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 +1 -1
- siibra/features/tabular/cell_density_profile.py +5 -3
- 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 +5 -3
- 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 +5 -3
- siibra/features/tabular/tabular.py +5 -3
- siibra/livequeries/__init__.py +1 -1
- siibra/livequeries/allen.py +9 -6
- siibra/livequeries/bigbrain.py +1 -1
- siibra/livequeries/ebrains.py +1 -1
- siibra/livequeries/query.py +1 -1
- siibra/locations/__init__.py +1 -1
- siibra/locations/boundingbox.py +51 -17
- siibra/locations/location.py +12 -4
- siibra/locations/point.py +10 -5
- siibra/locations/pointset.py +45 -11
- 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 +1 -1
- siibra/retrieval/requests.py +1 -1
- siibra/vocabularies/__init__.py +1 -1
- siibra/volumes/__init__.py +1 -1
- siibra/volumes/parcellationmap.py +38 -18
- 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 +7 -7
- siibra/volumes/providers/nifti.py +8 -4
- siibra/volumes/providers/provider.py +2 -2
- siibra/volumes/sparsemap.py +4 -2
- siibra/volumes/volume.py +114 -16
- {siibra-1.0a14.dist-info → siibra-1.0a19.dist-info}/METADATA +3 -3
- siibra-1.0a19.dist-info/RECORD +84 -0
- {siibra-1.0a14.dist-info → siibra-1.0a19.dist-info}/WHEEL +1 -1
- siibra-1.0a14.dist-info/RECORD +0 -84
- {siibra-1.0a14.dist-info → siibra-1.0a19.dist-info}/LICENSE +0 -0
- {siibra-1.0a14.dist-info → siibra-1.0a19.dist-info}/top_level.txt +0 -0
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");
|
|
@@ -70,7 +70,8 @@ class CellDensityProfile(
|
|
|
70
70
|
url: str,
|
|
71
71
|
anchor: _anchor.AnatomicalAnchor,
|
|
72
72
|
datasets: list = [],
|
|
73
|
-
id: str = None
|
|
73
|
+
id: str = None,
|
|
74
|
+
prerelease: bool = False,
|
|
74
75
|
):
|
|
75
76
|
"""
|
|
76
77
|
Generate a cell density profile from a URL to a cloud folder
|
|
@@ -83,7 +84,8 @@ class CellDensityProfile(
|
|
|
83
84
|
unit="cells / 0.1mm3",
|
|
84
85
|
anchor=anchor,
|
|
85
86
|
datasets=datasets,
|
|
86
|
-
id=id
|
|
87
|
+
id=id,
|
|
88
|
+
prerelease=prerelease,
|
|
87
89
|
)
|
|
88
90
|
self._step = 0.01
|
|
89
91
|
self._url = url
|
|
@@ -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");
|
|
@@ -57,7 +57,8 @@ class CorticalProfile(tabular.Tabular, Compoundable):
|
|
|
57
57
|
unit: str = None,
|
|
58
58
|
boundary_positions: Dict[Tuple[int, int], float] = None,
|
|
59
59
|
datasets: list = [],
|
|
60
|
-
id: str = None
|
|
60
|
+
id: str = None,
|
|
61
|
+
prerelease: bool = False,
|
|
61
62
|
):
|
|
62
63
|
"""Initialize profile.
|
|
63
64
|
|
|
@@ -98,7 +99,8 @@ class CorticalProfile(tabular.Tabular, Compoundable):
|
|
|
98
99
|
anchor=anchor,
|
|
99
100
|
data=None, # lazy loader below
|
|
100
101
|
datasets=datasets,
|
|
101
|
-
id=id
|
|
102
|
+
id=id,
|
|
103
|
+
prerelease=prerelease,
|
|
102
104
|
)
|
|
103
105
|
|
|
104
106
|
def _check_sanity(self):
|
|
@@ -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");
|
|
@@ -56,7 +56,8 @@ class LayerwiseCellDensity(
|
|
|
56
56
|
layerfiles: list,
|
|
57
57
|
anchor: _anchor.AnatomicalAnchor,
|
|
58
58
|
datasets: list = [],
|
|
59
|
-
id: str = None
|
|
59
|
+
id: str = None,
|
|
60
|
+
prerelease: bool = False,
|
|
60
61
|
):
|
|
61
62
|
tabular.Tabular.__init__(
|
|
62
63
|
self,
|
|
@@ -65,7 +66,8 @@ class LayerwiseCellDensity(
|
|
|
65
66
|
anchor=anchor,
|
|
66
67
|
datasets=datasets,
|
|
67
68
|
data=None, # lazy loading below
|
|
68
|
-
id=id
|
|
69
|
+
id=id,
|
|
70
|
+
prerelease=prerelease,
|
|
69
71
|
)
|
|
70
72
|
self.unit = "# detected cells/0.1mm3"
|
|
71
73
|
self._filepairs = list(zip(segmentfiles, layerfiles))
|
|
@@ -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");
|
|
@@ -43,7 +43,8 @@ class ReceptorDensityFingerprint(
|
|
|
43
43
|
tsvfile: str,
|
|
44
44
|
anchor: _anchor.AnatomicalAnchor,
|
|
45
45
|
datasets: list = [],
|
|
46
|
-
id: str = None
|
|
46
|
+
id: str = None,
|
|
47
|
+
prerelease: bool = False,
|
|
47
48
|
):
|
|
48
49
|
""" Generate a receptor fingerprint from a URL to a .tsv file
|
|
49
50
|
formatted according to the structure used by Palomero-Gallagher et al.
|
|
@@ -55,7 +56,8 @@ class ReceptorDensityFingerprint(
|
|
|
55
56
|
anchor=anchor,
|
|
56
57
|
data=None, # lazy loading below
|
|
57
58
|
datasets=datasets,
|
|
58
|
-
id=id
|
|
59
|
+
id=id,
|
|
60
|
+
prerelease=prerelease,
|
|
59
61
|
)
|
|
60
62
|
self._loader = requests.HttpRequest(tsvfile)
|
|
61
63
|
|
|
@@ -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");
|
|
@@ -42,7 +42,8 @@ class ReceptorDensityProfile(
|
|
|
42
42
|
tsvfile: str,
|
|
43
43
|
anchor: _anchor.AnatomicalAnchor,
|
|
44
44
|
datasets: list = [],
|
|
45
|
-
id: str = None
|
|
45
|
+
id: str = None,
|
|
46
|
+
prerelease: bool = False,
|
|
46
47
|
):
|
|
47
48
|
"""Generate a receptor density profile from a URL to a .tsv file
|
|
48
49
|
formatted according to the structure used by Palomero-Gallagher et al.
|
|
@@ -53,7 +54,8 @@ class ReceptorDensityProfile(
|
|
|
53
54
|
modality="Receptor density",
|
|
54
55
|
anchor=anchor,
|
|
55
56
|
datasets=datasets,
|
|
56
|
-
id=id
|
|
57
|
+
id=id,
|
|
58
|
+
prerelease=prerelease
|
|
57
59
|
)
|
|
58
60
|
self.receptor = receptor
|
|
59
61
|
self._data_cached = None
|
|
@@ -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");
|
|
@@ -49,7 +49,8 @@ class RegionalTimeseriesActivity(tabular.Tabular, Compoundable):
|
|
|
49
49
|
description: str = "",
|
|
50
50
|
datasets: list = [],
|
|
51
51
|
subject: str = "average",
|
|
52
|
-
id: str = None
|
|
52
|
+
id: str = None,
|
|
53
|
+
prerelease: bool = False,
|
|
53
54
|
):
|
|
54
55
|
"""
|
|
55
56
|
"""
|
|
@@ -60,7 +61,8 @@ class RegionalTimeseriesActivity(tabular.Tabular, Compoundable):
|
|
|
60
61
|
anchor=anchor,
|
|
61
62
|
datasets=datasets,
|
|
62
63
|
data=None, # lazy loading below
|
|
63
|
-
id=id
|
|
64
|
+
id=id,
|
|
65
|
+
prerelease=prerelease,
|
|
64
66
|
)
|
|
65
67
|
self.cohort = cohort.upper()
|
|
66
68
|
if isinstance(connector, str) and connector:
|
|
@@ -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");
|
|
@@ -45,7 +45,8 @@ class Tabular(feature.Feature):
|
|
|
45
45
|
anchor: _anchor.AnatomicalAnchor,
|
|
46
46
|
data: pd.DataFrame, # sample x feature dimension
|
|
47
47
|
datasets: list = [],
|
|
48
|
-
id: str = None
|
|
48
|
+
id: str = None,
|
|
49
|
+
prerelease: bool = False,
|
|
49
50
|
):
|
|
50
51
|
feature.Feature.__init__(
|
|
51
52
|
self,
|
|
@@ -53,7 +54,8 @@ class Tabular(feature.Feature):
|
|
|
53
54
|
description=description,
|
|
54
55
|
anchor=anchor,
|
|
55
56
|
datasets=datasets,
|
|
56
|
-
id=id
|
|
57
|
+
id=id,
|
|
58
|
+
prerelease=prerelease
|
|
57
59
|
)
|
|
58
60
|
self._data_cached = data
|
|
59
61
|
|
siibra/livequeries/__init__.py
CHANGED
siibra/livequeries/allen.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2024
|
|
2
2
|
# Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
|
|
3
3
|
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -19,7 +19,7 @@ from .query import LiveQuery
|
|
|
19
19
|
from ..core import space as _space, structure
|
|
20
20
|
from ..features import anchor as _anchor
|
|
21
21
|
from ..features.tabular.gene_expression import GeneExpressions
|
|
22
|
-
from ..commons import logger, Species
|
|
22
|
+
from ..commons import logger, Species
|
|
23
23
|
from ..locations import point, pointset
|
|
24
24
|
from ..retrieval import HttpRequest
|
|
25
25
|
from ..vocabularies import GENE_NAMES
|
|
@@ -101,9 +101,6 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
|
|
|
101
101
|
"""
|
|
102
102
|
LiveQuery.__init__(self, **kwargs)
|
|
103
103
|
gene = kwargs.get('gene')
|
|
104
|
-
self.maptype = kwargs.get("maptype", None)
|
|
105
|
-
if isinstance(self.maptype, str):
|
|
106
|
-
self.maptype = MapType[self.maptype.upper()]
|
|
107
104
|
|
|
108
105
|
def parse_gene(spec):
|
|
109
106
|
if isinstance(spec, str):
|
|
@@ -137,6 +134,9 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
|
|
|
137
134
|
measurements.append(measurement)
|
|
138
135
|
coordinates.append(pt)
|
|
139
136
|
|
|
137
|
+
if len(points_inside) == 0:
|
|
138
|
+
raise StopIteration
|
|
139
|
+
|
|
140
140
|
# Build the anatomical anchor and assignment to the query concept.
|
|
141
141
|
# It will be attached to the returned feature, with the set of matched
|
|
142
142
|
# MNI coordinates as anchor's location.
|
|
@@ -292,7 +292,10 @@ class AllenBrainAtlasQuery(LiveQuery, args=['gene'], FeatureType=GeneExpressions
|
|
|
292
292
|
url = AllenBrainAtlasQuery._QUERY["microarray"].format(
|
|
293
293
|
probe_ids=",".join([str(id) for id in probe_ids]), donor_id=donor_id
|
|
294
294
|
)
|
|
295
|
-
|
|
295
|
+
try:
|
|
296
|
+
response = HttpRequest(url, json.loads).get()
|
|
297
|
+
except json.JSONDecodeError as e:
|
|
298
|
+
raise RuntimeError(f"Allen institute site produced an empty response - please try again later.\n{e}")
|
|
296
299
|
if not response["success"]:
|
|
297
300
|
raise Exception(
|
|
298
301
|
"Invalid response when retrieving microarray data: {}".format(url)
|
siibra/livequeries/bigbrain.py
CHANGED
siibra/livequeries/ebrains.py
CHANGED
siibra/livequeries/query.py
CHANGED
siibra/locations/__init__.py
CHANGED
siibra/locations/boundingbox.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,6 +19,7 @@ from . import point, pointset, location
|
|
|
19
19
|
from ..commons import logger
|
|
20
20
|
from ..exceptions import SpaceWarpingFailedError
|
|
21
21
|
|
|
22
|
+
from itertools import product
|
|
22
23
|
import hashlib
|
|
23
24
|
import numpy as np
|
|
24
25
|
from typing import TYPE_CHECKING, Union
|
|
@@ -75,6 +76,7 @@ class BoundingBox(location.Location):
|
|
|
75
76
|
s1, s2 = sigma_mm
|
|
76
77
|
else:
|
|
77
78
|
raise ValueError(f"Cannot interpret sigma_mm parameter value {sigma_mm} for bounding box")
|
|
79
|
+
self.sigma_mm = [s1, s2]
|
|
78
80
|
self.minpoint = point.Point([min(xyz1[i], xyz2[i]) for i in range(3)], space, sigma_mm=s1)
|
|
79
81
|
self.maxpoint = point.Point([max(xyz1[i], xyz2[i]) for i in range(3)], space, sigma_mm=s2)
|
|
80
82
|
if minsize is not None:
|
|
@@ -82,6 +84,9 @@ class BoundingBox(location.Location):
|
|
|
82
84
|
if self.shape[d] < minsize:
|
|
83
85
|
self.maxpoint[d] = self.minpoint[d] + minsize
|
|
84
86
|
|
|
87
|
+
if self.volume == 0:
|
|
88
|
+
logger.warning("Created BoundedBox's have zero volume.")
|
|
89
|
+
|
|
85
90
|
@property
|
|
86
91
|
def id(self) -> str:
|
|
87
92
|
return hashlib.md5(str(self).encode("utf-8")).hexdigest()
|
|
@@ -110,12 +115,12 @@ class BoundingBox(location.Location):
|
|
|
110
115
|
def __str__(self):
|
|
111
116
|
if self.space is None:
|
|
112
117
|
return (
|
|
113
|
-
f"Bounding box from ({','.join(f'{v:.2f}' for v in self.minpoint)})
|
|
114
|
-
f"to ({','.join(f'{v:.2f}' for v in self.maxpoint)})
|
|
118
|
+
f"Bounding box from ({','.join(f'{v:.2f}' for v in self.minpoint)})mm "
|
|
119
|
+
f"to ({','.join(f'{v:.2f}' for v in self.maxpoint)})mm"
|
|
115
120
|
)
|
|
116
121
|
else:
|
|
117
122
|
return (
|
|
118
|
-
f"Bounding box from ({','.join(f'{v:.2f}' for v in self.minpoint)})
|
|
123
|
+
f"Bounding box from ({','.join(f'{v:.2f}' for v in self.minpoint)})mm "
|
|
119
124
|
f"to ({','.join(f'{v:.2f}' for v in self.maxpoint)})mm in {self.space.name} space"
|
|
120
125
|
)
|
|
121
126
|
|
|
@@ -183,12 +188,18 @@ class BoundingBox(location.Location):
|
|
|
183
188
|
result_minpt.append(A[dim])
|
|
184
189
|
result_maxpt.append(B[dim])
|
|
185
190
|
|
|
191
|
+
if result_minpt == result_maxpt:
|
|
192
|
+
return result_minpt
|
|
193
|
+
|
|
186
194
|
bbox = BoundingBox(
|
|
187
195
|
point1=point.Point(result_minpt, self.space),
|
|
188
196
|
point2=point.Point(result_maxpt, self.space),
|
|
189
197
|
space=self.space,
|
|
190
198
|
)
|
|
191
|
-
|
|
199
|
+
|
|
200
|
+
if bbox.volume == 0 and sum(cmin == cmax for cmin, cmax in zip(result_minpt, result_maxpt)) == 2:
|
|
201
|
+
return None
|
|
202
|
+
return bbox
|
|
192
203
|
|
|
193
204
|
def _intersect_mask(self, mask: 'Nifti1Image', threshold=0):
|
|
194
205
|
"""Intersect this bounding box with an image mask. Returns None if they do not intersect.
|
|
@@ -245,6 +256,35 @@ class BoundingBox(location.Location):
|
|
|
245
256
|
sigma_mm=[self.minpoint.sigma, self.maxpoint.sigma]
|
|
246
257
|
)
|
|
247
258
|
|
|
259
|
+
@property
|
|
260
|
+
def corners(self):
|
|
261
|
+
"""
|
|
262
|
+
Returns all 8 corners of the box as a pointset.
|
|
263
|
+
|
|
264
|
+
Note
|
|
265
|
+
----
|
|
266
|
+
x0, y0, z0 = self.minpoint
|
|
267
|
+
x1, y1, z1 = self.maxpoint
|
|
268
|
+
all_corners = [
|
|
269
|
+
(x0, y0, z0),
|
|
270
|
+
(x1, y0, z0),
|
|
271
|
+
(x0, y1, z0),
|
|
272
|
+
(x1, y1, z0),
|
|
273
|
+
(x0, y0, z1),
|
|
274
|
+
(x1, y0, z1),
|
|
275
|
+
(x0, y1, z1),
|
|
276
|
+
(x1, y1, z1)
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
TODO: deal with sigma. Currently, returns the mean of min and max point.
|
|
280
|
+
"""
|
|
281
|
+
xs, ys, zs = zip(self.minpoint, self.maxpoint)
|
|
282
|
+
return pointset.PointSet(
|
|
283
|
+
coordinates=[[x, y, z] for x, y, z in product(xs, ys, zs)],
|
|
284
|
+
space=self.space,
|
|
285
|
+
sigma_mm=np.mean([self.minpoint.sigma, self.maxpoint.sigma])
|
|
286
|
+
)
|
|
287
|
+
|
|
248
288
|
def warp(self, space):
|
|
249
289
|
"""Returns a new bounding box obtained by warping the
|
|
250
290
|
min- and maxpoint of this one into the new target space.
|
|
@@ -257,13 +297,10 @@ class BoundingBox(location.Location):
|
|
|
257
297
|
return self
|
|
258
298
|
else:
|
|
259
299
|
try:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
point2=self.maxpoint.warp(spaceobj),
|
|
263
|
-
space=spaceobj,
|
|
264
|
-
)
|
|
265
|
-
except ValueError:
|
|
300
|
+
warped_corners = self.corners.warp(spaceobj)
|
|
301
|
+
except SpaceWarpingFailedError:
|
|
266
302
|
raise SpaceWarpingFailedError(f"Warping {str(self)} to {spaceobj.name} not successful.")
|
|
303
|
+
return warped_corners.boundingbox
|
|
267
304
|
|
|
268
305
|
def transform(self, affine: np.ndarray, space=None):
|
|
269
306
|
"""Returns a new bounding box obtained by transforming the
|
|
@@ -282,12 +319,9 @@ class BoundingBox(location.Location):
|
|
|
282
319
|
"""
|
|
283
320
|
from ..core.space import Space
|
|
284
321
|
spaceobj = Space.get_instance(space)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
space=space,
|
|
289
|
-
sigma_mm=[self.minpoint.sigma, self.maxpoint.sigma] # TODO: error propagation
|
|
290
|
-
)
|
|
322
|
+
result = self.corners.transform(affine, spaceobj).boundingbox
|
|
323
|
+
result.sigma_mm = [self.minpoint.sigma, self.maxpoint.sigma] # TODO: error propagation
|
|
324
|
+
return result
|
|
291
325
|
|
|
292
326
|
def shift(self, offset):
|
|
293
327
|
return self.__class__(
|
siibra/locations/location.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");
|
|
@@ -21,6 +21,10 @@ from ..core.structure import BrainStructure
|
|
|
21
21
|
import numpy as np
|
|
22
22
|
from abc import abstractmethod
|
|
23
23
|
|
|
24
|
+
from typing import TYPE_CHECKING, Union, Dict
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from siibra.core.space import Space
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
class Location(BrainStructure):
|
|
26
30
|
"""
|
|
@@ -42,15 +46,19 @@ class Location(BrainStructure):
|
|
|
42
46
|
_MASK_MEMO = {} # cache region masks for Location._assign_region()
|
|
43
47
|
_ASSIGNMENT_CACHE = {} # caches assignment results, see Region.assign()
|
|
44
48
|
|
|
45
|
-
def __init__(self,
|
|
46
|
-
self._space_spec =
|
|
49
|
+
def __init__(self, spacespec: Union[str, Dict[str, str], "Space"]):
|
|
50
|
+
self._space_spec = spacespec
|
|
47
51
|
self._space_cached = None
|
|
48
52
|
|
|
49
53
|
@property
|
|
50
54
|
def space(self):
|
|
51
55
|
if self._space_cached is None:
|
|
52
56
|
from ..core.space import Space
|
|
53
|
-
|
|
57
|
+
if isinstance(self._space_spec, dict):
|
|
58
|
+
spec = self._space_spec.get("@id") or self._space_spec.get("name")
|
|
59
|
+
self._space_cached = Space.get_instance(spec)
|
|
60
|
+
else:
|
|
61
|
+
self._space_cached = Space.get_instance(self._space_spec)
|
|
54
62
|
return self._space_cached
|
|
55
63
|
|
|
56
64
|
@abstractmethod
|
siibra/locations/point.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");
|
|
@@ -18,6 +18,7 @@ from . import location, boundingbox, pointset
|
|
|
18
18
|
|
|
19
19
|
from ..commons import logger
|
|
20
20
|
from ..retrieval.requests import HttpRequest
|
|
21
|
+
from ..exceptions import SpaceWarpingFailedError, NoneCoordinateSuppliedError
|
|
21
22
|
|
|
22
23
|
from urllib.parse import quote
|
|
23
24
|
import re
|
|
@@ -55,10 +56,14 @@ class Point(location.Location):
|
|
|
55
56
|
if len(digits) == 3:
|
|
56
57
|
return tuple(float(d) for d in digits)
|
|
57
58
|
elif isinstance(spec, (tuple, list)) and len(spec) in [3, 4]:
|
|
59
|
+
if any(v is None for v in spec):
|
|
60
|
+
raise NoneCoordinateSuppliedError("Cannot parse cooridantes containing None values.")
|
|
58
61
|
if len(spec) == 4:
|
|
59
62
|
assert spec[3] == 1
|
|
60
63
|
return tuple(float(v.item()) if isinstance(v, np.ndarray) else float(v) for v in spec[:3])
|
|
61
64
|
elif isinstance(spec, np.ndarray) and spec.size == 3:
|
|
65
|
+
if any(np.isnan(v) for v in spec):
|
|
66
|
+
raise NoneCoordinateSuppliedError("Cannot parse cooridantes containing NaN values.")
|
|
62
67
|
return tuple(float(v.item()) if isinstance(v, np.ndarray) else float(v) for v in spec[:3])
|
|
63
68
|
elif isinstance(spec, Point):
|
|
64
69
|
return spec.coordinate
|
|
@@ -125,7 +130,7 @@ class Point(location.Location):
|
|
|
125
130
|
if spaceobj == self.space:
|
|
126
131
|
return self
|
|
127
132
|
if any(_ not in location.Location.SPACEWARP_IDS for _ in [self.space.id, spaceobj.id]):
|
|
128
|
-
raise
|
|
133
|
+
raise SpaceWarpingFailedError(
|
|
129
134
|
f"Cannot convert coordinates between {self.space.id} and {spaceobj.id}"
|
|
130
135
|
)
|
|
131
136
|
url = "{server}/transform-point?source_space={src}&target_space={tgt}&x={x}&y={y}&z={z}".format(
|
|
@@ -137,9 +142,9 @@ class Point(location.Location):
|
|
|
137
142
|
z=self.coordinate[2],
|
|
138
143
|
)
|
|
139
144
|
response = HttpRequest(url, lambda b: json.loads(b.decode())).get()
|
|
140
|
-
if any(
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
if np.any(np.isnan(response['target_point'])):
|
|
146
|
+
raise SpaceWarpingFailedError(f'Warping {str(self)} to {spaceobj.name} resulted in NaN')
|
|
147
|
+
|
|
143
148
|
return self.__class__(
|
|
144
149
|
coordinatespec=tuple(response["target_point"]),
|
|
145
150
|
space=spaceobj.id,
|