diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 7373affc..4498fcb5 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -3,9 +3,9 @@ name: Codespell on: push: - branches: [master] + branches: [master, dev] pull_request: - branches: [master] + branches: [master, dev] permissions: contents: read diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 22753d33..16b966b9 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up Python 3.11 - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install dependencies diff --git a/.github/workflows/make_binaries.yml b/.github/workflows/make_binaries.yml index ee17670e..3e3b96d3 100644 --- a/.github/workflows/make_binaries.yml +++ b/.github/workflows/make_binaries.yml @@ -19,7 +19,7 @@ jobs: # Check-out repository - uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' # Version range or exact version of a Python version to use, using SemVer's version range syntax architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified @@ -95,4 +95,4 @@ jobs: files: | download/artifact/* env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f89a4f26..41bb83fe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies diff --git a/.github/workflows/publish_doc.yaml b/.github/workflows/publish_doc.yaml index e2a2319a..17c4feea 100644 --- a/.github/workflows/publish_doc.yaml +++ b/.github/workflows/publish_doc.yaml @@ -6,6 +6,7 @@ on: push: branches: - master + - dev tags: - '[0-9]+.[0-9]+.[0-9]+' release: @@ -22,7 +23,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: "3.11" @@ -50,6 +51,7 @@ jobs: dcm2bids -h > docs_helper/help.txt dcm2bids_helper -h > docs_helper/helper.txt dcm2bids_scaffold -h > docs_helper/help_scaffold.txt + echo "export VERSION=$(dcm2bids -v | awk '/dcm2bids/ {print $3}')" > docs_helper/version.txt - name: Set git credentials run: | @@ -76,8 +78,9 @@ jobs: mike deploy -p ${{ github.ref_name }} - name: Deploy dev version - if: ${{ github.ref == 'refs/heads/master' }} + if: ${{ github.ref == 'refs/heads/dev' }} run: | VERSION=$(dcm2bids -v | awk '/dcm2bids/ {print $3}') - echo "Version: $VERSION" - mike deploy -p $VERSION dev -u + echo "Version: ${VERSION}-dev" + mike delete dev + mike deploy -p dev diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bfffcc7c..d37348ef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.gitignore b/.gitignore index ee1d0b5f..104f9265 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,4 @@ tests/data/* docs_helper/help.txt docs_helper/help_scaffold.txt docs_helper/helper.txt +docs_helper/version.txt \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e3acfb1c..21ab9c13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,205 @@ # CHANGELOG +## [3.1.1](https://github.com/UNFmontreal/Dcm2Bids/releases/tag/3.1.1) - 2023-10-12 + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.1.1) + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.1.1) + +Here is the newest release of dcm2bids ! πŸŽ‰ πŸ₯³ + +No new feature but two useful fixes ! +Hope you like this new feature less buggy β›” πŸ› + +Arnaud and Sam + +### What's Changed + +- Bump gitpython from 3.1.35 to 3.1.37 by [@dependabot](https://github.com/dependabot) in [#277](https://github.com/UNFmontreal/Dcm2Bids/pull/277) +- [BF] Auto extractors and merge regex expressions by [@arnaudbore](https://github.com/arnaudbore) in [#275](https://github.com/UNFmontreal/Dcm2Bids/pull/275) +- [ENH] fix binary by [@arnaudbore](https://github.com/arnaudbore) in [#278](https://github.com/UNFmontreal/Dcm2Bids/pull/278) + +**Full Changelog**: [3.1.0...3.1.1](https://github.com/UNFmontreal/Dcm2Bids/compare/3.1.0...3.1.1) + +## [3.1.0](https://github.com/UNFmontreal/Dcm2Bids/releases/tag/3.1.0) - 2023-09-13 + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.1.0) + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.1.0) + +We are excited to announce the newest release of dcm2bids ! πŸŽ‰ πŸ₯³ + +This update introduces some new features, improvements, and bug fixes to enhance your DICOM to BIDS conversion experience. +Everything embedded into this version comes from the community ❀️. Here are few examples. + +- During OHBM we've been asked to support archives as a dicom input, it's part of Dcm2bids 3.1.0. Check `dcm2bids --help` output. [link](https://unfmontreal.github.io/Dcm2Bids/3.1.0/tutorial/first-steps/#running-dcm2bids) +- While helping people on Neurostars, [@smeisler](https://github.com/smeisler) indicates that he needed to run pydeface after dcm2bids conversion since he needed both versions before and after defacing, it's now part of Dcm2bids 3.1.0. Check custom_entities in post-op section. [link](https://unfmontreal.github.io/Dcm2Bids/3.1.0/how-to/use-advanced-commands/#post_op) +- If you want to speed up dcm2bids conversion [@SamGuay](https://github.com/SamGuay) wrote a really nice [tutorial](https://unfmontreal.github.io/Dcm2Bids/3.1.0/tutorial/parallel/) to convert your data faster than ever. + +Your questions and concerns remain our top priority and continue to shape the future of Dcm2bids! + +Thank you +Arnaud and Sam + +We would like to thank [@smeisler](https://github.com/smeisler), [@Remi-Gau](https://github.com/Remi-Gau), [@raniaezzo](https://github.com/raniaezzo), [@arokem](https://github.com/arokem) and the users from [neurostars](https://neurostars.org/tag/dcm2bids) for their feedbacks. + +### What's Changed + +- [MAINT] validate citation.cff by [@Remi-Gau](https://github.com/Remi-Gau) in [#257](https://github.com/UNFmontreal/Dcm2Bids/pull/257) +- Bump gitpython from 3.1.32 to 3.1.34 by [@dependabot](https://github.com/dependabot) in [#259](https://github.com/UNFmontreal/Dcm2Bids/pull/259) +- Bump gitpython from 3.1.34 to 3.1.35 by [@dependabot](https://github.com/dependabot) in [#261](https://github.com/UNFmontreal/Dcm2Bids/pull/261) +- Fix typos in scaffold files by [@SamGuay](https://github.com/SamGuay) in [#263](https://github.com/UNFmontreal/Dcm2Bids/pull/263) +- [ENH] Add possibility to input dicom tar or zip archives by [@arnaudbore](https://github.com/arnaudbore) in [#262](https://github.com/UNFmontreal/Dcm2Bids/pull/262) +- Fix post op - pydeface by [@arnaudbore](https://github.com/arnaudbore) in [#260](https://github.com/UNFmontreal/Dcm2Bids/pull/260) +- Fix release tags in GHA by [@SamGuay](https://github.com/SamGuay) in [#266](https://github.com/UNFmontreal/Dcm2Bids/pull/266) +- Release 3.0.3 by [@arnaudbore](https://github.com/arnaudbore) in [#265](https://github.com/UNFmontreal/Dcm2Bids/pull/265) +- [ENH] - Automate help message in doc by [@SamGuay](https://github.com/SamGuay) in [#267](https://github.com/UNFmontreal/Dcm2Bids/pull/267) +- [DOC] - Tutorial on parallel x dcm2bids by [@SamGuay](https://github.com/SamGuay) in [#268](https://github.com/UNFmontreal/Dcm2Bids/pull/268) + +**Full Changelog**: [3.0.2...3.1.0](https://github.com/UNFmontreal/Dcm2Bids/compare/3.0.2...3.1.0) + +## [3.0.2](https://github.com/UNFmontreal/Dcm2Bids/releases/tag/3.0.2) - 2023-31-23 + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.0.2) + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.0.2) + +First of all thank you to everybody who came see our poster during OHBM 2023 in Montreal ! + +We listened to you and we added an option to reorganize your NIFTIs into a BIDS structure. Check this [link](https://unfmontreal.github.io/Dcm2Bids/3.0.1/how-to/use-advanced-commands/#-skip_dcm2niix). +We also extended the possibilities provided by the item sidecar_changes. + +Congrats πŸ₯³ + +### What's Changed + +- codespell: add config and action to codespell the code to avoid known typos by [@yarikoptic](https://github.com/yarikoptic) in [#245](https://github.com/UNFmontreal/Dcm2Bids/pull/245) +- Bump certifi from 2023.5.7 to 2023.7.22 by [@dependabot](https://github.com/dependabot) in [#247](https://github.com/UNFmontreal/Dcm2Bids/pull/247) +- Bring all 3 Arnauds into 1 by [@yarikoptic](https://github.com/yarikoptic) in [#246](https://github.com/UNFmontreal/Dcm2Bids/pull/246) +- Add option to skip_dcm2niix and reorganize NIFTI and JSON files by [@arnaudbore](https://github.com/arnaudbore) in [#248](https://github.com/UNFmontreal/Dcm2Bids/pull/248) +- Allowing Numericals in JSON custom fields by [@smeisler](https://github.com/smeisler) in [#250](https://github.com/UNFmontreal/Dcm2Bids/pull/250) +- Bump gitpython from 3.1.31 to 3.1.32 by [@dependabot](https://github.com/dependabot) in [#251](https://github.com/UNFmontreal/Dcm2Bids/pull/251) + +### New Contributors + +- [@yarikoptic](https://github.com/yarikoptic) made their first contribution in [#245](https://github.com/UNFmontreal/Dcm2Bids/pull/245) +- [@dependabot](https://github.com/dependabot) made their first contribution in [#247](https://github.com/UNFmontreal/Dcm2Bids/pull/247) + +**Full Changelog**: [3.0.1...3.0.2](https://github.com/UNFmontreal/Dcm2Bids/compare/3.0.1...3.0.2) + +## [3.0.1](https://github.com/UNFmontreal/Dcm2Bids/releases/tag/3.0.1) - 2023-07-23 + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.0.1) + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.0.1) + +We could not be more proud of the 3.0.1 dcm2bids release 😊 . We put everything we've learned from our past experiences and listen to all our users' ideas into this version. + +Advanced searching criteria such as extractors combined with custom entities, the ability to compare floats or the auto_extract_entities option directly accessible from dcm2bids command will make the conversion to BIDS smoother than ever and significantly reduce the complexity and the length of your configuration file especially for multi-site acquisitions. + +We highly encourage you to dive into the [documentation](https://unfmontreal.github.io/Dcm2Bids/3.0.1) since we added quite a lot of new features. + +Please don't hesitate to give us your feedback using this [#240](https://github.com/UNFmontreal/Dcm2Bids/discussions/240). + +Thank you again for all our users who contributed in some ways to this release. Thank you [@SamGuay](https://github.com/SamGuay) for the long discussions and late debug sessions. πŸŽ‰ + +Arnaud + +### What's Changed + +- [DOC] fix typo and add detail about entity reordering by [@Remi-Gau](https://github.com/Remi-Gau) in [#183](https://github.com/UNFmontreal/Dcm2Bids/pull/183) +- update version to match release by [@SamGuay](https://github.com/SamGuay) in [#185](https://github.com/UNFmontreal/Dcm2Bids/pull/185) +- [ENH] Refactorisation - Major API upgrade by [@arnaudbore](https://github.com/arnaudbore) in [#200](https://github.com/UNFmontreal/Dcm2Bids/pull/200) +- Get README from bids toolkit by [@arnaudbore](https://github.com/arnaudbore) in [#201](https://github.com/UNFmontreal/Dcm2Bids/pull/201) +- Add bids-validator option by [@arnaudbore](https://github.com/arnaudbore) in [#206](https://github.com/UNFmontreal/Dcm2Bids/pull/206) +- Upgrade feature Intended for by [@arnaudbore](https://github.com/arnaudbore) in [#207](https://github.com/UNFmontreal/Dcm2Bids/pull/207) +- [ENH] Upgrade custom entities by [@arnaudbore](https://github.com/arnaudbore) in [#208](https://github.com/UNFmontreal/Dcm2Bids/pull/208) +- [FIX] Broken scaffold no more by [@SamGuay](https://github.com/SamGuay) in [#209](https://github.com/UNFmontreal/Dcm2Bids/pull/209) +- [FIX,ENH] - Improve dcm2bids_helper mod by [@SamGuay](https://github.com/SamGuay) in [#210](https://github.com/UNFmontreal/Dcm2Bids/pull/210) +- Generalization of sidecarchanges using ids by [@arnaudbore](https://github.com/arnaudbore) in [#213](https://github.com/UNFmontreal/Dcm2Bids/pull/213) +- [ENH] dataType -> datatype and modalityLabel -> suffix by [@arnaudbore](https://github.com/arnaudbore) in [#214](https://github.com/UNFmontreal/Dcm2Bids/pull/214) +- Fix log n version by [@SamGuay](https://github.com/SamGuay) in [#219](https://github.com/UNFmontreal/Dcm2Bids/pull/219) +- [BF] valid participant by [@arnaudbore](https://github.com/arnaudbore) in [#215](https://github.com/UNFmontreal/Dcm2Bids/pull/215) +- Allow a criteria item to be a dict with a key - any (or) or all (and) by [@arnaudbore](https://github.com/arnaudbore) in [#217](https://github.com/UNFmontreal/Dcm2Bids/pull/217) +- Add an option to use duplicates instead of runs as suggested in heudiconv project. by [@arnaudbore](https://github.com/arnaudbore) in [#218](https://github.com/UNFmontreal/Dcm2Bids/pull/218) +- [BF] Valid session by [@arnaudbore](https://github.com/arnaudbore) in [#222](https://github.com/UNFmontreal/Dcm2Bids/pull/222) +- add test helper by [@arnaudbore](https://github.com/arnaudbore) in [#220](https://github.com/UNFmontreal/Dcm2Bids/pull/220) +- [BF] dcm2bids_scaffold by [@arnaudbore](https://github.com/arnaudbore) in [#224](https://github.com/UNFmontreal/Dcm2Bids/pull/224) +- prevent doc deployment by pushing to master by [@SamGuay](https://github.com/SamGuay) in [#226](https://github.com/UNFmontreal/Dcm2Bids/pull/226) +- [ENH] Add major OS executables on new release by [@SamGuay](https://github.com/SamGuay) in [#221](https://github.com/UNFmontreal/Dcm2Bids/pull/221) +- [ENH] Generalization of defaceTpl to post_op by [@arnaudbore](https://github.com/arnaudbore) in [#225](https://github.com/UNFmontreal/Dcm2Bids/pull/225) +- [ENH] Rename all cap var by [@arnaudbore](https://github.com/arnaudbore) in [#227](https://github.com/UNFmontreal/Dcm2Bids/pull/227) +- Revert doc to 2.1.9 by [@arnaudbore](https://github.com/arnaudbore) in [#228](https://github.com/UNFmontreal/Dcm2Bids/pull/228) +- Automated version-control documentation for 2.1.9 and up by [@SamGuay](https://github.com/SamGuay) in [#231](https://github.com/UNFmontreal/Dcm2Bids/pull/231) +- Fix GHA for docs + layouts by [@SamGuay](https://github.com/SamGuay) in [#234](https://github.com/UNFmontreal/Dcm2Bids/pull/234) +- [ENH] Add float comparison by [@arnaudbore](https://github.com/arnaudbore) in [#229](https://github.com/UNFmontreal/Dcm2Bids/pull/229) +- [Quick Fix] Add specific message when no acquisition was found by [@arnaudbore](https://github.com/arnaudbore) in [#235](https://github.com/UNFmontreal/Dcm2Bids/pull/235) +- Fix typo src_file dst_file by [@arnaudbore](https://github.com/arnaudbore) in [#236](https://github.com/UNFmontreal/Dcm2Bids/pull/236) +- Improve doc for v3.0.0 by [@arnaudbore](https://github.com/arnaudbore) in [#223](https://github.com/UNFmontreal/Dcm2Bids/pull/223) +- [FIX] - update the alias for dev and latest by [@SamGuay](https://github.com/SamGuay) in [#237](https://github.com/UNFmontreal/Dcm2Bids/pull/237) +- Quick fix version by [@arnaudbore](https://github.com/arnaudbore) in [#238](https://github.com/UNFmontreal/Dcm2Bids/pull/238) +- Update docs by [@smeisler](https://github.com/smeisler) in [#239](https://github.com/UNFmontreal/Dcm2Bids/pull/239) +- [BF] fix sidecars suggested by Sam by [@arnaudbore](https://github.com/arnaudbore) in [#243](https://github.com/UNFmontreal/Dcm2Bids/pull/243) + +### New Contributors + +- [@Remi-Gau](https://github.com/Remi-Gau) made their first contribution in [#183](https://github.com/UNFmontreal/Dcm2Bids/pull/183) +- [@smeisler](https://github.com/smeisler) made their first contribution in [#239](https://github.com/UNFmontreal/Dcm2Bids/pull/239) + +**Full Changelog**: [2.1.9...3.0.1](https://github.com/UNFmontreal/Dcm2Bids/compare/2.1.9...3.0.1) + +## [3.0.0rc1](https://github.com/UNFmontreal/Dcm2Bids/releases/tag/3.0.0rc1) - 2023-07-17 + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.0.0rc1) + +[](https://github.com/UNFmontreal/Dcm2Bids/releases/edit/3.0.0rc1) + +Ok let's be clear after working very hard on this we are definitively biased so we decided to ask ChatGPT to write for us a short fun description for our brand new version. + +Introducing the latest version of dcm2bids: the superhero of medical imaging data conversion! With simplified configuration, enhanced DICOM handling, comprehensive data validation, advanced options, and amazing new features, dcm2bids will transform your data into BIDS format like magic. Join the fun and unleash your scientific superpowers today! + +Please check the documentation πŸŽ‰ + +⚠️This is a release candidate. + +### What's Changed + +- [DOC] fix typo and add detail about entity reordering by [@Remi-Gau](https://github.com/Remi-Gau) in [#183](https://github.com/UNFmontreal/Dcm2Bids/pull/183) +- update version to match release by [@SamGuay](https://github.com/SamGuay) in [#185](https://github.com/UNFmontreal/Dcm2Bids/pull/185) +- [ENH] Refactorisation - Major API upgrade by [@arnaudbore](https://github.com/arnaudbore) in [#200](https://github.com/UNFmontreal/Dcm2Bids/pull/200) +- Get README from bids toolkit by [@arnaudbore](https://github.com/arnaudbore) in [#201](https://github.com/UNFmontreal/Dcm2Bids/pull/201) +- Add bids-validator option by [@arnaudbore](https://github.com/arnaudbore) in [#206](https://github.com/UNFmontreal/Dcm2Bids/pull/206) +- Upgrade feature Intended for by [@arnaudbore](https://github.com/arnaudbore) in [#207](https://github.com/UNFmontreal/Dcm2Bids/pull/207) +- [ENH] Upgrade custom entities by [@arnaudbore](https://github.com/arnaudbore) in [#208](https://github.com/UNFmontreal/Dcm2Bids/pull/208) +- [FIX] Broken scaffold no more by [@SamGuay](https://github.com/SamGuay) in [#209](https://github.com/UNFmontreal/Dcm2Bids/pull/209) +- [FIX,ENH] - Improve dcm2bids_helper mod by [@SamGuay](https://github.com/SamGuay) in [#210](https://github.com/UNFmontreal/Dcm2Bids/pull/210) +- Generalization of sidecarchanges using ids by [@arnaudbore](https://github.com/arnaudbore) in [#213](https://github.com/UNFmontreal/Dcm2Bids/pull/213) +- [ENH] dataType -> datatype and modalityLabel -> suffix by [@arnaudbore](https://github.com/arnaudbore) in [#214](https://github.com/UNFmontreal/Dcm2Bids/pull/214) +- Fix log n version by [@SamGuay](https://github.com/SamGuay) in [#219](https://github.com/UNFmontreal/Dcm2Bids/pull/219) +- [BF] valid participant by [@arnaudbore](https://github.com/arnaudbore) in [#215](https://github.com/UNFmontreal/Dcm2Bids/pull/215) +- Allow a criteria item to be a dict with a key - any (or) or all (and) by [@arnaudbore](https://github.com/arnaudbore) in [#217](https://github.com/UNFmontreal/Dcm2Bids/pull/217) +- Add an option to use duplicates instead of runs as suggested in heudiconv project. by [@arnaudbore](https://github.com/arnaudbore) in [#218](https://github.com/UNFmontreal/Dcm2Bids/pull/218) +- [BF] Valid session by [@arnaudbore](https://github.com/arnaudbore) in [#222](https://github.com/UNFmontreal/Dcm2Bids/pull/222) +- add test helper by [@arnaudbore](https://github.com/arnaudbore) in [#220](https://github.com/UNFmontreal/Dcm2Bids/pull/220) +- [BF] dcm2bids_scaffold by [@arnaudbore](https://github.com/arnaudbore) in [#224](https://github.com/UNFmontreal/Dcm2Bids/pull/224) +- prevent doc deployment by pushing to master by [@SamGuay](https://github.com/SamGuay) in [#226](https://github.com/UNFmontreal/Dcm2Bids/pull/226) +- [ENH] Add major OS executables on new release by [@SamGuay](https://github.com/SamGuay) in [#221](https://github.com/UNFmontreal/Dcm2Bids/pull/221) +- [ENH] Generalization of defaceTpl to post_op by [@arnaudbore](https://github.com/arnaudbore) in [#225](https://github.com/UNFmontreal/Dcm2Bids/pull/225) +- [ENH] Rename all cap var by [@arnaudbore](https://github.com/arnaudbore) in [#227](https://github.com/UNFmontreal/Dcm2Bids/pull/227) +- Revert doc to 2.1.9 by [@arnaudbore](https://github.com/arnaudbore) in [#228](https://github.com/UNFmontreal/Dcm2Bids/pull/228) +- Automated version-control documentation for 2.1.9 and up by [@SamGuay](https://github.com/SamGuay) in [#231](https://github.com/UNFmontreal/Dcm2Bids/pull/231) +- Fix GHA for docs + layouts by [@SamGuay](https://github.com/SamGuay) in [#234](https://github.com/UNFmontreal/Dcm2Bids/pull/234) +- [ENH] Add float comparison by [@arnaudbore](https://github.com/arnaudbore) in [#229](https://github.com/UNFmontreal/Dcm2Bids/pull/229) +- [Quick Fix] Add specific message when no acquisition was found by [@arnaudbore](https://github.com/arnaudbore) in [#235](https://github.com/UNFmontreal/Dcm2Bids/pull/235) +- Fix typo src_file dst_file by [@arnaudbore](https://github.com/arnaudbore) in [#236](https://github.com/UNFmontreal/Dcm2Bids/pull/236) +- Improve doc for v3.0.0 by [@arnaudbore](https://github.com/arnaudbore) in [#223](https://github.com/UNFmontreal/Dcm2Bids/pull/223) + +### New Contributors + +- [@Remi-Gau](https://github.com/Remi-Gau) made their first contribution in [#183](https://github.com/UNFmontreal/Dcm2Bids/pull/183) + +**Full Changelog**: [2.1.8...3.0.0rc1](https://github.com/UNFmontreal/Dcm2Bids/compare/2.1.8...3.0.0rc1) + ## **2.1.9 - 2022-06-17** Some issues with pypi. Sorry for this. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..fec52d41 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,57 @@ +# Conda image for installing FSL tools +FROM continuumio/miniconda3 AS build + +# Install FSL tools with conda +COPY environment.yml /tmp/environment.yml +RUN conda env create -f /tmp/environment.yml + +# Install and use conda-pack +RUN conda install -c conda-forge conda-pack +RUN conda-pack -n fsl -o /tmp/env.tar && \ + mkdir /venv && cd /venv && tar xf /tmp/env.tar && \ + rm /tmp/env.tar +RUN /venv/bin/conda-unpack + +# Runtime image for executing FSL tools +FROM debian:stable AS runtime + +# Copy the conda env from previous stage +COPY --from=build /venv /venv + +# Point to conda executables +ENV PATH="/venv/bin:$PATH" + +# Set FSL variables +ENV FSLDIR="/venv" +ENV FSLCONFDIR="${FSLDIR}/config" +ENV FSLOUTPUTTYPE="NIFTI" +ENV FSLMULTIFILEQUIT="TRUE" +ENV FSLTCLSH="${FSLDIR}/bin/fsltclsh" +ENV FSLWISH="${FSLDIR}/bin/fslwish" +ENV FSLGECUDAQ="cuda.q" + +# Update and install some utils +RUN apt-get -y update && apt-get -y install dc wget npm unzip + +# Fetch data +RUN wget -P ${FSLDIR}/data https://git.fmrib.ox.ac.uk/fsl/data_standard/-/raw/master/MNI152_T1_1mm_brain.nii.gz + +# Install bids-validator +RUN npm install -g bids-validator + +# Install dcm2niix +WORKDIR / +RUN wget https://github.com/rordenlab/dcm2niix/releases/download/v1.0.20240202/dcm2niix_lnx.zip +RUN unzip dcm2niix_lnx.zip +RUN mv dcm2niix /usr/bin/ + +# Install dcm2bids + +WORKDIR / +ADD . /dcm2bids +WORKDIR /dcm2bids +RUN pip install -e . + +RUN pip install pydeface + +ENTRYPOINT ["dcm2bids"] \ No newline at end of file diff --git a/README.md b/README.md index d249d0bb..67359872 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ Please take a look at the [documentation][dcm2bids-doc] to: * [Learn about bids][bids-spec] with some dataset [examples][bids-examples] * [Install dcm2bids][dcm2bids-install] -* [Follow the tutorial][dcm2bids-tutorial] +* [Use docker and Apptainer/Singularity][dcm2bids-container] +* [Follow the tutorials][dcm2bids-tutorial] * [Seek for more advanced usage][dcm2bids-advanced] ## Issues and Questions @@ -68,6 +69,7 @@ Before posting your question, you may want to first browse through questions tha [dcm2bids-install]: https://unfmontreal.github.io/Dcm2Bids/latest/get-started/install/ [dcm2bids-tutorial]: https://unfmontreal.github.io/Dcm2Bids/latest/tutorial/first-steps/#tutorial-first-steps [dcm2bids-advanced]: https://unfmontreal.github.io/Dcm2Bids/latest/advanced/ +[dcm2bids-container]: https://unfmontreal.github.io/Dcm2Bids/latest/how-to/container/ [dcm2bids-upgrade]: https://unfmontreal.github.io/Dcm2Bids/dev/upgrade/ [dcm2bids-issues]: https://github.com/UNFmontreal/Dcm2Bids/issues [dcm2niix-install]: https://github.com/rordenlab/dcm2niix#install diff --git a/containers/Dockerfile b/containers/Dockerfile deleted file mode 100644 index c23b365f..00000000 --- a/containers/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -From ubuntu:latest -ENV DEBIAN_FRONTEND=noninteractive - -MAINTAINER Arnaud BorΓ© - -RUN apt-get update -RUN apt-get -y upgrade - -RUN apt-get -y install wget build-essential cmake git pigz \ - nodejs python3 python3-pip unzip - -RUN pip3 install dcm2bids - -# Install dcm2niix from github -ENV DCM2NIIX_VERSION="v1.0.20230411" - -WORKDIR /usr/local/src -RUN git clone https://github.com/rordenlab/dcm2niix.git -WORKDIR /usr/local/src/dcm2niix -RUN git checkout tags/${DCM2NIIX_VERSION} -b install -RUN mkdir build -WORKDIR /usr/local/src/dcm2niix/build -RUN cmake .. -RUN make install - -WORKDIR / diff --git a/containers/singularity.def b/containers/singularity.def deleted file mode 100644 index 101dde23..00000000 --- a/containers/singularity.def +++ /dev/null @@ -1,2 +0,0 @@ -BootStrap: docker -From: unfmontreal/dcm2bids:latest diff --git a/dcm2bids/acquisition.py b/dcm2bids/acquisition.py index cba30c76..1c097729 100644 --- a/dcm2bids/acquisition.py +++ b/dcm2bids/acquisition.py @@ -4,6 +4,7 @@ import logging from os.path import join as opj +from os import sep from dcm2bids.utils.utils import DEFAULT from dcm2bids.version import __version__ @@ -30,6 +31,8 @@ def __init__( id=None, src_sidecar=None, sidecar_changes=None, + bids_uri=None, + do_not_reorder_entities=None, **kwargs ): self.logger = logging.getLogger(__name__) @@ -43,6 +46,8 @@ def __init__( self.suffix = suffix self.custom_entities = custom_entities self.src_sidecar = src_sidecar + self.bids_uri = bids_uri + self.do_not_reorder_entities = do_not_reorder_entities if sidecar_changes is None: self.sidecar_changes = {} @@ -156,8 +161,8 @@ def setExtraDstFile(self, new_entities): """ Return: The destination filename formatted following - the v1.8.0 BIDS entity key table - https://bids-specification.readthedocs.io/en/v1.8.0/99-appendices/04-entity-table.html + the v1.9.0 BIDS entity key table + https://bids-specification.readthedocs.io/en/v1.9.0/99-appendices/04-entity-table.html """ if self.custom_entities.strip() == "": @@ -196,11 +201,14 @@ def setExtraDstFile(self, new_entities): "compliant. Make sure you know what " "you are doing.") - if current_name != new_name: - self.logger.warning( - f"""βœ… Filename was reordered according to BIDS entity table order: - from: {current_name} - to: {new_name}""") + if not self.do_not_reorder_entities: + if current_name != new_name: + self.logger.warning( + f"""βœ… Filename was reordered according to BIDS entity table order: + from: {current_name} + to: {new_name}""") + else: + new_name = current_name self.extraDstFile = opj(self.participant.directory, self.datatype, @@ -210,8 +218,8 @@ def setDstFile(self): """ Return: The destination filename formatted following - the v1.8.0 BIDS entity key table - https://bids-specification.readthedocs.io/en/v1.8.0/99-appendices/04-entity-table.html + the v1.9.0 BIDS entity key table + https://bids-specification.readthedocs.io/en/v1.9.0/99-appendices/04-entity-table.html """ current_name = self.participant.prefix + self.build_suffix new_name = '' @@ -241,13 +249,15 @@ def setDstFile(self): "compliant. Make sure you know what " "you are doing.") - if current_name != new_name: - self.logger.warning( - f"""βœ… Filename was reordered according to BIDS entity table order: - from: {current_name} - to: {new_name}""") + self.dstFile = current_name + if not self.do_not_reorder_entities: + if current_name != new_name: + self.logger.warning( + f"""βœ… Filename was reordered according to BIDS entity table order: + from: {current_name} + to: {new_name}""") + self.dstFile = new_name - self.dstFile = new_name def dstSidecarData(self, idList): """ @@ -277,10 +287,16 @@ def dstSidecarData(self, idList): else: values.append(idList.get(val, val)) if values[-1] != val: - if isinstance(values[-1], list): - values[-1] = ["bids::" + img_dest for img_dest in values[-1]] + if self.bids_uri == DEFAULT.bids_uri: + if isinstance(values[-1], list): + values[-1] = ["bids::" + img_dest for img_dest in values[-1]] + else: + values[-1] = "bids::" + values[-1] else: - values[-1] = "bids::" + values[-1] + if isinstance(values[-1], list): + values[-1] = [img_dest.replace(self.participant.name + sep, "") for img_dest in values[-1]] + else: + values[-1] = values[-1].replace(self.participant.name + sep, "") # handle if nested list vs str flat_value_list = [] diff --git a/dcm2bids/cli/dcm2bids.py b/dcm2bids/cli/dcm2bids.py index 131ee01f..3342c30d 100644 --- a/dcm2bids/cli/dcm2bids.py +++ b/dcm2bids/cli/dcm2bids.py @@ -47,12 +47,21 @@ def _build_arg_parser(): default=DEFAULT.output_dir, help="Output BIDS directory. [%(default)s]") - p.add_argument("--auto_extract_entities", + g = p.add_mutually_exclusive_group() + g.add_argument("--auto_extract_entities", action='store_true', help="If set, it will automatically try to extract entity" "information [task, dir, echo] based on the suffix and datatype." " [%(default)s]") + g.add_argument("--do_not_reorder_entities", + action='store_true', + help="If set, it will not reorder entities according to the relative " + "ordering indicated in the BIDS specification and use the " + "order defined in custom_entities by the user.\n" + "Cannot be used with --auto_extract_entities. " + " [%(default)s]") + p.add_argument("--bids_validate", action='store_true', help="If set, once your conversion is done it " @@ -120,6 +129,7 @@ def main(): logger.info(f"config: {os.path.realpath(args.config)}") logger.info(f"BIDS directory: {os.path.realpath(args.output_dir)}") logger.info(f"Auto extract entities: {args.auto_extract_entities}") + logger.info(f"Reorder entities: {not args.do_not_reorder_entities}") logger.info(f"Validate BIDS: {args.bids_validate}\n") app = Dcm2BidsGen(**vars(args)).run() diff --git a/dcm2bids/cli/dcm2bids_helper.py b/dcm2bids/cli/dcm2bids_helper.py index 6e2599f7..8a8548aa 100644 --- a/dcm2bids/cli/dcm2bids_helper.py +++ b/dcm2bids/cli/dcm2bids_helper.py @@ -15,7 +15,6 @@ from dcm2bids.utils.utils import DEFAULT from dcm2bids.utils.tools import dcm2niix_version, check_latest from dcm2bids.utils.logger import setup_logging -from dcm2bids.utils.args import assert_dirs_empty from dcm2bids.version import __version__ @@ -59,12 +58,22 @@ def main(): """Let's go""" parser = _build_arg_parser() args = parser.parse_args() - out_dir = Path(args.output_dir) - log_file = (Path(DEFAULT.output_dir) - / DEFAULT.tmp_dir_name - / "log" - / f"helper_{datetime.now().strftime('%Y%m%d-%H%M%S')}.log") + out_dir = Path(args.output_dir) + if args.output_dir != parser.get_default('output_dir'): + out_dir = (Path(args.output_dir) + / DEFAULT.tmp_dir_name + / DEFAULT.helper_dir) + + log_file = (Path(args.output_dir) + / DEFAULT.tmp_dir_name + / "log" + / f"helper_{datetime.now().strftime('%Y%m%d-%H%M%S')}.log") + else: + log_file = (Path(DEFAULT.output_dir) + / DEFAULT.tmp_dir_name + / "log" + / f"helper_{datetime.now().strftime('%Y%m%d-%H%M%S')}.log") if args.nest: if isinstance(args.nest, str): log_file = Path( @@ -94,8 +103,6 @@ def main(): check_latest("dcm2bids") check_latest("dcm2niix") - assert_dirs_empty(parser, args, out_dir) - app = Dcm2niixGen(dicom_dirs=args.dicom_dir, bids_dir=out_dir, helper=True) rsl = app.run(force=args.overwrite) diff --git a/dcm2bids/dcm2bids_gen.py b/dcm2bids/dcm2bids_gen.py index ae5e4065..f97322c7 100644 --- a/dcm2bids/dcm2bids_gen.py +++ b/dcm2bids/dcm2bids_gen.py @@ -39,7 +39,8 @@ def __init__( config, output_dir=DEFAULT.output_dir, bids_validate=DEFAULT.bids_validate, - auto_extract_entities=False, + auto_extract_entities=DEFAULT.auto_extract_entities, + do_not_reorder_entities = DEFAULT.do_not_reorder_entities, session=DEFAULT.session, clobber=DEFAULT.clobber, force_dcm2bids=DEFAULT.force_dcm2bids, @@ -55,11 +56,17 @@ def __init__( self.clobber = clobber self.bids_validate = bids_validate self.auto_extract_entities = auto_extract_entities + self.do_not_reorder_entities = do_not_reorder_entities self.force_dcm2bids = force_dcm2bids self.skip_dcm2niix = skip_dcm2niix self.logLevel = log_level self.logger = logging.getLogger(__name__) + if self.auto_extract_entities and self.do_not_reorder_entities: + raise ValueError("Auto extract entities is set to True and " + "do not reorder entities is set to True. " + "Please choose only one option.") + @property def dicom_dirs(self): """List of DICOMs directories""" @@ -99,10 +106,12 @@ def run(self): self.config["descriptions"], self.config.get("extractors", {}), self.auto_extract_entities, + self.do_not_reorder_entities, self.config.get("search_method", DEFAULT.search_method), self.config.get("case_sensitive", DEFAULT.case_sensitive), self.config.get("dup_method", DEFAULT.dup_method), - self.config.get("post_op", DEFAULT.post_op) + self.config.get("post_op", DEFAULT.post_op), + self.config.get("bids_uri", DEFAULT.bids_uri) ) parser.build_graph() parser.build_acquisitions(self.participant) @@ -123,10 +132,12 @@ def run(self): if self.bids_validate: try: - self.logger.info(f"Validate if {self.output_dir} is BIDS valid.") - self.logger.info("Use bids-validator version: ") - run_shell_command(['bids-validator', '-v']) - run_shell_command(['bids-validator', self.bids_dir]) + self.logger.info("BIDS VALIDATION") + bids_version = run_shell_command(['bids-validator', '-v'], False) + self.logger.info(f"Use bids-validator version: {bids_version.decode()[:-1]}") + bids_report = run_shell_command(['bids-validator', self.bids_dir]) + self.logger.info("Report from bids-validator") + self.logger.info(bids_report.decode()) except Exception: self.logger.error("The bids-validator does not seem to work properly. " "The bids-validator may not be installed on your " @@ -186,8 +197,18 @@ def move(self, acq, idList, post_op): else: cmd = cmd.replace('dst_file', str(dstFile)) - run_shell_command(cmd.split()) - continue + try: + std_out = run_shell_command(cmd.split()) + self.logger.debug(f"Log from: {cmd}") + self.logger.debug(std_out.decode()) + self.logger.info("") + continue + except Exception: + self.logger.error( + f"The command post_op: \"{cmd}\" " + "does not seem to work properly. " + "Check if it is installed on your " + "computer.\n") if ".json" in ext: data = acq.dstSidecarData(idList) diff --git a/dcm2bids/dcm2niix_gen.py b/dcm2bids/dcm2niix_gen.py index 320af2b7..d43e81fe 100644 --- a/dcm2bids/dcm2niix_gen.py +++ b/dcm2bids/dcm2niix_gen.py @@ -142,9 +142,15 @@ def execute(self): shutil.rmtree(self.rm_tmp_dir) self.logger.info("Temporary dicom directory removed.") - self.logger.debug(f"\n{output}") - self.logger.info("Check log file for dcm2niix output\n") - + if "Warning" in output or "Error" in output: + self.logger.info("Log from dcm2niix execution") + if "Warning" in output: + self.logger.warning(f"{output}") + else: + self.logger.error(f"{output}") + else: + self.logger.debug(f"\n{output}") + self.logger.info("Check log file for dcm2niix output\n") else: for dicomDir in self.dicom_dirs: shutil.copytree(dicomDir, self.output_dir, dirs_exist_ok=True) diff --git a/dcm2bids/sidecar.py b/dcm2bids/sidecar.py index b0c344c0..0cb7ba07 100644 --- a/dcm2bids/sidecar.py +++ b/dcm2bids/sidecar.py @@ -11,7 +11,8 @@ from dcm2bids.acquisition import Acquisition from dcm2bids.utils.io import load_json -from dcm2bids.utils.utils import DEFAULT, convert_dir, combine_dict_extractors, splitext_ +from dcm2bids.utils.utils import (DEFAULT, convert_dir, combine_dict_extractors, + splitext_) compare_float_keys = ["lt", "gt", "le", "ge", "btw", "btwe"] @@ -96,24 +97,29 @@ def __init__(self, descriptions, extractors=DEFAULT.extractors, auto_extractor=DEFAULT.auto_extract_entities, + do_not_reorder_entities=DEFAULT.do_not_reorder_entities, search_method=DEFAULT.search_method, case_sensitive=DEFAULT.case_sensitive, dup_method=DEFAULT.dup_method, - post_op=DEFAULT.post_op): + post_op=DEFAULT.post_op, + bids_uri=DEFAULT.bids_uri): self.logger = logging.getLogger(__name__) self._search_method = "" self._dup_method = "" self._post_op = "" + self._bids_uri = "" self.graph = OrderedDict() self.acquisitions = [] self.extractors = extractors self.auto_extract_entities = auto_extractor + self.do_not_reorder_entities = do_not_reorder_entities self.sidecars = sidecars self.descriptions = descriptions self.search_method = search_method self.case_sensitive = case_sensitive self.dup_method = dup_method self.post_op = post_op + self.bids_uri = bids_uri @property def search_method(self): @@ -218,6 +224,25 @@ def post_op(self, value): raise ValueError("post_op is not defined correctly. " "Please check the documentation.") + @property + def bids_uri(self): + return self._bids_uri + + @bids_uri.setter + def bids_uri(self, value): + """ + Checks if the method bids_uri is using is implemented + Warns the user if not and fall back to default + """ + if value in DEFAULT.bids_uri_choices: + self._bids_uri = value + else: + self.bids_uri = DEFAULT.bids_uri + self.logger.warning( + "BIDS URI methods implemented: %s", DEFAULT.bids_uri_choices) + self.logger.warning(f"{value} is not a bids URI method implemented.") + self.logger.warning(f"Falling back to default: {DEFAULT.bids_uri}.") + @property def case_sensitive(self): return self._case_sensitive @@ -393,9 +418,11 @@ def build_acquisitions(self, participant): if len(valid_descriptions) == 1: desc = valid_descriptions[0] desc, sidecar = self.searchDcmTagEntity(sidecar, desc) - acq = Acquisition(participant, - src_sidecar=sidecar, **desc) + src_sidecar=sidecar, + bids_uri=self.bids_uri, + do_not_reorder_entities=self.do_not_reorder_entities, + **desc) acq.setDstFile() if acq.id: @@ -414,6 +441,8 @@ def build_acquisitions(self, participant): self.logger.warning(f"Several Pairing <- {sidecarName}") for desc in valid_descriptions: acq = Acquisition(participant, + bids_uri=self.bids_uri, + do_not_reorder_entities=self.do_not_reorder_entities, **desc) self.logger.warning(f" -> {acq.suffix}") @@ -427,6 +456,7 @@ def searchDcmTagEntity(self, sidecar, desc): """ descWithTask = desc.copy() concatenated_matches = {} + keys_custom_entities = [] entities = [] if "custom_entities" in desc.keys() or self.auto_extract_entities: if 'custom_entities' in desc.keys(): @@ -435,10 +465,12 @@ def searchDcmTagEntity(self, sidecar, desc): else: descWithTask["custom_entities"] = [] + keys_custom_entities = [curr_entity.split('-')[0] for curr_entity in descWithTask["custom_entities"]] + if self.auto_extract_entities: self.extractors = combine_dict_extractors(self.extractors, DEFAULT.auto_extractors) - + # Loop to check if we find self.extractor for dcmTag in self.extractors: if dcmTag in sidecar.data.keys(): dcmInfo = sidecar.data.get(dcmTag) @@ -447,38 +479,42 @@ def searchDcmTagEntity(self, sidecar, desc): if not isinstance(dcmInfo, list): if compile_regex.search(str(dcmInfo)) is not None: concatenated_matches.update( - compile_regex.search(str(dcmInfo)).groupdict()) + compile_regex.search(str(dcmInfo)).groupdict()) else: for curr_dcmInfo in dcmInfo: if compile_regex.search(curr_dcmInfo) is not None: concatenated_matches.update( - compile_regex.search(curr_dcmInfo).groupdict()) + compile_regex.search(curr_dcmInfo).groupdict()) break # Keep entities asked in custom_entities # If dir found in custom_entities and concatenated_matches.keys we keep it - if "custom_entities" in desc.keys(): + if "custom_entities" in desc.keys() and not self.auto_extract_entities: + entities = desc["custom_entities"] + elif "custom_entities" in desc.keys(): entities = set(concatenated_matches.keys()).intersection(set(descWithTask["custom_entities"])) # custom_entities not a key for extractor or auto_extract_entities complete_entities = [ent for ent in descWithTask["custom_entities"] if '-' in ent] entities = entities.union(set(complete_entities)) - if self.auto_extract_entities: auto_acq = '_'.join([descWithTask['datatype'], descWithTask["suffix"]]) if auto_acq in DEFAULT.auto_entities: # Check if these auto entities have been found before merging auto_entities = set(concatenated_matches.keys()).intersection(set(DEFAULT.auto_entities[auto_acq])) + left_auto_entities = auto_entities.symmetric_difference(set(DEFAULT.auto_entities[auto_acq])) + left_auto_entities = left_auto_entities.difference(keys_custom_entities) + if left_auto_entities: - self.logger.warning(f"{left_auto_entities} have not been found for datatype '{descWithTask['datatype']}' " + self.logger.warning(f"Entities {left_auto_entities} have not been found " + f"for datatype '{descWithTask['datatype']}' " f"and suffix '{descWithTask['suffix']}'.") - + entities = list(entities) + list(auto_entities) entities = list(set(entities)) descWithTask["custom_entities"] = entities - for curr_entity in entities: if curr_entity in concatenated_matches.keys(): if curr_entity == 'dir': diff --git a/dcm2bids/utils/args.py b/dcm2bids/utils/args.py index 1266e4aa..cbe8164e 100644 --- a/dcm2bids/utils/args.py +++ b/dcm2bids/utils/args.py @@ -28,12 +28,12 @@ def check(path: Path): "could be overwritten or deleted.\nRerun the command " "with --force option to overwrite " "existing output files.") - else: - for child in path.iterdir(): - if child.is_file(): - os.remove(child) - elif child.is_dir(): - shutil.rmtree(child) + else: + for child in path.iterdir(): + if child.is_file(): + os.remove(child) + elif child.is_dir(): + shutil.rmtree(child) if isinstance(required, str): required = Path(required) diff --git a/dcm2bids/utils/logger.py b/dcm2bids/utils/logger.py index eb5af2e3..9529ce15 100644 --- a/dcm2bids/utils/logger.py +++ b/dcm2bids/utils/logger.py @@ -19,7 +19,7 @@ def setup_logging(log_level, log_file=None): sh = logging.StreamHandler(sys.stdout) sh.setLevel(log_level) - sh_fmt = logging.Formatter(fmt="%(levelname)-8s| %(message)s") + sh_fmt = CustomFormatter(fmt="%(levelname)-8s| %(message)s") sh.setFormatter(sh_fmt) # default formatting is kept for the log file" @@ -30,3 +30,30 @@ def setup_logging(log_level, log_file=None): datefmt="%Y-%m-%d %H:%M:%S", handlers=[fh, sh] ) + + +class CustomFormatter(logging.Formatter): + """Logging colored formatter, adapted from https://stackoverflow.com/a/56944256/3638629""" + + grey = '\x1b[38;21m' + blue = '\x1b[38;5;39m' + yellow = '\x1b[38;5;226m' + red = '\x1b[38;5;196m' + bold_red = '\x1b[31;1m' + reset = '\x1b[0m' + + def __init__(self, fmt): + super().__init__() + self.fmt = fmt + self.FORMATS = { + logging.DEBUG: self.grey + self.fmt + self.reset, + logging.INFO: self.blue + self.fmt + self.reset, + logging.WARNING: self.yellow + self.fmt + self.reset, + logging.ERROR: self.red + self.fmt + self.reset, + logging.CRITICAL: self.bold_red + self.fmt + self.reset + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) \ No newline at end of file diff --git a/dcm2bids/utils/utils.py b/dcm2bids/utils/utils.py index fd9426c1..417cc717 100644 --- a/dcm2bids/utils/utils.py +++ b/dcm2bids/utils/utils.py @@ -5,7 +5,7 @@ import logging import os from pathlib import Path -from subprocess import check_output +from subprocess import Popen, PIPE class DEFAULT(object): @@ -28,6 +28,7 @@ class DEFAULT(object): session = "" # also Participant object bids_validate = False auto_extract_entities = False + do_not_reorder_entities = False clobber = False force_dcm2bids = False post_op = [] @@ -53,13 +54,28 @@ class DEFAULT(object): extractors = {} - auto_entities = {"anat_MEGRE": ["echo"], + auto_entities = {"anat_IRT1": ["inv"], + "anat_MEGRE": ["echo"], "anat_MESE": ["echo"], + "anat_MP2RAGE": ["inv"], + "anat_MPM": ["flip", "mt"], + "anat_MTS": ["flip", "mt"], + "anat_MTR": ["mt"], + "anat_VFA": ["flip"], "func_cbv": ["task"], "func_bold": ["task"], "func_sbref": ["task"], - "fmap_epi": ["dir"]} - + "func_event": ["task"], + "func_stim": ["task"], + "func_phase": ["task"], + "fmap_epi": ["dir"], + "fmap_m0scan": ["dir"], + "fmap_TB1DAM": ["flip"], + "fmap_TB1EPI": ["echo", "flip"], + "fmap_TB1SRGE": ["echo", "inv"], + "perf_physio": ["task"], + "perf_stim": ["task"]} + compKeys = ["AcquisitionTime", "SeriesNumber", "SidecarFilename"] search_methodChoices = ["fnmatch", "re"] search_method = "fnmatch" @@ -67,13 +83,17 @@ class DEFAULT(object): dup_method = "run" runTpl = "_run-{:02d}" dupTpl = "_dup-{:02d}" + bids_uri_choices = ["URI", "relative"] + bids_uri = "URI" case_sensitive = True # Entity table: - # https://bids-specification.readthedocs.io/en/v1.7.0/99-appendices/04-entity-table.html - entityTableKeys = ["sub", "ses", "task", "acq", "ce", "rec", "dir", - "run", "mod", "echo", "flip", "inv", "mt", "part", - "recording"] + # https://bids-specification.readthedocs.io/en/v1.9.0/99-appendices/04-entity-table.html + entityTableKeys = ["sub", "ses", "sample", "task", "tracksys", + "acq", "ce", "trc", "stain", "rec", "dir", + "run", "mod", "echo", "flip", "inv", "mt", + "part", "proc", "hemi", "space", "split", "recording", + "chunk", "seg", "res", "den", "label", "desc"] keyWithPathsidecar_changes = ['IntendedFor', 'Sources'] @@ -82,7 +102,7 @@ class DEFAULT(object): helper_dir = "helper" # BIDS version - bids_version = "v1.8.0" + bids_version = "v1.9.0" def write_participants(filename, participants): @@ -128,7 +148,11 @@ def run_shell_command(commandLine, log=True): if log: logger = logging.getLogger(__name__) logger.info("Running: %s", " ".join(str(item) for item in commandLine)) - return check_output(commandLine) + + pipes = Popen(commandLine, stdout=PIPE, stderr=PIPE) + std_out, std_err = pipes.communicate() + + return std_out def convert_dir(dir): @@ -143,15 +167,15 @@ def convert_dir(dir): def combine_dict_extractors(d1, d2): - """ combine dict + """ combine dict Args: d1 (dic): dictionary d2 (dic): dictionary - + Returns: dict: dictionary with combined information if d1 d2 use the same keys, return dict will return a list of items. - """ + """ return { k: [d[k][0] for d in (d1, d2) if k in d] for k in set(d1.keys()) | set(d2.keys()) diff --git a/dcm2bids/version.py b/dcm2bids/version.py index f9b3b65c..530c5ab3 100644 --- a/dcm2bids/version.py +++ b/dcm2bids/version.py @@ -2,8 +2,8 @@ # Format expected by setup.py and doc/source/conf.py: string of form "X.Y.Z" _version_major = 3 -_version_minor = 1 -_version_micro = 1 +_version_minor = 2 +_version_micro = 0 _version_extra = '' # Construct full version string from these. diff --git a/docs/get-started/install.md b/docs/get-started/install.md index 8f7952e1..cd9d7148 100644 --- a/docs/get-started/install.md +++ b/docs/get-started/install.md @@ -9,6 +9,25 @@ date: 2022-04-17 There are several ways to install dcm2bids. +## Containers + +We provide a container image that includes both dcm2niix and dcm2bids as well as pydeface and the BIDS validator. +You can install it using [Docker][docker] or [Apptainer/Singularity][apptainer]. + +=== "Docker" + + ``` + --8<-- "docs_helper/version.txt" + docker pull unfmontreal/dcm2bids:${VERSION} + ``` + +=== "Apptainer/Singularity" + + ``` + --8<-- "docs_helper/version.txt" + apptainer pull dcm2bids.sif docker://unfmontreal/dcm2bids:${VERSION} + ``` + ## Installing binary executables From dcm2bids>=3.0.0, we provide binaries for macOS, Windows and Linux @@ -104,7 +123,7 @@ dcm2bids. ### Python As dcm2bids is a Python package, the first prerequisite is that Python must be -installed on the machine you will use dcm2bids. You will need **Python 3.7 or +installed on the machine you will use dcm2bids. You will need **Python 3.8 or above** to run dcm2bids properly. If you are unsure what version(s) of Python is available on your machine, you @@ -129,7 +148,7 @@ line. >>> exit() ``` -If your system-wide version of Python is lower 3.7, it is okay. We will make +If your system-wide version of Python is lower 3.8, it is okay. We will make sure to use a higher version in the isolated environment that will be created for dcm2bids. The important part is to verify that Python is installed. @@ -163,7 +182,7 @@ the next section. We recommend to install all the dependencies at once when installing dcm2bids on a machine or server. As mentioned above the minimal installation requires only -dcm2bids, dcm2niix and Python >= 3.7. For ease of use and to make sure we have a +dcm2bids, dcm2niix and Python >= 3.8. For ease of use and to make sure we have a reproducible environment, we recommend to use a dedicated environment through [conda][conda] or, for those who have it installed, [Anaconda][anaconda]. Note that you **don't need** to use specifically them to use dcm2bids, but it will @@ -260,7 +279,7 @@ name: dcm2bids channels: - conda-forge dependencies: - - python>=3.7 + - python>=3.8 - dcm2niix - dcm2bids ``` @@ -277,7 +296,7 @@ In short, here's what the fields mean: environment. If you are creating an environment for your analysis project, this is where you would list other dependencies such as `nilearn`, `pandas`, and especially as `pip` since you don't want to use the pip outside of your - environment Note that we specify `python>=3.7` to make sure the requirement is + environment Note that we specify `python>=3.8` to make sure the requirement is satisfied for dcm2bids as the newer version of dcm2bids may face issue with Python 3.6 and below. @@ -353,18 +372,6 @@ VoilΓ , you are ready to use dcm2bids or at least [Go to the How-to section](../../how-to/){ .md-button } -## Containers - -We also provide a container image that includes both dcm2niix and dcm2bids which -you can install using [Docker][docker] or [Apptainer/Singularity][apptainer]. - -=== "Docker" - - `docker pull unfmontreal/dcm2bids:latest` - -=== "Apptainer/Singularity" - - `singularity pull dcm2bids_latest.sif docker://unfmontreal/dcm2bids:latest ` ## Summary of the steps @@ -384,7 +391,7 @@ containers: channels: - conda-forge dependencies: - - python>=3.7 + - python>=3.8 - dcm2niix - dcm2bids diff --git a/docs/how-to/container.md b/docs/how-to/container.md new file mode 100644 index 00000000..9a693577 --- /dev/null +++ b/docs/how-to/container.md @@ -0,0 +1,134 @@ +# dcm2bids with Docker and Apptainer / Singularity + +We provide a container image that includes both dcm2niix and dcm2bids as well as pydeface and the BIDS validator. You can find it on [Docker Hub](https://hub.docker.com/r/unfmontreal/dcm2bids). + +You can install it using [Docker](https://www.docker.com/get-started) or [Apptainer/Singularity](https://www.apptainer.org). + +## Prerequisites + +Before you begin, make sure you have at least one of the following installed: + +- Docker: [Download and install Docker](https://www.docker.com/get-started) +- Apptainer, formerly known as Singularity : [Download and install Apptainer](https://apptainer.org/docs/admin/main/installation.html) + +!!! note + If you are using a HPC cluster, Apptainer is the recommended choice and is probably installed on your system. Simply load the module (e.g., + `module load apptainer`) and use the `apptainer` command, + +## Step 1: Pull the dcm2bids container + +To start, you can either pull the dcm2bids image from the Docker Hub repository or [build it from the Dockerfile in the repository](https://github.com/UNFmontreal/Dcm2Bids/blob/dev/Dockerfile).: + + + +=== "Docker" + + ``` + --8<-- "docs_helper/version.txt" + docker pull unfmontreal/dcm2bids:${VERSION} + ``` + +=== "Apptainer/Singularity" + + ``` + --8<-- "docs_helper/version.txt" + apptainer pull dcm2bids_${VERSION}.sif docker://unfmontreal/dcm2bids:${VERSION} + ``` + +## Step 2: Test dcm2bids + +The default command, or the point of entry, for the container is `dcm2bids`. So every time you run the container, you can pass the `dcm2bids` arguments and options directly. To test the container, run the following command to display the help message for the `dcm2bids` command. + +=== "Docker" + + ``` + docker run --rm -it unfmontreal/dcm2bids:${VERSION} --help + ``` + +=== "Apptainer/Singularity" + + ``` + apptainer run -e --containall dcm2bids.sif --help + ``` + +## Step 3: Run `dcm2bids_scaffold` + +To run `dcm2bids_scaffold` with Apptainer/Singularity, you need to *execute* a command instead of *running* the pre-specified command (`dcm2bids`). You need to bind the respective volumes. + +=== "Docker" + + ``` + docker run --rm -it \ + --entrypoint /venv/bin/dcm2bids_scaffold \ + -v /path/to/bids:/bids \ + unfmontreal/dcm2bids:${VERSION} -o /bids/new_scaffold + ``` + +=== "Apptainer/Singularity" + + ``` + apptainer exec \ + -e --containall \ + -B /path/to/bids:/bids \ + dcm2bids.sif dcm2bids_scaffold -o /bids/new_scaffold + ``` + + +## Step 4: Run `dcm2bids_helper` + +To run `dcm2bids_helper` with Apptainer/Singularity, you need to *execute* a command instead of *running* the pre-specified command (`dcm2bids`). To bind the respective volumes, you have two options: + +1. Put the input data in the same parent directory as the output directory. +2. Specify the input data directory as a separate volume. + +If you bind the newly scaffolded directory on its own, you can simply use the `-o /bids` instead of having to specify the full path to the scaffolded directory. Same goes for the input data directory, if the input data directory is one subject, you can bind it directly to `/dicoms`. If it is the parent directory of multiple subjects, you can bind it to `/dicoms` and specify the specific subject directory (e.g, `-d /dicoms/subject-01`). + +=== "Docker" + + ``` + docker run --rm -it --entrypoint /venv/bin/dcm2bids_helper \ + -v /path/to/dicoms:/dicoms:ro \ + -v /path/to/bids/new_scaffold:/bids \ + unfmontreal/dcm2bids:${VERSION} -o /bids -d /dicoms + ``` + +=== "Apptainer/Singularity" + + ``` + apptainer exec \ + -e --containall \ + -B /path/to/dicoms:/dicoms:ro \ + -B /path/to/bids/new_scaffold:/bids \ + dcm2bids.sif dcm2bids_helper -o /bids -d /dicoms + ``` + +## Step 5: Run `dcm2bids` + +You can use `run` as in Step 2 or use `exec dcm2bids` to run `dcm2bids` with the appropriate arguments and options. You need to bind the respective volumes. + +You can put input data in the same parent directory as the output directory, or you can specify the input data directory as a separate volume. You must also specify the path to the configuration file. If you use the scaffolded dataset, the config file is usually in the `code/` directory. + +You can also [deface your data](use-advanced-commands.md#post_op) and [validate your BIDS data](use-advanced-commands.md#-bids_validate) using the `--bids_validate` flag. + +=== "Docker" + + ``` + docker run --rm -it \ + -v /path/to/dicoms:/dicoms:ro \ + -v /path/to/config.json:/config.json:ro \ + -v /path/to/bids/new_scaffold:/bids \ + unfmontreal/dcm2bids:${VERSION} --auto_extract_entities --bids_validate \ + -o /bids -d /dicoms -c /config.json -p 001 + ``` + +=== "Apptainer/Singularity" + + ``` + apptainer run \ + -e --containall \ + -B /path/to/dicoms:/dicoms:ro \ + -B /path/to/config.json:/config.json:ro \ + -B /path/to/bids/new_scaffold:/bids \ + dcm2bids.sif --auto_extract_entities --bids_validate \ + -o /bids -d /dicoms -c /config.json -p 001 + ``` diff --git a/docs/how-to/create-config-file.md b/docs/how-to/create-config-file.md index 1681eb71..e89e5a8b 100644 --- a/docs/how-to/create-config-file.md +++ b/docs/how-to/create-config-file.md @@ -128,9 +128,8 @@ specifications][bids-spec]. For a longer example of a Dcm2Bids config json, see [here](https://github.com/unfmontreal/Dcm2Bids/blob/master/example/config.json). -Note that the different bids labels must come in a very specific order to be -bids valid filenames. If the custom_entities fields that are entered that are in -the wrong order, then dcm2bids will reorder them for you. + +Note that the different BIDS entities have a specific order to be considered valid filenames, as specified in the [Entity table of the BIDS Specification](https://bids-specification.readthedocs.io/en/stable/appendices/entity-table.html). If the custom_entities fields are entered in a different order, dcm2bids will automatically reorder them for you. For example if you entered: @@ -149,6 +148,12 @@ WARNING:dcm2bids.structure:βœ… Filename was reordered according to BIDS entity t custom_entities could also be combined with extractors. See [custom_entities combined with extractors](./use-advanced-commands.md#custom_entities-combined-with-extractors) +### Manuel ordering + +!!! tip "`--do_not_reorder_entities`" + + If you prefer to have manual control over the order of `custom_entities`, you can use the `--do_not_reorder_entities` flag. This flag allows you to keep the order defined by you, the user, in the `custom_entities` field. However, please note that this flag cannot be used in conjunction with the `--auto_extract_entities` flag. Also please keep in mind that a custom non-BIDS compliant entity order may preclude downstream usage of BIDS apps such as fMRIPrep. + ## sidecar_changes, id and IntendedFor Optional field to change or add information in a sidecar. diff --git a/docs/how-to/index.md b/docs/how-to/index.md index 1c8e215a..77ca0ce6 100644 --- a/docs/how-to/index.md +++ b/docs/how-to/index.md @@ -16,6 +16,8 @@ title: How-to guides - [Use advanced commands](./use-advanced-commands.md) +- [Use dcm2bids with Docker or Apptainer/Singularity](./container.md) + ## Development and Community - [Contribute to dcm2bids](./contributing.md) diff --git a/docs/how-to/use-advanced-commands.md b/docs/how-to/use-advanced-commands.md index 5684a3da..5fccff5d 100644 --- a/docs/how-to/use-advanced-commands.md +++ b/docs/how-to/use-advanced-commands.md @@ -58,7 +58,7 @@ field. By using the same keys in custom_entities and if found, it will add this new entities directly into the final filename. custom_entities can be a list that combined extractor keys and regular entities. If key is `task` it will -automatically add the field "TaskName" inside the sidecase file. +automatically add the field "TaskName" inside the sidecar file. ### `search_method` @@ -72,11 +72,11 @@ to match criteria. default: `"dup_method": "run"` -run is the default behavior and will add '\_run-' to the customEntities of the +run is the default behavior and will add `_run-` to the custom_entities of the acquisition if it finds duplicate destination roots. dup will keep the last duplicate description and put `_dup-`to the -customEntities of the other acquisitions. This behavior is a +custom_entities of the other acquisitions. This behavior is a [heudiconv](https://heudiconv.readthedocs.io/en/latest/changes.html) inspired feature. @@ -87,6 +87,13 @@ default: `"case_sensitive": "true"` If false, comparisons between strings/lists will be not case sensitive. It's only disabled when used with `"search_method": "fnmatch"`. +### `bids_uri` + +default: `"bids_uri": "URI"` +option: `"bids_uri": "relative"` + +Using `"bids_uri": "relative"` triggers the old behavior of dcm2bids (v2.1.9) that provides the path relative to within the subject directory without the BIDS URI (e.g., `bids::sub-01/`). This option has been brought back for compatibility reasons, especially for [fMRIprep users (pre v24.0.0)](https://fmriprep.org/en/latest/changes.html#june-17-2024). + ### `post_op` default: `"post_op": []` @@ -290,6 +297,13 @@ like this. :radioactive: You can find more detailed information by looking at the file [`dcm2bids/utils/utils.py`](../dcm2bids/utils/utils/) and more specifically *`auto_extractors`* and *`auto_entities`* variables. +!!! danger "You cannot use `--auto_extract_entities` in conjunction with `--do_not_reorder_entities`" + Refer to the [Manuel ordering](../create-config-file/#custom_entities) section for more information. + +### `--do_not_reorder_entities` + +This option will keep the order of the entities as they are entered in the config file by the user in the `custom_entities` field. However, please note that this flag cannot be used in conjunction with the `--auto_extract_entities` flag. + ### `--bids_validate` By default, dcm2bids will not validate your final BIDS structure. If needed, you diff --git a/docs/how-to/use-main-commands.md b/docs/how-to/use-main-commands.md index 75570e8b..dbef4b6b 100644 --- a/docs/how-to/use-main-commands.md +++ b/docs/how-to/use-main-commands.md @@ -1,9 +1,14 @@ # How to use main commands -## Command Line Interface (CLI) +## Command Line Interface (CLI) usage -How to launch dcm2bids when you have build your configuration file ? First `cd` -in your BIDS directory. +See `dcm2bids -h` or `dcm2bids --help` to show the complete list of options and arguments. + +```bash +--8<-- "docs_helper/help.txt" +``` + +### Main command: `dcm2bids` ```bash dcm2bids -d DICOM_DIR -p PARTICIPANT_ID -c CONFIG_FILE @@ -15,11 +20,6 @@ If your participant have a session ID: dcm2bids -d DICOM_DIR -p PARTICIPANT_ID -s SESSION_ID -c CONFIG_FILE ``` -dcm2bids creates log files inside `tmp_dcm2bids/log` - -See `dcm2bids -h` or `dcm2bids --help` to show the help message that contains -more information. - !!! important If your directory or file names have space in them, we recommend that you @@ -29,14 +29,12 @@ more information. `dcm2bids -d "DICOM DIR" -p PARTICIPANT_ID -c "path/with spaces to/CONFIG FILE.json"` - - ## Output dcm2bids creates a `sub-` directory in the output directory (by default the folder where the script is launched). -Sidecars with one matching description will be convert to BIDS. If a file +Sidecars with one matching description will be converted to BIDS. If a file already exists, dcm2bids won't overwrite it. You should use the `--clobber` option to overwrite files. @@ -47,28 +45,35 @@ Sidecars with no or more than one matching descriptions are kept in `tmp_dcm2bids` directory. Users can review these mismatches to change the configuration file accordingly. +dcm2bids creates log files inside `tmp_dcm2bids/log` directory. + ## Tools -- Helper +### Scaffold + +the `dcm2bids_scaffold` command creates basic BIDS files and directories based on the [bids-starter-kit](https://github.com/bids-standard/bids-starter-kit). The output directory is set to the location where the script is launched by default. ```bash -dcm2bids_helper -d DICOM_DIR [-o OUTPUT_DIR] +dcm2bids_scaffold [-o OUTPUT_DIR] ``` -To build the configuration file, you need to have a example of the sidecars. You -can use `dcm2bids_helper` with the DICOMs of one participant. It will launch -dcm2niix and save the result inside the `tmp_dcm2bids/helper` of the output -directory. +```bash +--8<-- "docs_helper/help_scaffold.txt" +``` + +### Helper -- Scaffold +To build the configuration file, you need to have examples of sidecar files. You +can use `dcm2bids_helper` with the DICOMs of one participant. It will launch +dcm2niix and save the result inside the `tmp_dcm2bids/helper` directory by default. ```bash -dcm2bids_scaffold [-o OUTPUT_DIR] +dcm2bids_helper -d DICOM_DIR [-o OUTPUT_DIR] ``` -Create basic BIDS files and directories in the output directory (by default -folder where the script is launched). - +```bash +--8<-- "docs_helper/helper.txt" +``` [json-editor]: http://jsoneditoronline.org/ [^1]: diff --git a/docs/tutorial/parallel.md b/docs/tutorial/parallel.md index e8f68c58..f69d9b59 100644 --- a/docs/tutorial/parallel.md +++ b/docs/tutorial/parallel.md @@ -58,7 +58,7 @@ First thing first, let's make sure our software are usable. ```sh (dcm2bids) sam:~$ dcm2bids -v dcm2bids version: 3.1.0 - Based on BIDS version: v1.8.0 + Based on BIDS version: v1.9.0 (dcm2bids) sam:~$ parallel --version GNU parallel 20230722 Copyright (C) 2007-2023 Ole Tange, http://ole.tange.dk and Free Software diff --git a/docs_helper/README.md b/docs_helper/README.md new file mode 100644 index 00000000..2e2145d8 --- /dev/null +++ b/docs_helper/README.md @@ -0,0 +1,12 @@ +# How to build the doc locally + +The GHA workflow is set to build the documentation and has more steps than +the usual `mkdocs serve`. So if you want to see the documentation locally, +you can use the following steps: + +1. Create a virtual environment and install the dependencies in `requirements-docs.txt` with python 3.11. +2. Install dcm2bids within the virtual environment: `pip install -e .`. +3. Run `dcm2bids -h > docs_helper/help.txt && dcm2bids_helper -h > docs_helper/helper.txt && dcm2bids_scaffold -h > docs_helper/help_scaffold.txt` +4. Run `mkdocs serve` to see the documentation locally. + +Note that this will only run the latest local version of the documentation. `mike` takes care of the versioning through the GHA workflow. \ No newline at end of file diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000..75447a4f --- /dev/null +++ b/environment.yml @@ -0,0 +1,7 @@ +name: fsl +channels: + - https://fsl.fmrib.ox.ac.uk/fsldownloads/fslconda/public/ + - conda-forge +dependencies: + - fsl-bet2 + - fsl-flirt \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index f41d4b26..cfe0631c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,10 +29,11 @@ nav: - Use main commands: how-to/use-main-commands.md - Create a config file: how-to/create-config-file.md - Use advanced commands: how-to/use-advanced-commands.md + - Use container images: how-to/container.md + - Upgrade dcm2bids: upgrade.md - Contribute to dcm2bids: how-to/contributing.md - Changelog: - changelog/index.md - - How to upgrade: upgrade.md - API Reference: dcm2bids - Code of conduct: code_of_conduct.md @@ -76,6 +77,7 @@ theme: - content.action.view - content.code.annotate - content.code.copy + # - content.tabs.link - content.tooltips - navigation.footer - navigation.indexes diff --git a/tests/data/config_test_multiple_intendedfor_uri_relative.json b/tests/data/config_test_multiple_intendedfor_uri_relative.json new file mode 100644 index 00000000..2c3e9053 --- /dev/null +++ b/tests/data/config_test_multiple_intendedfor_uri_relative.json @@ -0,0 +1,34 @@ +{ + "search_method": "fnmatch", + "extractors": {"SeriesDescription": ["task-(?P[a-zA-Z0-9]+)"]}, + "bids_uri": "relative", + "descriptions": [ + { + "id": "localizer", + "datatype": "localizer", + "suffix": "localizer", + "criteria": { + "SeriesDescription": "locali*" + } + }, + { + "id": "T1", + "datatype": "anat", + "suffix": "T1w", + "criteria": { + "SidecarFilename": "*MPRAGE*" + } + }, + { + "datatype": "fmap", + "suffix": "fmap", + "criteria": { + "EchoNumber": 1, + "EchoTime": 0.00492 + }, + "sidecar_changes": { + "IntendedFor": ["localizer", "T1"] + } + } + ] +} diff --git a/tests/data/config_test_no_reorder.json b/tests/data/config_test_no_reorder.json new file mode 100644 index 00000000..fdd7de1c --- /dev/null +++ b/tests/data/config_test_no_reorder.json @@ -0,0 +1,16 @@ +{ + "search_method": "fnmatch", + "extractors": {"SeriesDescription": ["task-(?P[a-zA-Z0-9]+)"]}, + "descriptions": [ + { + "id": "func_task-rest", + "datatype": "func", + "suffix": "bold", + "custom_entities": ["acq-highres", "task"], + "criteria": { + "SeriesDescription": "*bold*", + "ImageType": ["ORIG?NAL", "PRIMARY", "M", "ND", "MOSAIC"] + } + } + ] +} diff --git a/tests/test_dcm2bids.py b/tests/test_dcm2bids.py index 17adb36b..71860d8f 100644 --- a/tests/test_dcm2bids.py +++ b/tests/test_dcm2bids.py @@ -571,3 +571,51 @@ def test_dcm2bids_multiple_intendedFor(): "bids::" + os.path.join("sub-01", "anat", "sub-01_T1w.nii")] + + +def test_dcm2bids_no_reorder_entities(): + bids_dir = TemporaryDirectory() + + tmp_sub_dir = os.path.join(bids_dir.name, DEFAULT.tmp_dir_name, "sub-01") + shutil.copytree(os.path.join(TEST_DATA_DIR, "sidecars"), tmp_sub_dir) + + app = Dcm2BidsGen(TEST_DATA_DIR, "01", + os.path.join(TEST_DATA_DIR, "config_test_no_reorder.json"), + bids_dir.name, + do_not_reorder_entities=True, + auto_extract_entities=False) + + app.run() + + # existing field + func_json = os.path.join(bids_dir.name, "sub-01", + "func", + "sub-01_acq-highres_task-rest_bold.json") + assert os.path.exists(func_json) + + +def test_dcm2bids_multiple_intendedFor(): + bids_dir = TemporaryDirectory() + + tmp_sub_dir = os.path.join(bids_dir.name, DEFAULT.tmp_dir_name, "sub-01") + shutil.copytree(os.path.join(TEST_DATA_DIR, "sidecars"), tmp_sub_dir) + + app = Dcm2BidsGen(TEST_DATA_DIR, "01", + os.path.join(TEST_DATA_DIR, + "config_test_multiple_intendedfor_uri_relative.json"), + bids_dir.name, + auto_extract_entities=True) + app.run() + + epi_file = os.path.join(bids_dir.name, "sub-01", "fmap", "sub-01_fmap.json") + data = load_json(epi_file) + + assert os.path.exists(epi_file) + assert data["IntendedFor"] == [os.path.join("localizer", + "sub-01_run-01_localizer.nii"), + os.path.join("localizer", + "sub-01_run-02_localizer.nii"), + os.path.join("localizer", + "sub-01_run-03_localizer.nii"), + os.path.join("anat", + "sub-01_T1w.nii")] \ No newline at end of file