siibra 1.0a9__py3-none-any.whl → 1.0a11__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/commons.py +43 -26
- siibra/configuration/factory.py +15 -16
- siibra/core/atlas.py +40 -16
- siibra/core/region.py +241 -38
- siibra/features/__init__.py +19 -8
- siibra/features/connectivity/functional_connectivity.py +1 -1
- siibra/features/connectivity/regional_connectivity.py +45 -3
- siibra/features/feature.py +62 -12
- siibra/features/image/image.py +3 -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 +79 -15
- siibra/features/tabular/gene_expression.py +110 -1
- siibra/features/tabular/layerwise_bigbrain_intensities.py +1 -1
- siibra/features/tabular/layerwise_cell_density.py +3 -1
- siibra/features/tabular/receptor_density_fingerprint.py +3 -1
- siibra/features/tabular/receptor_density_profile.py +3 -5
- siibra/features/tabular/regional_timeseries_activity.py +59 -10
- siibra/features/tabular/tabular.py +4 -2
- siibra/livequeries/bigbrain.py +34 -0
- siibra/retrieval/cache.py +14 -9
- siibra/retrieval/requests.py +30 -1
- siibra/volumes/parcellationmap.py +17 -21
- siibra/volumes/providers/__init__.py +1 -0
- siibra/volumes/providers/freesurfer.py +113 -0
- siibra/volumes/providers/neuroglancer.py +55 -25
- siibra/volumes/providers/nifti.py +14 -16
- siibra/volumes/sparsemap.py +1 -1
- siibra/volumes/volume.py +13 -15
- {siibra-1.0a9.dist-info → siibra-1.0a11.dist-info}/METADATA +1 -1
- {siibra-1.0a9.dist-info → siibra-1.0a11.dist-info}/RECORD +35 -34
- {siibra-1.0a9.dist-info → siibra-1.0a11.dist-info}/LICENSE +0 -0
- {siibra-1.0a9.dist-info → siibra-1.0a11.dist-info}/WHEEL +0 -0
- {siibra-1.0a9.dist-info → siibra-1.0a11.dist-info}/top_level.txt +0 -0
siibra/core/region.py
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
from . import concept, structure, space as _space, parcellation as _parcellation
|
|
18
18
|
from .assignment import Qualification, AnatomicalAssignment
|
|
19
19
|
|
|
20
|
+
from ..retrieval.cache import cache_user_fn
|
|
20
21
|
from ..locations import location, point, pointset
|
|
21
22
|
from ..volumes import parcellationmap, volume
|
|
22
23
|
from ..commons import (
|
|
@@ -34,7 +35,7 @@ from ..exceptions import NoMapAvailableError, SpaceWarpingFailedError
|
|
|
34
35
|
import numpy as np
|
|
35
36
|
import re
|
|
36
37
|
import anytree
|
|
37
|
-
from typing import List, Union, Iterable, Dict, Callable
|
|
38
|
+
from typing import List, Union, Iterable, Dict, Callable, Tuple
|
|
38
39
|
from difflib import SequenceMatcher
|
|
39
40
|
from dataclasses import dataclass, field
|
|
40
41
|
from ebrains_drive import BucketApiClient
|
|
@@ -136,7 +137,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
|
|
|
136
137
|
self._str_aliases = None
|
|
137
138
|
self._CACHED_REGION_SEARCHES = {}
|
|
138
139
|
|
|
139
|
-
def get_related_regions(self) -> Iterable["
|
|
140
|
+
def get_related_regions(self) -> Iterable["RegionRelationAssessments"]:
|
|
140
141
|
"""
|
|
141
142
|
Get assements on relations of this region to others defined on EBRAINS.
|
|
142
143
|
|
|
@@ -257,6 +258,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
|
|
|
257
258
|
Parameters
|
|
258
259
|
----------
|
|
259
260
|
region: Region
|
|
261
|
+
|
|
260
262
|
Returns
|
|
261
263
|
-------
|
|
262
264
|
bool
|
|
@@ -287,10 +289,12 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
|
|
|
287
289
|
If True (requires `filter_children=True`), will return parent
|
|
288
290
|
structures if all children are matched, even though the parent
|
|
289
291
|
itself might not match the specification.
|
|
292
|
+
|
|
290
293
|
Returns
|
|
291
294
|
-------
|
|
292
295
|
list[Region]
|
|
293
296
|
list of regions matching to the regionspec
|
|
297
|
+
|
|
294
298
|
Tip
|
|
295
299
|
---
|
|
296
300
|
See example 01-003, find regions.
|
|
@@ -697,7 +701,8 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
|
|
|
697
701
|
restrict_space=False,
|
|
698
702
|
**fetch_kwargs
|
|
699
703
|
):
|
|
700
|
-
"""
|
|
704
|
+
"""
|
|
705
|
+
Compute the bounding box of this region in the given space.
|
|
701
706
|
|
|
702
707
|
Parameters
|
|
703
708
|
----------
|
|
@@ -758,10 +763,12 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
|
|
|
758
763
|
----------
|
|
759
764
|
space: Space
|
|
760
765
|
reference space in which the computation will be performed
|
|
766
|
+
|
|
761
767
|
Returns
|
|
762
768
|
-------
|
|
763
769
|
PointSet
|
|
764
770
|
Found centroids (as Point objects) in a PointSet
|
|
771
|
+
|
|
765
772
|
Note
|
|
766
773
|
----
|
|
767
774
|
A region can generally have multiple centroids if it has multiple
|
|
@@ -853,7 +860,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
|
|
|
853
860
|
return anytree.PreOrderIter(self)
|
|
854
861
|
|
|
855
862
|
def intersection(self, other: "location.Location") -> "location.Location":
|
|
856
|
-
"""
|
|
863
|
+
"""Use this region for filtering a location object."""
|
|
857
864
|
|
|
858
865
|
if self.supports_space(other.space):
|
|
859
866
|
try:
|
|
@@ -878,6 +885,85 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
|
|
|
878
885
|
return None
|
|
879
886
|
|
|
880
887
|
|
|
888
|
+
@cache_user_fn
|
|
889
|
+
def _get_related_regions_str(pe_id: str) -> Tuple[Tuple[str, str, str, str], ...]:
|
|
890
|
+
logger.info("LONG CALC...", pe_id)
|
|
891
|
+
return_val = []
|
|
892
|
+
region_relation_assessments = RegionRelationAssessments.translate_pes(pe_id, pe_id)
|
|
893
|
+
for asgmt in region_relation_assessments:
|
|
894
|
+
assert isinstance(asgmt, RegionRelationAssessments), f"Expecting type to be of RegionRelationAssessments, but is {type(asgmt)}"
|
|
895
|
+
assert isinstance(asgmt.assigned_structure, Region), f"Expecting assigned structure to be of type Region, but is {type(asgmt.assigned_structure)}"
|
|
896
|
+
return_val.append((
|
|
897
|
+
asgmt.assigned_structure.parcellation.id,
|
|
898
|
+
asgmt.assigned_structure.name,
|
|
899
|
+
asgmt.qualification.name,
|
|
900
|
+
asgmt.explanation
|
|
901
|
+
))
|
|
902
|
+
return tuple(return_val)
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
def get_peid_from_region(region: Region) -> str:
|
|
906
|
+
"""
|
|
907
|
+
Given a region, obtain the Parcellation Entity ID.
|
|
908
|
+
|
|
909
|
+
Parameters
|
|
910
|
+
----------
|
|
911
|
+
region : Region
|
|
912
|
+
|
|
913
|
+
Returns
|
|
914
|
+
-------
|
|
915
|
+
str
|
|
916
|
+
"""
|
|
917
|
+
if region._spec:
|
|
918
|
+
region_peid = region._spec.get("ebrains", {}).get("openminds/ParcellationEntity")
|
|
919
|
+
if region_peid:
|
|
920
|
+
return region_peid
|
|
921
|
+
# In some cases (e.g. Julich Brain, PE is defined on the parent leaf nodes)
|
|
922
|
+
if region.parent and region.parent._spec:
|
|
923
|
+
parent_peid = region.parent._spec.get("ebrains", {}).get("openminds/ParcellationEntity")
|
|
924
|
+
if parent_peid:
|
|
925
|
+
return parent_peid
|
|
926
|
+
return None
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def get_related_regions(region: Region) -> Iterable["RegionRelationAssessments"]:
|
|
930
|
+
"""
|
|
931
|
+
Get assements on relations of a region to others defined on EBRAINS.
|
|
932
|
+
|
|
933
|
+
Parameters
|
|
934
|
+
----------
|
|
935
|
+
region: Region
|
|
936
|
+
|
|
937
|
+
Yields
|
|
938
|
+
------
|
|
939
|
+
Qualification
|
|
940
|
+
|
|
941
|
+
Example
|
|
942
|
+
-------
|
|
943
|
+
>>> region = siibra.get_region("monkey", "PG")
|
|
944
|
+
>>> for assesment in siibra.core.region.get_related_regions(region):
|
|
945
|
+
>>> print(assesment)
|
|
946
|
+
'PG' is homologous to 'Area PGa (IPL)'
|
|
947
|
+
'PG' is homologous to 'Area PGa (IPL) left'
|
|
948
|
+
'PG' is homologous to 'Area PGa (IPL) right'
|
|
949
|
+
'PG' is homologous to 'Area PGa (IPL)'
|
|
950
|
+
'PG' is homologous to 'Area PGa (IPL) left'
|
|
951
|
+
'PG' is homologous to 'Area PGa (IPL) right'
|
|
952
|
+
'PG' is homologous to 'Area PGa (IPL)'
|
|
953
|
+
'PG' is homologous to 'Area PGa (IPL) right'
|
|
954
|
+
'PG' is homologous to 'Area PGa (IPL) left'
|
|
955
|
+
"""
|
|
956
|
+
logger.info("get related region called")
|
|
957
|
+
pe_id = get_peid_from_region(region)
|
|
958
|
+
if not pe_id:
|
|
959
|
+
return []
|
|
960
|
+
|
|
961
|
+
for parc_id, region_name, qual, explanation in _get_related_regions_str(pe_id):
|
|
962
|
+
parc = _parcellation.Parcellation.get_instance(parc_id)
|
|
963
|
+
found_region = parc.get_region(region_name)
|
|
964
|
+
yield RegionRelationAssessments(region, found_region, qual, explanation)
|
|
965
|
+
|
|
966
|
+
|
|
881
967
|
_get_reg_relation_asmgt_types: Dict[str, Callable] = {}
|
|
882
968
|
|
|
883
969
|
|
|
@@ -893,11 +979,32 @@ def _register_region_reference_type(ebrain_type: str):
|
|
|
893
979
|
|
|
894
980
|
|
|
895
981
|
class RegionRelationAssessments(AnatomicalAssignment[Region]):
|
|
982
|
+
"""
|
|
983
|
+
A collection of methods on finding related regions and the quantification
|
|
984
|
+
of the relationship.
|
|
985
|
+
"""
|
|
896
986
|
|
|
897
987
|
anony_client = BucketApiClient()
|
|
898
988
|
|
|
899
989
|
@staticmethod
|
|
900
|
-
def get_uuid(long_id: Union[str, Dict]):
|
|
990
|
+
def get_uuid(long_id: Union[str, Dict]) -> str:
|
|
991
|
+
"""
|
|
992
|
+
Returns the uuid portion of either a fully formed openminds id, or get
|
|
993
|
+
the 'id' property first, and extract the uuid portion of the id.
|
|
994
|
+
|
|
995
|
+
Parameters
|
|
996
|
+
----------
|
|
997
|
+
long_id: str, dict[str, str]
|
|
998
|
+
|
|
999
|
+
Returns
|
|
1000
|
+
-------
|
|
1001
|
+
str
|
|
1002
|
+
|
|
1003
|
+
Raises
|
|
1004
|
+
------
|
|
1005
|
+
AssertionError
|
|
1006
|
+
RuntimeError
|
|
1007
|
+
"""
|
|
901
1008
|
if isinstance(long_id, str):
|
|
902
1009
|
pass
|
|
903
1010
|
elif isinstance(long_id, dict):
|
|
@@ -911,6 +1018,22 @@ class RegionRelationAssessments(AnatomicalAssignment[Region]):
|
|
|
911
1018
|
|
|
912
1019
|
@staticmethod
|
|
913
1020
|
def parse_id_arg(_id: Union[str, List[str]]) -> List[str]:
|
|
1021
|
+
"""
|
|
1022
|
+
Normalizes the ebrains id property. The ebrains id field can be either
|
|
1023
|
+
a str or list[str]. This method normalizes it to always be list[str].
|
|
1024
|
+
|
|
1025
|
+
Parameters
|
|
1026
|
+
----------
|
|
1027
|
+
_id: strl, list[str]
|
|
1028
|
+
|
|
1029
|
+
Returns
|
|
1030
|
+
-------
|
|
1031
|
+
list[str]
|
|
1032
|
+
|
|
1033
|
+
Raises
|
|
1034
|
+
------
|
|
1035
|
+
RuntimeError
|
|
1036
|
+
"""
|
|
914
1037
|
if isinstance(_id, list):
|
|
915
1038
|
assert all(isinstance(_i, str) for _i in _id), "all instances of pev should be str"
|
|
916
1039
|
elif isinstance(_id, str):
|
|
@@ -921,11 +1044,34 @@ class RegionRelationAssessments(AnatomicalAssignment[Region]):
|
|
|
921
1044
|
|
|
922
1045
|
@classmethod
|
|
923
1046
|
def get_object(cls, obj: str):
|
|
1047
|
+
"""
|
|
1048
|
+
Gets given a object (path), loads the content and serializes to json.
|
|
1049
|
+
Relative to the bucket 'reference-atlas-data'.
|
|
1050
|
+
|
|
1051
|
+
Parameters
|
|
1052
|
+
----------
|
|
1053
|
+
obj: str
|
|
1054
|
+
|
|
1055
|
+
Returns
|
|
1056
|
+
-------
|
|
1057
|
+
dict
|
|
1058
|
+
"""
|
|
924
1059
|
bucket = cls.anony_client.buckets.get_bucket("reference-atlas-data")
|
|
925
1060
|
return json.loads(bucket.get_file(obj).get_content())
|
|
926
1061
|
|
|
927
1062
|
@classmethod
|
|
928
1063
|
def get_snapshot_factory(cls, type_str: str):
|
|
1064
|
+
"""
|
|
1065
|
+
Factory method for given type.
|
|
1066
|
+
|
|
1067
|
+
Parameters
|
|
1068
|
+
----------
|
|
1069
|
+
type_str: str
|
|
1070
|
+
|
|
1071
|
+
Returns
|
|
1072
|
+
-------
|
|
1073
|
+
Callable[[str|list[str]], dict]
|
|
1074
|
+
"""
|
|
929
1075
|
def get_objects(_id: Union[str, List[str]]):
|
|
930
1076
|
_id = cls.parse_id_arg(_id)
|
|
931
1077
|
with ThreadPoolExecutor() as ex:
|
|
@@ -938,7 +1084,19 @@ class RegionRelationAssessments(AnatomicalAssignment[Region]):
|
|
|
938
1084
|
|
|
939
1085
|
@classmethod
|
|
940
1086
|
def parse_relationship_assessment(cls, src: "Region", assessment):
|
|
1087
|
+
"""
|
|
1088
|
+
Given a region, and the fetched assessment json, yield
|
|
1089
|
+
RegionRelationAssignment object.
|
|
941
1090
|
|
|
1091
|
+
Parameters
|
|
1092
|
+
----------
|
|
1093
|
+
src: Region
|
|
1094
|
+
assessment: dict
|
|
1095
|
+
|
|
1096
|
+
Returns
|
|
1097
|
+
-------
|
|
1098
|
+
Iterable[RegionRelationAssessments]
|
|
1099
|
+
"""
|
|
942
1100
|
all_regions = [
|
|
943
1101
|
region
|
|
944
1102
|
for p in _parcellation.Parcellation.registry()
|
|
@@ -959,7 +1117,11 @@ class RegionRelationAssessments(AnatomicalAssignment[Region]):
|
|
|
959
1117
|
]
|
|
960
1118
|
|
|
961
1119
|
for found_target in found_targets:
|
|
962
|
-
yield cls(
|
|
1120
|
+
yield cls(
|
|
1121
|
+
query_structure=src,
|
|
1122
|
+
assigned_structure=found_target,
|
|
1123
|
+
qualification=Qualification.parse_relation_assessment(overlap)
|
|
1124
|
+
)
|
|
963
1125
|
|
|
964
1126
|
if "https://openminds.ebrains.eu/sands/ParcellationEntity" in target.get("type"):
|
|
965
1127
|
pev_uuids = [
|
|
@@ -969,28 +1131,47 @@ class RegionRelationAssessments(AnatomicalAssignment[Region]):
|
|
|
969
1131
|
]
|
|
970
1132
|
for reg in all_regions:
|
|
971
1133
|
if reg in pev_uuids:
|
|
972
|
-
yield cls(
|
|
1134
|
+
yield cls(
|
|
1135
|
+
query_structure=src,
|
|
1136
|
+
assigned_structure=reg,
|
|
1137
|
+
qualification=Qualification.parse_relation_assessment(overlap)
|
|
1138
|
+
)
|
|
973
1139
|
|
|
974
1140
|
@classmethod
|
|
975
1141
|
@_register_region_reference_type("openminds/CustomAnatomicalEntity")
|
|
976
1142
|
def translate_cae(cls, src: "Region", _id: Union[str, List[str]]):
|
|
1143
|
+
"""Register how CustomAnatomicalEntity should be parsed
|
|
1144
|
+
|
|
1145
|
+
Parameters
|
|
1146
|
+
----------
|
|
1147
|
+
src: Region
|
|
1148
|
+
_id: str|list[str]
|
|
1149
|
+
|
|
1150
|
+
Returns
|
|
1151
|
+
-------
|
|
1152
|
+
Iterable[RegionRelationAssessments]
|
|
1153
|
+
"""
|
|
977
1154
|
caes = cls.get_snapshot_factory("CustomAnatomicalEntity")(_id)
|
|
978
1155
|
for cae in caes:
|
|
979
1156
|
for ass in cae.get("relationAssessment", []):
|
|
980
1157
|
yield from cls.parse_relationship_assessment(src, ass)
|
|
981
1158
|
|
|
982
1159
|
@classmethod
|
|
983
|
-
@_register_region_reference_type("openminds/
|
|
984
|
-
def
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1160
|
+
@_register_region_reference_type("openminds/ParcellationEntity")
|
|
1161
|
+
def translate_pes(cls, src: "Region", _id: Union[str, List[str]]):
|
|
1162
|
+
"""
|
|
1163
|
+
Register how ParcellationEntity should be parsed
|
|
1164
|
+
|
|
1165
|
+
Parameters
|
|
1166
|
+
----------
|
|
1167
|
+
src: Region
|
|
1168
|
+
_id: str|list[str]
|
|
1169
|
+
|
|
1170
|
+
Returns
|
|
1171
|
+
-------
|
|
1172
|
+
Iterable[RegionRelationAssessments]
|
|
1173
|
+
"""
|
|
1174
|
+
pes = cls.get_snapshot_factory("ParcellationEntity")(_id)
|
|
994
1175
|
|
|
995
1176
|
all_regions = [
|
|
996
1177
|
region
|
|
@@ -999,29 +1180,14 @@ class RegionRelationAssessments(AnatomicalAssignment[Region]):
|
|
|
999
1180
|
]
|
|
1000
1181
|
|
|
1001
1182
|
for pe in pes:
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
has_versions = pe.get("hasVersion", [])
|
|
1005
|
-
for has_version in has_versions:
|
|
1006
|
-
uuid = cls.get_uuid(has_version)
|
|
1007
|
-
|
|
1008
|
-
# ignore if uuid is referring to src region
|
|
1009
|
-
if uuid == src:
|
|
1010
|
-
continue
|
|
1011
|
-
|
|
1012
|
-
found_targets = [
|
|
1013
|
-
region
|
|
1014
|
-
for region in all_regions
|
|
1015
|
-
if region == uuid
|
|
1016
|
-
]
|
|
1017
|
-
if len(found_targets) == 0:
|
|
1018
|
-
logger.warning(f"other version with uuid {uuid} not found")
|
|
1183
|
+
for region in all_regions:
|
|
1184
|
+
if region is src:
|
|
1019
1185
|
continue
|
|
1020
|
-
|
|
1021
|
-
|
|
1186
|
+
region_peid = get_peid_from_region(region)
|
|
1187
|
+
if region_peid and (region_peid in pe.get("id")):
|
|
1022
1188
|
yield cls(
|
|
1023
1189
|
query_structure=src,
|
|
1024
|
-
assigned_structure=
|
|
1190
|
+
assigned_structure=region,
|
|
1025
1191
|
qualification=Qualification.OTHER_VERSION
|
|
1026
1192
|
)
|
|
1027
1193
|
|
|
@@ -1030,8 +1196,45 @@ class RegionRelationAssessments(AnatomicalAssignment[Region]):
|
|
|
1030
1196
|
for relation in relations:
|
|
1031
1197
|
yield from cls.parse_relationship_assessment(src, relation)
|
|
1032
1198
|
|
|
1199
|
+
@classmethod
|
|
1200
|
+
@_register_region_reference_type("openminds/ParcellationEntityVersion")
|
|
1201
|
+
def translate_pevs(cls, src: "Region", _id: Union[str, List[str]]):
|
|
1202
|
+
"""
|
|
1203
|
+
Register how ParcellationEntityVersion should be parsed
|
|
1204
|
+
|
|
1205
|
+
Parameters
|
|
1206
|
+
----------
|
|
1207
|
+
src: Region
|
|
1208
|
+
_id: str|list[str]
|
|
1209
|
+
|
|
1210
|
+
Returns
|
|
1211
|
+
-------
|
|
1212
|
+
Iterable[RegionRelationAssessments]
|
|
1213
|
+
"""
|
|
1214
|
+
pe_uuids = [
|
|
1215
|
+
uuid for uuid in
|
|
1216
|
+
{
|
|
1217
|
+
cls.get_uuid(pe)
|
|
1218
|
+
for pev in cls.get_snapshot_factory("ParcellationEntityVersion")(_id)
|
|
1219
|
+
for pe in pev.get("isVersionOf")
|
|
1220
|
+
}
|
|
1221
|
+
]
|
|
1222
|
+
yield from cls.translate_pes(src, pe_uuids)
|
|
1223
|
+
|
|
1033
1224
|
@classmethod
|
|
1034
1225
|
def parse_from_region(cls, region: "Region") -> Iterable["RegionRelationAssessments"]:
|
|
1226
|
+
"""
|
|
1227
|
+
Main entry on how related regions should be retrieved. Given a region,
|
|
1228
|
+
retrieves all RegionRelationAssessments
|
|
1229
|
+
|
|
1230
|
+
Parameters
|
|
1231
|
+
----------
|
|
1232
|
+
region: Region
|
|
1233
|
+
|
|
1234
|
+
Returns
|
|
1235
|
+
-------
|
|
1236
|
+
Iterable[RegionRelationAssessments]
|
|
1237
|
+
"""
|
|
1035
1238
|
if not region._spec:
|
|
1036
1239
|
return None
|
|
1037
1240
|
for ebrain_type, ebrain_ref in region._spec.get("ebrains", {}).items():
|
siibra/features/__init__.py
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
"""Multimodal data features types and query mechanisms."""
|
|
16
16
|
|
|
17
|
+
from typing import Union
|
|
18
|
+
from functools import partial
|
|
19
|
+
|
|
17
20
|
from . import (
|
|
18
21
|
connectivity,
|
|
19
22
|
tabular,
|
|
@@ -21,7 +24,8 @@ from . import (
|
|
|
21
24
|
dataset,
|
|
22
25
|
)
|
|
23
26
|
|
|
24
|
-
from
|
|
27
|
+
from ..commons import logger
|
|
28
|
+
|
|
25
29
|
from .feature import Feature
|
|
26
30
|
from ..retrieval import cache
|
|
27
31
|
from ..commons import siibra_tqdm
|
|
@@ -49,7 +53,7 @@ def __getattr__(attr: str):
|
|
|
49
53
|
|
|
50
54
|
|
|
51
55
|
@cache.Warmup.register_warmup_fn()
|
|
52
|
-
def
|
|
56
|
+
def _warm_feature_cache_instances():
|
|
53
57
|
"""Preload preconfigured multimodal data features."""
|
|
54
58
|
for ftype in TYPES.values():
|
|
55
59
|
_ = ftype._get_instances()
|
|
@@ -60,18 +64,25 @@ def _warm_feature_cache_data():
|
|
|
60
64
|
return_callables = []
|
|
61
65
|
for ftype in TYPES.values():
|
|
62
66
|
instances = ftype._get_instances()
|
|
67
|
+
|
|
68
|
+
# the instances *must* be cleared, or it will impede the garbage collection, and results in memleak
|
|
69
|
+
ftype._clean_instances()
|
|
63
70
|
tally = siibra_tqdm(desc=f"Warming data {ftype.__name__}", total=len(instances))
|
|
64
71
|
for f in instances:
|
|
65
|
-
def get_data():
|
|
72
|
+
def get_data(arg):
|
|
73
|
+
tally = arg.pop("tally")
|
|
74
|
+
feature = arg.pop("feature")
|
|
66
75
|
# TODO
|
|
67
76
|
# the try catch is as a result of https://github.com/FZJ-INM1-BDA/siibra-python/issues/509
|
|
68
77
|
# sometimes f.data can fail
|
|
69
78
|
try:
|
|
70
|
-
_ =
|
|
71
|
-
except Exception:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
_ = feature.data
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.warn(f"Feature {feature.name} warmup failed: {str(e)}")
|
|
82
|
+
finally:
|
|
83
|
+
tally.update(1)
|
|
84
|
+
# append dictionary, so that popping the dictionary will mark the feature to be garbage collected
|
|
85
|
+
return_callables.append(partial(get_data, {"feature": f, "tally": tally}))
|
|
75
86
|
return return_callables
|
|
76
87
|
|
|
77
88
|
|
|
@@ -19,7 +19,7 @@ from ..tabular.tabular import Tabular
|
|
|
19
19
|
|
|
20
20
|
from .. import anchor as _anchor
|
|
21
21
|
|
|
22
|
-
from ...commons import logger, QUIET
|
|
22
|
+
from ...commons import logger, QUIET, siibra_tqdm
|
|
23
23
|
from ...core import region as _region
|
|
24
24
|
from ...locations import pointset
|
|
25
25
|
from ...retrieval.repositories import RepositoryConnector
|
|
@@ -57,7 +57,8 @@ class RegionalConnectivity(Feature, Compoundable):
|
|
|
57
57
|
description: str = "",
|
|
58
58
|
datasets: list = [],
|
|
59
59
|
subject: str = "average",
|
|
60
|
-
feature: str = None
|
|
60
|
+
feature: str = None,
|
|
61
|
+
id: str = None
|
|
61
62
|
):
|
|
62
63
|
"""
|
|
63
64
|
Construct a parcellation-averaged connectivity matrix.
|
|
@@ -91,6 +92,7 @@ class RegionalConnectivity(Feature, Compoundable):
|
|
|
91
92
|
description=description,
|
|
92
93
|
anchor=anchor,
|
|
93
94
|
datasets=datasets,
|
|
95
|
+
id=id
|
|
94
96
|
)
|
|
95
97
|
self.cohort = cohort.upper()
|
|
96
98
|
if isinstance(connector, str) and connector:
|
|
@@ -116,7 +118,7 @@ class RegionalConnectivity(Feature, Compoundable):
|
|
|
116
118
|
|
|
117
119
|
@property
|
|
118
120
|
def name(self):
|
|
119
|
-
return f"{
|
|
121
|
+
return f"{self.feature or self.subject} - " + super().name + f" cohort: {self.cohort}"
|
|
120
122
|
|
|
121
123
|
@property
|
|
122
124
|
def data(self) -> pd.DataFrame:
|
|
@@ -132,6 +134,45 @@ class RegionalConnectivity(Feature, Compoundable):
|
|
|
132
134
|
self._load_matrix()
|
|
133
135
|
return self._matrix.copy()
|
|
134
136
|
|
|
137
|
+
@classmethod
|
|
138
|
+
def _merge_elements(
|
|
139
|
+
cls,
|
|
140
|
+
elements: List["RegionalConnectivity"],
|
|
141
|
+
description: str,
|
|
142
|
+
modality: str,
|
|
143
|
+
anchor: _anchor.AnatomicalAnchor,
|
|
144
|
+
):
|
|
145
|
+
assert len({f.cohort for f in elements}) == 1
|
|
146
|
+
merged = cls(
|
|
147
|
+
cohort=elements[0].cohort,
|
|
148
|
+
regions=elements[0].regions,
|
|
149
|
+
connector=elements[0]._connector,
|
|
150
|
+
decode_func=elements[0]._decode_func,
|
|
151
|
+
filename="",
|
|
152
|
+
subject="average",
|
|
153
|
+
feature="average",
|
|
154
|
+
description=description,
|
|
155
|
+
modality=modality,
|
|
156
|
+
anchor=anchor,
|
|
157
|
+
**{"paradigm": "averaged (by siibra)"} if getattr(elements[0], "paradigm", None) else {}
|
|
158
|
+
)
|
|
159
|
+
if isinstance(elements[0]._connector, HttpRequest):
|
|
160
|
+
getter = lambda elm: elm._connector.get()
|
|
161
|
+
else:
|
|
162
|
+
getter = lambda elm: elm._connector.get(elm._filename, decode_func=elm._decode_func)
|
|
163
|
+
all_arrays = [
|
|
164
|
+
getter(elm)
|
|
165
|
+
for elm in siibra_tqdm(
|
|
166
|
+
elements,
|
|
167
|
+
total=len(elements),
|
|
168
|
+
desc=f"Averaging {len(elements)} connectivity matrices"
|
|
169
|
+
)
|
|
170
|
+
]
|
|
171
|
+
merged._matrix = elements[0]._arraylike_to_dataframe(
|
|
172
|
+
np.stack(all_arrays).mean(0)
|
|
173
|
+
)
|
|
174
|
+
return merged
|
|
175
|
+
|
|
135
176
|
def _plot_matrix(
|
|
136
177
|
self, regions: List[str] = None,
|
|
137
178
|
logscale: bool = False, *args, backend="nilearn", **kwargs
|
|
@@ -179,6 +220,7 @@ class RegionalConnectivity(Feature, Compoundable):
|
|
|
179
220
|
**kwargs
|
|
180
221
|
)
|
|
181
222
|
elif backend == "plotly":
|
|
223
|
+
kwargs["title"] = kwargs["title"].replace("\n", "<br>")
|
|
182
224
|
from plotly.express import imshow
|
|
183
225
|
return imshow(matrix, *args, x=regions, y=regions, **kwargs)
|
|
184
226
|
else:
|