From 495346bd4ed1b2e227b3a8ee14ba0a58e6ca1243 Mon Sep 17 00:00:00 2001 From: Marcel Zwiers Date: Tue, 19 Dec 2023 18:11:17 +0100 Subject: [PATCH] Version bumped to 4.3.0 + minor tweaks --- CONTRIBUTING.rst | 2 +- README.rst | 6 +++--- bidscoin/bcoin.py | 7 ++++--- bidscoin/bidsapps/deface.py | 2 +- bidscoin/bidsapps/echocombine.py | 2 +- bidscoin/bidsapps/medeface.py | 2 +- bidscoin/bidsapps/skullstrip.py | 2 +- bidscoin/bidsapps/slicereport.py | 2 +- bidscoin/bidscoiner.py | 4 ++-- bidscoin/cli/_bcoin.py | 2 +- docs/CHANGELOG.md | 9 ++++++++- docs/bidsmap.rst | 2 +- docs/installation.rst | 4 ++-- tests/test_bidscoiner.py | 1 - 14 files changed, 27 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 7f812dd0..c33c4615 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -33,7 +33,7 @@ The preferred way to contribute to the BIDScoin code base or documentation is to 2. Set up a virtual environment and install BIDScoin's dependencies:: - python -m venv venv # Or use any other tool (such as conda). NB: Use Python < 3.11.2 (due to pypet4bids) + python -m venv venv # Or use any other tool (such as conda) source venv/bin/activate # On Linux, see the documentation for other operating systems pip install bidscoin[dev] # Unfortunately pyproject.toml has no way to install BIDScoin's dependencies only pip uninstall bidscoin # Hence we need to retrospectively remove BIDScoin from site-packages diff --git a/README.rst b/README.rst index 2a082590..f0173ab7 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ BIDScoin: Coin your imaging data to BIDS  -|PyPI version| |Neurodesk| |BIDS| |PyPI - Python Version| |GPLv3| |RTD| |Tests| |DOI| +|PyPI version| |Neurodesk| |BIDS| |Python Version| |GPLv3| |RTD| |Tests| |DOI| BIDScoin is a user-friendly Python application suite that converts ("coins") source-level (raw) neuroimaging data sets to standardized data sets that are organized according to the Brain Imaging Data Structure (`BIDS `__) specification. Rather than depending on complex programmatic logic for source data-type identification, BIDScoin uses a mapping approach to discover the different source data types in your repository and convert them into BIDS data types. Different runs of source data are uniquely identified by their file system properties (e.g. file name or size) and by their attributes (e.g. ``ProtocolName`` from the DICOM header). Mapping information can be pre-specified (e.g. per site), allowing BIDScoin to make intelligent first suggestions on how to classify and convert the data. While this command-line procedure exploits all information available on disk, BIDScoin presents a `Graphical User Interface (GUI) <./screenshots.html>`__ for researchers to check and edit these mappings -- bringing in the missing knowledge that often exists only in their heads. @@ -56,9 +56,9 @@ Are you a Python programmer with an interest in BIDS who knows about GE and/or P .. |PyPI version| image:: https://img.shields.io/pypi/v/bidscoin?color=success :target: https://pypi.org/project/bidscoin :alt: BIDScoin -.. |PyPI - Python Version| image:: https://img.shields.io/pypi/pyversions/bidscoin.svg +.. |Python Version| image:: https://img.shields.io/pypi/pyversions/bidscoin.svg :alt: Python 3 -.. |Neurodesk| image:: https://img.shields.io/badge/Neurodesk-v4.2.1-green +.. |Neurodesk| image:: https://img.shields.io/badge/Neurodesk-green :target: https://www.neurodesk.org/docs/overview/applications/ :alt: Neurodesk .. |GPLv3| image:: https://img.shields.io/badge/License-GPLv3+-blue.svg diff --git a/bidscoin/bcoin.py b/bidscoin/bcoin.py index 931c0b00..3ccee88d 100755 --- a/bidscoin/bcoin.py +++ b/bidscoin/bcoin.py @@ -606,11 +606,12 @@ def reportcredits(args: list) -> None: parser = argparse.ArgumentParser() cmd_summary.setup_parser(parser) dueargs = parser.parse_args([arg if n%2 else '--'+arg for n,arg in enumerate(args[1:])]) # Assumes key-value pairs, e.g. args[1] = 'style', args[2] = 'apa' - for report in (Path(args[0])/'code'/'bidscoin').glob('.duecredit_*'): # args[0] = bidsfolder - LOGGER.info(f"DueCredit report for {report.stem.replace('.duecredit_', '')}:") + for report in sorted((Path(args[0])/'code'/'bidscoin').glob('.duecredit_*')): # args[0] = bidsfolder + print(f"\n{'-'*42}") + LOGGER.info(f"DueCredit summary for {report.stem.replace('.duecredit_', '').upper()}():") + print(f"{'-'*42}") dueargs.filename = report cmd_summary.run(dueargs) - print(f"{'-'*40}\n") def settracking(value: str) -> None: diff --git a/bidscoin/bidsapps/deface.py b/bidscoin/bidsapps/deface.py index eaebf4e6..473522e2 100755 --- a/bidscoin/bidsapps/deface.py +++ b/bidscoin/bidsapps/deface.py @@ -6,7 +6,7 @@ import sys if __name__ == "__main__" and os.getenv('DUECREDIT_ENABLE','').lower() not in ('1', 'yes', 'true'): # Ideally the due state (`self.__active=True`) should also be checked (but that's impossible) os.environ['DUECREDIT_ENABLE'] = 'yes' - os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_deface.p') # NB: argv[2] = bidsfolder + os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_deface.p') # NB: argv[1] = bidsfolder import shutil import json diff --git a/bidscoin/bidsapps/echocombine.py b/bidscoin/bidsapps/echocombine.py index 85bf89ad..c191a1ef 100755 --- a/bidscoin/bidsapps/echocombine.py +++ b/bidscoin/bidsapps/echocombine.py @@ -6,7 +6,7 @@ import sys if __name__ == "__main__" and os.getenv('DUECREDIT_ENABLE','').lower() not in ('1', 'yes', 'true'): # Ideally the due state (`self.__active=True`) should also be checked (but that's impossible) os.environ['DUECREDIT_ENABLE'] = 'yes' - os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_echocombine.p') # NB: argv[2] = bidsfolder + os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_echocombine.p') # NB: argv[1] = bidsfolder import json import logging diff --git a/bidscoin/bidsapps/medeface.py b/bidscoin/bidsapps/medeface.py index 1ee278ec..464a9a64 100755 --- a/bidscoin/bidsapps/medeface.py +++ b/bidscoin/bidsapps/medeface.py @@ -6,7 +6,7 @@ import sys if __name__ == "__main__" and os.getenv('DUECREDIT_ENABLE','').lower() not in ('1', 'yes', 'true'): # Ideally the due state (`self.__active=True`) should also be checked (but that's impossible) os.environ['DUECREDIT_ENABLE'] = 'yes' - os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_medeface.p') # NB: argv[2] = bidsfolder + os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_medeface.p') # NB: argv[1] = bidsfolder import shutil import json diff --git a/bidscoin/bidsapps/skullstrip.py b/bidscoin/bidsapps/skullstrip.py index 40ca0b2e..e28a0353 100755 --- a/bidscoin/bidsapps/skullstrip.py +++ b/bidscoin/bidsapps/skullstrip.py @@ -6,7 +6,7 @@ import sys if __name__ == "__main__" and os.getenv('DUECREDIT_ENABLE','').lower() not in ('1', 'yes', 'true'): # Ideally the due state (`self.__active=True`) should also be checked (but that's impossible) os.environ['DUECREDIT_ENABLE'] = 'yes' - os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_skullstrip.p') # NB: argv[2] = bidsfolder + os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_skullstrip.p') # NB: argv[1] = bidsfolder import shutil import json diff --git a/bidscoin/bidsapps/slicereport.py b/bidscoin/bidsapps/slicereport.py index d730997c..c04b1bd8 100755 --- a/bidscoin/bidsapps/slicereport.py +++ b/bidscoin/bidsapps/slicereport.py @@ -6,7 +6,7 @@ import sys if __name__ == "__main__" and os.getenv('DUECREDIT_ENABLE','').lower() not in ('1', 'yes', 'true'): # Ideally the due state (`self.__active=True`) should also be checked (but that's impossible) os.environ['DUECREDIT_ENABLE'] = 'yes' - os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_slicereport.p') # NB: argv[2] = bidsfolder + os.environ['DUECREDIT_FILE'] = os.path.join(sys.argv[1], 'code', 'bidscoin', '.duecredit_slicereport.p') # NB: argv[1] = bidsfolder import logging import subprocess diff --git a/bidscoin/bidscoiner.py b/bidscoin/bidscoiner.py index 7db2be4e..63bc7ae8 100755 --- a/bidscoin/bidscoiner.py +++ b/bidscoin/bidscoiner.py @@ -289,7 +289,7 @@ def bidscoiner(rawfolder: str, bidsfolder: str, subjects: list=(), force: bool=F if next(bidssession.rglob('*.json'), None): bids.addparticipant(bidsfolder/'participants.tsv', subid, sesid, personals) - # Add the special fieldmap metadata (IntendedFor, B0FieldIdentifier, TE, etc) + # Add the special fieldmap metadata (IntendedFor, TE, etc) addmetadata(bidssession, subid, sesid) # Clean-up the temporary unpacked data @@ -304,7 +304,7 @@ def bidscoiner(rawfolder: str, bidsfolder: str, subjects: list=(), force: bool=F def addmetadata(bidsses: Path, subid: str, sesid: str) -> None: """ - Adds the special fieldmap metadata (IntendedFor, B0FieldIdentifier, TE, etc.) + Adds the special fieldmap metadata (IntendedFor, TE, etc.) :param bidsses: The session folder with the BIDS session data :param subid: The subject 'sub-label' identifier diff --git a/bidscoin/cli/_bcoin.py b/bidscoin/cli/_bcoin.py index 31e1fe7a..17b7191c 100755 --- a/bidscoin/cli/_bcoin.py +++ b/bidscoin/cli/_bcoin.py @@ -11,7 +11,7 @@ them to your needs with any plain text editor) Set the environment variable BIDSCOIN_DEBUG=TRUE in your console to run BIDScoin in its more verbose DEBUG logging mode. -A custom-made citation report can be generated with the help of duecredit (https://github.com/duecredit/duecredit) +Citation reports can be generated with the help of duecredit (https://github.com/duecredit/duecredit) For more documentation see: https://bidscoin.readthedocs.io """ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 53522367..543f1cc8 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,13 +4,19 @@ ## [dev] +## [4.3.0] - 2023-12-20 + ### Added - fslmaths preprocessing on slicereport input images - A `--cluster` option for running bidscoiner in parallel on a (DRMAA enabled) HPC +- Option to generate BIDScoin specific duecredit reports +- Support for BIDS 1.9.0 +- A new special `<>` dynamic meta-data value (most notably useful for creating session specific B0FieldIdentifier/Source tags) ### Changed - `bidscoiner_plugin()` API: you can just return a personals dict instead of writing it to `participants.tsv` - Using DRMAA library for skullstrip (instead of qsub/sbatch) +- Removed the pet2bids and phys2bids plugins (code is no longer actively developed) ## [4.2.1] - 2023-10-30 @@ -413,7 +419,8 @@ A first stable release of BIDScoin :-) ### To do - Add support for non-imaging data -[dev]: https://github.com/Donders-Institute/bidscoin/compare/4.2.1...HEAD +[dev]: https://github.com/Donders-Institute/bidscoin/compare/4.3.0...HEAD +[4.3.0]: https://github.com/Donders-Institute/bidscoin/compare/4.2.1...4.3.0 [4.2.1]: https://github.com/Donders-Institute/bidscoin/compare/4.2.0...4.2.1 [4.2.0]: https://github.com/Donders-Institute/bidscoin/compare/4.1.1...4.2.0 [4.1.1]: https://github.com/Donders-Institute/bidscoin/compare/4.1.0...4.1.1 diff --git a/docs/bidsmap.rst b/docs/bidsmap.rst index 2acda9f4..9cffd482 100644 --- a/docs/bidsmap.rst +++ b/docs/bidsmap.rst @@ -60,7 +60,7 @@ If the run index is encoded in the header or filename, then the index number can Fieldmaps: IntendedFor ^^^^^^^^^^^^^^^^^^^^^^ -According to the BIDS specification, the IntendedFor value of fieldmaps must be a list of relative pathnames of associated output files. However, these output files may vary from session to session, i.e. the 'IntendedFor' value is dependent on the presence of other files in the output folder. To handle that, the dynamic `IntendedFor` value of the meta dictionary can be specified using Unix shell-style wildcard search strings. In that way, during bidscoiner runtime, the exact paths of these images on disk will be looked up using the Python ``glob(*value*)`` expression (see `here `__ for the exact syntax). For instance, using a simple ``{IntendedFor: <>}`` value will use ``glob(*task*)`` to lookup all functional runs in the BIDS subject[/session] folder (since in BIDS these runs always have 'task' in their filename), whereas a more advanced ``{IntendedFor: <>}`` value will select all 'Stop1Go'-, 'Stop2Go'- and 'Reward' bold-runs in the func sub-folder. +According to the BIDS specification, the IntendedFor value of fieldmaps must be a list of relative pathnames of associated output files. However, these output files may vary from session to session, i.e. the 'IntendedFor' value is dependent on the presence of other files in the output folder. To handle that, the dynamic `IntendedFor` value of the meta dictionary can be specified using Unix shell-style wildcard search strings. In that way, during bidscoiner runtime, the exact paths of these images on disk will be looked up using the Python ``glob(*value*)`` expression (see `here `__ for the exact syntax). For instance, using a simple ``{IntendedFor: <>}`` value will use ``glob(*task*)`` to lookup all functional runs in the BIDS subject[/session] folder (since in BIDS these runs always have 'task' in their filename), whereas a more advanced ``{IntendedFor: <>}`` value will select all 'Stop1Go'-, 'Stop2Go'- and 'Reward' bold-runs in the func sub-folder. In case duplicated field maps are acquired (e.g. when a scan failed or a session was interrupted) you can limit the search scope by appending a colon-separated "bounding" term to the search pattern. E.g. ``{IntendedFor: <>}`` will bound the wildcard search to files that are 'uninterruptedly connected' to the current field map, i.e. without there being another run of the field map in between. The bounded search can be further constrained by limiting the maximum number of matches, indicated with lower and upper limits. For instance ``{IntendedFor: <>}`` will limit the bounded search to maximally three runs preceding the field map. Similarly, ``{IntendedFor: <>}`` will limit the bounded search to maximally two preceding and two subsequent runs, and ``{IntendedFor: <>}`` will limit the bounded search to all matches acquired after the field map. In this latter case, for the first field map, only ``task-Stop_run-1`` and ``task-Stop_run-2`` will match the bounded search if the 5 collected runs were named: 1) ``fieldmap_run-1``, 2) ``task-Stop_run-1``, 3) ``task-Stop_run-2``, 4) ``fieldmap_run-2``, 5) ``task-Stop_run-3``. The second run of the field map will match with ``task-Stop_run-3`` only (note that the second field map would have matched all task runs if the bounding term would have been ``[]``, ``[:]`` or ``[-2:2]``). diff --git a/docs/installation.rst b/docs/installation.rst index bef9135d..6043a0f8 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -35,11 +35,11 @@ If you do not have git (or any other version control system) installed you can ` $ pip install ./bidscoin[dcm2niix2bids] -If you are installing BIDScoin on an older system and you are getting Qt6 errors, you can try to install a ``+qt5`` build, e.g. for version 4.2.1: +If you are installing BIDScoin on an older system and you are getting Qt6 errors, you can try to install a ``+qt5`` build, e.g. for version 4.3.0: .. code-block:: console - $ pip install bidscoin[dcm2niix2bids]@git+https://github.com/Donders-Institute/bidscoin@v4.2.0+qt5 + $ pip install bidscoin[dcm2niix2bids]@git+https://github.com/Donders-Institute/bidscoin@v4.3.0+qt5 Updating BIDScoin ^^^^^^^^^^^^^^^^^ diff --git a/tests/test_bidscoiner.py b/tests/test_bidscoiner.py index d993b0e4..91dfcd57 100644 --- a/tests/test_bidscoiner.py +++ b/tests/test_bidscoiner.py @@ -27,7 +27,6 @@ def test_bidscoiner(raw_dicomdir, bids_dicomdir, bidsmap_dicomdir): assert (bids_dicomdir/'sub-Peter'/'ses-01').is_dir() assert (bids_dicomdir/'sub-Peter'/'ses-04BrainMRA').is_dir() assert len(list(bids_dicomdir.rglob('*.nii*'))) > 3 # Exact number (10) is a bit arbitrary (depends on what dcm2niix can convert) - assert sidecars[0].is_file() with sidecars[0].open('r') as json_fid: metadict = json.load(json_fid) assert metadict.get('ProtocolName') == 'T/S/C RF FAST PILOT'