Skip to content

Commit e8ad28e

Browse files
Merge pull request #95 from apdavison/contribution-docs
Add contribution and architecture docs
2 parents c09cbf5 + 94c5388 commit e8ad28e

11 files changed

Lines changed: 388 additions & 5 deletions

File tree

.github/workflows/test.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ jobs:
3636
run: |
3737
pip install -e .[test]
3838
39+
- name: Minimal test of command-line
40+
run: |
41+
bids2openminds --help
42+
3943
- name: Test installed package
4044
run: |
4145
pytest -v --cov=bids2openminds --cov-report=lcov
@@ -46,3 +50,23 @@ jobs:
4650
with:
4751
github-token: ${{ secrets.GITHUB_TOKEN }}
4852
path-to-lcov: coverage.lcov
53+
54+
docs:
55+
runs-on: ubuntu-latest
56+
steps:
57+
58+
- name: Checkout Repository
59+
uses: actions/checkout@v4
60+
61+
- name: Set up Python 3.11
62+
uses: actions/setup-python@v5
63+
with:
64+
python-version: 3.11
65+
66+
- name: Install Sphinx dependencies
67+
run: |
68+
pip install sphinx sphinx-rtd-theme
69+
70+
- name: Build documentation
71+
run: |
72+
sphinx-build -W docs/source docs/_build/html

CODE_OF_CONDUCT.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Code of Conduct
2+
3+
Contributing to bids2openminds should be a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
4+
5+
## Our Pledge
6+
7+
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
8+
9+
## Our Standards
10+
11+
Examples of behavior that contributes to creating a positive environment include:
12+
13+
- Using welcoming and inclusive language
14+
- Being respectful of differing viewpoints and experiences
15+
- Gracefully accepting constructive criticism
16+
- Focusing on what is best for the community
17+
- Showing empathy towards other community members
18+
19+
Examples of unacceptable behavior by participants include:
20+
21+
- Use of sexualized language or imagery
22+
- Unwelcome sexual attention or advances
23+
- Trolling, insulting/derogatory comments, and personal or political attacks
24+
- Public or private harassment
25+
- Publishing others' private information (e.g. email address) without explicit permission
26+
- Other conduct which could reasonably be considered inappropriate in a professional setting
27+
28+
## Our Responsibilities
29+
30+
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
31+
32+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
33+
34+
## Scope
35+
36+
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or its community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
37+
38+
## Enforcement
39+
40+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the openMINDS team at `support@openmetadatainitiative.org`. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The openMINDS team is obligated to maintain confidentiality with regard to the reporter of an incident.
41+
42+
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
43+
44+
## Attribution
45+
46+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), [version 1.4](http://contributor-covenant.org/version/1/4/).

CONTRIBUTING.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Contributing to bids2openminds
2+
3+
Full contribution guidelines are in the [documentation](https://bids2openminds.readthedocs.io/en/latest/contributing.html).
4+
5+
## Quick start
6+
7+
```bash
8+
git clone --recurse-submodules https://github.com/openMetadataInitiative/bids2openminds.git
9+
cd bids2openminds
10+
pip install -e ".[test]"
11+
pytest
12+
```
13+
14+
## Key points
15+
16+
- Feature branches off `main`; PR required to merge; CI must pass
17+
- Google-style docstrings for all public functions
18+
- Deprecations get a `DeprecationWarning` for one release before removal
19+
- Contributors are expected to abide by the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)

bids2openminds/utility.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ def table_filter(dataframe: pd.DataFrame, filter_str: str, column: str = "suffix
6565

6666

6767
def pd_table_value(data_frame, column_name, not_list: bool = True):
68+
"""Extract a value from a single-row DataFrame column.
69+
70+
Parameters:
71+
- data_frame (pd.DataFrame): A DataFrame expected to contain a single row.
72+
- column_name (str): The column to read.
73+
- not_list (bool, optional): If True (default), return the scalar value; if False, return a list.
74+
75+
Returns:
76+
- The column value as a scalar (not_list=True) or list (not_list=False), or None if the column is absent.
77+
"""
6878
try:
6979
if column_name in data_frame.columns:
7080
value = data_frame[column_name].to_list()
@@ -111,14 +121,33 @@ def file_hash(file_path: str, algorithm: str = "MD5"):
111121

112122

113123
def file_storage_size(file_path: str):
124+
"""Return the storage size of a file as an openMINDS QuantitativeValue and a raw byte count.
125+
126+
Parameters:
127+
- file_path (str): The path to the file.
128+
129+
Returns:
130+
- tuple: A (QuantitativeValue, int) pair where the QuantitativeValue expresses the size in bytes
131+
as an openMINDS object and the int is the raw byte count.
132+
"""
114133
file_stats = os.stat(file_path)
115134
file_size = QuantitativeValue(
116135
value=file_stats.st_size, unit=UnitOfMeasurement.by_name("byte"))
117136
return file_size, file_stats.st_size
118137

119138

120139
def detect_nifti_version(file_name, extension, file_size):
140+
"""Detect the NIfTI format version of a file by inspecting its header bytes.
121141
142+
Parameters:
143+
- file_name (str): Path to the NIfTI file.
144+
- extension (str): File extension, either ``".nii"`` or ``".nii.gz"``.
145+
- file_size (int): Size of the file in bytes (unused; reserved for future use).
146+
147+
Returns:
148+
- ContentType or None: The openMINDS ContentType for NIfTI-1 or NIfTI-2, or None if the
149+
header cannot be read or the format is unrecognised.
150+
"""
122151
nii1_sizeof_hdr = 348
123152
nii2_sizeof_hdr = 540
124153

docs/source/architecture.rst

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
Architecture
2+
============
3+
4+
bids2openminds converts `BIDS <https://bids.neuroimaging.io/>`_ neuroimaging datasets into
5+
`openMINDS <https://openminds-documentation.readthedocs.io/>`_ metadata collections
6+
serialised as JSON-LD.
7+
8+
Conversion pipeline
9+
-------------------
10+
11+
The top-level entry point is :func:`bids2openminds.converter.convert`.
12+
It orchestrates the following steps:
13+
14+
1. Validate that the input path is a directory and load a BIDS layout via
15+
`ancpBIDS <https://github.com/ANCPLabOldenburg/ancp-bids>`_.
16+
2. Flatten the layout into a Pandas ``DataFrame`` (one row per file) via the
17+
``layout_to_df()`` shim in ``converter.py``.
18+
3. Call functions in ``main.py`` to create typed openMINDS objects:
19+
20+
* Subjects and their biological states
21+
* Persons (authors / contributors)
22+
* Behavioural protocols (tasks)
23+
* File bundles and individual files
24+
* A ``DatasetVersion`` and its parent ``Dataset``
25+
26+
4. Validate the resulting ``openminds.Collection`` against the openMINDS schema.
27+
5. Optionally serialise to JSON-LD (single file or one file per object).
28+
6. Print a human-readable conversion report.
29+
30+
Module descriptions
31+
-------------------
32+
33+
``converter.py``
34+
~~~~~~~~~~~~~~~~
35+
36+
CLI entry point (Click command) and the ``convert()`` function that orchestrates
37+
the pipeline. Also contains ``layout_to_df()``, a shim that translates the
38+
ancpBIDS object model into a flat Pandas DataFrame so the rest of the code can
39+
query files without depending directly on the ancpBIDS API.
40+
41+
``main.py``
42+
~~~~~~~~~~~
43+
44+
All object-creation functions that map BIDS concepts to openMINDS schema objects:
45+
``create_subjects()``, ``create_persons()``, ``create_behavioral_protocol()``,
46+
``create_file()``, ``create_dataset_version()``, and ``create_dataset()``.
47+
48+
``mapping.py``
49+
~~~~~~~~~~~~~~
50+
51+
Controlled-vocabulary dictionaries that translate BIDS terms (file suffixes,
52+
acquisition labels, species identifiers, sex codes, handedness codes) into
53+
openMINDS ``ControlledTerm`` instances. Entries that do not yet have a
54+
corresponding openMINDS instance are marked ``None`` with a ``# TODO`` comment.
55+
56+
``utility.py``
57+
~~~~~~~~~~~~~~
58+
59+
Helper functions: JSON reading, Pandas DataFrame filtering and value extraction,
60+
file hashing (returns an openMINDS ``Hash`` object), file size (returns an
61+
openMINDS ``QuantitativeValue`` object), and NIfTI version detection.
62+
63+
``report.py``
64+
~~~~~~~~~~~~~
65+
66+
Generates a human-readable conversion summary printed after a successful run,
67+
showing counts of subjects, files, persons, and protocols.
68+
69+
openMINDS object model
70+
----------------------
71+
72+
All metadata objects are instances from the ``openminds.v4`` package:
73+
74+
* ``openminds.v4.core`` — ``Dataset``, ``DatasetVersion``, ``SubjectGroup``,
75+
``Subject``, ``SubjectState``, ``Person``, ``File``, ``FileBundle``, etc.
76+
* ``openminds.v4.controlled_terms`` — ``Species``, ``BiologicalSex``,
77+
``Handedness``, ``Technique``, ``ExperimentalApproach``, ``UnitOfMeasurement``, etc.
78+
79+
Objects are added to an ``openminds.Collection``, which enforces the schema and
80+
handles JSON-LD serialisation.
81+
82+
Test structure
83+
--------------
84+
85+
``test/test_bids_examples.py``
86+
End-to-end conversion of datasets from the `bids-examples
87+
<https://github.com/bids-standard/bids-examples>`_ corpus (checked out as a
88+
git submodule at ``bids-examples/``). Each test asserts on the counts of
89+
openMINDS objects produced.
90+
91+
``test/test_thorough_bids_example.py``
92+
Deep validation on a more complex BIDS dataset.
93+
94+
``test_dataset_version.py``, ``test_example_datasets_click.py``, ``test/test_person.py``, ``test/test_task.py``, ``test/test_subject_age.py``, ``test/test_file_bundle.py``, ``test/test_mapping_completeness.py``
95+
Unit tests for individual mapping and utility functions.
96+
97+
The ``bids-examples`` submodule must be initialised before running the full test suite::
98+
99+
git submodule update --init

docs/source/contributing.rst

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
Contributing
2+
============
3+
4+
This document describes how to set up a development environment, our workflow conventions, and the standards we follow.
5+
6+
Development setup
7+
-----------------
8+
9+
Clone the repository **with its submodules** (the BIDS example test corpus
10+
lives in a submodule)::
11+
12+
git clone --recurse-submodules https://github.com/openMetadataInitiative/bids2openminds.git
13+
cd bids2openminds
14+
pip install -e ".[test]"
15+
16+
Run the full test suite::
17+
18+
pytest
19+
20+
If you cloned without ``--recurse-submodules``, initialise the submodule afterwards::
21+
22+
git submodule update --init
23+
24+
Branching model
25+
---------------
26+
27+
- All development happens on feature branches created from ``main``.
28+
- Do not push commits directly to ``main``.
29+
- Open a pull request to merge your branch into ``main``.
30+
- At least one maintainer approval is required.
31+
- CI must pass (tests on Linux/Windows/macOS, codespell, docs build) before merging.
32+
33+
Review process
34+
--------------
35+
36+
- Keep pull requests focused; one logical change per PR makes review easier.
37+
- Add an entry to ``CHANGES.rst`` in the *Unreleased* section (or under the next version)
38+
describing the change.
39+
- Respond to review comments; squash fixup commits before the PR is merged if requested.
40+
41+
Versioning
42+
----------
43+
44+
We follow `Semantic Versioning <https://semver.org/>`_ (``MAJOR.MINOR.PATCH``).
45+
While the project is at ``0.x``, minor version bumps may introduce breaking API changes.
46+
Once we reach ``1.0``, breaking changes will only occur in major releases.
47+
48+
Releasing a new version
49+
-----------------------
50+
51+
See :doc:`maintenance` for the full release procedure.
52+
53+
Docstring standard
54+
------------------
55+
56+
Use `Google-style docstrings
57+
<https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings>`_
58+
for all public functions and classes. A minimal example:
59+
60+
.. code-block:: python
61+
62+
def my_function(param: str) -> dict:
63+
"""One-line summary ending with a period.
64+
65+
Parameters:
66+
param (str): Description of the parameter.
67+
68+
Returns:
69+
dict: Description of the return value.
70+
71+
Raises:
72+
ValueError: If param is empty.
73+
"""
74+
75+
Communication
76+
-------------
77+
78+
- **Bugs and feature requests**: `GitHub Issues <https://github.com/openMetadataInitiative/bids2openminds/issues>`_
79+
- **Questions and general discussion**: `GitHub Discussions <https://github.com/openMetadataInitiative/bids2openminds/discussions>`_
80+
81+
Deprecation policy
82+
------------------
83+
84+
When a feature is to be removed:
85+
86+
1. Add a ``DeprecationWarning`` in the current release pointing to the replacement.
87+
2. Document the deprecation in ``CHANGES.rst``.
88+
3. Remove the feature in the next minor (or major) release.
89+
90+
Code of Conduct
91+
---------------
92+
93+
This project follows the `openMINDS Code of Conduct
94+
<https://github.com/openMetadataInitiative/bids2openminds/blob/main/CODE_OF_CONDUCT.md>`_.
95+
All participants are expected to uphold its standards.

docs/source/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ We are currently supporting Linux, Windows and Mac OS.
1717
.. toctree::
1818
installation
1919
usage
20+
architecture
21+
contributing
22+
maintenance
2023
changelog

docs/source/installation.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ Installation
44
Dependencies
55
############
66

7-
- `pybids` : Python tools for querying and manipulating BIDS datasets.
8-
- `openminds` : Python package for the openMINDS metadata models. The package contains all openMINDS schemas as Python classes in addition to schema base classes and utility methods.
7+
- `ancpbids` : Python tools for querying BIDS datasets.
8+
- `openminds` : Python package for the openMINDS metadata models. The package contains all openMINDS schemas as Python classes in addition to schema base classes, utility methods, and the library of openMINDS terminologies.
9+
- `nameparser` : A simple Python module for parsing human names into their individual components.
910
- `pandas` : Flexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more
11+
- `PyYAML` : PyYAML is a YAML parser and emitter for Python.
1012
- `click` : Used to create a command-line interface for this function.
1113
- `nameparser` : A simple Python module for parsing human names into their individual components.
1214

0 commit comments

Comments
 (0)