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/volumes/sparsemap.py
CHANGED
|
@@ -15,16 +15,15 @@
|
|
|
15
15
|
"""Represents lists of probabilistic brain region maps."""
|
|
16
16
|
from . import parcellationmap, volume as _volume
|
|
17
17
|
|
|
18
|
-
from ..commons import MapIndex, logger
|
|
18
|
+
from ..commons import MapIndex, logger, iterate_connected_components, siibra_tqdm
|
|
19
19
|
from ..locations import boundingbox
|
|
20
20
|
from ..retrieval import cache
|
|
21
21
|
|
|
22
22
|
from os import path
|
|
23
23
|
import gzip
|
|
24
|
-
from typing import Dict, Union, TYPE_CHECKING
|
|
24
|
+
from typing import Dict, Union, TYPE_CHECKING, List
|
|
25
25
|
from nilearn import image
|
|
26
26
|
from nibabel import Nifti1Image, load
|
|
27
|
-
from tqdm import tqdm
|
|
28
27
|
import numpy as np
|
|
29
28
|
|
|
30
29
|
if TYPE_CHECKING:
|
|
@@ -111,8 +110,10 @@ class SparseIndex:
|
|
|
111
110
|
return x, y, z, v
|
|
112
111
|
|
|
113
112
|
def to_cache(self, prefix: str):
|
|
114
|
-
"""
|
|
115
|
-
using the given prefix for the cache
|
|
113
|
+
"""
|
|
114
|
+
Serialize this index to the cache, using the given prefix for the cache
|
|
115
|
+
filenames.
|
|
116
|
+
"""
|
|
116
117
|
probsfile = cache.CACHE.build_filename(f"{prefix}", suffix="probs.txt.gz")
|
|
117
118
|
bboxfile = cache.CACHE.build_filename(f"{prefix}", suffix="bboxes.txt.gz")
|
|
118
119
|
voxelfile = cache.CACHE.build_filename(f"{prefix}", suffix="voxels.nii.gz")
|
|
@@ -132,9 +133,18 @@ class SparseIndex:
|
|
|
132
133
|
@classmethod
|
|
133
134
|
def from_cache(cls, prefix: str):
|
|
134
135
|
"""
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
Attempts to build a sparse index from the siibra cache, looking for
|
|
137
|
+
suitable cache files with the specified prefix.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
prefix: str
|
|
142
|
+
Prefix of the filenames.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
SparseIndex
|
|
147
|
+
None if cached files are not found or suitable.
|
|
138
148
|
"""
|
|
139
149
|
|
|
140
150
|
probsfile = cache.CACHE.build_filename(f"{prefix}", suffix="probs.txt.gz")
|
|
@@ -152,12 +162,11 @@ class SparseIndex:
|
|
|
152
162
|
|
|
153
163
|
with gzip.open(probsfile, "rt") as f:
|
|
154
164
|
lines = f.readlines()
|
|
155
|
-
for line in
|
|
165
|
+
for line in siibra_tqdm(
|
|
156
166
|
lines,
|
|
157
167
|
total=len(lines),
|
|
158
168
|
desc="Loading sparse index",
|
|
159
|
-
unit="voxels"
|
|
160
|
-
disable=logger.level > 20,
|
|
169
|
+
unit="voxels"
|
|
161
170
|
):
|
|
162
171
|
fields = line.strip().split(" ")
|
|
163
172
|
mapindices = list(map(int, fields[0::2]))
|
|
@@ -179,21 +188,22 @@ class SparseIndex:
|
|
|
179
188
|
|
|
180
189
|
|
|
181
190
|
class SparseMap(parcellationmap.Map):
|
|
182
|
-
"""
|
|
191
|
+
"""
|
|
192
|
+
A sparse representation of list of statistical (e.g. probabilistic) brain
|
|
193
|
+
region maps.
|
|
183
194
|
|
|
184
195
|
It represents the 3D statistical maps of N brain regions by two data structures:
|
|
185
|
-
1) 'spatial_index', a 3D volume where non-negative values represent unique
|
|
186
|
-
indices into a list of region assignments
|
|
187
|
-
2) 'probs', a list of region assignments where each entry is a dict
|
|
188
196
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
197
|
+
1) 'spatial_index', a 3D volume where non-negative values represent unique indices into a list of region assignments
|
|
198
|
+
2) 'probs', a list of region assignments where each entry is a dict
|
|
199
|
+
|
|
200
|
+
More precisely, given ``i = sparse_index.voxels[x, y, z]`` we define that
|
|
201
|
+
|
|
202
|
+
- if `i<0`, no brain region is assigned at this location
|
|
203
|
+
- if `i>=0`, ``probs[i]`` defines the probabilities of brain regions.
|
|
194
204
|
|
|
195
205
|
Each entry in probs is a dictionary that represents the region assignments for
|
|
196
|
-
the unique voxel where spatial_index==i
|
|
206
|
+
the unique voxel where ``spatial_index == i``. The assignment maps from a MapIndex
|
|
197
207
|
to the actual (probability) value.
|
|
198
208
|
"""
|
|
199
209
|
|
|
@@ -235,15 +245,14 @@ class SparseMap(parcellationmap.Map):
|
|
|
235
245
|
@property
|
|
236
246
|
def sparse_index(self):
|
|
237
247
|
if self._sparse_index_cached is None:
|
|
238
|
-
prefix = f"{self.parcellation.id}_{self.space.id}_{self.maptype}_index"
|
|
248
|
+
prefix = f"{self.parcellation.id}_{self.space.id}_{self.maptype}_{self.name}_index"
|
|
239
249
|
spind = SparseIndex.from_cache(prefix)
|
|
240
|
-
|
|
241
|
-
|
|
250
|
+
if spind is None:
|
|
251
|
+
with _volume.SubvolumeProvider.UseCaching():
|
|
242
252
|
spind = SparseIndex()
|
|
243
|
-
for vol in
|
|
253
|
+
for vol in siibra_tqdm(
|
|
244
254
|
range(len(self)), total=len(self), unit="maps",
|
|
245
|
-
desc=f"Fetching {len(self)} volumetric maps"
|
|
246
|
-
disable=logger.level > 20,
|
|
255
|
+
desc=f"Fetching {len(self)} volumetric maps"
|
|
247
256
|
):
|
|
248
257
|
img = super().fetch(
|
|
249
258
|
index=MapIndex(volume=vol, label=None)
|
|
@@ -253,8 +262,8 @@ class SparseMap(parcellationmap.Map):
|
|
|
253
262
|
logger.error(f"Cannot retrieve volume #{vol} for {region.name}, it will not be included in the sparse map.")
|
|
254
263
|
continue
|
|
255
264
|
spind.add_img(img)
|
|
256
|
-
|
|
257
|
-
|
|
265
|
+
spind.to_cache(prefix)
|
|
266
|
+
self._sparse_index_cached = spind
|
|
258
267
|
assert self._sparse_index_cached.max() == len(self._sparse_index_cached.probs) - 1
|
|
259
268
|
return self._sparse_index_cached
|
|
260
269
|
|
|
@@ -278,15 +287,19 @@ class SparseMap(parcellationmap.Map):
|
|
|
278
287
|
Recreate a particular volumetric map from the sparse
|
|
279
288
|
representation.
|
|
280
289
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
region_or_index:
|
|
290
|
+
Parameters
|
|
291
|
+
----------
|
|
292
|
+
region_or_index: str, Region, MapIndex
|
|
284
293
|
Lazy match the specification.
|
|
285
294
|
index : MapIndex
|
|
286
295
|
The index to be fetched.
|
|
287
|
-
region:
|
|
288
|
-
Region name specification. If given, will be used to
|
|
289
|
-
|
|
296
|
+
region: str, Region
|
|
297
|
+
Region name specification. If given, will be used to decode the map
|
|
298
|
+
index of a particular region.
|
|
299
|
+
|
|
300
|
+
Returns
|
|
301
|
+
-------
|
|
302
|
+
An image or mesh
|
|
290
303
|
"""
|
|
291
304
|
if kwargs.get('format') in ['mesh'] + _volume.Volume.MESH_FORMATS:
|
|
292
305
|
# a mesh is requested, this is not handled by the sparse map
|
|
@@ -349,7 +362,7 @@ class SparseMap(parcellationmap.Map):
|
|
|
349
362
|
for volume, value in spind.probs[voxel].items()
|
|
350
363
|
)
|
|
351
364
|
|
|
352
|
-
def _assign_image(self, queryimg: Nifti1Image, minsize_voxel: int, lower_threshold: float):
|
|
365
|
+
def _assign_image(self, queryimg: Nifti1Image, minsize_voxel: int, lower_threshold: float) -> List[parcellationmap.AssignImageResult]:
|
|
353
366
|
"""
|
|
354
367
|
Assign an image volume to this parcellation map.
|
|
355
368
|
|
|
@@ -383,7 +396,7 @@ class SparseMap(parcellationmap.Map):
|
|
|
383
396
|
|
|
384
397
|
querydata = np.asanyarray(queryimg.dataobj).squeeze()
|
|
385
398
|
|
|
386
|
-
for mode, modeimg in
|
|
399
|
+
for mode, modeimg in iterate_connected_components(queryimg):
|
|
387
400
|
|
|
388
401
|
# determine bounding box of the mode
|
|
389
402
|
modemask = np.asanyarray(modeimg.dataobj)
|
|
@@ -400,12 +413,11 @@ class SparseMap(parcellationmap.Map):
|
|
|
400
413
|
|
|
401
414
|
spind = self.sparse_index
|
|
402
415
|
|
|
403
|
-
for volume in
|
|
416
|
+
for volume in siibra_tqdm(
|
|
404
417
|
range(len(self)),
|
|
405
418
|
desc=f"Assigning structure #{mode} to {len(self)} sparse maps",
|
|
406
419
|
total=len(self),
|
|
407
|
-
unit=" map"
|
|
408
|
-
disable=logger.level > 20,
|
|
420
|
+
unit=" map"
|
|
409
421
|
):
|
|
410
422
|
bbox1 = boundingbox.BoundingBox(
|
|
411
423
|
self.sparse_index.bboxes[volume]["minpoint"],
|
|
@@ -447,8 +459,6 @@ class SparseMap(parcellationmap.Map):
|
|
|
447
459
|
iou = intersection / np.sum(
|
|
448
460
|
(v1 > 0) | (v2 > 0)
|
|
449
461
|
) # np.maximum(v1, v2).sum()
|
|
450
|
-
contains = intersection / (v1 > 0).sum()
|
|
451
|
-
contained = intersection / (v2 > 0).sum()
|
|
452
462
|
|
|
453
463
|
v1d = v1 - v1.mean()
|
|
454
464
|
v2d = v2 - v2.mean()
|
|
@@ -460,16 +470,20 @@ class SparseMap(parcellationmap.Map):
|
|
|
460
470
|
|
|
461
471
|
maxval = v1.max()
|
|
462
472
|
|
|
463
|
-
assignments.append(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
473
|
+
assignments.append(
|
|
474
|
+
parcellationmap.AssignImageResult(
|
|
475
|
+
input_structure=mode,
|
|
476
|
+
centroid=tuple(position.round(2)),
|
|
477
|
+
volume=volume,
|
|
478
|
+
fragment=None,
|
|
479
|
+
map_value=maxval,
|
|
480
|
+
intersection_over_union=iou,
|
|
481
|
+
intersection_over_first=intersection / (v1 > 0).sum(),
|
|
482
|
+
intersection_over_second=intersection / (v2 > 0).sum(),
|
|
483
|
+
correlation=rho,
|
|
484
|
+
weighted_mean_of_first=np.sum(v1 * v2) / np.sum(v2),
|
|
485
|
+
weighted_mean_of_second=np.sum(v1 * v2) / np.sum(v1)
|
|
486
|
+
)
|
|
487
|
+
)
|
|
474
488
|
|
|
475
489
|
return assignments
|
siibra/volumes/volume.py
CHANGED
|
@@ -20,9 +20,12 @@ from ..core import space
|
|
|
20
20
|
|
|
21
21
|
import nibabel as nib
|
|
22
22
|
from abc import ABC, abstractmethod
|
|
23
|
-
from typing import List, Dict, Union, Set
|
|
23
|
+
from typing import List, Dict, Union, Set, TYPE_CHECKING
|
|
24
24
|
import json
|
|
25
25
|
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from ..retrieval.datasets import EbrainsDataset
|
|
28
|
+
TypeDataset = EbrainsDataset
|
|
26
29
|
|
|
27
30
|
class ColorVolumeNotSupported(NotImplementedError):
|
|
28
31
|
pass
|
|
@@ -55,11 +58,13 @@ class Volume:
|
|
|
55
58
|
providers: List['VolumeProvider'],
|
|
56
59
|
name: str = "",
|
|
57
60
|
variant: str = None,
|
|
61
|
+
datasets: List['TypeDataset'] = [],
|
|
58
62
|
):
|
|
59
63
|
self._name_cached = name # see lazy implementation below
|
|
60
64
|
self._space_spec = space_spec
|
|
61
65
|
self.variant = variant
|
|
62
66
|
self._providers: Dict[str, 'VolumeProvider'] = {}
|
|
67
|
+
self.datasets = datasets
|
|
63
68
|
for provider in providers:
|
|
64
69
|
srctype = provider.srctype
|
|
65
70
|
assert srctype not in self._providers
|
|
@@ -69,13 +74,20 @@ class Volume:
|
|
|
69
74
|
|
|
70
75
|
@property
|
|
71
76
|
def name(self):
|
|
72
|
-
"""
|
|
77
|
+
"""
|
|
78
|
+
Allows derived classes to implement a lazy name specification.
|
|
79
|
+
"""
|
|
73
80
|
return self._name_cached
|
|
74
81
|
|
|
75
82
|
@property
|
|
76
83
|
def providers(self):
|
|
84
|
+
def concat(url: Union[str, Dict[str, str]], concat: str):
|
|
85
|
+
if isinstance(url, str):
|
|
86
|
+
return url + concat
|
|
87
|
+
return {key: url[key] + concat for key in url}
|
|
77
88
|
return {
|
|
78
|
-
srctype: prov._url
|
|
89
|
+
srctype: concat(prov._url, f" {prov.label}" if hasattr(prov, "label") else "")
|
|
90
|
+
for srctype, prov in self._providers.items()
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
@property
|
|
@@ -149,16 +161,16 @@ class Volume:
|
|
|
149
161
|
----------
|
|
150
162
|
format: str
|
|
151
163
|
Requested format. Per default, several formats are tried,
|
|
152
|
-
starting with volumetric formats.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
starting with volumetric formats. It can be explicitly specified as:
|
|
165
|
+
- 'surface' or 'mesh' to fetch a surface format
|
|
166
|
+
- 'volumetric' or 'voxel' to fetch a volumetric format
|
|
167
|
+
- supported format types, see SUPPORTED_FORMATS. This includes
|
|
168
|
+
'nii', 'zip/nii', 'neuroglancer/precomputed', 'gii-mesh',
|
|
169
|
+
'neuroglancer/precompmesh', 'gii-label'
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
An image or mesh
|
|
162
174
|
"""
|
|
163
175
|
|
|
164
176
|
if format is None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: siibra
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4a46
|
|
4
4
|
Summary: siibra - Software interfaces for interacting with brain atlases
|
|
5
5
|
Home-page: https://github.com/FZJ-INM1-BDA/siibra-python
|
|
6
6
|
Author: Big Data Analytics Group, Forschungszentrum Juelich, Institute of Neuroscience and Medicine (INM-1)
|
|
@@ -22,6 +22,7 @@ Requires-Dist: scikit-image
|
|
|
22
22
|
Requires-Dist: requests
|
|
23
23
|
Requires-Dist: neuroglancer-scripts
|
|
24
24
|
Requires-Dist: nilearn
|
|
25
|
+
Requires-Dist: filelock
|
|
25
26
|
Requires-Dist: typing-extensions ; python_version < "3.8"
|
|
26
27
|
|
|
27
28
|
|License| |PyPI version| |Python versions| |Documentation Status|
|
|
@@ -39,7 +40,7 @@ Medicine (INM-1), Forschungszentrum Jülich GmbH*
|
|
|
39
40
|
``siibra`` is a Python client to a brain atlas framework that integrates brain parcellations and reference spaces at different spatial scales, and connects them with a broad range of multimodal regional data features.
|
|
40
41
|
It aims to facilitate programmatic and reproducible incorporation of brain parcellations and brain region features from different sources into neuroscience workflows.
|
|
41
42
|
|
|
42
|
-
**Note:**
|
|
43
|
+
**Note:** ``siibra-python`` *is still in development. While care is taken that it works reliably, its API is not yet stable and you may still encounter bugs when using it.*
|
|
43
44
|
|
|
44
45
|
``siibra`` provides structured access to parcellation schemes in different brain reference spaces, including volumetric reference templates at macroscopic and microscopic resolutions as well as surface representations.
|
|
45
46
|
It supports both discretely labelled and statistical (probabilistic) parcellation maps, which can be used to assign brain regions to spatial locations and image signals, to retrieve region-specific neuroscience datasets from multiple online repositories, and to sample information from high-resolution image data.
|
|
@@ -48,7 +49,7 @@ The datasets anchored to brain regions address features of molecular, cellular a
|
|
|
48
49
|
``siibra`` was developed in the frame of the `Human Brain Project <https://humanbrainproject.eu>`__ for accessing the `EBRAINS
|
|
49
50
|
human brain atlas <https://ebrains.eu/service/human-brain-atlas>`__.
|
|
50
51
|
It stores most of its contents as sustainable and open datasets in the `EBRAINS Knowledge Graph <https://kg.ebrains.eu>`__, and is designed to support the `OpenMINDS metadata standards <https://github.com/HumanBrainProject/openMINDS_SANDS>`__.
|
|
51
|
-
Its functionalities include common actions known from the interactive viewer ``siibra
|
|
52
|
+
Its functionalities include common actions known from the interactive viewer ``siibra-explorer`` `hosted at EBRAINS <https://atlases.ebrains.eu/viewer>`__.
|
|
52
53
|
In fact, the viewer is a good resource for exploring ``siibra``\ ’s core functionalities interactively: Selecting different parcellations, browsing and searching brain region hierarchies, downloading maps, identifying brain regions, and accessing multimodal features and connectivity information associated with brain regions.
|
|
53
54
|
Feature queries in ``siibra`` are parameterized by data modality and anatomical location, while the latter could be a brain region, brain parcellation, or location in reference space.
|
|
54
55
|
Beyond the explorative focus of ``siibra-explorer``, the Python library supports a range of data analysis functions suitable for typical neuroscience workflows.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
siibra/VERSION,sha256=L13hDahuVhINiq4SVkPO6tDOa7WbXfZmeuvUoNN_ovw,7
|
|
2
|
+
siibra/__init__.py,sha256=VWA9EoVSVpkgwn3fmPw-nv4_kN63iWfyAwbU5W0_phY,3812
|
|
3
|
+
siibra/commons.py,sha256=dNANERrRyaRt_DYi24i-uyOb3ajt7zTfy-BS-gGg3_Q,24325
|
|
4
|
+
siibra/configuration/__init__.py,sha256=gXtcw2wbHCJ1o6jnd_HBudpaptHWBke71gcBd3ze7zE,688
|
|
5
|
+
siibra/configuration/configuration.py,sha256=YB02grlZbCHC5CfD0FX8bc4eEAQiLmZUzBRVT9lhXQ8,6886
|
|
6
|
+
siibra/configuration/factory.py,sha256=nonPGcDCBLavUQZQJBrPupBdUrOj8Te-Q_ghUBnPuvg,19405
|
|
7
|
+
siibra/core/__init__.py,sha256=YQkeqGqpOT3aH-PPHeGb1kxSyMbgR3ZHqDCy2I0Zlsk,735
|
|
8
|
+
siibra/core/atlas.py,sha256=FDtzpW1d492hFekttMz9aX-ggDwQrSwXlw1fWWF2tfE,7947
|
|
9
|
+
siibra/core/concept.py,sha256=zP76RKGOrityiJZQjKcYmsL1Z92IC4MPZUDl3qwz01k,8091
|
|
10
|
+
siibra/core/parcellation.py,sha256=eRX9FXEEPV-f6CsIUzh7hgSx100M48rLtnxZd7rBrSg,13405
|
|
11
|
+
siibra/core/region.py,sha256=cBcc5Jp5fgxxR_I-k3mDEbcxP0h2bDmF3f4XRUpCaGg,24613
|
|
12
|
+
siibra/core/space.py,sha256=iweTByicSWqSk-dJEscBceAGBUJ6k9pDFEUc8j29qXc,5959
|
|
13
|
+
siibra/features/__init__.py,sha256=da1Eyti0i6gMeZlwCQkf7SgZJpePc9CoUP7I53-DC6c,1315
|
|
14
|
+
siibra/features/anchor.py,sha256=Zz_0ovU_k5W_WM3z7sv7IjG2kGjt9jhku8RRw4jcByM,16131
|
|
15
|
+
siibra/features/feature.py,sha256=s_fixmcxRTR30r2WSOdKAlirD8rxQCEEXoq76vB5abA,15497
|
|
16
|
+
siibra/features/connectivity/__init__.py,sha256=c5ITfy6p6n-vze1jcgSguMq0Wk6QXVibmRvC-d92AbU,805
|
|
17
|
+
siibra/features/connectivity/functional_connectivity.py,sha256=9YYaeTSSDOv6Ha7gE0zJFgRP1bTL-0c7knukZtw2nqE,1407
|
|
18
|
+
siibra/features/connectivity/regional_connectivity.py,sha256=oUT_xvS2ngPVoxNG4UmkAGfJXKB8HvqipKsHvxnbpiQ,12083
|
|
19
|
+
siibra/features/connectivity/streamline_counts.py,sha256=ngl7xCiCUOyflyBvjZYa3ShOmtf21E5e0A_BeWfFtSQ,1064
|
|
20
|
+
siibra/features/connectivity/streamline_lengths.py,sha256=0a09Dag-eRvs1KgHSU47I3xQwbgHICsDozhZyuNzQME,1067
|
|
21
|
+
siibra/features/dataset/__init__.py,sha256=FB7PGXFyl0CEuVuzEU5VHPkxf7AwQK6Oyej3waClBKM,687
|
|
22
|
+
siibra/features/dataset/ebrains.py,sha256=vLWZvJAdTxtREf8-dlniIPiN4_itNujcvLhfic830V8,2699
|
|
23
|
+
siibra/features/image/__init__.py,sha256=514GkYVuxEwYpNZkRptHnygnYtoUaEgyUUNthXPxBKU,833
|
|
24
|
+
siibra/features/image/image.py,sha256=_P0V3WiF6TB_OF4F2Fo9Lobwm1esP8gNNve5-aqY5Dw,2819
|
|
25
|
+
siibra/features/image/sections.py,sha256=3Xllns8VBXdqrQHfIHJWt88tMN8uo8RHCEgvTZ3NQ7g,915
|
|
26
|
+
siibra/features/image/volume_of_interest.py,sha256=c4L5FMvfF0SWhD9m-_cpou64f6JwFWiO--oKA7W4DcI,2138
|
|
27
|
+
siibra/features/tabular/__init__.py,sha256=EuROA0DhSsMgw0FTLbIXGmPyOEG8UJ-ZQtl9wNQmbEU,1196
|
|
28
|
+
siibra/features/tabular/bigbrain_intensity_profile.py,sha256=JWG4KC9DlmsXazSn9tLdWkx7F8AlCtpwrBQcYZ0tRw0,2215
|
|
29
|
+
siibra/features/tabular/cell_density_profile.py,sha256=hPGE-lW36QCJrfLWHk9W4DmAow6HxSJG18onBcOicK8,9096
|
|
30
|
+
siibra/features/tabular/cortical_profile.py,sha256=-0xbLndu5_IYZYw-ElyMtBcLWkXhGuGefj3FVomDfJI,7911
|
|
31
|
+
siibra/features/tabular/gene_expression.py,sha256=gR2neifFbo_baPKEsXbnrn52CAg5kF5IovOxd6GvGKA,4798
|
|
32
|
+
siibra/features/tabular/layerwise_bigbrain_intensities.py,sha256=qbuOcV2EBuUFJDVrHBzDVLY3emV_xFslgESniOxl6cg,2138
|
|
33
|
+
siibra/features/tabular/layerwise_cell_density.py,sha256=hKwykSmzh7Z9zKgQXk_YQ3UUfULPnOHE85cIPQDkaCk,4266
|
|
34
|
+
siibra/features/tabular/receptor_density_fingerprint.py,sha256=xHt7woUKHRVz08i8dON7HTnzTR1R2jcAxyWL60mEeFE,6010
|
|
35
|
+
siibra/features/tabular/receptor_density_profile.py,sha256=6JO14y9WB40PbDDcxu8KvC_0HGi3l3rnUiGJWjgsk4w,3577
|
|
36
|
+
siibra/features/tabular/regional_timeseries_activity.py,sha256=C-QCBKraXH3qw1qHCRM8M4pyR_30eDyQwZ1UBakMHME,7247
|
|
37
|
+
siibra/features/tabular/tabular.py,sha256=r9CJ0UCEZImfjPkhWvzgITDJ9q37Cz_AKmupUhE2xMs,3201
|
|
38
|
+
siibra/livequeries/__init__.py,sha256=b5qKiJt_SrgSzy4abRYqvnbLSo01vUHrK8-K2zbnKIo,804
|
|
39
|
+
siibra/livequeries/allen.py,sha256=-XHS1zA4tMQCEX3x4tMhuADuvjCUK5RpafyfrTeK5mc,12179
|
|
40
|
+
siibra/livequeries/bigbrain.py,sha256=Rtfp2hwpX4pR45j0ZtFlEZx0AdHkpSEQgW_V59A5GRY,6834
|
|
41
|
+
siibra/livequeries/ebrains.py,sha256=tdDwiaVhIrlzwT68aNV3Ds8JLjbgJ0nOx6GGAPmBCbA,5789
|
|
42
|
+
siibra/livequeries/query.py,sha256=GYD_nidlKmC9N7M8Jo54iolSrcvg4P2ixjurbWMk0dk,1786
|
|
43
|
+
siibra/locations/__init__.py,sha256=ItA2BnaaqP9wdXUH78mMhPFR0dSDxnz66o-y9vJH2Ag,773
|
|
44
|
+
siibra/locations/boundingbox.py,sha256=Oh2FxRFcDIh3a59ko1wbp5LQBVgLPhJx8Tkn4R3z_MQ,16824
|
|
45
|
+
siibra/locations/location.py,sha256=s1HEafa8jGi2C47ohwgG9XgVKAotK1HtS7Ez5gMsf_U,4577
|
|
46
|
+
siibra/locations/point.py,sha256=1ea3X9cGKltNPZnnQyBoKoVhCE0U0hGtzWljf-xZw6I,12586
|
|
47
|
+
siibra/locations/pointset.py,sha256=GaWnHDN7NRuKvl4hSU6YREcydFX1yWygbEOJBKTW2Ws,7110
|
|
48
|
+
siibra/retrieval/__init__.py,sha256=VaR81n9ldwwbpPgIHmoUPmn8QHlZ0_R633yDqQrx6JA,1028
|
|
49
|
+
siibra/retrieval/cache.py,sha256=NfieYe-3stgjpWQXVGTJSVSLJQOoat-eswj5eWEEaB8,4889
|
|
50
|
+
siibra/retrieval/datasets.py,sha256=-MB8A-r3bmqPMOelSWZkaHq41Vu0YHjQEVHeq36qfW0,7660
|
|
51
|
+
siibra/retrieval/repositories.py,sha256=i_eDQcLmpIbImdVWkIWLSkshCPYQ9fWJe4gmvUMM790,26309
|
|
52
|
+
siibra/retrieval/requests.py,sha256=UYeo38hpbxWxmKOofiTIFcHabbwvIHWIcw0J9_xJsT0,20940
|
|
53
|
+
siibra/retrieval/exceptions/__init__.py,sha256=STIPOyunBN2f7d26NTElLTL3xM1KoB3U7jLhNBivguA,173
|
|
54
|
+
siibra/vocabularies/__init__.py,sha256=I_tTB-3xYAdVzRGP0CCVGgd3dP6QiltoJqEZFTr-lkI,1261
|
|
55
|
+
siibra/vocabularies/gene_names.json,sha256=i-gnh753GyZtQfX_dWibNYr_d5ccDPHooOwsdeKUYqE,1647972
|
|
56
|
+
siibra/vocabularies/receptor_symbols.json,sha256=F6DZIArPCBmJV_lWGV-zDpBBH_GOJOZm67LBE4qzMa4,5722
|
|
57
|
+
siibra/vocabularies/region_aliases.json,sha256=RFxH36D3JS-T6lGlATthcNbPkKINjiqZxFoeb7OTtJY,8449
|
|
58
|
+
siibra/volumes/__init__.py,sha256=b6O5aWKfFU0JGT8PMTUF6OewCti7aRyOno_kjJ4es_U,830
|
|
59
|
+
siibra/volumes/gifti.py,sha256=idULD08pB_wxqzSFHlC_JkhFr-KuHP7FZnqp62QLXGo,5504
|
|
60
|
+
siibra/volumes/neuroglancer.py,sha256=QX7rYsgLPowQsGbOYCUsFj-r3z16c1c7VFTDDL9dkNk,24056
|
|
61
|
+
siibra/volumes/nifti.py,sha256=3psZ8cdOVRe9nsf82sUivb7VwfhD5MtYqAOs9IzW8Is,8948
|
|
62
|
+
siibra/volumes/parcellationmap.py,sha256=lldGnmqp2_VZNv9cr5e7tvA_pgzXFapjzJ9WmhNPg74,42172
|
|
63
|
+
siibra/volumes/sparsemap.py,sha256=rRl8xZFIpI8WxMjDF_WQYqxtKoH6fCQWBfBvCf_mPJs,18071
|
|
64
|
+
siibra/volumes/volume.py,sha256=2YZdjcHScmYhdJI0gH-wzyE8ZjQcKrlhtEk9udtcyeE,9915
|
|
65
|
+
siibra-0.4a46.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
66
|
+
siibra-0.4a46.dist-info/METADATA,sha256=_us0IIlXJHZ25hgSeQc3b5S1JAdnweu5cXe7r5P5o6s,9248
|
|
67
|
+
siibra-0.4a46.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
68
|
+
siibra-0.4a46.dist-info/top_level.txt,sha256=NF0OSGLL0li2qyC7MaU0iBB5Y9S09_euPpvisD0-8Hg,7
|
|
69
|
+
siibra-0.4a46.dist-info/RECORD,,
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
# Copyright 2018-2021
|
|
2
|
-
# Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
|
|
3
|
-
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
from .. import anchor as _anchor
|
|
17
|
-
|
|
18
|
-
from ...commons import logger
|
|
19
|
-
from ...core import concept
|
|
20
|
-
from ...core import space, region, parcellation
|
|
21
|
-
|
|
22
|
-
from typing import Union, TYPE_CHECKING, List, Dict, Type
|
|
23
|
-
from tqdm import tqdm
|
|
24
|
-
from hashlib import md5
|
|
25
|
-
from collections import defaultdict
|
|
26
|
-
|
|
27
|
-
if TYPE_CHECKING:
|
|
28
|
-
from ...retrieval.datasets import EbrainsDataset
|
|
29
|
-
TypeDataset = EbrainsDataset
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class Feature:
|
|
33
|
-
"""
|
|
34
|
-
Base class for anatomically anchored data features.
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
SUBCLASSES: Dict[Type['Feature'], List[Type['Feature']]] = defaultdict(list)
|
|
38
|
-
|
|
39
|
-
def __init__(
|
|
40
|
-
self,
|
|
41
|
-
modality: str,
|
|
42
|
-
description: str,
|
|
43
|
-
anchor: _anchor.AnatomicalAnchor,
|
|
44
|
-
datasets: List['TypeDataset'] = []
|
|
45
|
-
):
|
|
46
|
-
"""
|
|
47
|
-
Parameters
|
|
48
|
-
----------
|
|
49
|
-
modality: str
|
|
50
|
-
A textual description of the type of measured information
|
|
51
|
-
description: str
|
|
52
|
-
A textual description of the feature.
|
|
53
|
-
anchor: AnatomicalAnchor
|
|
54
|
-
datasets : list
|
|
55
|
-
list of datasets corresponding to this feature
|
|
56
|
-
"""
|
|
57
|
-
self._modality_cached = modality
|
|
58
|
-
self._description = description
|
|
59
|
-
self._anchor_cached = anchor
|
|
60
|
-
self.datasets = datasets
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def modality(self):
|
|
64
|
-
# allows subclasses to implement lazy loading of an anchor
|
|
65
|
-
return self._modality_cached
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def anchor(self):
|
|
69
|
-
# allows subclasses to implement lazy loading of an anchor
|
|
70
|
-
return self._anchor_cached
|
|
71
|
-
|
|
72
|
-
def __init_subclass__(cls, configuration_folder=None):
|
|
73
|
-
# extend the subclass lists
|
|
74
|
-
|
|
75
|
-
# Iterate over all mro, not just immediate base classes
|
|
76
|
-
for BaseCls in cls.__mro__:
|
|
77
|
-
# some base classes may not be sub class of feature, ignore these
|
|
78
|
-
if not issubclass(BaseCls, Feature):
|
|
79
|
-
continue
|
|
80
|
-
cls.SUBCLASSES[BaseCls].append(cls)
|
|
81
|
-
|
|
82
|
-
cls._live_queries = []
|
|
83
|
-
cls._preconfigured_instances = None
|
|
84
|
-
cls._configuration_folder = configuration_folder
|
|
85
|
-
return super().__init_subclass__()
|
|
86
|
-
|
|
87
|
-
@classmethod
|
|
88
|
-
def _get_subclasses(cls):
|
|
89
|
-
return {Cls.__name__: Cls for Cls in cls.SUBCLASSES}
|
|
90
|
-
|
|
91
|
-
@property
|
|
92
|
-
def description(self):
|
|
93
|
-
""" Allowssubclasses to overwrite the description with a function call. """
|
|
94
|
-
return self._description
|
|
95
|
-
|
|
96
|
-
@property
|
|
97
|
-
def name(self):
|
|
98
|
-
"""Returns a short human-readable name of this feature."""
|
|
99
|
-
return f"{self.__class__.__name__} ({self.modality}) anchored at {self.anchor}"
|
|
100
|
-
|
|
101
|
-
@classmethod
|
|
102
|
-
def get_instances(cls, **kwargs) -> List['Feature']:
|
|
103
|
-
"""
|
|
104
|
-
Retrieve objects of a particular feature subclass.
|
|
105
|
-
Objects can be preconfigured in the configuration,
|
|
106
|
-
or delivered by Live queries.
|
|
107
|
-
"""
|
|
108
|
-
result = []
|
|
109
|
-
|
|
110
|
-
if hasattr(cls, "_preconfigured_instances"):
|
|
111
|
-
if cls._preconfigured_instances is None:
|
|
112
|
-
if cls._configuration_folder is None:
|
|
113
|
-
cls._preconfigured_instances = []
|
|
114
|
-
else:
|
|
115
|
-
from ...configuration.configuration import Configuration
|
|
116
|
-
conf = Configuration()
|
|
117
|
-
Configuration.register_cleanup(cls.clean_instances)
|
|
118
|
-
assert cls._configuration_folder in conf.folders
|
|
119
|
-
cls._preconfigured_instances = [
|
|
120
|
-
o for o in conf.build_objects(cls._configuration_folder)
|
|
121
|
-
if isinstance(o, cls)
|
|
122
|
-
]
|
|
123
|
-
logger.debug(
|
|
124
|
-
f"Built {len(cls._preconfigured_instances)} preconfigured {cls.__name__} "
|
|
125
|
-
f"objects from {cls._configuration_folder}."
|
|
126
|
-
)
|
|
127
|
-
result.extend(cls._preconfigured_instances)
|
|
128
|
-
|
|
129
|
-
return result
|
|
130
|
-
|
|
131
|
-
@classmethod
|
|
132
|
-
def clean_instances(cls):
|
|
133
|
-
""" Removes all instantiated object instances"""
|
|
134
|
-
cls._preconfigured_instances = None
|
|
135
|
-
|
|
136
|
-
def matches(self, concept: concept.AtlasConcept) -> bool:
|
|
137
|
-
if self.anchor and self.anchor.matches(concept):
|
|
138
|
-
self.anchor._last_matched_concept = concept
|
|
139
|
-
return True
|
|
140
|
-
self.anchor._last_matched_concept = None
|
|
141
|
-
return False
|
|
142
|
-
|
|
143
|
-
@property
|
|
144
|
-
def last_match_result(self):
|
|
145
|
-
return None if self.anchor is None \
|
|
146
|
-
else self.anchor.last_match_result
|
|
147
|
-
|
|
148
|
-
@property
|
|
149
|
-
def last_match_description(self):
|
|
150
|
-
return "" if self.anchor is None \
|
|
151
|
-
else self.anchor.last_match_description
|
|
152
|
-
|
|
153
|
-
@property
|
|
154
|
-
def id(self):
|
|
155
|
-
prefix = ''
|
|
156
|
-
id_set = {ds.id for ds in self.datasets if hasattr(ds, 'id')}
|
|
157
|
-
if len(id_set) == 1:
|
|
158
|
-
prefix = list(id_set)[0] + '--'
|
|
159
|
-
return prefix + md5(self.name.encode("utf-8")).hexdigest()
|
|
160
|
-
|
|
161
|
-
@classmethod
|
|
162
|
-
def match(cls, concept: Union[region.Region, parcellation.Parcellation, space.Space], feature_type: Union[str, Type['Feature'], list], **kwargs) -> List['Feature']:
|
|
163
|
-
"""
|
|
164
|
-
Retrieve data features of the desired modality.
|
|
165
|
-
|
|
166
|
-
Parameters
|
|
167
|
-
----------
|
|
168
|
-
concept: AtlasConcept
|
|
169
|
-
An anatomical concept, typically a brain region or parcellation.
|
|
170
|
-
modality: subclass of Feature
|
|
171
|
-
specififies the type of features ("modality")
|
|
172
|
-
"""
|
|
173
|
-
if isinstance(feature_type, list):
|
|
174
|
-
# a list of feature types is given, collect match results on those
|
|
175
|
-
assert all((isinstance(t, str) or issubclass(t, cls)) for t in feature_type)
|
|
176
|
-
return sum((cls.match(concept, t, **kwargs) for t in feature_type), [])
|
|
177
|
-
|
|
178
|
-
if isinstance(feature_type, str):
|
|
179
|
-
# feature type given as a string. Decode the corresponding class.
|
|
180
|
-
# Some string inputs, such as connectivity, may hit multiple matches
|
|
181
|
-
# In this case
|
|
182
|
-
candidates = [
|
|
183
|
-
feattype
|
|
184
|
-
for FeatCls, feattypes in cls.SUBCLASSES.items()
|
|
185
|
-
if all(w.lower() in FeatCls.__name__.lower() for w in feature_type.split())
|
|
186
|
-
for feattype in feattypes
|
|
187
|
-
]
|
|
188
|
-
if len(candidates) == 0:
|
|
189
|
-
raise ValueError(f"feature_type {str(feature_type)} did not match with any features. Available features are: {', '.join(cls.SUBCLASSES.keys())}")
|
|
190
|
-
|
|
191
|
-
return [feat for c in candidates for feat in cls.match(concept, c, **kwargs)]
|
|
192
|
-
|
|
193
|
-
assert issubclass(feature_type, Feature)
|
|
194
|
-
|
|
195
|
-
if not isinstance(concept, (region.Region, parcellation.Parcellation, space.Space)):
|
|
196
|
-
raise ValueError(
|
|
197
|
-
"Feature.match / siibra.features.get only accepts Region, "
|
|
198
|
-
"Space and Parcellation objects as concept."
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
msg = f"Matching {feature_type.__name__} to {concept}"
|
|
202
|
-
instances = [
|
|
203
|
-
instance
|
|
204
|
-
for f_type in cls.SUBCLASSES[feature_type]
|
|
205
|
-
for instance in f_type.get_instances()
|
|
206
|
-
]
|
|
207
|
-
|
|
208
|
-
if logger.getEffectiveLevel() > 20:
|
|
209
|
-
preconfigured_instances = [f for f in instances if f.matches(concept)]
|
|
210
|
-
else:
|
|
211
|
-
preconfigured_instances = [f for f in tqdm(instances, desc=msg, total=len(instances)) if f.matches(concept)]
|
|
212
|
-
|
|
213
|
-
live_instances = []
|
|
214
|
-
if hasattr(feature_type, "_live_queries"):
|
|
215
|
-
for QueryType in feature_type._live_queries:
|
|
216
|
-
argstr = f" ({', '.join('='.join(map(str,_)) for _ in kwargs.items())})" \
|
|
217
|
-
if len(kwargs) > 0 else ""
|
|
218
|
-
logger.info(
|
|
219
|
-
f"Running live query for {QueryType.feature_type.__name__} "
|
|
220
|
-
f"objects linked to {str(concept)}{argstr}"
|
|
221
|
-
)
|
|
222
|
-
q = QueryType(**kwargs)
|
|
223
|
-
live_instances.extend(q.query(concept))
|
|
224
|
-
|
|
225
|
-
return preconfigured_instances + live_instances
|
|
226
|
-
|
|
227
|
-
@classmethod
|
|
228
|
-
def get_ascii_tree(cls):
|
|
229
|
-
# build an Ascii representation of class hierarchy
|
|
230
|
-
# under this feature class
|
|
231
|
-
from anytree.importer import DictImporter
|
|
232
|
-
from anytree import RenderTree
|
|
233
|
-
|
|
234
|
-
def create_treenode(feature_type):
|
|
235
|
-
return {
|
|
236
|
-
'name': feature_type.__name__,
|
|
237
|
-
'children': [
|
|
238
|
-
create_treenode(c)
|
|
239
|
-
for c in feature_type.__subclasses__()
|
|
240
|
-
]
|
|
241
|
-
}
|
|
242
|
-
D = create_treenode(cls)
|
|
243
|
-
importer = DictImporter()
|
|
244
|
-
tree = importer.import_(D)
|
|
245
|
-
return "\n".join(
|
|
246
|
-
"%s%s" % (pre, node.name)
|
|
247
|
-
for pre, _, node in RenderTree(tree)
|
|
248
|
-
)
|