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.
- siibra/VERSION +1 -1
- siibra/__init__.py +2 -0
- siibra/commons.py +53 -8
- siibra/configuration/configuration.py +21 -17
- siibra/configuration/factory.py +95 -19
- siibra/core/atlas.py +11 -8
- siibra/core/concept.py +41 -8
- siibra/core/parcellation.py +94 -43
- siibra/core/region.py +160 -187
- siibra/core/space.py +44 -39
- siibra/features/__init__.py +19 -19
- siibra/features/anchor.py +9 -6
- siibra/features/connectivity/__init__.py +0 -8
- siibra/features/connectivity/functional_connectivity.py +11 -3
- siibra/features/{basetypes → connectivity}/regional_connectivity.py +46 -33
- siibra/features/connectivity/streamline_counts.py +3 -2
- siibra/features/connectivity/streamline_lengths.py +3 -2
- siibra/features/{basetypes → dataset}/__init__.py +2 -0
- siibra/features/{external → dataset}/ebrains.py +3 -3
- siibra/features/feature.py +420 -0
- siibra/{samplers → features/image}/__init__.py +7 -1
- siibra/features/{basetypes/volume_of_interest.py → image/image.py} +12 -7
- siibra/features/{external/__init__.py → image/sections.py} +8 -5
- siibra/features/image/volume_of_interest.py +70 -0
- siibra/features/{cellular → tabular}/__init__.py +7 -11
- siibra/features/{cellular → tabular}/bigbrain_intensity_profile.py +5 -2
- siibra/features/{cellular → tabular}/cell_density_profile.py +6 -2
- siibra/features/{basetypes → tabular}/cortical_profile.py +48 -41
- siibra/features/{molecular → tabular}/gene_expression.py +5 -2
- siibra/features/{cellular → tabular}/layerwise_bigbrain_intensities.py +6 -2
- siibra/features/{cellular → tabular}/layerwise_cell_density.py +9 -3
- siibra/features/{molecular → tabular}/receptor_density_fingerprint.py +3 -2
- siibra/features/{molecular → tabular}/receptor_density_profile.py +6 -2
- siibra/features/tabular/regional_timeseries_activity.py +213 -0
- siibra/features/{basetypes → tabular}/tabular.py +14 -9
- siibra/livequeries/allen.py +1 -1
- siibra/livequeries/bigbrain.py +2 -3
- siibra/livequeries/ebrains.py +3 -9
- siibra/livequeries/query.py +1 -1
- siibra/locations/location.py +4 -3
- siibra/locations/point.py +21 -17
- siibra/locations/pointset.py +2 -2
- siibra/retrieval/__init__.py +1 -1
- siibra/retrieval/cache.py +8 -2
- siibra/retrieval/datasets.py +149 -29
- siibra/retrieval/repositories.py +19 -8
- siibra/retrieval/requests.py +98 -116
- siibra/volumes/gifti.py +26 -11
- siibra/volumes/neuroglancer.py +35 -19
- siibra/volumes/nifti.py +8 -9
- siibra/volumes/parcellationmap.py +341 -184
- siibra/volumes/sparsemap.py +67 -53
- siibra/volumes/volume.py +25 -13
- {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/METADATA +4 -3
- siibra-0.4a46.dist-info/RECORD +69 -0
- {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/WHEEL +1 -1
- siibra/features/basetypes/feature.py +0 -248
- siibra/features/fibres/__init__.py +0 -14
- siibra/features/functional/__init__.py +0 -14
- siibra/features/molecular/__init__.py +0 -26
- siibra/samplers/bigbrain.py +0 -181
- siibra-0.4a33.dist-info/RECORD +0 -71
- {siibra-0.4a33.dist-info → siibra-0.4a46.dist-info}/LICENSE +0 -0
- {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
|
|
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
|
|
92
|
+
modality: str or None
|
|
81
93
|
Specification of the modality used for specifying this region
|
|
82
94
|
publications: list
|
|
83
|
-
List of
|
|
84
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
203
|
-
- a string with a possibly inexact name
|
|
204
|
-
|
|
205
|
-
- a
|
|
206
|
-
- a
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = [
|
|
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
|
-
|
|
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
|
|
315
|
+
Checks whether this region matches the given region specification.
|
|
318
316
|
|
|
319
317
|
Parameters
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
376
|
+
using the specified MapTypes.
|
|
378
377
|
|
|
379
378
|
Parameters
|
|
380
379
|
----------
|
|
381
|
-
space: Space
|
|
380
|
+
space: Space or str
|
|
382
381
|
The requested reference space
|
|
383
|
-
maptype: MapType
|
|
384
|
-
|
|
385
|
-
threshold: float
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
via_space: Space
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
"""
|
|
598
|
+
"""
|
|
599
|
+
Compute the centroids of the region in the given space.
|
|
610
600
|
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
[
|
|
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
|
|
632
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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.
|
|
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
|
-
|
|
676
|
-
|
|
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
|
-
|
|
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
|