siibra 1.0a8__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.

Files changed (37) hide show
  1. siibra/VERSION +1 -1
  2. siibra/commons.py +75 -30
  3. siibra/configuration/factory.py +16 -17
  4. siibra/core/atlas.py +40 -16
  5. siibra/core/region.py +242 -39
  6. siibra/features/__init__.py +19 -8
  7. siibra/features/connectivity/functional_connectivity.py +1 -1
  8. siibra/features/connectivity/regional_connectivity.py +45 -3
  9. siibra/features/feature.py +63 -13
  10. siibra/features/image/image.py +3 -1
  11. siibra/features/tabular/bigbrain_intensity_profile.py +1 -1
  12. siibra/features/tabular/cell_density_profile.py +5 -3
  13. siibra/features/tabular/cortical_profile.py +79 -15
  14. siibra/features/tabular/gene_expression.py +110 -1
  15. siibra/features/tabular/layerwise_bigbrain_intensities.py +1 -1
  16. siibra/features/tabular/layerwise_cell_density.py +3 -1
  17. siibra/features/tabular/receptor_density_fingerprint.py +3 -1
  18. siibra/features/tabular/receptor_density_profile.py +3 -5
  19. siibra/features/tabular/regional_timeseries_activity.py +59 -10
  20. siibra/features/tabular/tabular.py +4 -2
  21. siibra/livequeries/bigbrain.py +34 -0
  22. siibra/locations/location.py +1 -1
  23. siibra/retrieval/cache.py +15 -10
  24. siibra/retrieval/repositories.py +2 -2
  25. siibra/retrieval/requests.py +30 -1
  26. siibra/volumes/parcellationmap.py +18 -22
  27. siibra/volumes/providers/__init__.py +1 -0
  28. siibra/volumes/providers/freesurfer.py +113 -0
  29. siibra/volumes/providers/neuroglancer.py +55 -25
  30. siibra/volumes/providers/nifti.py +14 -16
  31. siibra/volumes/sparsemap.py +1 -1
  32. siibra/volumes/volume.py +13 -15
  33. {siibra-1.0a8.dist-info → siibra-1.0a11.dist-info}/METADATA +1 -1
  34. {siibra-1.0a8.dist-info → siibra-1.0a11.dist-info}/RECORD +37 -36
  35. {siibra-1.0a8.dist-info → siibra-1.0a11.dist-info}/LICENSE +0 -0
  36. {siibra-1.0a8.dist-info → siibra-1.0a11.dist-info}/WHEEL +0 -0
  37. {siibra-1.0a8.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["Qualification"]:
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.
@@ -309,7 +313,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
309
313
 
310
314
  for flag in flags or []: # catch if flags is nullish
311
315
  if flag not in self._accepted_flags:
312
- raise Exception(f"only accepted flag are in { self._accepted_flags }. {flag} is not within them")
316
+ raise Exception(f"only accepted flag are in {self._accepted_flags}. {flag} is not within them")
313
317
  search_regex = (f"(?{flags})" if flags else "") + expression
314
318
  regionspec = re.compile(search_regex)
315
319
 
@@ -697,7 +701,8 @@ class Region(anytree.NodeMixin, concept.AtlasConcept, structure.BrainStructure):
697
701
  restrict_space=False,
698
702
  **fetch_kwargs
699
703
  ):
700
- """Compute the bounding box of this region in the given space.
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
- """ Use this region for filtering a location object. """
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(query_structure=src, assigned_structure=found_target, qualification=Qualification.parse_relation_assessment(overlap))
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(query_structure=src, assigned_structure=reg, qualification=Qualification.parse_relation_assessment(overlap))
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/ParcellationEntityVersion")
984
- def translate_pevs(cls, src: "Region", _id: Union[str, List[str]]):
985
- pe_uuids = [
986
- uuid for uuid in
987
- {
988
- cls.get_uuid(pe)
989
- for pev in cls.get_snapshot_factory("ParcellationEntityVersion")(_id)
990
- for pe in pev.get("isVersionOf")
991
- }
992
- ]
993
- pes = cls.get_snapshot_factory("ParcellationEntity")(pe_uuids)
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
- # other versions
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
- for found_target in found_targets:
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=found_target,
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():
@@ -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 typing import Union
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 _warm_feature_cache_insntaces():
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
- _ = f.data
71
- except Exception:
72
- ...
73
- tally.update(1)
74
- return_callables.append(get_data)
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
 
@@ -41,7 +41,7 @@ class FunctionalConnectivity(
41
41
 
42
42
  @property
43
43
  def name(self):
44
- return f"{super().name}, {self.paradigm} paradigm"
44
+ return super().name + f", paradigm: {self.paradigm}"
45
45
 
46
46
 
47
47
  class AnatomoFunctionalConnectivity(
@@ -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"{super().name} with cohort {self.cohort} - {self.feature or self.subject}"
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: