Skip to content

Commit f9a2487

Browse files
authored
Merge branch 'main' into contribution-docs
2 parents 1ae1e91 + c09cbf5 commit f9a2487

5 files changed

Lines changed: 196 additions & 23 deletions

File tree

bids2openminds/converter.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import warnings
22
import os
3+
import yaml
34
import pandas as pd
45
from ancpbids import BIDSLayout
56
from ancpbids.query import Artifact
@@ -18,6 +19,7 @@
1819
("participants.tsv", "participants", ".tsv"),
1920
("participants.json", "participants", ".json"),
2021
("CHANGES", None, None),
22+
("CITATION.cff", "CITATION", None),
2123
("README", None, None),
2224
("README.md", None, None),
2325
]
@@ -77,10 +79,14 @@ def convert(input_path, save_output=False, output_path=None, multiple_files=Fal
7779

7880
subjects_id = bids_layout.get_subjects()
7981

80-
# imprting the dataset description file containing some of the
82+
# importing the dataset description file containing some of the metadata
8183
dataset_description_path = utility.table_filter(layout_df, "description")
82-
8384
dataset_description = utility.read_json(dataset_description_path.iat[0, 0])
85+
citation_path = utility.table_filter(layout_df, "CITATION")
86+
citation = None
87+
if not citation_path.empty:
88+
with open(citation_path.iat[0, 0], encoding="utf-8") as fp:
89+
citation = yaml.safe_load(fp)
8490

8591
[subjects_dict, subject_state_dict, subjects_list] = main.create_subjects(
8692
subjects_id, layout_df, bids_layout, collection)
@@ -92,7 +98,7 @@ def convert(input_path, save_output=False, output_path=None, multiple_files=Fal
9298
layout_df, input_path, collection)
9399

94100
dataset_version = main.create_dataset_version(
95-
bids_layout, dataset_description, layout_df, subjects_list, file_repository, behavioral_protocols, collection)
101+
bids_layout, citation, dataset_description, layout_df, subjects_list, file_repository, behavioral_protocols, collection)
96102

97103
dataset = main.create_dataset(
98104
dataset_description, dataset_version, collection)

bids2openminds/main.py

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,35 @@ def create_openminds_person(full_name):
4848

4949

5050
def create_persons(dataset_description, collection):
51+
# citation.cff case
52+
if "authors" in dataset_description:
53+
person_list = dataset_description["authors"]
54+
openminds_list=[]
55+
for person in person_list:
56+
person_orcid = None
57+
person_affiliation = None
58+
person_contact_information = None
59+
if 'orcid' in person:
60+
person_orcid = [omcore.ORCID(identifier=person['orcid'])]
61+
if 'email' in person:
62+
person_contact_information = omcore.ContactInformation(email=person['email'])
63+
if 'affiliation' in person:
64+
# Handle multiple affiliations separated by semicolon
65+
affiliation_list = [item.strip() for item in person['affiliation'].split(';')]
66+
person_affiliation = []
67+
for affiliation in affiliation_list:
68+
person_affiliation.append(omcore.Affiliation(
69+
member_of=omcore.Organization(full_name=affiliation)))
70+
71+
openminds_person = omcore.Person(
72+
affiliations=person_affiliation, digital_identifiers=person_orcid, given_name=person['given-names'],
73+
family_name=person['family-names'], contact_information=person_contact_information)
74+
openminds_list.append(openminds_person)
75+
collection.add(openminds_person)
76+
return openminds_list
5177

52-
if "Authors" in dataset_description:
78+
# dataset_description.json case
79+
elif "Authors" in dataset_description:
5380
person_list = dataset_description["Authors"]
5481
else:
5582
return None
@@ -185,21 +212,39 @@ def create_openminds_age(data_subject):
185212
return None
186213

187214

188-
def create_dataset_version(bids_layout, dataset_description, layout_df, studied_specimens, file_repository, behavioral_protocols, collection):
215+
def create_dataset_version(bids_layout, citation, dataset_description, layout_df, studied_specimens, file_repository, behavioral_protocols, collection):
189216

190217
# Fetch the dataset type from dataset description file
191218

192219
# dataset_type=bids2openminds_instance(dataset_description.get("DatasetType",None))
193-
194-
# Fetch the digitalIdentifier from dataset description file
195-
196-
if "DatasetDOI" in dataset_description:
197-
digital_identifier = omcore.DOI(
198-
identifier=dataset_description["DatasetDOI"])
220+
license = None
221+
digital_identifier = None
222+
name = None
223+
version_identifier = None
224+
# General rules on the usage of CITATION.cff and dataset_description.json
225+
# https://bids-specification.readthedocs.io/en/stable/modality-agnostic-files/dataset-description.html#citationcff
226+
if citation:
227+
if 'doi' in citation:
228+
digital_identifier = omcore.DOI(identifier=citation['doi'])
229+
if 'license' in citation:
230+
license = omcore.License.by_name(citation['license'])
231+
if license is None:
232+
warn(f"Could not resolve license '{citation['license']}' "
233+
"to an openMINDS License."
234+
)
235+
if 'title' in citation:
236+
name = citation['title']
237+
if 'version' in citation:
238+
version_identifier = citation['version']
239+
authors = create_persons(citation, collection)
199240
else:
200-
digital_identifier = None
241+
# if CITATION.cff is present, the "Authors" field of dataset_description.json MUST be omitted
242+
authors = create_persons(dataset_description, collection)
201243

202-
authors = create_persons(dataset_description, collection)
244+
name = dataset_description["Name"] if name is None else name
245+
# Fetch the digitalIdentifier from dataset description file
246+
if digital_identifier is None and "DatasetDOI" in dataset_description:
247+
digital_identifier = omcore.DOI(identifier=dataset_description["DatasetDOI"])
203248

204249
if "Acknowledgements" in dataset_description:
205250
other_contribution = dataset_description["Acknowledgements"]
@@ -235,19 +280,20 @@ def create_dataset_version(bids_layout, dataset_description, layout_df, studied_
235280
experimental_approaches = create_approaches(layout_df)
236281

237282
dataset_version = omcore.DatasetVersion(
283+
authors=authors,
284+
behavioral_protocols=behavioral_protocols,
285+
data_types=dataset_type,
238286
digital_identifier=digital_identifier,
239287
experimental_approaches=experimental_approaches,
240-
short_name=dataset_description["Name"],
241-
full_name=dataset_description["Name"],
242-
studied_specimens=studied_specimens,
243-
authors=authors,
244-
techniques=techniques,
288+
full_name=name,
245289
how_to_cite=how_to_cite,
290+
license=license,
246291
repository=file_repository,
247-
behavioral_protocols=behavioral_protocols,
248-
data_types=dataset_type
292+
short_name=name,
293+
studied_specimens=studied_specimens,
294+
techniques=techniques,
295+
version_identifier=version_identifier,
249296
# other_contributions=other_contribution # needs to be a Contribution object
250-
# version_identifier
251297
)
252298

253299
collection.add(dataset_version)

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ dependencies = [
66
"openminds >= 0.2.3",
77
"click>=8.1",
88
"pandas>=1.3",
9-
"nameparser >= 1.1.3"
9+
"nameparser >= 1.1.3",
10+
"PyYAML >= 6.0"
1011
]
1112
requires-python = ">=3.9"
1213
authors = [

test/test_dataset_version.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import pytest
2+
3+
import pandas as pd
4+
from openminds import Collection
5+
import openminds.v4.core as omcore
6+
7+
from bids2openminds.main import create_dataset_version
8+
9+
# Mock BIDS layout DataFrame
10+
layout_df = pd.DataFrame({
11+
"suffix": ["T1w", "T2w", "bold"],
12+
"path": ["sub-01/anat/sub-01_T1w.nii.gz", "sub-01/anat/sub-01_T2w.nii.gz", "sub-01/func/sub-01_task-rest_bold.nii.gz"],
13+
"datatype": ["anat", "anat", "func"]
14+
})
15+
16+
17+
def test_create_dataset_version_citation():
18+
# Mock loaded CITATION.cff
19+
citation = {
20+
"title": "My Dataset",
21+
"version": "1.05.9",
22+
"doi": "10.5281/zenodo.123456",
23+
"license": "Apache-2.0"
24+
}
25+
26+
citation_dataset_version = create_dataset_version(
27+
"", citation, {}, layout_df, [], [], [], Collection()
28+
)
29+
30+
expected = omcore.DatasetVersion(
31+
digital_identifier=omcore.DOI(identifier="10.5281/zenodo.123456"),
32+
full_name="My Dataset",
33+
short_name="My Dataset",
34+
license=omcore.License.apache_2_0,
35+
version_identifier="1.05.9"
36+
)
37+
38+
for field in ["full_name", "short_name", "version_identifier", "license"]:
39+
assert getattr(citation_dataset_version, field) == getattr(expected, field)
40+
assert citation_dataset_version.digital_identifier.identifier == expected.digital_identifier.identifier
41+
42+
43+
def test_create_dataset_version_without_citation():
44+
# Mock missing CITATION.cff
45+
citation = None
46+
dataset_description = {
47+
"Name": "My Dataset",
48+
"DatasetDOI": "10.5281/zenodo.123456"
49+
}
50+
51+
dataset_version = create_dataset_version(
52+
"", citation, dataset_description, layout_df, [], [], [], Collection()
53+
)
54+
55+
expected = omcore.DatasetVersion(
56+
digital_identifier=omcore.DOI(identifier="10.5281/zenodo.123456"),
57+
full_name="My Dataset",
58+
short_name="My Dataset"
59+
)
60+
61+
for field in ["full_name", "short_name"]:
62+
assert getattr(dataset_version, field) == getattr(expected, field)
63+
assert dataset_version.digital_identifier.identifier == expected.digital_identifier.identifier

test/test_person.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# test for create_openminds_person function in the main
22
import pytest
3-
from bids2openminds.main import create_openminds_person
3+
4+
from openminds import Collection
45
import openminds.v4.core as omcore
56

7+
from bids2openminds.main import create_openminds_person, create_persons
8+
69
# Test data: (full_name, given_name, family_name)
710
example_names = [("John Ronald Reuel Tolkien", "John Ronald Reuel", "Tolkien"),
811
("Bilbo Baggins", "Bilbo", "Baggins"),
@@ -28,6 +31,60 @@ def test_create_openminds_person(full_name, given_name, family_name):
2831
# assert openminds_person_object == bids2openminds_person_object
2932

3033

34+
def test_create_openminds_person_citation_explicit():
35+
citation = {
36+
"authors": [
37+
{
38+
"family-names": "Awart",
39+
"given-names": "Peter",
40+
"affiliation": "Place1",
41+
"orcid": "https://orcid.org/1234-5678-9123-4567"
42+
},
43+
{
44+
"family-names": "Detienne",
45+
"given-names": "Franck",
46+
"affiliation": "Place1; Place2"
47+
}
48+
]
49+
}
50+
51+
persons = create_persons(citation, Collection())
52+
author1 = persons[0]
53+
author2 = persons[1]
54+
55+
# Expected objects
56+
expected_author1 = omcore.Person(
57+
given_name="Peter",
58+
family_name="Awart",
59+
affiliations=[
60+
omcore.Affiliation(member_of=omcore.Organization(full_name="Place1"))
61+
],
62+
digital_identifiers=[omcore.ORCID(identifier="https://orcid.org/1234-5678-9123-4567")]
63+
)
64+
65+
expected_author2 = omcore.Person(
66+
given_name="Franck",
67+
family_name="Detienne",
68+
affiliations=[
69+
omcore.Affiliation(member_of=omcore.Organization(full_name="Place1")),
70+
omcore.Affiliation(member_of=omcore.Organization(full_name="Place2"))
71+
]
72+
)
73+
74+
assert author1.given_name == expected_author1.given_name
75+
assert author1.family_name == expected_author1.family_name
76+
assert author1.digital_identifiers[0].identifier == expected_author1.digital_identifiers[0].identifier
77+
assert author2.given_name == expected_author2.given_name
78+
assert author2.family_name == expected_author2.family_name
79+
80+
# Single affiliation
81+
assert author1.affiliations[0].member_of.full_name == expected_author1.affiliations[0].member_of.full_name
82+
83+
# Multiple affiliations
84+
assert author2.affiliations[0].member_of.full_name == expected_author2.affiliations[0].member_of.full_name
85+
assert author2.affiliations[1].member_of.full_name == expected_author2.affiliations[1].member_of.full_name
86+
87+
3188
@pytest.mark.parametrize("full_not_name", example_not_names)
3289
def test_create_openminds_person_not_names(full_not_name):
3390
bids2openminds_person_object = create_openminds_person(full_not_name)

0 commit comments

Comments
 (0)