Skip to content

Commit ec27932

Browse files
authored
Merge pull request #205 from FZJ-INM1-BDA/feat_addDescRatMouse
bugfix: add desc to waxholm & allen
2 parents 700bc83 + 4b6ba84 commit ec27932

File tree

5 files changed

+210
-6
lines changed

5 files changed

+210
-6
lines changed

siibra/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .commons import logger, QUIET, VERBOSE
1717

1818
# __version__ is parsed by setup.py
19-
__version__ = "0.3a23"
19+
__version__ = "0.3a24"
2020
logger.info(f"Version: {__version__}")
2121
logger.warning("This is a development release. Use at your own risk.")
2222
logger.info(

siibra/core/datasets.py

+105-2
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16-
import hashlib
1716
from .serializable_concept import JSONSerializable
1817
from ..commons import logger
1918
from ..retrieval import EbrainsKgQuery
19+
from ..retrieval.requests import DECODERS, EbrainsRequest
2020
from ..openminds.core.v4.products.datasetVersion import Model as DatasetVersionModel
2121
from ..openminds.base import ConfigBaseModel
2222

23+
import hashlib
2324
import re
2425
from datetime import date
25-
from typing import List, Optional
26+
from typing import Any, Dict, List, Optional
2627
from pydantic import Field
2728

2829
class Url(ConfigBaseModel):
@@ -185,6 +186,108 @@ def _from_json(cls, spec):
185186
)
186187

187188

189+
class _EbrainsKgV3Base:
190+
BASE_URL = "https://core.kg.ebrains.eu/v3-beta/queries"
191+
QUERY_ID = None
192+
STAGE = "RELEASED"
193+
194+
def __init__(self, _id_spec: Dict[str, Any]) -> None:
195+
self._id_spec = _id_spec
196+
self._spec = None
197+
198+
199+
@classmethod
200+
def _query(cls, spec: Dict[str, Any]=None):
201+
202+
# for easy compatibility with data returned by KG
203+
# KG no longer uses @id for key, but instead id (by default)
204+
# also id prepends domain information (e.g. https://kg.ebrains.eu/api/instances/{uuid})
205+
# therefore, also extract the uuid protion
206+
207+
if spec is not None:
208+
at_id=spec.get("@id")
209+
kg_id = spec.get("id")
210+
kg_id_search = kg_id and re.search(r'[a-f0-9-]+$', kg_id)
211+
212+
assert at_id is not None or kg_id_search is not None
213+
uuid = at_id or kg_id_search.group()
214+
215+
assert hasattr(cls, 'type_id')
216+
217+
# for easy compatibility with data returned by KG
218+
# KG no longer uses @type for key, but type
219+
# also type is List[str]
220+
assert spec.get("@type") == cls.type_id or any ([t == cls.type_id for t in spec.get("type", [])])
221+
222+
223+
url=f"{cls.BASE_URL}/{cls.QUERY_ID}/instances?stage={cls.STAGE}"
224+
if spec is not None:
225+
url += f"&instanceId={uuid}"
226+
227+
result = EbrainsRequest(url, DECODERS['.json']).get()
228+
229+
assert 'data' in result
230+
231+
if spec is not None:
232+
assert result.get('total') == 1
233+
assert result.get('size') == 1
234+
return result.get("data")[0]
235+
236+
return result.get('data', [])
237+
238+
239+
class EbrainsKgV3Dataset(Dataset, _EbrainsKgV3Base, type_id="https://openminds.ebrains.eu/core/Dataset"):
240+
BASE_URL = "https://core.kg.ebrains.eu/v3-beta/queries"
241+
QUERY_ID = "138111f9-1aa4-43f5-8e0a-6e6ed085fa3e"
242+
243+
def __init__(self, spec: Dict[str, Any]):
244+
super().__init__(None)
245+
found = re.search(r'[a-f0-9-]+$', spec.get('id'))
246+
assert found
247+
self.id = found.group()
248+
self._description_cached = spec.get("description")
249+
self._spec = spec
250+
251+
@classmethod
252+
def _from_json(cls, spec: Dict[str, Any]):
253+
json_obj = cls._query(spec)
254+
return cls(json_obj)
255+
256+
257+
class EbrainsKgV3DatasetVersion(Dataset, _EbrainsKgV3Base, type_id="https://openminds.ebrains.eu/core/DatasetVersion"):
258+
259+
BASE_URL = "https://core.kg.ebrains.eu/v3-beta/queries"
260+
QUERY_ID = "f7489d01-2f90-410c-9812-9ee7d10cc5be"
261+
262+
def __init__(self, _id_spec: Dict[str, Any]):
263+
_EbrainsKgV3Base.__init__(self, _id_spec)
264+
Dataset.__init__(self, None)
265+
266+
@classmethod
267+
def _from_json(cls, spec: Dict[str, Any]):
268+
return cls(spec)
269+
270+
@property
271+
def description(self):
272+
if self._spec is None:
273+
self._spec = self._query(self._id_spec)
274+
275+
self._description_cached = self._spec.get("description")
276+
277+
if self._description_cached is not None and self._description_cached != '':
278+
return self._description_cached
279+
280+
parent_datasets = self._spec.get("belongsTo", [])
281+
if len(parent_datasets) == 0:
282+
return None
283+
if len(parent_datasets) > 1:
284+
logger.warn(f"EbrainsKgV3DatasetVersion.description: more than one parent dataset found. Using the first one...")
285+
286+
parent = EbrainsKgV3Dataset._from_json(parent_datasets[0])
287+
return parent.description
288+
289+
290+
188291
class EbrainsDataset(Dataset, type_id="minds/core/dataset/v1.0.0"):
189292
def __init__(self, id, name, embargo_status=None):
190293
Dataset.__init__(self, id, description=None)

siibra/core/parcellation.py

+21-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from .region import Region
1818
from .concept import AtlasConcept, provide_registry
1919
from .serializable_concept import JSONSerializable
20-
from .datasets import DatasetJsonModel, OriginDescription, EbrainsDataset
20+
from .datasets import DatasetJsonModel, OriginDescription, EbrainsDataset, EbrainsKgV3DatasetVersion, EbrainsKgV3Dataset
2121

2222
from ..commons import logger, MapType, ParcellationIndex, Registry
2323
from ..volumes import ParcellationMap
@@ -208,14 +208,32 @@ def __init__(
208208
"""
209209
AtlasConcept.__init__(self, identifier, name, dataset_specs)
210210
self.version = version
211-
self.description = ""
211+
self._description = ""
212212
self.modality = modality
213213
self._regiondefs = regiondefs
214214
if maps is not None:
215215
if self._datasets_cached is None:
216216
self._datasets_cached = []
217217
self._datasets_cached.extend(maps)
218218
self.atlases = set()
219+
220+
@property
221+
def description(self):
222+
223+
metadata_datasets = [info
224+
for info in self.infos
225+
if isinstance(info, EbrainsDataset)
226+
or isinstance(info, EbrainsKgV3DatasetVersion)
227+
or isinstance(info, EbrainsKgV3Dataset)
228+
or isinstance(info, OriginDescription)]
229+
230+
if len(metadata_datasets) == 0:
231+
return self._description
232+
233+
if len(metadata_datasets) > 1:
234+
logger.debug(f"Parcellation.description multiple metadata_datasets found. Using the first one.")
235+
236+
return metadata_datasets[0].description
219237

220238
@property
221239
def regiontree(self):
@@ -560,7 +578,7 @@ def _from_json(cls, obj):
560578
result.modality = obj["modality"]
561579

562580
if "description" in obj:
563-
result.description = obj["description"]
581+
result._description = obj["description"]
564582

565583
if "publications" in obj:
566584
result._publications = obj["publications"]

test/core/test_datasets.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from unittest import TestCase, mock, main as run_test
2+
from siibra.core.datasets import EbrainsKgV3DatasetVersion, _EbrainsKgV3Base, EbrainsKgV3Dataset
3+
4+
DATASET_VERSION_TYPE = "https://openminds.ebrains.eu/core/DatasetVersion"
5+
DATASET_VERSION_ID = "fadcd2cb-9e8b-4e01-9777-f4d4df8f1ebc"
6+
7+
DATASET_TYPE = ["https://openminds.ebrains.eu/core/Dataset"]
8+
DATASTE_ID = "https://kg.ebrains.eu/api/instances/82f91c95-6799-485a-ab9a-010c75f9e790"
9+
10+
class TestEbrainsKgV3DatasetVersion(TestCase):
11+
12+
def test_lazy_on_init(self):
13+
with mock.patch.object(_EbrainsKgV3Base, '_query') as mock_query:
14+
EbrainsKgV3DatasetVersion({
15+
'@id': DATASET_VERSION_ID,
16+
'@type': DATASET_VERSION_TYPE
17+
})
18+
assert not mock_query.called
19+
20+
def test_on_try_desc_called(self):
21+
with mock.patch.object(_EbrainsKgV3Base, '_query') as mock_query:
22+
EbrainsKgV3DatasetVersion({
23+
'@id': DATASET_VERSION_ID,
24+
'@type': DATASET_VERSION_TYPE
25+
}).description
26+
assert mock_query.called
27+
28+
def test_return_desc_if_exists(self):
29+
with mock.patch.object(EbrainsKgV3Dataset, '_from_json') as mock_parent_json:
30+
with mock.patch.object(_EbrainsKgV3Base, '_query') as mock_query:
31+
mock_query.return_value = {
32+
'description': 'foo-bar'
33+
}
34+
EbrainsKgV3DatasetVersion({
35+
'@id': DATASET_VERSION_ID,
36+
'@type': DATASET_VERSION_TYPE
37+
}).description
38+
assert not mock_parent_json.called
39+
40+
def test_fallback_to_parent_if_null_desc(self):
41+
with mock.patch.object(EbrainsKgV3Dataset, '_from_json') as mock_parent_json:
42+
with mock.patch.object(_EbrainsKgV3Base, '_query') as mock_query:
43+
mock_query.return_value = {
44+
'description': '',
45+
'belongsTo': [{
46+
"type": DATASET_TYPE,
47+
"id": DATASTE_ID
48+
}]
49+
}
50+
EbrainsKgV3DatasetVersion({
51+
'@id': DATASET_VERSION_ID,
52+
'@type': DATASET_VERSION_TYPE
53+
}).description
54+
assert mock_parent_json.called
55+
56+
57+
if __name__ == "__main__":
58+
run_test()

test/core/test_parcellation.py

+25
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,28 @@ def test_should_have_ebrains_doi(atlas_id,parc_id):
114114

115115
if __name__ == "__main__":
116116
unittest.main()
117+
118+
119+
parc_has_desc = [
120+
("human", "julich brain 2.9"),
121+
122+
("rat", "v4"),
123+
("rat", "v3"),
124+
("rat", "v2"),
125+
("rat", "v1"),
126+
127+
("mouse", "2015"),
128+
("mouse", "2017"),
129+
]
130+
131+
@pytest.mark.parametrize("atlas_id,parc_id", parc_has_desc)
132+
def test_should_have_desc(atlas_id,parc_id):
133+
134+
atlas = siibra.atlases[atlas_id]
135+
parc = atlas.parcellations[parc_id]
136+
model = parc.to_model()
137+
138+
all(
139+
len(ver.description) > 20
140+
for ver in model.brain_atlas_versions
141+
)

0 commit comments

Comments
 (0)