Skip to content

Commit 593ab2c

Browse files
Merge branch 'main' into citation-file-handling
# Conflicts: # bids2openminds/converter.py # pyproject.toml # test/test_person.py
2 parents e8efa81 + df52e82 commit 593ab2c

23 files changed

Lines changed: 2189 additions & 1939 deletions

.github/workflows/codespell.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# Codespell configuration is within pyproject.toml
12
---
23
name: Codespell
34

@@ -18,7 +19,7 @@ jobs:
1819
steps:
1920
- name: Checkout
2021
uses: actions/checkout@v4
22+
- name: Annotate locations with typos
23+
uses: codespell-project/codespell-problem-matcher@v1
2124
- name: Codespell
2225
uses: codespell-project/actions-codespell@v2
23-
with:
24-
skip: light_openMINDS-bids2openminds-logo.svg, dark_openMINDS-bids2openminds-logo.svg

.github/workflows/test.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,15 @@ jobs:
3434

3535
- name: Install package with test dependencies
3636
run: |
37-
pip install .[test]
37+
pip install -e .[test]
3838
3939
- name: Test installed package
4040
run: |
41-
pytest -v
41+
pytest -v --cov=bids2openminds --cov-report=lcov
42+
43+
- name: Upload coverage to Coveralls
44+
if: matrix.os == 'ubuntu-latest'
45+
uses: coverallsapp/github-action@v2
46+
with:
47+
github-token: ${{ secrets.GITHUB_TOKEN }}
48+
path-to-lcov: coverage.lcov

CHANGES.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Changelog
2+
=========
3+
4+
0.2.0 (2026-05-07)
5+
------------------
6+
7+
- Update to openMINDS v4
8+
- Add content-type metadata to file objects
9+
- Improved conversion report: now lists detected behavioral protocols and tasks
10+
11+
0.1.1 (2025-06-05)
12+
------------------
13+
14+
- Pin to openMINDS v3 for stability
15+
- Fix compatibility with updated BIDS example datasets
16+
- Add Read the Docs documentation
17+
- Add project logo
18+
19+
0.1.0 (2024-06-21)
20+
------------------
21+
22+
Initial release. Features include:
23+
24+
- Convert BIDS datasets to openMINDS metadata collections (JSON-LD)
25+
- CLI interface (``bids2openminds`` command)
26+
- Support for subjects, authors/persons, file repositories, behavioral protocols, and dataset versions
27+
- NIfTI version detection and content-type mapping
28+
- Subject age validation and anomaly checking
29+
- Conversion summary report
30+
- Test suite using BIDS example datasets
31+
- Cross-platform CI (Linux, Windows, macOS)

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# bids2openminds
1010
A tool to generate openMINDS metadata from BIDS datasets
1111

12-
In active development, please try the first alpha release and send us feedback by creating an issue.
12+
In active development, please send us feedback by creating an issue.
1313

1414
## Installation
1515

@@ -23,7 +23,7 @@ pip install bids2openminds
2323
Usage: bids2openminds [OPTIONS] INPUT_PATH
2424
2525
Options:
26-
-o, --output-path PATH The output path or filename for OpenMINDS
26+
-o, --output-path PATH The output path or filename for openMINDS
2727
file/files.
2828
--single-file Save the entire collection into a single
2929
file (default).

bids2openminds/converter.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,63 @@
11
import warnings
2-
from bids import BIDSLayout, BIDSValidator
3-
from openminds import Collection
42
import os
53
import yaml
4+
import pandas as pd
5+
from ancpbids import BIDSLayout
6+
from ancpbids.query import Artifact
7+
from ancpbids.model_base import DatatypeFolder
8+
from openminds import Collection
69
import click
710
from . import main
811
from . import utility
912
from . import report
1013

14+
_ENTITY_RENAMES = {"sub": "subject", "ses": "session"}
15+
16+
# Root-level BIDS files that ancpBIDS does not expose as Artifacts
17+
_ROOT_BIDS_FILES = [
18+
("dataset_description.json", "description", ".json"),
19+
("participants.tsv", "participants", ".tsv"),
20+
("participants.json", "participants", ".json"),
21+
("CHANGES", None, None),
22+
("README", None, None),
23+
("README.md", None, None),
24+
]
25+
26+
27+
def layout_to_df(layout):
28+
dataset = layout.get_dataset()
29+
rows = []
30+
31+
for obj in layout.get(return_type='object', scope='raw'):
32+
if not isinstance(obj, Artifact):
33+
continue
34+
parent = obj.get_parent()
35+
datatype = parent.name if isinstance(parent, DatatypeFolder) else None
36+
row = {
37+
"path": obj.get_absolute_path(),
38+
"suffix": obj.suffix,
39+
"datatype": datatype,
40+
"extension": obj.extension,
41+
}
42+
for entity in obj.entities:
43+
key = _ENTITY_RENAMES.get(entity.key, entity.key)
44+
row[key] = entity.value
45+
rows.append(row)
46+
47+
base_dir = os.path.abspath(dataset.base_dir_)
48+
for fname, suffix, extension in _ROOT_BIDS_FILES:
49+
path = os.path.join(base_dir, fname)
50+
if not os.path.exists(path):
51+
continue
52+
row = {"path": path, "datatype": None}
53+
if suffix is not None:
54+
row["suffix"] = suffix
55+
if extension is not None:
56+
row["extension"] = extension
57+
rows.append(row)
58+
59+
return pd.DataFrame(rows)
60+
1161

1262
def convert(input_path, save_output=False, output_path=None, multiple_files=False, include_empty_properties=False, quiet=False):
1363
if not (os.path.isdir(input_path)):
@@ -24,7 +74,7 @@ def convert(input_path, save_output=False, output_path=None, multiple_files=Fal
2474
collection = Collection()
2575
bids_layout = BIDSLayout(input_path)
2676

27-
layout_df = bids_layout.to_df()
77+
layout_df = layout_to_df(bids_layout)
2878

2979
subjects_id = bids_layout.get_subjects()
3080

@@ -74,7 +124,7 @@ def convert(input_path, save_output=False, output_path=None, multiple_files=Fal
74124

75125
@click.command()
76126
@click.argument("input-path", type=click.Path(file_okay=False, exists=True))
77-
@click.option("-o", "--output-path", default=None, type=click.Path(file_okay=True, writable=True), help="The output path or filename for OpenMINDS file/files.")
127+
@click.option("-o", "--output-path", default=None, type=click.Path(file_okay=True, writable=True), help="The output path or filename for openMINDS file/files.")
78128
@click.option("--single-file", "multiple_files", flag_value=False, default=False, help="Save the entire collection into a single file (default).")
79129
@click.option("--multiple-files", "multiple_files", flag_value=True, help="Each node is saved into a separate file within the specified directory. 'output-path' if specified, must be a directory.")
80130
@click.option("-e", "--include-empty-properties", is_flag=True, default=False, help="Whether to include empty properties in the final file.")

bids2openminds/main.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import pandas as pd
77
from nameparser import HumanName
88

9-
import openminds.v3.core as omcore
10-
import openminds.v3.controlled_terms as controlled_terms
9+
import openminds.v4.core as omcore
10+
import openminds.v4.controlled_terms as controlled_terms
1111
from openminds import IRI
1212

1313
from .utility import table_filter, pd_table_value, file_hash, file_storage_size, detect_nifti_version
@@ -122,6 +122,7 @@ def create_behavioral_protocol(layout, collection):
122122

123123

124124
def techniques_openminds(suffix):
125+
# TODO "MRIPulseSequence" and "MRIWeighting" should be added as soon as openMINDS v4 becomes available.
125126
possible_types = ["Technique", "AnalysisTechnique", "StimulationApproach",
126127
"StimulationTechnique"]
127128

bids2openminds/report.py

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ def create_report(dataset, dataset_version, collection, dataset_description, inp
77
file_bundle_number = 0
88
files_number = 0
99
behavioral_protocols_numbers = 0
10+
content_type_list = ""
1011

1112
for item in collection:
1213
if item.type_.endswith("Subject"):
@@ -26,6 +27,10 @@ def create_report(dataset, dataset_version, collection, dataset_description, inp
2627

2728
behavioral_protocols_numbers += 1
2829

30+
if item.type_.endswith("ContentType"):
31+
32+
content_type_list += f"{item.name}\n"
33+
2934
experimental_approaches_list = ""
3035
if dataset_version.experimental_approaches is not None:
3136
for approache in dataset_version.experimental_approaches:
@@ -39,6 +44,20 @@ def create_report(dataset, dataset_version, collection, dataset_description, inp
3944
else:
4045
data_types_list = f"{dataset_version.data_types.name}\n"
4146

47+
techniques_list = ""
48+
if dataset_version.techniques is not None:
49+
for technique in dataset_version.techniques:
50+
techniques_list += f"{technique.name}\n"
51+
else:
52+
techniques_list = "No techniques were detected. Please follow the BIDS recommendations for suffixes, as bids2openminds detects techniques based on suffixes."
53+
54+
behavioral_protocols_list = ""
55+
if dataset_version.behavioral_protocols is not None:
56+
for behavioral_protocol in dataset_version.behavioral_protocols:
57+
behavioral_protocols_list += f"{behavioral_protocol.name}\n"
58+
else:
59+
behavioral_protocols_list = "No behavioral protocols were detected. Please follow the BIDS recommendations for task labels, as bids2openminds detects behavioral protocols based on task labels."
60+
4261
author_list = ""
4362
i = 1
4463
if dataset_version.authors is not None:
@@ -65,14 +84,6 @@ def create_report(dataset, dataset_version, collection, dataset_description, inp
6584
Dataset title : {dataset.full_name}
6685
6786
68-
Experimental approaches detected:
69-
------------------------------------------
70-
{experimental_approaches_list}
71-
72-
Detected data types:
73-
------------------------------------------
74-
{data_types_list}
75-
7687
The following elements were converted:
7788
------------------------------------------
7889
+ number of authors : {len(dataset_version.authors or [])}
@@ -84,6 +95,27 @@ def create_report(dataset, dataset_version, collection, dataset_description, inp
8495
+ number of behavioral protocols: {behavioral_protocols_numbers}
8596
8697
98+
Experimental approaches detected:
99+
------------------------------------------
100+
{experimental_approaches_list}
101+
102+
Detected data types:
103+
------------------------------------------
104+
{data_types_list}
105+
106+
Detected content types:
107+
------------------------------------------
108+
{content_type_list}
109+
110+
Detected techniques:
111+
------------------------------------------
112+
{techniques_list}
113+
114+
Detected behavioral protocols:
115+
------------------------------------------
116+
{behavioral_protocols_list}
117+
118+
87119
88120
**Important Notes**
89121
------------------------------------------

bids2openminds/utility.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
import pandas as pd
99

10-
import openminds.v3.controlled_terms as controlled_terms
11-
from openminds.v3.core import Hash, QuantitativeValue, ContentType
12-
from openminds.v3.controlled_terms import UnitOfMeasurement
10+
import openminds.v4.controlled_terms as controlled_terms
11+
from openminds.v4.core import Hash, QuantitativeValue, ContentType
12+
from openminds.v4.controlled_terms import UnitOfMeasurement
1313

1414

1515
def read_json(file_path: str) -> dict:
@@ -174,4 +174,4 @@ def detect_nifti_version(file_name, extension, file_size):
174174
elif sizeof_hdr == nii2_sizeof_hdr:
175175
return ContentType.by_name("application/vnd.nifti.2")
176176

177-
return None
177+
return ContentType.by_name("application/vnd.nifti.1")

codemeta.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
"codeRepository": "https://github.com/openMetadataInitiative/bids2openminds",
3434
"contIntegration": "https://github.com/openMetadataInitiative/bids2openminds/actions",
3535
"dateCreated": "2024-02-02",
36-
"dateModified": "2025-06-05",
36+
"dateModified": "2026-05-07",
3737
"datePublished": "2024-06-21",
3838
"description": "A tool to generate openMINDS metadata from BIDS datasets",
3939
"developmentStatus": "active",
40-
"downloadUrl": "https://files.pythonhosted.org/packages/28/7b/eeedbb1d40eb92dd87d9cd566f2301d31f2cf69a2a96c8f37f150af8a83c/bids2openminds-0.1.1.tar.gz",
40+
"downloadUrl": "https://files.pythonhosted.org/packages/bd/54/a5edea82cf22de4d956b4992cd837f4db7220fd44533eb59e36c7b4bc867/bids2openminds-0.2.0.tar.gz",
4141
"funder": {
4242
"@type": "Organization",
4343
"name": "European Commission"
@@ -61,5 +61,5 @@
6161
"https://pypi.org/project/click/",
6262
"https://pypi.org/project/nameparser/"
6363
],
64-
"version": "0.1.1"
64+
"version": "0.2.0"
6565
}

docs/source/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. include:: ../../CHANGES.rst

0 commit comments

Comments
 (0)