siibra 0.4a33__py3-none-any.whl → 0.4a46__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 (64) hide show
  1. siibra/VERSION +1 -1
  2. siibra/__init__.py +2 -0
  3. siibra/commons.py +53 -8
  4. siibra/configuration/configuration.py +21 -17
  5. siibra/configuration/factory.py +95 -19
  6. siibra/core/atlas.py +11 -8
  7. siibra/core/concept.py +41 -8
  8. siibra/core/parcellation.py +94 -43
  9. siibra/core/region.py +160 -187
  10. siibra/core/space.py +44 -39
  11. siibra/features/__init__.py +19 -19
  12. siibra/features/anchor.py +9 -6
  13. siibra/features/connectivity/__init__.py +0 -8
  14. siibra/features/connectivity/functional_connectivity.py +11 -3
  15. siibra/features/{basetypes → connectivity}/regional_connectivity.py +46 -33
  16. siibra/features/connectivity/streamline_counts.py +3 -2
  17. siibra/features/connectivity/streamline_lengths.py +3 -2
  18. siibra/features/{basetypes → dataset}/__init__.py +2 -0
  19. siibra/features/{external → dataset}/ebrains.py +3 -3
  20. siibra/features/feature.py +420 -0
  21. siibra/{samplers → features/image}/__init__.py +7 -1
  22. siibra/features/{basetypes/volume_of_interest.py → image/image.py} +12 -7
  23. siibra/features/{external/__init__.py → image/sections.py} +8 -5
  24. siibra/features/image/volume_of_interest.py +70 -0
  25. siibra/features/{cellular → tabular}/__init__.py +7 -11
  26. siibra/features/{cellular → tabular}/bigbrain_intensity_profile.py +5 -2
  27. siibra/features/{cellular → tabular}/cell_density_profile.py +6 -2
  28. siibra/features/{basetypes → tabular}/cortical_profile.py +48 -41
  29. siibra/features/{molecular → tabular}/gene_expression.py +5 -2
  30. siibra/features/{cellular → tabular}/layerwise_bigbrain_intensities.py +6 -2
  31. siibra/features/{cellular → tabular}/layerwise_cell_density.py +9 -3
  32. siibra/features/{molecular → tabular}/receptor_density_fingerprint.py +3 -2
  33. siibra/features/{molecular → tabular}/receptor_density_profile.py +6 -2
  34. siibra/features/tabular/regional_timeseries_activity.py +213 -0
  35. siibra/features/{basetypes → tabular}/tabular.py +14 -9
  36. siibra/livequeries/allen.py +1 -1
  37. siibra/livequeries/bigbrain.py +2 -3
  38. siibra/livequeries/ebrains.py +3 -9
  39. siibra/livequeries/query.py +1 -1
  40. siibra/locations/location.py +4 -3
  41. siibra/locations/point.py +21 -17
  42. siibra/locations/pointset.py +2 -2
  43. siibra/retrieval/__init__.py +1 -1
  44. siibra/retrieval/cache.py +8 -2
  45. siibra/retrieval/datasets.py +149 -29
  46. siibra/retrieval/repositories.py +19 -8
  47. siibra/retrieval/requests.py +98 -116
  48. siibra/volumes/gifti.py +26 -11
  49. siibra/volumes/neuroglancer.py +35 -19
  50. siibra/volumes/nifti.py +8 -9
  51. siibra/volumes/parcellationmap.py +341 -184
  52. siibra/volumes/sparsemap.py +67 -53
  53. siibra/volumes/volume.py +25 -13
  54. {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/METADATA +4 -3
  55. siibra-0.4a46.dist-info/RECORD +69 -0
  56. {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/WHEEL +1 -1
  57. siibra/features/basetypes/feature.py +0 -248
  58. siibra/features/fibres/__init__.py +0 -14
  59. siibra/features/functional/__init__.py +0 -14
  60. siibra/features/molecular/__init__.py +0 -26
  61. siibra/samplers/bigbrain.py +0 -181
  62. siibra-0.4a33.dist-info/RECORD +0 -71
  63. {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/LICENSE +0 -0
  64. {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/top_level.txt +0 -0
siibra/core/region.py CHANGED
@@ -20,12 +20,11 @@ from ..volumes import parcellationmap
20
20
 
21
21
  from ..commons import (
22
22
  logger,
23
- MapIndex,
24
23
  MapType,
25
- compare_maps,
26
24
  affine_scaling,
27
25
  create_key,
28
26
  clear_name,
27
+ siibra_tqdm,
29
28
  InstanceTable,
30
29
  SIIBRA_DEFAULT_MAPTYPE,
31
30
  SIIBRA_DEFAULT_MAP_THRESHOLD
@@ -37,12 +36,25 @@ import anytree
37
36
  from typing import List, Set, Union
38
37
  from nibabel import Nifti1Image
39
38
  from difflib import SequenceMatcher
39
+ from dataclasses import dataclass, field
40
40
 
41
41
 
42
42
  REGEX_TYPE = type(re.compile("test"))
43
43
 
44
44
  THRESHOLD_STATISTICAL_MAPS = None
45
45
 
46
+ @dataclass
47
+ class SpatialPropCmpt:
48
+ centroid: point.Point
49
+ volume: int
50
+
51
+
52
+ @dataclass
53
+ class SpatialProp:
54
+ cog:SpatialPropCmpt=None
55
+ components:List[SpatialPropCmpt]=field(default_factory=list)
56
+ space:_space.Space=None
57
+
46
58
 
47
59
  class Region(anytree.NodeMixin, concept.AtlasConcept):
48
60
  """
@@ -71,19 +83,20 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
71
83
  ----------
72
84
  name : str
73
85
  Human-readable name of the region
74
- children: list of Regions,
86
+ children: list[Region]
75
87
  parent: Region
76
88
  shortname: str
77
89
  Shortform of human-readable name (optional)
78
90
  description: str
79
91
  Textual description of the parcellation
80
- modality : str or None
92
+ modality: str or None
81
93
  Specification of the modality used for specifying this region
82
94
  publications: list
83
- List of ssociated publications, each a dictionary with "doi" and/or "citation" fields
84
- datasets : list
95
+ List of associated publications, each a dictionary with "doi"
96
+ and/or "citation" fields
97
+ datasets: list
85
98
  datasets associated with this region
86
- rgb: str, default None
99
+ rgb: str, default: None
87
100
  Hexcode of preferred color of this region (e.g. "#9FE770")
88
101
  """
89
102
  anytree.NodeMixin.__init__(self)
@@ -134,7 +147,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
134
147
  @staticmethod
135
148
  def copy(other: 'Region'):
136
149
  """
137
- copy contructor must detach the parent to avoid problems with
150
+ copy constructor must detach the parent to avoid problems with
138
151
  the Anytree implementation.
139
152
  """
140
153
  # create an isolated object, detached from the other's tree
@@ -153,13 +166,9 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
153
166
  c.parent = region
154
167
  return region
155
168
 
156
- @property
157
- def labels(self):
158
- return {r.index.label for r in self if r.index.label is not None} # Potenially a BUG
159
-
160
169
  @property
161
170
  def names(self):
162
- return {r.key for r in self}
171
+ return {r.name for r in self}
163
172
 
164
173
  def __eq__(self, other):
165
174
  """
@@ -171,9 +180,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
171
180
  elif isinstance(other, str):
172
181
  return any([self.name == other, self.key == other, self.id == other])
173
182
  else:
174
- raise ValueError(
175
- f"Cannot compare object of type {type(other)} to {self.__class__.__name__}"
176
- )
183
+ return False
177
184
 
178
185
  def __hash__(self):
179
186
  return hash(self.id)
@@ -183,7 +190,15 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
183
190
 
184
191
  def includes(self, region):
185
192
  """
186
- Determine wether this regiontree includes the given region.
193
+ Determine whether this region-tree includes the given region.
194
+
195
+ Parameters
196
+ ----------
197
+ region: Region
198
+ Returns
199
+ -------
200
+ bool
201
+ True if the region is in the region-tree.
187
202
  """
188
203
  return region == self or region in self.descendants
189
204
 
@@ -199,23 +214,24 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
199
214
 
200
215
  Parameters
201
216
  ----------
202
- regionspec : any of
203
- - a string with a possibly inexact name, which is matched both
204
- against the name and the identifier key,
205
- - a string in '/pattern/flags' format to use regex search (acceptable flags: aiLmsux),
206
- - a regex applied to region names,
207
- - an integer, which is interpreted as a labelindex,
208
- - a full MapIndex
209
- - a region object
210
- filter_children : Boolean
211
- If true, children of matched parents will not be returned
212
- find_topmost : Bool, default: True
213
- If True, will return parent structures if all children are matched,
214
- even though the parent itself might not match the specification.
215
-
216
- Yield
217
- -----
218
- list of matching regions
217
+ regionspec: str, regex, int, Region
218
+ - a string with a possibly inexact name (matched both against the name and the identifier key)
219
+ - a string in '/pattern/flags' format to use regex search (acceptable flags: aiLmsux)
220
+ - a regex applied to region names
221
+ - a Region object
222
+ filter_children : bool, default: False
223
+ If True, children of matched parents will not be returned
224
+ find_topmost : bool, default: True
225
+ If True (requires `filter_children=True`), will return parent
226
+ structures if all children are matched, even though the parent
227
+ itself might not match the specification.
228
+ Returns
229
+ -------
230
+ list[Region]
231
+ list of regions matching to the regionspec
232
+ Tip
233
+ ---
234
+ See example 01-003, find regions.
219
235
  """
220
236
  key = (regionspec, filter_children, find_topmost)
221
237
  MEM = self._CACHED_REGION_SEARCHES
@@ -234,40 +250,19 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
234
250
  search_regex = (f"(?{flags})" if flags else "") + expression
235
251
  regionspec = re.compile(search_regex)
236
252
 
237
- if regionspec in self.names:
238
- # key is given, this gives us an exact region
239
- match = anytree.search.find_by_attr(self, regionspec, name="key")
240
- MEM[key] = [] if match is None else [match]
241
- return list(MEM[key])
242
-
243
253
  candidates = list(
244
- set(anytree.search.findall(self, lambda node: node.matches(regionspec)))
254
+ anytree.search.findall(self, lambda node: node.matches(regionspec))
245
255
  )
246
256
 
247
257
  if len(candidates) > 1 and filter_children:
248
-
249
258
  filtered = []
250
259
  for region in candidates:
251
260
  children_included = [c for c in region.children if c in candidates]
252
- # if the parcellation index matches only approximately,
253
- # while a child has an exact matching index, use the child.
254
261
  if len(children_included) > 0:
255
- if not (
256
- isinstance(regionspec, MapIndex)
257
- and (region.index != regionspec)
258
- and any(c.index == regionspec for c in children_included)
259
- ):
260
- filtered.append(region)
262
+ filtered.append(region)
261
263
  else:
262
264
  if region.parent not in candidates:
263
265
  filtered.append(region)
264
- else:
265
- if (
266
- isinstance(regionspec, MapIndex)
267
- and (region.index == regionspec)
268
- and (region.parent.index != regionspec)
269
- ):
270
- filtered.append(region)
271
266
 
272
267
  # find any non-matched regions of which all children are matched
273
268
  if find_topmost:
@@ -285,7 +280,10 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
285
280
  else:
286
281
  # filter child regions again
287
282
  filtered += complete_parents
288
- candidates = [r for r in filtered if r.parent not in filtered]
283
+ candidates = [
284
+ r for r in filtered
285
+ if (r.parent not in filtered) or r == regionspec
286
+ ]
289
287
  else:
290
288
  candidates = filtered
291
289
 
@@ -297,12 +295,12 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
297
295
  else:
298
296
  candidates = list(candidates)
299
297
 
300
- found_regions = sorted(candidates, key=lambda r: r.depth)
298
+ found_regions = sorted(set(candidates), key=lambda r: r.depth)
301
299
 
302
300
  # reverse is set to True, since SequenceMatcher().ratio(), higher == better
303
301
  MEM[key] = (
304
302
  sorted(
305
- set(found_regions),
303
+ found_regions,
306
304
  reverse=True,
307
305
  key=lambda region: SequenceMatcher(None, str(region), regionspec).ratio(),
308
306
  )
@@ -314,20 +312,19 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
314
312
 
315
313
  def matches(self, regionspec):
316
314
  """
317
- Checks wether this region matches the given region specification.
315
+ Checks whether this region matches the given region specification.
318
316
 
319
317
  Parameters
320
- ---------
321
-
322
- regionspec : any of
323
- - a string with a possibly inexact name, which is matched both
324
- against the name and the identifier key,
318
+ ----------
319
+ regionspec: str, regex, Region
320
+ - a string with a possibly inexact name, which is matched both against the name and the identifier key,
325
321
  - a regex applied to region names,
326
322
  - a region object
327
323
 
328
- Yield
329
- -----
330
- True or False
324
+ Returns
325
+ -------
326
+ bool
327
+ If the regionspec matches to the Region.
331
328
  """
332
329
  if regionspec is None:
333
330
  return False
@@ -360,6 +357,8 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
360
357
  elif isinstance(regionspec, REGEX_TYPE):
361
358
  # match regular expression
362
359
  return any(regionspec.search(s) is not None for s in [self.name, self.key])
360
+ elif isinstance(regionspec, (list, tuple)):
361
+ return any(self.matches(_) for _ in regionspec)
363
362
  else:
364
363
  raise TypeError(
365
364
  f"Cannot interpret region specification of type '{type(regionspec)}'"
@@ -374,27 +373,37 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
374
373
  ):
375
374
  """
376
375
  Attempts to build a binary mask of this region in the given space,
377
- using the specified maptypes.
376
+ using the specified MapTypes.
378
377
 
379
378
  Parameters
380
379
  ----------
381
- space: Space object or space name
380
+ space: Space or str
382
381
  The requested reference space
383
- maptype: MapType
384
- the type of map to be used (labelled or statistical)
385
- threshold: float
386
- (optional) When fetching a statistical map, use this
387
- threshold to convert it to a binary mask
388
- via_space: Space object or space name
382
+ maptype: MapType, default: SIIBRA_DEFAULT_MAPTYPE
383
+ The type of map to be used ('labelled' or 'statistical')
384
+ threshold: float, optional
385
+ When fetching a statistical map, use this threshold to convert
386
+ it to a binary mask
387
+ via_space: Space or str
389
388
  If specified, fetch the map in this space first, and then perform
390
389
  a linear warping from there to the requested space.
391
- You might want to use this if a map in the requested space is not available.
392
- Note that this linear warping is an affine approximation of the
393
- nonlinear deformation, computed from the warped corenr points
394
- of the bounding box (see siibra.location.BoundingBox.estimate_affine()).
395
- It does not require voxel resampling,
396
- just replaces the affine matrix, but is less accurate than a full
397
- nonlinear warping, which is not supported in siibra-python for images so far.
390
+
391
+ Tip
392
+ ---
393
+ You might want to use this if a map in the requested space
394
+ is not available.
395
+
396
+ Note
397
+ ----
398
+ This linear warping is an affine approximation of the
399
+ nonlinear deformation, computed from the warped corner points
400
+ of the bounding box (see siibra.locations.BoundingBox.estimate_affine()).
401
+ It does not require voxel resampling, just replaces the affine
402
+ matrix, but is less accurate than a full nonlinear warping,
403
+ which is currently not supported in siibra-python for images.
404
+ Returns
405
+ -------
406
+ Nifti1Image
398
407
  """
399
408
  if isinstance(maptype, str):
400
409
  maptype = MapType[maptype.upper()]
@@ -408,14 +417,12 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
408
417
  fetch_space = _space.Space.get_instance(fetch_space)
409
418
 
410
419
  for m in parcellationmap.Map.registry():
411
- if all(
412
- [
413
- m.space.matches(fetch_space),
414
- m.parcellation == self.parcellation,
415
- m.provides_image,
416
- m.maptype == maptype,
417
- self.name in m.regions
418
- ]
420
+ if (
421
+ m.space.matches(fetch_space) and
422
+ m.parcellation == self.parcellation and
423
+ m.provides_image and
424
+ m.maptype == maptype and
425
+ self.name in m.regions
419
426
  ):
420
427
  result = m.fetch(region=self, format='image')
421
428
  if (maptype == MapType.STATISTICAL) and (threshold is not None):
@@ -430,7 +437,12 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
430
437
  dataobj = None
431
438
  affine = None
432
439
  if all(c.mapped_in_space(fetch_space) for c in self.children):
433
- for c in self.children:
440
+ for c in siibra_tqdm(
441
+ self.children,
442
+ desc=f"Building mask of {self.name}",
443
+ leave=False,
444
+ unit=" child region"
445
+ ):
434
446
  mask = c.fetch_regional_map(fetch_space, maptype, threshold)
435
447
  if dataobj is None:
436
448
  dataobj = np.asanyarray(mask.dataobj)
@@ -466,6 +478,16 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
466
478
  def mapped_in_space(self, space, recurse: bool = True) -> bool:
467
479
  """
468
480
  Verifies wether this region is defined by an explicit map in the given space.
481
+
482
+ Parameters
483
+ ----------
484
+ space: Space or str
485
+ reference space
486
+ recurse: bool, default: True
487
+ If True, check if all child regions are mapped instead
488
+ Returns
489
+ -------
490
+ bool
469
491
  """
470
492
  from ..volumes.parcellationmap import Map
471
493
  for m in Map.registry():
@@ -505,42 +527,6 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
505
527
  elements={s.key: s for s in self.supported_spaces},
506
528
  )
507
529
 
508
- def __getitem__(self, labelindex):
509
- """
510
- Given an integer label index, return the corresponding region.
511
- If multiple matches are found, return the unique parent if possible.
512
- Otherwise, return None
513
-
514
- Parameters
515
- ----------
516
-
517
- regionlabel: int
518
- label index of the desired region.
519
-
520
- Return
521
- ------
522
- Region object
523
- """
524
- if not isinstance(labelindex, int):
525
- raise TypeError(
526
- "Index access into the regiontree expects label indices of integer type"
527
- )
528
-
529
- # first test this head node
530
- if self.index.label == labelindex:
531
- return self
532
-
533
- # Consider children, and return the one with smallest depth
534
- matches = list(
535
- filter(lambda x: x is not None, [c[labelindex] for c in self.children])
536
- )
537
- if matches:
538
- parentmatches = [m for m in matches if m.parent not in matches]
539
- if len(parentmatches) == 1:
540
- return parentmatches[0]
541
-
542
- return None
543
-
544
530
  def __str__(self):
545
531
  return self.name
546
532
 
@@ -548,6 +534,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
548
534
  return self.name
549
535
 
550
536
  def tree2str(self):
537
+ """Render region-tree as a string"""
551
538
  return "\n".join(
552
539
  "%s%s" % (pre, node.name)
553
540
  for pre, _, node
@@ -555,6 +542,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
555
542
  )
556
543
 
557
544
  def render_tree(self):
545
+ """Prints the tree representation of the region"""
558
546
  print(self.tree2str())
559
547
 
560
548
  def get_bounding_box(
@@ -567,17 +555,18 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
567
555
 
568
556
  Parameters
569
557
  ----------
570
- space : Space or str):
558
+ space: Space or str
571
559
  Requested reference space
572
- maptype: MapType
560
+ maptype: MapType, default: MapType.LABELLED
573
561
  Type of map to build ('labelled' will result in a binary mask,
574
562
  'statistical' attempts to build a statistical mask, possibly by
575
- elementwise maximum of statistical maps of children )
563
+ elementwise maximum of statistical maps of children)
576
564
  threshold_statistical: float, or None
577
565
  if not None, masks will be preferably constructed by thresholding
578
566
  statistical maps with the given value.
579
- Returns:
580
- BoundingBox
567
+ Returns
568
+ -------
569
+ BoundingBox
581
570
  """
582
571
  spaceobj = _space.Space.get_instance(space)
583
572
  try:
@@ -593,7 +582,7 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
593
582
  maptype=maptype,
594
583
  threshold=threshold_statistical,
595
584
  )
596
- logger.warn(
585
+ logger.warning(
597
586
  f"No bounding box for {self.name} defined in {spaceobj.name}, "
598
587
  f"will warp the bounding box from {other_space.name} instead."
599
588
  )
@@ -606,14 +595,25 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
606
595
  return None
607
596
 
608
597
  def compute_centroids(self, space: _space.Space) -> pointset.PointSet:
609
- """Compute the centroids of the region in the given space.
598
+ """
599
+ Compute the centroids of the region in the given space.
610
600
 
611
- Note that a region can generally have multiple centroids
612
- if it has multiple connected components in the map.
601
+ Parameters
602
+ ----------
603
+ space: Space
604
+ reference space in which the computation will be performed
605
+ Returns
606
+ -------
607
+ PointSet
608
+ Found centroids (as Point objects) in a PointSet
609
+ Note
610
+ ----
611
+ A region can generally have multiple centroids if it has multiple
612
+ connected components in the map.
613
613
  """
614
614
  props = self.spatial_props(space)
615
615
  return pointset.PointSet(
616
- [tuple(c["centroid"]) for c in props["components"] if "centroid" in c],
616
+ [c.centroid for c in props.components],
617
617
  space=space
618
618
  )
619
619
 
@@ -622,34 +622,36 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
622
622
  space: _space.Space,
623
623
  maptype: MapType = MapType.LABELLED,
624
624
  threshold_statistical=None,
625
- ):
625
+ ) -> SpatialProp:
626
626
  """
627
627
  Compute spatial properties for connected components of this region in the given space.
628
628
 
629
629
  Parameters
630
630
  ----------
631
- space : _space.Space
632
- the space in which the computation shall be performed
633
- maptype: MapType
631
+ space: Space
632
+ reference space in which the computation will be performed
633
+ maptype: MapType, default: MapType.LABELLED
634
634
  Type of map to build ('labelled' will result in a binary mask,
635
635
  'statistical' attempts to build a statistical mask, possibly by
636
- elementwise maximum of statistical maps of children )
636
+ elementwise maximum of statistical maps of children)
637
637
  threshold_statistical: float, or None
638
638
  if not None, masks will be preferably constructed by thresholding
639
639
  statistical maps with the given value.
640
640
 
641
- Return
642
- ------
643
- dictionary of regionprops.
641
+ Returns
642
+ -------
643
+ Dict
644
+ Dictionary of region's spatial properties
644
645
  """
645
- result = {"space": space, "components": []}
646
646
  from skimage import measure
647
647
 
648
648
  if not isinstance(space, _space.Space):
649
649
  space = _space.Space.get_instance(space)
650
650
 
651
+ result = SpatialProp(space=space)
652
+
651
653
  if not self.mapped_in_space(space):
652
- logger.warn(
654
+ logger.warning(
653
655
  f"Spatial properties of {self.name} cannot be computed in {space.name}. "
654
656
  "This region is only mapped in these spaces: "
655
657
  f"{', '.join(s.name for s in self.supported_spaces)}"
@@ -670,50 +672,21 @@ class Region(anytree.NodeMixin, concept.AtlasConcept):
670
672
 
671
673
  # compute spatial properties of each connected component
672
674
  for label in range(1, C.max() + 1):
673
- props = {}
674
675
  nonzero = np.c_[np.nonzero(C == label)]
675
- props["centroid"] = point.Point(
676
- np.dot(pimg.affine, np.r_[nonzero.mean(0), 1])[:3], space=space
676
+ result.components.append(
677
+ SpatialPropCmpt(
678
+ centroid=point.Point(
679
+ np.dot(pimg.affine, np.r_[nonzero.mean(0), 1])[:3], space=space
680
+ ),
681
+ volume=nonzero.shape[0] * scale,
682
+ )
677
683
  )
678
- props["volume"] = nonzero.shape[0] * scale
679
684
 
680
- result["components"].append(props)
685
+ # sort by volume
686
+ result.components.sort(key=lambda cmp: cmp.volume, reverse=True)
681
687
 
682
688
  return result
683
689
 
684
- def compare(
685
- self,
686
- img: Nifti1Image,
687
- space: _space.Space,
688
- use_maptype: MapType = MapType.STATISTICAL,
689
- threshold_statistical: float = None,
690
- resolution_mm: float = None,
691
- ):
692
- """
693
- Compare the given image to the map of this region in the specified space.
694
-
695
- Parameters:
696
- -----------
697
- img: Nifti1Image
698
- Image to compare with
699
- space: Space
700
- Reference space to use
701
- use_maptype: MapType
702
- Type of map to build ('labelled' will result in a binary mask,
703
- 'statistical' attempts to build a statistical mask, possibly by
704
- elementwise maximum of statistical maps of children )
705
- threshold_statistical: float, or None
706
- if not None, masks will be preferably constructed by thresholding
707
- statistical maps with the given value.
708
- """
709
- mask = self.fetch_regional_map(
710
- space,
711
- resolution_mm,
712
- maptype=use_maptype,
713
- threshold=threshold_statistical,
714
- )
715
- return compare_maps(mask, img)
716
-
717
690
  def __iter__(self):
718
691
  """
719
692
  Returns an iterator that goes through all regions in this subtree