siibra 0.5a2__py3-none-any.whl → 1.0.0a1__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 +20 -12
- siibra/commons.py +145 -90
- siibra/configuration/__init__.py +1 -1
- siibra/configuration/configuration.py +22 -17
- siibra/configuration/factory.py +177 -128
- siibra/core/__init__.py +1 -8
- siibra/core/{relation_qualification.py → assignment.py} +17 -14
- siibra/core/atlas.py +66 -35
- siibra/core/concept.py +81 -39
- siibra/core/parcellation.py +83 -67
- siibra/core/region.py +569 -263
- siibra/core/space.py +7 -39
- siibra/core/structure.py +111 -0
- siibra/exceptions.py +63 -0
- siibra/experimental/__init__.py +19 -0
- siibra/experimental/contour.py +61 -0
- siibra/experimental/cortical_profile_sampler.py +57 -0
- siibra/experimental/patch.py +98 -0
- siibra/experimental/plane3d.py +256 -0
- siibra/explorer/__init__.py +16 -0
- siibra/explorer/url.py +112 -52
- siibra/explorer/util.py +31 -9
- siibra/features/__init__.py +73 -8
- siibra/features/anchor.py +75 -196
- siibra/features/connectivity/__init__.py +1 -1
- siibra/features/connectivity/functional_connectivity.py +2 -2
- siibra/features/connectivity/regional_connectivity.py +99 -10
- siibra/features/connectivity/streamline_counts.py +1 -1
- siibra/features/connectivity/streamline_lengths.py +1 -1
- siibra/features/connectivity/tracing_connectivity.py +1 -1
- siibra/features/dataset/__init__.py +1 -1
- siibra/features/dataset/ebrains.py +3 -3
- siibra/features/feature.py +219 -110
- siibra/features/image/__init__.py +1 -1
- siibra/features/image/image.py +21 -13
- siibra/features/image/sections.py +1 -1
- siibra/features/image/volume_of_interest.py +1 -1
- siibra/features/tabular/__init__.py +1 -1
- siibra/features/tabular/bigbrain_intensity_profile.py +24 -13
- siibra/features/tabular/cell_density_profile.py +111 -69
- siibra/features/tabular/cortical_profile.py +82 -16
- siibra/features/tabular/gene_expression.py +117 -6
- siibra/features/tabular/layerwise_bigbrain_intensities.py +7 -9
- siibra/features/tabular/layerwise_cell_density.py +9 -24
- siibra/features/tabular/receptor_density_fingerprint.py +11 -6
- siibra/features/tabular/receptor_density_profile.py +12 -15
- siibra/features/tabular/regional_timeseries_activity.py +74 -18
- siibra/features/tabular/tabular.py +17 -8
- siibra/livequeries/__init__.py +1 -7
- siibra/livequeries/allen.py +139 -77
- siibra/livequeries/bigbrain.py +104 -128
- siibra/livequeries/ebrains.py +7 -4
- siibra/livequeries/query.py +1 -2
- siibra/locations/__init__.py +32 -25
- siibra/locations/boundingbox.py +153 -127
- siibra/locations/location.py +45 -80
- siibra/locations/point.py +97 -83
- siibra/locations/pointcloud.py +349 -0
- siibra/retrieval/__init__.py +1 -1
- siibra/retrieval/cache.py +107 -13
- siibra/retrieval/datasets.py +9 -14
- siibra/retrieval/exceptions/__init__.py +2 -1
- siibra/retrieval/repositories.py +147 -53
- siibra/retrieval/requests.py +64 -29
- siibra/vocabularies/__init__.py +2 -2
- siibra/volumes/__init__.py +7 -9
- siibra/volumes/parcellationmap.py +396 -253
- siibra/volumes/providers/__init__.py +20 -0
- siibra/volumes/providers/freesurfer.py +113 -0
- siibra/volumes/{gifti.py → providers/gifti.py} +29 -18
- siibra/volumes/{neuroglancer.py → providers/neuroglancer.py} +204 -92
- siibra/volumes/{nifti.py → providers/nifti.py} +64 -44
- siibra/volumes/providers/provider.py +107 -0
- siibra/volumes/sparsemap.py +159 -260
- siibra/volumes/volume.py +720 -152
- {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/METADATA +25 -28
- siibra-1.0.0a1.dist-info/RECORD +84 -0
- {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/WHEEL +1 -1
- siibra/locations/pointset.py +0 -198
- siibra-0.5a2.dist-info/RECORD +0 -74
- {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/LICENSE +0 -0
- {siibra-0.5a2.dist-info → siibra-1.0.0a1.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2024
|
|
2
2
|
# Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
|
|
3
3
|
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
|
-
"""Qualification between two
|
|
15
|
+
"""Qualification between two BrainStructures"""
|
|
16
16
|
|
|
17
17
|
from enum import Enum
|
|
18
|
-
from typing import Dict, TypeVar, Generic
|
|
19
18
|
from dataclasses import dataclass
|
|
19
|
+
from typing import Dict, Generic, TypeVar, TYPE_CHECKING
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from .structure import BrainStructure
|
|
20
22
|
|
|
21
23
|
T = TypeVar("T")
|
|
22
24
|
|
|
@@ -43,14 +45,14 @@ class Qualification(Enum):
|
|
|
43
45
|
Qualification.CONTAINS: 'contains',
|
|
44
46
|
Qualification.APPROXIMATE: 'approximates to',
|
|
45
47
|
Qualification.HOMOLOGOUS: 'is homologous to',
|
|
46
|
-
Qualification.OTHER_VERSION: 'is another version of'
|
|
48
|
+
Qualification.OTHER_VERSION: 'is another version of'
|
|
47
49
|
}
|
|
48
|
-
assert self in transl, f"{str(self)} verb cannot be found
|
|
50
|
+
assert self in transl, f"{str(self)} verb cannot be found."
|
|
49
51
|
return transl[self]
|
|
50
52
|
|
|
51
53
|
def invert(self):
|
|
52
54
|
"""
|
|
53
|
-
Return
|
|
55
|
+
Return qualification with the inverse meaning
|
|
54
56
|
"""
|
|
55
57
|
inverses = {
|
|
56
58
|
Qualification.EXACT: Qualification.EXACT,
|
|
@@ -61,7 +63,7 @@ class Qualification(Enum):
|
|
|
61
63
|
Qualification.HOMOLOGOUS: Qualification.HOMOLOGOUS,
|
|
62
64
|
Qualification.OTHER_VERSION: Qualification.OTHER_VERSION,
|
|
63
65
|
}
|
|
64
|
-
assert self in inverses, f"{str(self)} inverses cannot be found"
|
|
66
|
+
assert self in inverses, f"{str(self)} inverses cannot be found."
|
|
65
67
|
return inverses[self]
|
|
66
68
|
|
|
67
69
|
def __str__(self):
|
|
@@ -79,9 +81,10 @@ class Qualification(Enum):
|
|
|
79
81
|
|
|
80
82
|
|
|
81
83
|
@dataclass
|
|
82
|
-
class
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
class AnatomicalAssignment(Generic[T]):
|
|
85
|
+
"""Represents a qualified assignment between anatomical structures."""
|
|
86
|
+
query_structure: "BrainStructure"
|
|
87
|
+
assigned_structure: "BrainStructure"
|
|
85
88
|
qualification: Qualification
|
|
86
89
|
explanation: str = ""
|
|
87
90
|
|
|
@@ -94,14 +97,14 @@ class RelationAssignment(Generic[T]):
|
|
|
94
97
|
return msg if self.explanation == "" else f"{msg} - {self.explanation}"
|
|
95
98
|
|
|
96
99
|
def invert(self):
|
|
97
|
-
return
|
|
100
|
+
return AnatomicalAssignment(
|
|
98
101
|
self.assigned_structure,
|
|
99
102
|
self.query_structure,
|
|
100
103
|
self.qualification.invert(),
|
|
101
104
|
self.explanation
|
|
102
105
|
)
|
|
103
106
|
|
|
104
|
-
def __lt__(self, other: '
|
|
105
|
-
if not isinstance(other,
|
|
106
|
-
raise ValueError(f"Cannot compare
|
|
107
|
+
def __lt__(self, other: 'AnatomicalAssignment'):
|
|
108
|
+
if not isinstance(other, AnatomicalAssignment):
|
|
109
|
+
raise ValueError(f"Cannot compare AnatomicalAssignment with instances of '{type(other)}'")
|
|
107
110
|
return self.qualification.value < other.qualification.value
|
siibra/core/atlas.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2024
|
|
2
2
|
# Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
|
|
3
3
|
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -30,14 +30,15 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
|
|
|
30
30
|
spaces, as well as common functionalities of those.
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
def __init__(self, identifier: str, name: str, species: Species):
|
|
33
|
+
def __init__(self, identifier: str, name: str, species: Species, **kwargs):
|
|
34
34
|
"""Construct an empty atlas object with a name and identifier."""
|
|
35
35
|
|
|
36
36
|
concept.AtlasConcept.__init__(
|
|
37
37
|
self,
|
|
38
38
|
identifier=identifier,
|
|
39
39
|
name=name,
|
|
40
|
-
species=species
|
|
40
|
+
species=species,
|
|
41
|
+
**kwargs
|
|
41
42
|
)
|
|
42
43
|
self._parcellation_ids: List[str] = []
|
|
43
44
|
self._space_ids: List[str] = []
|
|
@@ -64,9 +65,20 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
|
|
|
64
65
|
matchfunc=_parcellation.Parcellation.match,
|
|
65
66
|
)
|
|
66
67
|
|
|
67
|
-
def get_parcellation(self, parcellation=None):
|
|
68
|
-
"""
|
|
69
|
-
|
|
68
|
+
def get_parcellation(self, parcellation=None) -> "_parcellation.Parcellation":
|
|
69
|
+
"""
|
|
70
|
+
Returns a valid parcellation object defined by the atlas. If no
|
|
71
|
+
specification is provided, the default is returned.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
parcellation: str, Parcellation
|
|
76
|
+
specification of a parcellation or a parcellation object
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
Parcellation
|
|
81
|
+
"""
|
|
70
82
|
|
|
71
83
|
if parcellation is None:
|
|
72
84
|
parcellation_obj = self.parcellations[self._parcellation_ids[0]]
|
|
@@ -80,12 +92,19 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
|
|
|
80
92
|
|
|
81
93
|
return self.parcellations[parcellation]
|
|
82
94
|
|
|
83
|
-
def get_space(self, space=None):
|
|
84
|
-
"""
|
|
85
|
-
|
|
95
|
+
def get_space(self, space=None) -> "_space.Space":
|
|
96
|
+
"""
|
|
97
|
+
Returns a valid reference space object defined by the atlas. If no
|
|
98
|
+
specification is provided, the default is returned.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
space: str, Space
|
|
103
|
+
specification of a space or a space object
|
|
86
104
|
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
Space
|
|
89
108
|
"""
|
|
90
109
|
if space is None:
|
|
91
110
|
space_obj = self.spaces[self._space_ids[0]]
|
|
@@ -105,12 +124,13 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
|
|
|
105
124
|
parcellation: _parcellation.Parcellation = None,
|
|
106
125
|
maptype: MapType = MapType.LABELLED,
|
|
107
126
|
):
|
|
108
|
-
"""
|
|
127
|
+
"""
|
|
128
|
+
Returns a parcellation map in the given space.
|
|
109
129
|
|
|
110
130
|
Parameters
|
|
111
131
|
----------
|
|
112
132
|
|
|
113
|
-
space
|
|
133
|
+
space: Space
|
|
114
134
|
The requested reference space. If None, the default is used.
|
|
115
135
|
parcellation: Parcellation
|
|
116
136
|
The requested parcellation. If None, the default is used.
|
|
@@ -160,39 +180,46 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
|
|
|
160
180
|
def get_voi(self, space: _space.Space, point1: tuple, point2: tuple):
|
|
161
181
|
"""Get a volume of interest spanned by two points in the given reference space.
|
|
162
182
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
space: Space, str
|
|
186
|
+
The target reference space, or a string specification of the space
|
|
187
|
+
point1: Tuple
|
|
188
|
+
A 3D coordinate given in this reference space
|
|
189
|
+
point2: Tuple
|
|
190
|
+
Another 3D coordinate given in this reference space
|
|
167
191
|
|
|
168
|
-
Returns
|
|
169
|
-
|
|
192
|
+
Returns
|
|
193
|
+
-------
|
|
194
|
+
BoundingBox
|
|
170
195
|
"""
|
|
171
|
-
return self.
|
|
196
|
+
return self.get_template(space).get_boundingbox(point1, point2)
|
|
172
197
|
|
|
173
198
|
def find_regions(
|
|
174
199
|
self,
|
|
175
|
-
regionspec,
|
|
176
|
-
all_versions=False,
|
|
177
|
-
filter_children=True,
|
|
178
|
-
|
|
200
|
+
regionspec: str,
|
|
201
|
+
all_versions: bool = False,
|
|
202
|
+
filter_children: bool = True,
|
|
203
|
+
find_topmost: bool = False
|
|
179
204
|
):
|
|
180
205
|
"""
|
|
181
|
-
Find regions with the given specification in all
|
|
182
|
-
|
|
183
|
-
are passed on to Parcellation.find().
|
|
206
|
+
Find regions with the given specification in all parcellations offered
|
|
207
|
+
by the atlas.
|
|
184
208
|
|
|
185
209
|
Parameters
|
|
186
210
|
----------
|
|
187
|
-
regionspec: str, regex
|
|
211
|
+
regionspec: str, regex
|
|
188
212
|
- a string with a possibly inexact name (matched both against the name and the identifier key)
|
|
189
|
-
- a string in '/pattern/flags' format to use regex search (acceptable flags: aiLmsux)
|
|
213
|
+
- a string in '/pattern/flags' format to use regex search (acceptable flags: aiLmsux, see at https://docs.python.org/3/library/re.html#flags)
|
|
190
214
|
- a regex applied to region names
|
|
191
|
-
- a Region object
|
|
192
215
|
all_versions : Bool, default: False
|
|
193
216
|
If True, matched regions for all versions of a parcellation are returned.
|
|
194
217
|
filter_children : bool, default: True
|
|
195
218
|
If False, children of matched parents will be returned.
|
|
219
|
+
find_topmost : bool, default: False
|
|
220
|
+
If True (requires `filter_children=True`), will return parent
|
|
221
|
+
structures if all children are matched, even though the parent
|
|
222
|
+
itself might not match the specification.
|
|
196
223
|
|
|
197
224
|
Returns
|
|
198
225
|
-------
|
|
@@ -200,9 +227,13 @@ class Atlas(concept.AtlasConcept, configuration_folder="atlases"):
|
|
|
200
227
|
list of regions matching to the regionspec
|
|
201
228
|
"""
|
|
202
229
|
result = []
|
|
203
|
-
for p in self.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
230
|
+
for p in self.parcellations:
|
|
231
|
+
if p.is_newest_version or all_versions:
|
|
232
|
+
result.extend(
|
|
233
|
+
p.find(
|
|
234
|
+
regionspec=regionspec,
|
|
235
|
+
filter_children=filter_children,
|
|
236
|
+
find_topmost=find_topmost
|
|
237
|
+
)
|
|
238
|
+
)
|
|
208
239
|
return result
|
siibra/core/concept.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2018-
|
|
1
|
+
# Copyright 2018-2024
|
|
2
2
|
# Institute of Neuroscience and Medicine (INM-1), Forschungszentrum Jülich GmbH
|
|
3
3
|
|
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -21,11 +21,18 @@ from ..commons import (
|
|
|
21
21
|
Species,
|
|
22
22
|
TypePublication
|
|
23
23
|
)
|
|
24
|
+
from ..retrieval import cache
|
|
24
25
|
|
|
25
26
|
import re
|
|
26
|
-
from typing import TypeVar, Type, Union, List, TYPE_CHECKING
|
|
27
|
+
from typing import TypeVar, Type, Union, List, TYPE_CHECKING, Dict
|
|
27
28
|
|
|
28
29
|
T = TypeVar("T", bound="AtlasConcept")
|
|
30
|
+
_REGISTRIES: Dict[Type[T], InstanceTable[T]] = {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@cache.Warmup.register_warmup_fn(is_factory=True)
|
|
34
|
+
def _atlas_concept_warmup():
|
|
35
|
+
return [cls.registry for cls in _REGISTRIES]
|
|
29
36
|
|
|
30
37
|
|
|
31
38
|
if TYPE_CHECKING:
|
|
@@ -33,6 +40,15 @@ if TYPE_CHECKING:
|
|
|
33
40
|
TypeDataset = EbrainsDataset
|
|
34
41
|
|
|
35
42
|
|
|
43
|
+
def get_registry(subclass_name: str):
|
|
44
|
+
subclasses = {c.__name__: c for c in _REGISTRIES}
|
|
45
|
+
if subclass_name in subclasses:
|
|
46
|
+
return subclasses[subclass_name].registry()
|
|
47
|
+
else:
|
|
48
|
+
logger.warn(f"No registry for atlas concepts named {subclass_name}")
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
36
52
|
class AtlasConcept:
|
|
37
53
|
"""
|
|
38
54
|
Parent class encapsulating commonalities of the basic siibra concept like atlas, parcellation, space, region.
|
|
@@ -51,7 +67,8 @@ class AtlasConcept:
|
|
|
51
67
|
modality: str = "",
|
|
52
68
|
publications: List[TypePublication] = [],
|
|
53
69
|
datasets: List['TypeDataset'] = [],
|
|
54
|
-
spec=None
|
|
70
|
+
spec=None,
|
|
71
|
+
prerelease: bool = False,
|
|
55
72
|
):
|
|
56
73
|
"""
|
|
57
74
|
Construct a new atlas concept base object.
|
|
@@ -78,7 +95,7 @@ class AtlasConcept:
|
|
|
78
95
|
The preconfigured specification.
|
|
79
96
|
"""
|
|
80
97
|
self._id = identifier
|
|
81
|
-
self.name = name
|
|
98
|
+
self.name = name if not prerelease else f"[PRERELEASE] {name}"
|
|
82
99
|
self._species_cached = None if species is None \
|
|
83
100
|
else Species.decode(species) # overwritable property implementation below
|
|
84
101
|
self.shortname = shortname
|
|
@@ -87,6 +104,8 @@ class AtlasConcept:
|
|
|
87
104
|
self._publications = publications
|
|
88
105
|
self.datasets = datasets
|
|
89
106
|
self._spec = spec
|
|
107
|
+
self._CACHED_MATCHES = {} # we cache match() function results
|
|
108
|
+
self._prerelease = prerelease
|
|
90
109
|
|
|
91
110
|
@property
|
|
92
111
|
def description(self):
|
|
@@ -99,15 +118,29 @@ class AtlasConcept:
|
|
|
99
118
|
|
|
100
119
|
@property
|
|
101
120
|
def LICENSE(self) -> str:
|
|
102
|
-
|
|
121
|
+
licenses = []
|
|
122
|
+
for ds in self.datasets:
|
|
123
|
+
if ds.LICENSE is None or ds.LICENSE == "No license information is found.":
|
|
124
|
+
continue
|
|
125
|
+
if isinstance(ds.LICENSE, str):
|
|
126
|
+
licenses.append(ds.LICENSE)
|
|
127
|
+
if isinstance(ds.LICENSE, list):
|
|
128
|
+
licenses.extend(ds.LICENSE)
|
|
129
|
+
if len(licenses) == 0:
|
|
130
|
+
logger.warning("No license information is found.")
|
|
131
|
+
return ""
|
|
132
|
+
if len(licenses) > 1:
|
|
133
|
+
logger.info("Found multiple licenses corresponding to datasets.")
|
|
134
|
+
return '\n'.join(licenses)
|
|
103
135
|
|
|
104
136
|
@property
|
|
105
|
-
def
|
|
106
|
-
|
|
137
|
+
def urls(self) -> List[str]:
|
|
138
|
+
"""The list of URLs (including DOIs) associated with this atlas concept."""
|
|
139
|
+
return [
|
|
107
140
|
url.get("url")
|
|
108
141
|
for ds in self.datasets
|
|
109
142
|
for url in ds.urls
|
|
110
|
-
]
|
|
143
|
+
]
|
|
111
144
|
|
|
112
145
|
@property
|
|
113
146
|
def authors(self):
|
|
@@ -139,7 +172,7 @@ class AtlasConcept:
|
|
|
139
172
|
def registry(cls: Type[T]) -> InstanceTable[T]:
|
|
140
173
|
if cls._configuration_folder is None:
|
|
141
174
|
return None
|
|
142
|
-
if cls
|
|
175
|
+
if _REGISTRIES[cls] is None:
|
|
143
176
|
from ..configuration import Configuration
|
|
144
177
|
conf = Configuration()
|
|
145
178
|
# visit the configuration to provide a cleanup function
|
|
@@ -159,17 +192,20 @@ class AtlasConcept:
|
|
|
159
192
|
r = siibra.volumes.Map.registry()
|
|
160
193
|
"""
|
|
161
194
|
if len({o.__class__ for o in objects}) > 1:
|
|
162
|
-
logger.warning(
|
|
195
|
+
logger.warning(
|
|
196
|
+
f"{cls.__name__} registry contains multiple classes: "
|
|
197
|
+
f"{', '.join(list({o.__class__.__name__ for o in objects}))}"
|
|
198
|
+
)
|
|
163
199
|
assert hasattr(objects[0].__class__, "match") and callable(objects[0].__class__.match)
|
|
164
|
-
cls
|
|
200
|
+
_REGISTRIES[cls] = InstanceTable(
|
|
165
201
|
elements={o.key: o for o in objects},
|
|
166
202
|
matchfunc=objects[0].__class__.match
|
|
167
203
|
)
|
|
168
|
-
return cls
|
|
204
|
+
return _REGISTRIES[cls]
|
|
169
205
|
|
|
170
206
|
@classmethod
|
|
171
207
|
def clear_registry(cls):
|
|
172
|
-
cls
|
|
208
|
+
_REGISTRIES[cls] = None
|
|
173
209
|
|
|
174
210
|
@classmethod
|
|
175
211
|
def get_instance(cls, spec: str):
|
|
@@ -204,45 +240,51 @@ class AtlasConcept:
|
|
|
204
240
|
This method is called whenever AtlasConcept gets subclassed
|
|
205
241
|
(see https://docs.python.org/3/reference/datamodel.html)
|
|
206
242
|
"""
|
|
207
|
-
cls._registry_cached = None
|
|
208
243
|
cls._configuration_folder = configuration_folder
|
|
244
|
+
_REGISTRIES[cls] = None
|
|
209
245
|
return super().__init_subclass__()
|
|
210
246
|
|
|
211
247
|
def __str__(self):
|
|
212
|
-
return
|
|
248
|
+
return self.name
|
|
249
|
+
|
|
250
|
+
def __repr__(self):
|
|
251
|
+
return f"<{self.__class__.__name__}(identifier='{self.id}', name='{self.name}', species='{self.species}')>"
|
|
213
252
|
|
|
214
|
-
def matches(self, spec):
|
|
253
|
+
def matches(self, spec) -> bool:
|
|
215
254
|
"""
|
|
216
255
|
Parameters
|
|
217
256
|
----------
|
|
218
|
-
|
|
219
|
-
|
|
257
|
+
spec: str
|
|
258
|
+
Specification checked within the concept name, key or id
|
|
259
|
+
|
|
220
260
|
Returns
|
|
221
261
|
-------
|
|
222
|
-
|
|
223
|
-
|
|
262
|
+
bool
|
|
263
|
+
Whether the given specification matches the name, key or id of the concept.
|
|
224
264
|
"""
|
|
225
|
-
if
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
265
|
+
if spec not in self._CACHED_MATCHES:
|
|
266
|
+
self._CACHED_MATCHES[spec] = False
|
|
267
|
+
if isinstance(spec, self.__class__) and (spec == self):
|
|
268
|
+
self._CACHED_MATCHES[spec] = True
|
|
269
|
+
elif isinstance(spec, str):
|
|
270
|
+
if spec == self.key:
|
|
271
|
+
self._CACHED_MATCHES[spec] = True
|
|
272
|
+
elif spec == self.id:
|
|
273
|
+
self._CACHED_MATCHES[spec] = True
|
|
274
|
+
else:
|
|
275
|
+
# match the name
|
|
276
|
+
words = [w for w in re.split("[ -]", spec)]
|
|
277
|
+
squeezedname = clear_name(self.name.lower()).replace(" ", "")
|
|
278
|
+
self._CACHED_MATCHES[spec] = any(
|
|
279
|
+
[
|
|
280
|
+
all(w.lower() in squeezedname for w in words),
|
|
281
|
+
spec.replace(" ", "") in squeezedname,
|
|
282
|
+
]
|
|
283
|
+
)
|
|
284
|
+
return self._CACHED_MATCHES[spec]
|
|
243
285
|
|
|
244
286
|
@classmethod
|
|
245
|
-
def match(cls, obj, spec):
|
|
287
|
+
def match(cls, obj, spec) -> bool:
|
|
246
288
|
"""Match a given object specification. """
|
|
247
289
|
assert isinstance(obj, cls)
|
|
248
290
|
return obj.matches(spec)
|