From 40f34b9fb57d4ed1bfe95dbfb4534ed507413b3f Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Wed, 27 Sep 2023 13:45:06 -0400 Subject: [PATCH 01/82] BF: do not demand --files for all commands, even those which do not care about it It refactors how we do check and assertion now for mypy to stay quiet. Closes #707 --- heudiconv/main.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/heudiconv/main.py b/heudiconv/main.py index d356ec6d..5672cd72 100644 --- a/heudiconv/main.py +++ b/heudiconv/main.py @@ -56,7 +56,7 @@ def _pdb_excepthook( def process_extra_commands( outdir: str, command: str, - files: list[str], + files: Optional[list[str]], heuristic: Optional[str], session: Optional[str], subjs: Optional[list[str]], @@ -72,8 +72,8 @@ def process_extra_commands( Output directory command : {'treat-json', 'ls', 'populate-templates', 'populate-intended-for'} Heudiconv command to run - files : list of str - List of files + files : list of str or None + List of files if command needs/expects it heuristic : str or None Path to heuristic file or name of builtin heuristic. session : str or None @@ -83,12 +83,21 @@ def process_extra_commands( grouping : {'studyUID', 'accession_number', 'all', 'custom'} How to group dicoms. """ + + def ensure_has_files() -> None: + if files is None: + raise ValueError(f"command {command} expects --files being provided") + if command == "treat-jsons": + ensure_has_files() + assert files is not None # for mypy now for fname in files: treat_infofile(fname) elif command == "ls": ensure_heuristic_arg(heuristic) assert heuristic is not None + ensure_has_files() + assert files is not None # for mypy now heuristic_mod = load_heuristic(heuristic) heuristic_ls = getattr(heuristic_mod, "ls", None) for fname in files: @@ -111,10 +120,14 @@ def process_extra_commands( elif command == "populate-templates": ensure_heuristic_arg(heuristic) assert heuristic is not None + ensure_has_files() + assert files is not None # for mypy now heuristic_mod = load_heuristic(heuristic) for fname in files: populate_bids_templates(fname, getattr(heuristic_mod, "DEFAULT_FIELDS", {})) elif command == "sanitize-jsons": + ensure_has_files() + assert files is not None # for mypy now tuneup_bids_json_files(files) elif command == "heuristics": from .utils import get_known_heuristics_with_descriptions @@ -357,8 +370,6 @@ def workflow( ) if command: - if files is None: - raise ValueError("'command' given but 'files' is None") assert dicom_dir_template is None process_extra_commands( outdir, From 330ed763c1cee94dc5b9d4967af34caf3e3e907c Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 28 Sep 2023 11:56:09 -0400 Subject: [PATCH 02/82] Convert assertion into a warning that we would not use dicom dir template option To avoid users confusion etc. Ref: https://github.com/nipy/heudiconv/issues/707#issuecomment-1738371656 --- heudiconv/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/heudiconv/main.py b/heudiconv/main.py index 5672cd72..97c3be05 100644 --- a/heudiconv/main.py +++ b/heudiconv/main.py @@ -370,7 +370,12 @@ def workflow( ) if command: - assert dicom_dir_template is None + if dicom_dir_template: + lgr.warning( + f"DICOM directory template {dicom_dir_template!r} was provided but will be ignored since " + f"commands do not care about it ATM" + ) + process_extra_commands( outdir, command, From 24127ff1a761af9a576f6c2ac925cae506d71e02 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 20 Nov 2023 11:22:25 -0500 Subject: [PATCH 03/82] ENH: give informative assertion message when multiple values are found --- heudiconv/heuristics/reproin.py | 20 +++++++++++------ heudiconv/heuristics/test_reproin.py | 32 ++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/heudiconv/heuristics/reproin.py b/heudiconv/heuristics/reproin.py index 5ddde1be..0d919aa5 100644 --- a/heudiconv/heuristics/reproin.py +++ b/heudiconv/heuristics/reproin.py @@ -637,12 +637,14 @@ def from_series_info(name: str) -> Optional[str]: # For scouts -- we want only dicoms # https://github.com/nipy/heudiconv/issues/145 outtype: tuple[str, ...] - if "_Scout" in s.series_description or ( - datatype == "anat" - and datatype_suffix - and datatype_suffix.startswith("scout") - ) or ( - s.series_description.lower() == s.protocol_name.lower() + "_setter" + if ( + "_Scout" in s.series_description + or ( + datatype == "anat" + and datatype_suffix + and datatype_suffix.startswith("scout") + ) + or (s.series_description.lower() == s.protocol_name.lower() + "_setter") ): outtype = ("dicom",) else: @@ -725,7 +727,11 @@ def get_unique(seqinfos: list[SeqInfo], attr: str) -> Any: """ values = set(getattr(si, attr) for si in seqinfos) - assert len(values) == 1 + if len(values) != 1: + raise AssertionError( + f"Was expecting a single value for attribute {attr!r} " + f"but got: {', '.join(sorted(values))}" + ) return values.pop() diff --git a/heudiconv/heuristics/test_reproin.py b/heudiconv/heuristics/test_reproin.py index 2007df38..45b42cfc 100644 --- a/heudiconv/heuristics/test_reproin.py +++ b/heudiconv/heuristics/test_reproin.py @@ -7,6 +7,8 @@ from typing import NamedTuple from unittest.mock import patch +import pytest + from . import reproin from .reproin import ( filter_files, @@ -14,12 +16,20 @@ fix_dbic_protocol, fixup_subjectid, get_dups_marked, + get_unique, md5sum, parse_series_spec, sanitize_str, ) +class FakeSeqInfo(NamedTuple): + accession_number: str + study_description: str + field1: str + field2: str + + def test_get_dups_marked() -> None: no_dups: dict[tuple[str, tuple[str, ...], None], list[int]] = { ("some", ("foo",), None): [1] @@ -97,12 +107,6 @@ class FakeSeqInfo(NamedTuple): def test_fix_dbic_protocol() -> None: - class FakeSeqInfo(NamedTuple): - accession_number: str - study_description: str - field1: str - field2: str - accession_number = "A003" seq1 = FakeSeqInfo( accession_number, @@ -235,3 +239,19 @@ def test_parse_series_spec() -> None: "session": "01", "dir": "AP", } + + +def test_get_unique() -> None: + accession_number = "A003" + acqs = [ + FakeSeqInfo(accession_number, "mystudy", "nochangeplease", "nochangeeither"), + FakeSeqInfo(accession_number, "mystudy2", "nochangeplease", "nochangeeither"), + ] + + assert get_unique(acqs, "accession_number") == accession_number # type: ignore[arg-type] + with pytest.raises(AssertionError) as ce: + get_unique(acqs, "study_description") # type: ignore[arg-type] + assert ( + str(ce.value) + == "Was expecting a single value for attribute 'study_description' but got: mystudy, mystudy2" + ) From c1ed1c0e07a98c852cc43de6b280ef3ec1fcc567 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 7 Dec 2023 14:09:41 -0500 Subject: [PATCH 04/82] Add script to sensor dicoms -- for the error where dcm2niix might or might not fail but issues an Error --- utils/sensor-dicoms | 80 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100755 utils/sensor-dicoms diff --git a/utils/sensor-dicoms b/utils/sensor-dicoms new file mode 100755 index 00000000..bc42e46c --- /dev/null +++ b/utils/sensor-dicoms @@ -0,0 +1,80 @@ +#!/bin/bash + +set -eu + +# Function to show usage +show_usage() { + echo "Usage: $0 [--dry-run|-n] --move-to DIRNAME directory [directory2 ...]" +} + +# Parsing options +TEMP=$(getopt -o 'n' --long move-to:,dry-run -n "$(basename "$0")" -- "$@") +# shellcheck disable=SC2181 +if [ $? != 0 ]; then echo "Terminating..." >&2; exit 1; fi + +# Note the quotes around `$TEMP`: they are essential! +eval set -- "$TEMP" + +# Initialize variables +MOVE_TO_DIR="" +DRY_RUN="" + +# Extract options and their arguments into variables +while true; do + case "$1" in + --move-to) + MOVE_TO_DIR="$2" + shift 2 + ;; + -n|--dry-run) + DRY_RUN=1 + shift + ;; + --) + shift + break + ;; + *) + echo "Internal error!" + exit 1 + ;; + esac +done + +# Check for mandatory option +if [ -z "$MOVE_TO_DIR" ]; then + echo "Error: --move-to option is required." + show_usage + exit 1 +fi + +# Create MOVE_TO_DIR if it does not exist +if [ ! -d "$MOVE_TO_DIR" ]; then + mkdir -p "$MOVE_TO_DIR" +fi + +TEMP=$(mktemp -d "${TMPDIR:-/tmp}/dl-XXXXXXX") + +# Process the remaining arguments (directories) +for dir in "$@"; do + echo "" + echo "Processing directory: $dir" + rm -rf "${TEMP:?}/*" + failed= + dcm2niix -z y -b y -o "$TEMP/" "$dir" 2>"$TEMP/stderr" >"$TEMP/stdout" || { + echo " Exited with $?; We will proceed with the analysis. Standard error output was:" + sed -e 's,^, ,g' "$TEMP/stderr" + } + + if grep "Error: Check sorted order: 4D dataset has" "$TEMP/stderr"; then + failed=1 + fi + if [ -n "$failed" ]; then + if [ -n "$DRY_RUN" ]; then + echo mv "$dir" "$MOVE_TO_DIR" + else + echo " Moving $dir to $MOVE_TO_DIR" + mv "$dir" "$MOVE_TO_DIR" + fi + fi +done From 16b509876cb67d2cd962a06c738f9dd31ffce8ad Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 8 Dec 2023 11:51:55 -0500 Subject: [PATCH 05/82] Make sensor-dicoms use gnu-getopt if present (on OSX) --- utils/sensor-dicoms | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/utils/sensor-dicoms b/utils/sensor-dicoms index bc42e46c..c47f8442 100755 --- a/utils/sensor-dicoms +++ b/utils/sensor-dicoms @@ -7,8 +7,15 @@ show_usage() { echo "Usage: $0 [--dry-run|-n] --move-to DIRNAME directory [directory2 ...]" } +# On OSX we better use GNU one +if [ -e /usr/local/opt/gnu-getopt/bin/getopt ]; then + getopt=/usr/local/opt/gnu-getopt/bin/getopt +else + getopt=getopt +fi + # Parsing options -TEMP=$(getopt -o 'n' --long move-to:,dry-run -n "$(basename "$0")" -- "$@") +TEMP=$("$getopt" -o 'n' --long move-to:,dry-run -n "$(basename "$0")" -- "$@") # shellcheck disable=SC2181 if [ $? != 0 ]; then echo "Terminating..." >&2; exit 1; fi From 9591160d79ee3e7a8447cd5c5af6a8a3f82c0d0f Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 8 Dec 2023 16:56:09 -0500 Subject: [PATCH 06/82] Ran pre-commit on everything, black decided to adjust some formatting (no functional changes) --- heudiconv/heuristics/example.py | 5 +++-- heudiconv/parser.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/heudiconv/heuristics/example.py b/heudiconv/heuristics/example.py index 1649ae7f..28ae6c7d 100644 --- a/heudiconv/heuristics/example.py +++ b/heudiconv/heuristics/example.py @@ -72,11 +72,12 @@ def infotodict( } last_run = len(seqinfo) for s in seqinfo: - series_num_str = s.series_id.split('-', 1)[0] + series_num_str = s.series_id.split("-", 1)[0] if not series_num_str.isdecimal(): raise ValueError( f"This heuristic can operate only on data when series_id has form -, " - f"and is a numeric number. Got series_id={s.series_id}") + f"and is a numeric number. Got series_id={s.series_id}" + ) series_num: int = int(series_num_str) sl, nt = (s.dim3, s.dim4) if (sl == 176) and (nt == 1) and ("MPRAGE" in s.protocol_name): diff --git a/heudiconv/parser.py b/heudiconv/parser.py index bb8b73e1..27d822e1 100644 --- a/heudiconv/parser.py +++ b/heudiconv/parser.py @@ -279,8 +279,9 @@ def infotoids( lgr.info("Study session for %r", study_session_info) if grouping != "all": - assert (study_session_info not in study_sessions), ( + assert study_session_info not in study_sessions, ( f"Existing study session {study_session_info} " - f"already in analyzed sessions {study_sessions.keys()}") + f"already in analyzed sessions {study_sessions.keys()}" + ) study_sessions[study_session_info] = seqinfo return study_sessions From 712c91db7beacff1a1edba8040be1d147e0ba605 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 8 Dec 2023 17:16:05 -0500 Subject: [PATCH 07/82] Simplify pattern and start with .* for a warning to ignore for some reason on CI it is not getting ignored --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index a120066b..5e62db0c 100644 --- a/tox.ini +++ b/tox.ini @@ -41,7 +41,7 @@ filterwarnings = # ignore:.*pkg_resources:DeprecationWarning # - ignore:Use setlocale\(\), getencoding\(\) and getlocale\(\) instead:DeprecationWarning:nipype + ignore:.*Use setlocale.* instead:DeprecationWarning:nipype [coverage:run] include = heudiconv/* From 9e254c8afc444fb870133dc719557b4f1c32a413 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 8 Dec 2023 17:17:27 -0500 Subject: [PATCH 08/82] Drop Python 3.7 and announce support for 3.12 --- .github/workflows/test.yml | 2 +- heudiconv/info.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7925b1e9..6ca1e10e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,11 +14,11 @@ jobs: fail-fast: false matrix: python-version: - - '3.7' - '3.8' - '3.9' - '3.10' - '3.11' + - '3.12' steps: - name: Check out repository uses: actions/checkout@v4 diff --git a/heudiconv/info.py b/heudiconv/info.py index e76bc37f..955dacf5 100644 --- a/heudiconv/info.py +++ b/heudiconv/info.py @@ -11,16 +11,16 @@ "Environment :: Console", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", "Typing :: Typed", ] -PYTHON_REQUIRES = ">=3.7" +PYTHON_REQUIRES = ">=3.8" REQUIRES = [ # not usable in some use cases since might be just a downloader, not binary From d87696874be371f928bb0d4de918e4aaf1fd926e Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 8 Dec 2023 17:21:53 -0500 Subject: [PATCH 09/82] [DATALAD RUNCMD] Use 3.8 in workflows === Do not change lines below === { "chain": [], "cmd": "sed -i -e 's,3\\.7,3.8,g' .github/workflows/codespell.yml .github/workflows/docker.yml .github/workflows/lint.yml .github/workflows/release.yml .github/workflows/test.yml .github/workflows/typing.yml", "exit": 0, "extra_inputs": [], "inputs": [], "outputs": [], "pwd": "." } ^^^ Do not change lines above ^^^ --- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/typing.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d721fa01..aeaf4f42 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.7' + python-version: '3.8' - name: Install dependencies run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc4b74c9..b720c2d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: if: steps.auto-version.outputs.version != '' uses: actions/setup-python@v4 with: - python-version: '^3.7' + python-version: '^3.8' - name: Install Python dependencies if: steps.auto-version.outputs.version != '' diff --git a/.github/workflows/typing.yml b/.github/workflows/typing.yml index bc6b7d76..82a24486 100644 --- a/.github/workflows/typing.yml +++ b/.github/workflows/typing.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.7' + python-version: '3.8' - name: Install dependencies run: | From 5ab3193f90d1cabea239b6d5fe1bbb5caea1ab53 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 8 Dec 2023 17:28:27 -0500 Subject: [PATCH 10/82] Try newer versioningit (may be would be the one which allows later setuptools) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c077c97b..c25936e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] requires = [ "setuptools >= 46.4.0", - "versioningit ~= 1.0", + "versioningit ~= 2.3", "wheel ~= 0.32" ] build-backend = "setuptools.build_meta" From 2e761a5d532f68474497bfdb0e80af81ada54dba Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 8 Dec 2023 17:56:51 -0500 Subject: [PATCH 11/82] Not ready for 3.12 yet due to pylibjpeg-libjpeg/ --- .github/workflows/test.yml | 1 - heudiconv/info.py | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6ca1e10e..ef3a9c6d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,6 @@ jobs: - '3.9' - '3.10' - '3.11' - - '3.12' steps: - name: Check out repository uses: actions/checkout@v4 diff --git a/heudiconv/info.py b/heudiconv/info.py index 955dacf5..74deecf2 100644 --- a/heudiconv/info.py +++ b/heudiconv/info.py @@ -15,7 +15,6 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", "Typing :: Typed", ] From f9670a925a6321d1d22e124026a02049a7174c34 Mon Sep 17 00:00:00 2001 From: auto Date: Fri, 8 Dec 2023 23:08:38 +0000 Subject: [PATCH 12/82] Update CHANGELOG.md [skip ci] --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d85a7655..75f0ff2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +# v1.0.1 (Fri Dec 08 2023) + +#### 🐛 Bug Fix + +- Drop Python 3.7 support [#722](https://github.com/nipy/heudiconv/pull/722) ([@yarikoptic](https://github.com/yarikoptic)) +- ReproIn: give an informative assertion message when multiple values are found [#718](https://github.com/nipy/heudiconv/pull/718) ([@yarikoptic](https://github.com/yarikoptic)) +- Convert assertion into a warning that we would not use dicom dir tempate option [#709](https://github.com/nipy/heudiconv/pull/709) ([@yarikoptic](https://github.com/yarikoptic)) +- Do not demand --files for all commands, even those which do not care about it (like populate-intended-for) [#708](https://github.com/nipy/heudiconv/pull/708) ([@yarikoptic](https://github.com/yarikoptic)) + +#### ⚠️ Pushed to `master` + +- Add script to sensor dicoms -- for the error where dcm2niix might or might not fail but issues an Error ([@yarikoptic](https://github.com/yarikoptic)) + +#### 🏠 Internal + +- Ran pre-commit on everything, black decided to adjust some formatting [#721](https://github.com/nipy/heudiconv/pull/721) ([@yarikoptic](https://github.com/yarikoptic)) +- Make sensor-dicoms use gnu-getopt if present (on OSX) [#721](https://github.com/nipy/heudiconv/pull/721) ([@yarikoptic](https://github.com/yarikoptic)) + +#### Authors: 1 + +- Yaroslav Halchenko ([@yarikoptic](https://github.com/yarikoptic)) + +--- + # v1.0.0 (Wed Sep 20 2023) #### 💥 Breaking Change From 22bcb967af0b96675cb3a925d3ff85ac2d72feb9 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Wed, 13 Dec 2023 10:13:57 -0500 Subject: [PATCH 13/82] Make README more concrete --- README.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 10e65cba..9f2839a7 100644 --- a/README.rst +++ b/README.rst @@ -60,13 +60,13 @@ on heudiconv.readthedocs.io . HOWTO 101 --------- -In a nutshell -- ``heudiconv`` operates using a heuristic which, given metadata from DICOMs, would decide how to name -resultant (from conversion using `dcm2niix`_) files. Heuristic `convertall `_ could actually be used with no real -heuristic and by simply establish your own conversion mapping through editing produced mapping files. -In most use-cases of retrospective study data conversion, you would need to create your custom heuristic following -`existing heuristics as examples `_ and/or -referring to `"Heuristic" section `_ in the documentation. +In a nutshell -- ``heudiconv`` is given a file tree of DICOMs, and it produces a restructured file tree of NifTI files (conversion handled by `dcm2niix`_) with accompanying metadata files. +The input and output structure is as flexible as your data, which is accomplished by using a Python file called a ``heuristic`` that knows how to read your input structure and decides how to name the resultant files. +``heudiconv`` comes with `existing heuristics `_ which can be used as is, or as examples. +For instance, the Heuristic `convertall `_ extracts standard metadata from all matching DICOMs. +``heudiconv`` creates mapping files, ``.edit.text`` which lets researchers simply establish their own conversion mapping. + +In most use-cases of retrospective study data conversion, you would need to create your custom heuristic following the examples and the `"Heuristic" section `_ in the documentation. **Note** that `ReproIn heuristic `_ is generic and powerful enough to be adopted virtually for *any* study: For prospective studies, you would just need to name your sequences following the `ReproIn convention `_, and for From 1d614cd4be8a8e70fbe42462393bf0a9133de597 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Wed, 13 Dec 2023 10:45:49 -0500 Subject: [PATCH 14/82] Codespell fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f0ff2c..937478a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Drop Python 3.7 support [#722](https://github.com/nipy/heudiconv/pull/722) ([@yarikoptic](https://github.com/yarikoptic)) - ReproIn: give an informative assertion message when multiple values are found [#718](https://github.com/nipy/heudiconv/pull/718) ([@yarikoptic](https://github.com/yarikoptic)) -- Convert assertion into a warning that we would not use dicom dir tempate option [#709](https://github.com/nipy/heudiconv/pull/709) ([@yarikoptic](https://github.com/yarikoptic)) +- Convert assertion into a warning that we would not use dicom dir template option [#709](https://github.com/nipy/heudiconv/pull/709) ([@yarikoptic](https://github.com/yarikoptic)) - Do not demand --files for all commands, even those which do not care about it (like populate-intended-for) [#708](https://github.com/nipy/heudiconv/pull/708) ([@yarikoptic](https://github.com/yarikoptic)) #### ⚠️ Pushed to `master` From 636eb16c5e5d713b36b1329e96ec25b9c0fd2180 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 11 Jan 2024 14:49:51 -0500 Subject: [PATCH 15/82] fix GE hyperband multi-echo bvecs/bvals --- heudiconv/convert.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 8e149bb3..57b2a143 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -886,8 +886,13 @@ def save_converted_files( safe_movefile(res.outputs.bvals, outname_bvals, overwrite) else: if bvals_are_zero(res.outputs.bvals): - os.remove(res.outputs.bvecs) - os.remove(res.outputs.bvals) + to_remove = [] + if isinstance(res.outputs.bvals, str): + to_remove = [res.outputs.bvals, res.outputs.bvecs] + else: + to_remove = list(res.outputs.bvals) + list(res.outputs.bvecs) + for ftr in to_remove: + os.remove(ftr) lgr.debug( "%s and %s were removed since not dwi", res.outputs.bvecs, @@ -1077,6 +1082,10 @@ def bvals_are_zero(bval_file: str) -> bool: True if all are all 0 or 5; False otherwise. """ + # GE hyperband multi-echo containing diffusion info + if isinstance(bval_file, TraitListObject): + return all([bvals_are_zero(bvf) for bvf in bval_file]) + with open(bval_file) as f: bvals = f.read().split() From 758628dc5d3d114bc7e5b7136846a32723be9d03 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 11 Jan 2024 15:45:43 -0500 Subject: [PATCH 16/82] add missing import --- heudiconv/convert.py | 1 + 1 file changed, 1 insertion(+) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 57b2a143..2f696ae0 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -15,6 +15,7 @@ import filelock from nipype import Node +from traits.trait_list_object import TraitListObject from .bids import ( BIDS_VERSION, From 2ea5c82045a75deba1b5b5488a54e4b250e68eb0 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Thu, 11 Jan 2024 16:18:03 -0500 Subject: [PATCH 17/82] import from nipype to avoid adding traits to mypy ignores --- heudiconv/convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 2f696ae0..486439ef 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -15,7 +15,7 @@ import filelock from nipype import Node -from traits.trait_list_object import TraitListObject +from nipype.interfaces.base import TraitListObject from .bids import ( BIDS_VERSION, From 19e759c92accc134e5812d36e7ea94fab0cc4360 Mon Sep 17 00:00:00 2001 From: Horea Christian Date: Fri, 12 Jan 2024 14:23:55 -0500 Subject: [PATCH 18/82] Added figures to master branch --- figs/environment.png | Bin 0 -> 68215 bytes figs/workflow.png | Bin 0 -> 205674 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 figs/environment.png create mode 100644 figs/workflow.png diff --git a/figs/environment.png b/figs/environment.png new file mode 100644 index 0000000000000000000000000000000000000000..e2945d4bd42344c4ecc8c7c613f16445e1243937 GIT binary patch literal 68215 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 z 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 Date: Fri, 12 Jan 2024 14:24:51 -0500 Subject: [PATCH 19/82] Using environment figure in about section --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index 9f2839a7..e6f2fe14 100644 --- a/README.rst +++ b/README.rst @@ -51,6 +51,10 @@ into structured directory layouts. - It integrates with `DataLad `_ to place converted and original data under git/git-annex version control while automatically annotating files with sensitive information (e.g., non-defaced anatomicals, etc). +Heudiconv can be inserted into your workflow to provide automatic conversion as part of a data acquisition pipeline, as seen in the figure below: + +.. image:: figs/environment.png + Installation ------------ From d89bf6ec96d2ac028bd8901d4022ec41522700a9 Mon Sep 17 00:00:00 2001 From: Horea Christian Date: Fri, 12 Jan 2024 14:39:21 -0500 Subject: [PATCH 20/82] Adding workflow figure --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index e6f2fe14..7cb21498 100644 --- a/README.rst +++ b/README.rst @@ -66,6 +66,11 @@ HOWTO 101 In a nutshell -- ``heudiconv`` is given a file tree of DICOMs, and it produces a restructured file tree of NifTI files (conversion handled by `dcm2niix`_) with accompanying metadata files. The input and output structure is as flexible as your data, which is accomplished by using a Python file called a ``heuristic`` that knows how to read your input structure and decides how to name the resultant files. +You can run your conversion automatically (which will produce a ``.heudiconv`` directory storing the used parameters), or generate the default parameters, edit them to customize file naming, and continue conversion via an additional invocation of `heudiconv`: + +.. image:: figs/workflow.png + + ``heudiconv`` comes with `existing heuristics `_ which can be used as is, or as examples. For instance, the Heuristic `convertall `_ extracts standard metadata from all matching DICOMs. ``heudiconv`` creates mapping files, ``.edit.text`` which lets researchers simply establish their own conversion mapping. From 3aa4b2d708c11597b77a4107d88d0e6e61c10d5c Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Fri, 12 Jan 2024 14:49:40 -0500 Subject: [PATCH 21/82] Add documentation building instructions --- CONTRIBUTING.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 88bef0bc..772b17f3 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -78,6 +78,18 @@ This is best accomplished via:: pip install -e .[all] +Documentation +------------- + +To contribute to the documentation, we recommend building the docs +locally prior to submitting a patch. + +To build the docs locally: + + 1. From the root of the heudiconv repository, `pip install -r docs/requirements.txt` + 2. From the `docs/` directory, run `make html` + + Additional Hints ---------------- From 81f0d2fb41885fd2e9d699f9bf9ed52d166b7e94 Mon Sep 17 00:00:00 2001 From: Horea Christian Date: Fri, 12 Jan 2024 14:56:55 -0500 Subject: [PATCH 22/82] Allowing RTD to access images under the same path as README --- docs/figs | 1 + 1 file changed, 1 insertion(+) create mode 120000 docs/figs diff --git a/docs/figs b/docs/figs new file mode 120000 index 00000000..d440220d --- /dev/null +++ b/docs/figs @@ -0,0 +1 @@ +../figs/ \ No newline at end of file From 35ad5283c61a7152b573ccdfa7bf1f74f95ca8d8 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Thu, 18 Jan 2024 10:23:05 -0500 Subject: [PATCH 23/82] Copy tutorials from University of Arizona Originally written by Dianne Patterson for the U of A group, these tutorials are generally useful to all heudiconv users. This commit brings those tutorials verbatim and will be edited in subsequent commits. Co-authored-by: Dianne Patterson --- docs/tutorials.rst | 498 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) diff --git a/docs/tutorials.rst b/docs/tutorials.rst index ec12e36a..a037fd70 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -2,6 +2,504 @@ User Tutorials ============== + +Lesson 1: Running heuristic.py +********************************* + +* Download and unzip `sub-219_dicom.zip `_. You will see a directory called MRIS. + +* Under the MRIS directory, is the *dicom* subdirectory: Under the subject number *219* the session *itbs* is nested. Each dicom sequence folder is nested under the session. You can delete sequences folders if they are of no interest:: + + dicom + └── 219 + └── itbs + ├── Bzero_verify_PA_17 + ├── DTI_30_DIRs_AP_15 + ├── Localizers_1 + ├── MoCoSeries_19 + ├── MoCoSeries_31 + ├── Post_TMS_restingstate_30 + ├── T1_mprage_1mm_13 + ├── field_mapping_20 + ├── field_mapping_21 + └── restingstate_18 + + +* Pull the HeuDiConv Docker container to your machine:: + + docker pull nipy/heudiconv + +* From a BASH shell (no, zsh will not do), navigate to the MRIS directory and run the ``hdc_run.sh`` script for subject *219*, session *itbs*, like this:: + + ./hdc_run.sh heuristic1.py 219 itbs + +* This should complete the conversion. After running, the *Nifti* directory will contain a bids-compliant subject directory:: + + + └── sub-219 + └── ses-itbs + ├── anat + ├── dwi + ├── fmap + └── func + +* The following required BIDS text files are also created in the Nifti directory. Details for filling in these skeleton text files can be found under `tabular files `_ in the BIDS specification:: + + CHANGES + README + dataset_description.json + participants.json + participants.tsv + task-rest_bold.json + +* Next, visit the `bids validator `_. +* Click `Choose File` and then select the *Nifti* directory. There should be no errors (though there are a couple of warnings). + + .. Note:: Your files are not uploaded to the BIDS validator, so there are no privacy concerns! +* Look at the directory structure and files that were generated. +* When you are ready, remove everything that was just created:: + + rm -rf Nifti/sub-* Nifti/.heudiconv Nifti/code/__pycache__ Nifti/*.json Nifti/*.tsv Nifti/README Nifti/CHANGE + +* Now you know what the results should look like. +* In the following sections, you will build *heuristic.py* yourself so you can test different options and understand how to work with your own data. + + +Running HeuDiConv is a 3-step process +********************************************* + +* :ref:`Step1 ` By passing some path information and flags to HeuDiConv, you generate a heuristic (translation) file skeleton and some associated descriptor text files. These all get placed in a hidden directory, *.heudiconv* under the *Nifti* directory. +* :ref:`Step2 ` Copy *MRIS/Nifti/.heudiconv/heuristic.py* to *MRIS/Nifti/code/heuristic.py*. You will modify the copied *heuristic.py* to specify BIDS output names and directories, and the input DICOM characteristics. Available input DICOM characteristics are listed in *MRIS/Nifti/.heudiconv/dicominfo.tsv*. +* :ref:`Step3 ` Having revised *MRIS/Nifti/code/heuristic.py*, you can now call HeuDiConv to run on more subjects and sessions. Each time you run it, additional subdirectories are created under *.heudiconv* that record the details of each subject (and session) conversion. Detailed provenance information is retained in the *.heudiconv* hidden directory. You can rename your heuristic file, which may be useful if you have multiple heuristic files for the same dataset. + +TIPS +====== + +* **Name Directories as you wish**: You can name the project directory (e.g., **MRIS**) and the output directory (e.g., **Nifti**) as you wish (just don't put spaces in the names!). +* **IMA vs dcm**: You are likely to have ``IMA`` files if you copied your images from the scanner to an external drive, but ``.dcm`` files if you exported your images from `Horos `_ or Osirix. Watch out for capitalization differences in the sequence names (``.dcm`` files are typically lower case, but ``IMA`` files are typically upper case). +* **Age and Sex Extraction**: Heudiconv will extract age and sex info from the DICOM header. If there is any reason to believe this information is wrong in the DICOM header (for example, it was made-up because no one knew how old the subject was, or it was considered a privacy concern), then you need to check the output. If you have Horos (or another DICOM editor), you can edit the values in the DICOM headers, otherwise you need to edit the values in the BIDS text file *participants.tsv*. +* **Separating Sessions**: If you have multiple sessions at the scanner, you should create an *Exam* folder for each session. This will help you to keep the data organized and *Exam* will be reported in the *study_description* in your *dicominfo.tsv*, so that you can use it as a criterion. +* **Don't manually combine DICOMS from different sessions**: If you combine multiple sessions in one subject DICOM folder, heudiconv will fail to run and will complain about ``conflicting study identifiers``. You can get around the problem by figuring out which DICOMs are from different sessions and separating them so you deal with one set at a time. This may mean you have to manually edit the BIDS output. + + * Why might you manually combine sessions you ask? Because you never intended to have multiple sessions, but the subject had to complete some scans the next day. Or, because the scanner had to be rebooted. +* **Don't assume all your subjects' dicoms have the same names or that the sequences were always run in the same order**: If you develop a *heuristic.py* on one subject, try it and carefully evaluate the results on your other subjects. This is especially true if you already collected the data before you started thinking about automating the output. Every time you run HeuDiConv with *heuristic.py*, a new *dicominfo.tsv* file is generated. Inspect this for differences in protocol names and series descriptions etc. +* **Decompressing DICOMS**: On 03/11/2019 I found that heudiconv failed if the data I exported from Horos was not decompressed. This was especially confusing because dcm2niix succeeded on this data...hmm. +* **Create unique DICOM protocol names at the scanner** If you have the opportunity to influence the DICOM naming strategies, then try to ensure that there is a unique protocol name for every run. For example, if you repeat the fmri protocol three times, name the first one fmri_1, the next fmri_2, and the last fmri_3 (or any variation on this theme). This will make it much easier to uniquely specify the sequences when you convert and reduce your chance of errors. + +.. _heudiconv_step1: + +Lesson 2: Step 1 +******************** + +From the *MRIS* directory, run the following Docker command to process the ``dcm`` files that you downloaded and unzipped for this tutorial. The subject number is 219:: + + docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/*/*/*.dcm -o /base/Nifti/ -f convertall -s 219 -c none + +.. Warning:: The above Docker command works in bash, but may not work in other shells, (e.g., zsh) + +* ``--rm`` means Docker should cleanup after itself +* ``-it`` means Docker should run interactively +* ``-v ${PWD}:/base`` binds your current directory to ``/base`` inside the container. You could also provide an **absolute path** to the *MRIS* directory. +* ``nipy/heudiconv:latest`` identifies the Docker container to run (the latest version of heudiconv). +* ``-d /base/dicom/{subject}/*/*/*.dcm`` identifies the path to the DICOM files and specifies that they have the extension ``.dcm`` in this case. +* ``-o /base/Nifti/`` is the output in *Nifti*. If the output directory does not exist, it will be created. +* ``-f convertall`` This creates a *heuristic.py* template from an existing heuristic module. There are `other heuristic modules `_ , e.g., banda-bids.py, bids_with_ses.py, cmrr_heuristic.py, example.py, multires_7Tbold.py, reproin.py, studyforrest_phase2.py, test_reproin.py, uc_bids.py. *heuristic.py* is a good default though. +* ``-s 219`` specifies the subject number. ``219`` will replace {subject} in the ``-d`` argument when Docker actually runs. +* ``-c none`` indicates you are not actually doing any conversion right now. +* Heudiconv generates a hidden directory *MRIS/Nifti/.heudiconv/219/info* and populates it with two files of interest: a skeleton *heuristic.py* and a *dicominfo.tsv* file. +* After Step 1, the *heuristic.py* template contains explanatory text for you to read. I have removed this from *heuristic1.py* to keep it short. + +The ``.heudiconv`` hidden directory +====================================== + + * **The Good** Every time you run conversion to create the BIDS NIfTI files and directories, a detailed record of what you did is recorded in the *.heudiconv* directory. This includes a copy of the *heuristic.py* module that you ran for each subject and session. Keep in mind that the hidden *.heudiconv* directory gets updated every time you run heudiconv. Together your *code* and *.heudiconv* directories provide valuable provenance information that should remain with your data. + * **The Bad** If you rerun *heuristic.py* for some subject and session that has already been run, heudiconv quietly uses the conversion routines it stored in *.heudiconv*. This can be really annoying if you are troubleshooting *heuristic.py*. + * **More Good** You can remove subject and session information from *.heudiconv* and run it fresh. In fact, you can entirely remove the *.heudiconv* directory and still run the *heuristic.py* you put in the *code* directory. + +* Step 1 only needs to be completed once correctly for each project. + +.. _heudiconv_step2: + +Lesson 2: Step 2 +****************** + +* You will modify three sections in *heuristic.py*. It is okay to rename this file, or to have several versions with different names. You just don't want to mix up your new *heuristic.py* and the finished *heuristic1.py* while you are learning. +* Your goal is to produce a working *heuristic.py* that will arrange the output in a BIDS directory structure. Once you create a working *heuristic.py*, you can run it for different subjects and sessions (keep reading). +* I provide three section labels (1, 1b and 2) to facilitate exposition here. Each of these sections should be manually modified by you for your project. + +Section 1 +============== + +* This *heuristic.py* does not import all sequences in the example *Dicom* directory. This is a feature of heudiconv: You do not need to import scouts, motion corrected images or other DICOMs of no interest. +* You may wish to add, modify or remove keys from this section for your own data:: + + # Section 1: These key definitions should be revised by the user + ################################################################### + # For each sequence, define a key variables (e.g., t1w, dwi etc) and template using the create_key function: + # key = create_key(output_directory_path_and_name). + + ###### TIPS ####### + # If there are sessions, then session must be subfolder name. + # Do not prepend the ses key to the session! It will be prepended automatically for the subfolder and the filename. + # The final value in the filename should be the modality. It does not have a key, just a value. + # Otherwise, there is a key for every value. + # Filenames always start with subject, optionally followed by session, and end with modality. + + ###### Definitions ####### + # The "data" key creates sequential numbers which can be used for naming sequences. + # This is especially valuable if you run the same sequence multiple times at the scanner. + data = create_key('run-{item:03d}') + + t1w = create_key('sub-{subject}/{session}/anat/sub-{subject}_{session}_T1w') + + dwi = create_key('sub-{subject}/{session}/dwi/sub-{subject}_{session}_dir-AP_dwi') + + # Save the RPE (reverse phase-encode) B0 image as a fieldmap (fmap). It will be used to correct + # the distortion in the DWI + fmap_rev_phase = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_dir-PA_epi') + + fmap_mag = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_magnitude') + + fmap_phase = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_phasediff') + + # Even if this is resting state, you still need a task key + func_rest = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_run-01_bold') + func_rest_post = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_run-02_bold') + +* **Key** + + * Define a short informative key variable name for each image sequence you wish to export. Note that you can use any key names you want (e.g. *foo* would work as well as *fmap_phase*), but you need to be consistent. + * The ``key`` name is to the left of the ``=`` for each row in the above example. +* **Template** + + * Use the variable ``{subject}`` to make the code general purpose, so you can apply it to different subjects in Step 3. + * Use the variable ``{session}`` to make the code general purpose only if you have multiple sessions for each subject. + + * Once you use the variable ``{session}``: + * Ensure that a session gets added to the **output path**, e.g., ``sub-{subject}/{session}/anat/`` AND + * Session gets added to the **output filename**: ``sub-{subject}_{session}_T1w`` for every image in the session. + * Otherwise you will get :ref:`bids-validator errors `. + + * Define the output directories and file names according to the `BIDS specification `_ + * Note the output names for the fieldmap images (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*, *sub-219_ses-itbs_magnitude1.nii.gz*, *sub-219_ses-itbs_magnitude2.nii.gz*, *sub-219_ses-itbs_phasediff.nii.gz*). + * The reverse_phase encode dwi image (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*) is grouped with the fieldmaps because it is used to correct other images. + * Data that is not yet defined in the BIDS specification will cause the bids-validator to produce an error unless you include it in a :ref:`.bidsignore ` file. +* **data** + + * a key definition that creates sequential numbering + * ``03d`` means *create three slots for digits* ``3d``, *and pad with zeros* ``0``. + * This is useful if you have a scanner sequence with a single name but you run it repeatedly and need to generate separate files for each run. For example, you might define a single functional sequence at the scanner and then run it several times instead of creating separate names for each run. + + .. Note:: It is usually better to name your sequences explicitly (e.g., run-01, run-02 etc.) rather than depending on sequential numbering. There will be less confusion later. + + * If you have a sequence with the same name that you run repeatedly WITHOUT the sequential numbering, HeuDiConv will overwrite earlier sequences with later ones. + * To ensure that a sequence includes sequential numbering, you also need to add ``run-{item:03d}`` (for example) to the key-value specification for that sequence. + * Here I illustrate with the t1w key-value pair: + + * If you started with: + + * ``t1w = create_key('sub-{subject}/anat/sub-{subject}_T1w')``, + * You could add sequence numbering like this: + + * ``t1w = create_key('sub-{subject}/anat/sub-{subject}_run-{item:03d}_T1w')``. + * Now if you export several T1w images for the same subject and session, using the exact same protocol, each will get a separate run number like this: + + * *sub-219_ses_run-001_T1w.nii.gz, sub-219_ses_run-002_T1w.nii.gz* etc. + +Section 1b +==================== + +* Based on your chosen keys, create a data dictionary called *info*:: + + # Section 1b: This data dictionary (below) should be revised by the user. + ########################################################################### + # info is a Python dictionary containing the following keys from the infotodict defined above. + # This list should contain all and only the sequences you want to export from the dicom directory. + info = {t1w: [], dwi: [], fmap_rev_phase: [], fmap_mag: [], fmap_phase: [], func_rest: [], func_rest_post: []} + + # The following line does no harm, but it is not part of the dictionary. + last_run = len(seqinfo) + +* Enter each key in the dictionary in this format ``key: []``, for example, ``t1w: []``. +* Separate the entries with commas as illustrated above. + +Section 2 +=============== + +* Define the criteria for identifying each DICOM series that corresponds to one of the keys you want to export:: + + # Section 2: These criteria should be revised by the user. + ########################################################## + # Define test criteria to check that each DICOM sequence is correct + # seqinfo (s) refers to information in dicominfo.tsv. Consult that file for + # available criteria. + # Each sequence to export must have been defined in Section 1 and included in Section 1b. + # The following illustrates the use of multiple criteria: + for idx, s in enumerate(seqinfo): + # Dimension 3 must equal 176 and the string 'mprage' must appear somewhere in the protocol_name + if (s.dim3 == 176) and ('mprage' in s.protocol_name): + info[t1w].append(s.series_id) + + # Dimension 3 must equal 74 and dimension 4 must equal 32, and the string 'DTI' must appear somewhere in the protocol_name + if (s.dim3 == 74) and (s.dim4 == 32) and ('DTI' in s.protocol_name): + info[dwi].append(s.series_id) + + # The string 'verify_P-A' must appear somewhere in the protocol_name + if ('verify_P-A' in s.protocol_name): + info[fmap_rev_phase] = [s.series_id] + + # Dimension 3 must equal 64, and the string 'field_mapping' must appear somewhere in the protocol_name + if (s.dim3 == 64) and ('field_mapping' in s.protocol_name): + info[fmap_mag] = [s.series_id] + + # Dimension 3 must equal 32, and the string 'field_mapping' must appear somewhere in the protocol_name + if (s.dim3 == 32) and ('field_mapping' in s.protocol_name): + info[fmap_phase] = [s.series_id] + + # The string 'resting_state' must appear somewhere in the protocol_name and the Boolean field is_motion_corrected must be False (i.e. not motion corrected) + # This ensures I do NOT get the motion corrected MOCO series instead of the raw series! + if ('restingstate' == s.protocol_name) and (not s.is_motion_corrected): + info[func_rest].append(s.series_id) + + # The string 'Post_TMS_resting_state' must appear somewhere in the protocol_name and the Boolean field is_motion_corrected must be False (i.e. not motion corrected) + + # This ensures I do NOT get the motion corrected MOCO series instead of the raw series. + if ('Post_TMS_restingstate' == s.protocol_name) and (not s.is_motion_corrected): + info[func_rest_post].append(s.series_id) + + * To define the criteria, look at *dicominfo.tsv* in *.heudiconv/info*. This file contains tab-separated values so you can easily view it in Excel or any similar spreadsheet program. *dicominfo.tsv* is not used programmatically to run heudiconv (i.e., you could delete it with no adverse consequences), but it is very useful for defining the test criteria for Section 2 of *heuristic.py*. + * Some values in *dicominfo.tsv* might be wrong. For example, my reverse phase encode sequence with two acquisitions of 74 slices each is reported as one acquisition with 148 slices (2018_12_11). Hopefully they'll fix this. Despite the error in *dicominfo.tsv*, dcm2niix reconstructed the images correctly. + * You will be adding, removing or altering values in conditional statements based on the information you find in *dicominfo.tsv*. + * ``seqinfo`` (s) refers to the same information you can view in *dicominfo.tsv* (although seqinfo does not rely on *dicominfo.tsv*). + * Here are two types of criteria: + + * ``s.dim3 == 176`` is an **equivalence** (e.g., good for checking dimensions for a numerical data type). For our sample T1w image to be exported from DICOM, it must have 176 slices in the third dimension. + * ``'mprage' in s.protocol_name`` says the protocol name string must **include** the word *mprage* for the *T1w* image to be exported from DICOM. This criterion string is case-sensitive. + + * ``info[t1w].append(s.series_id)`` Given that the criteria are satisfied, the series should be named and organized as described in *Section 1* and referenced by the info dictionary. The information about the processing steps is saved in the *.heudiconv* subdirectory. + * Here I have organized each conditional statement so that the sequence protocol name comes first followed by other criteria if relevant. This is not necessary, though it does make the resulting code easier to read. + + +.. _heudiconv_step3: + +Lesson 2: Step 3 +******************* + +* You have now done all the hard work for your project. When you want to add a subject or session, you only need to run this third step for that subject or session (A record of each run is kept in .heudiconv for you):: + + docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/*/*.dcm -o /base/Nifti/ -f /base/Nifti/code/heuristic.py -s 219 -ss itbs -c dcm2niix -b --minmeta --overwrite + +.. Warning:: The above Docker command WORKS IN BASH, but may not work in other shells! For example, zsh is upset by the form ``{subject}`` but bash actually doesn't mind. + +* The first time you run this step, several important text files are generated (e.g., CHANGES, dataset_description.json, participants.tsv, README etc.). On subsequent runs, information may be added (e.g., *participants.tsv* will be updated). Other files, like the *README* and *dataset_description.json* should be filled in manually after they are first generated. +* This Docker command is slightly different from the previous Docker command you ran. + + * ``-f /base/Nifti/code/heuristic.py`` now tells HeuDiConv to use your revised *heuristic.py* in the *code* directory. + * In this case, we specify the subject we wish to process ``-s 219`` and the name of the session ``-ss itbs``. + + * We could specify multiple subjects like this: ``-s 219 220 -ss itbs`` + * ``-c dcm2niix -b`` indicates that we want to use the dcm2niix converter with the -b flag (which creates BIDS). + * ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so *minmeta* provides a layer of protection against such corruption. + * ``--overwrite`` This is a peculiar option. Without it, I have found the second run of a sequence does not get generated. But with it, everything gets written again (even if it already exists). I don't know if this is my problem or the tool...but for now, I'm using ``--overwrite``. + * Step 3 should produce a tree like this:: + + Nifti + ├── CHANGES + ├── README + ├── code + │   ├── __pycache__ + │   │   └── heuristic1.cpython-36.pyc + │   ├── heuristic1.py + │   └── heuristic2.py + ├── dataset_description.json + ├── participants.json + ├── participants.tsv + ├── sub-219 + │   └── ses-itbs + │   ├── anat + │   │   ├── sub-219_ses-itbs_T1w.json + │   │   └── sub-219_ses-itbs_T1w.nii.gz + │   ├── dwi + │   │   ├── sub-219_ses-itbs_dir-AP_dwi.bval + │   │   ├── sub-219_ses-itbs_dir-AP_dwi.bvec + │   │   ├── sub-219_ses-itbs_dir-AP_dwi.json + │   │   └── sub-219_ses-itbs_dir-AP_dwi.nii.gz + │   ├── fmap + │   │   ├── sub-219_ses-itbs_dir-PA_epi.json + │   │   ├── sub-219_ses-itbs_dir-PA_epi.nii.gz + │   │   ├── sub-219_ses-itbs_magnitude1.json + │   │   ├── sub-219_ses-itbs_magnitude1.nii.gz + │   │   ├── sub-219_ses-itbs_magnitude2.json + │   │   ├── sub-219_ses-itbs_magnitude2.nii.gz + │   │   ├── sub-219_ses-itbs_phasediff.json + │   │   └── sub-219_ses-itbs_phasediff.nii.gz + │   ├── func + │   │   ├── sub-219_ses-itbs_task-rest_run-01_bold.json + │   │   ├── sub-219_ses-itbs_task-rest_run-01_bold.nii.gz + │   │   ├── sub-219_ses-itbs_task-rest_run-01_events.tsv + │   │   ├── sub-219_ses-itbs_task-rest_run-02_bold.json + │   │   ├── sub-219_ses-itbs_task-rest_run-02_bold.nii.gz + │   │   └── sub-219_ses-itbs_task-rest_run-02_events.tsv + │   ├── sub-219_ses-itbs_scans.json + │   └── sub-219_ses-itbs_scans.tsv + └── task-rest_bold.json + + +Exploring Criteria +********************** + +*dicominfo.tsv* contains a human readable version of seqinfo. Each column of data can be used as criteria for identifying the correct DICOM image. We have already provided examples of using string types, numbers, and Booleans (True-False). Tuples (immutable lists) are also available and examples of using these are provided below. To ensure that you are extracting the images you want, you need to be very careful about creating your initial *heuristic.py*. + +Why Experiment? +==================== + +* Criteria can be tricky. Ensure the NIfTI files you create are the correct ones (for example, not the derived or motion corrected if you didn't want that). In addition to looking at the images created (which tells you whether you have a fieldmap or T1w etc.), you should look at the dimensions of the image. Not only the dimensions, but the range of intensity values and the size of the image on disk should match for dcm2niix and heudiconv's *heuristic.py*. +* For really tricky cases, download and install dcm2niix on your local machine and run it for a sequence of concern (in my experience, it is usually fieldmaps that go wrong). +* Although Python does not require you to use parentheses while defining criteria, parentheses are a good idea. Parentheses will help ensure that complex criteria involving multiple logical operators ``and, or, not`` make sense and behave as expected. + +Tuples +--------- + +Suppose you want to use the values in the field ``image_type``? It is not a number or string or Boolean. To discover the data type of a column, you can add a statement like this ``print(type(s.image_type))`` to the for loop in Section 2 of *heuristic.py*. Then run *heuristic.py* (preferably without any actual conversions) and you should see an output like this ````. Here is an example of using a value from ``image_type`` as a criterion:: + + if ('ASL_3D_tra_iso' == s.protocol_name) and ('TTEST' in s.image_type): + info[asl_der].append(s.series_id) + +Note that this differs from testing for a string because you cannot test for any substring (e.g., 'TEST' would not work). String tests will not work on a tuple datatype. + +.. Note:: *image_type* is described in the `DICOM specification `_ + +Lesson 3: reproin.py +*********************** + +If you don't want to modify a Python file as you did for *heuristic.py*, an alternative is to name your image sequences at the scanner using the *reproin* naming convention. Take some time getting the scanner protocol right, because it is the critical job for running *reproin*. Then a single Docker command converts your DICOMS to the BIDS data structure. There are more details about *reproin* in the :ref:`Links ` section above. + +* You should already have Docker installed and have downloaded HeuDiConv as described in Lesson 1. +* Download and unzip the phantom dataset: `reproin_dicom.zip `_ generated here at the University of Arizona on our Siemens Skyra 3T with Syngo MR VE11c software on 2018_02_08. +* You should see a new directory *REPROIN*. This is a simple reproin-compliant dataset without sessions. Derived dwi images (ADC, FA etc.) that the scanner produced were removed. +* Change directory to *REPROIN*. The directory structure should look like this:: + + REPROIN + ├── data + └── dicom + └── 001 + └── Patterson_Coben\ -\ 1 + ├── Localizers_4 + ├── anatT1w_acqMPRAGE_6 + ├── dwi_dirAP_9 + ├── fmap_acq4mm_7 + ├── fmap_acq4mm_8 + ├── fmap_dirPA_15 + └── func_taskrest_16 + +* From the *REPROIN* directory, run this Docker command:: + + docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -f reproin --bids -o /base/data --files /base/dicom/001 --minmeta +* ``--rm`` means Docker should cleanup after itself +* ``-it`` means Docker should run interactively +* ``-v ${PWD}:/base`` binds your current directory to ``/base`` inside the container. Alternatively, you could provide an **absolute path** to the *REPROIN* directory. +* ``nipy/heudiconv:latest`` identifies the Docker container to run (the latest version of heudiconv). +* ``-f reproin`` specifies the converter file to use +* ``-o /base/data/`` specifies the output directory *data*. If the output directory does not exist, it will be created. +* ``--files /base/dicom/001`` identifies the path to the DICOM files. +* ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so minmeta provides a layer of protection against such corruption. + +That's it. Below we'll unpack what happened. + +Output Directory Structure +=============================== + +*Reproin* produces a hierarchy of BIDS directories like this:: + + data + └── Patterson + └── Coben + ├── sourcedata + │   └── sub-001 + │   ├── anat + │   ├── dwi + │   ├── fmap + │   └── func + └── sub-001 + ├── anat + ├── dwi + ├── fmap + └── func + + +* The dataset is nested under two levels in the output directory: *Region* (Patterson) and *Exam* (Coben). *Tree* is reserved for other purposes at the UA research scanner. +* Although the Program *Patient* is not visible in the output hierarchy, it is important. If you have separate sessions, then each session should have its own Program name. +* **sourcedata** contains tarred gzipped (tgz) sets of DICOM images corresponding to each NIFTI image. +* **sub-001** contains the BIDS dataset. +* The hidden directory is generated: *REPROIN/data/Patterson/Coben/.heudiconv*. + +At the Scanner +==================== + +Here is this phantom dataset displayed in the scanner dot cockpit. The directory structure is defined at the top: *Patterson >> Coben >> Patient* + +* *Region* = *Patterson* +* *Exam* = *Coben* +* *Program* = *Patient* + +.. image:: /pictures/heudiconv_reproin_dot_cockpit.png + :alt: Dot Cockpit interface on Siemens scanner with reproin naming + + + +Reproin Scanner File Names +============================== + +* For both BIDS and *reproin*, names are composed of an ordered series of key-value pairs. Each key and its value are joined with a dash ``-`` (e.g., ``acq-MPRAGE``, ``dir-AP``). These key-value pairs are joined to other key-value pairs with underscores ``_``. The exception is the modality label, which is discussed more below. +* *Reproin* scanner sequence names are simplified relative to the final BIDS output and generally conform to this scheme (but consult the `reference `_ for additional options): ``sequence type-modality label`` _ ``session-session name`` _ ``task-task name`` _ ``acquisition-acquisition detail`` _ ``run-run number`` _ ``direction-direction label``:: + + | func-bold_ses-pre_task-faces_acq-1mm_run-01_dir-AP + +* Each sequence name begins with the seqtype key. The seqtype key is the modality and corresponds to the name of the BIDS directory where the sequence belongs, e.g., ``anat``, ``dwi``, ``fmap`` or ``func``. +* The seqtype key is optionally followed by a dash ``-`` and a modality label value (e.g., ``anat-scout`` or ``anat-T2W``). Often, the modality label is not needed because there is a predictable default for most seqtypes: +* For **anat** the default modality is ``T1W``. Thus a sequence named ``anat`` will have the same output BIDS files as a sequence named ``anat-T1w``: *sub-001_T1w.nii.gz*. +* For **fmap** the default modality is ``epi``. Thus ``fmap_dir-PA`` will have the same output as ``fmap-epi_dir-PA``: *sub-001_dir-PA_epi.nii.gz*. +* For **func** the default modality is ``bold``. Thus, ``func-bold_task-rest`` will have the same output as ``func_task-rest``: *sub-001_task-rest_bold.nii.gz*. +* *Reproin* gets the subject number from the DICOM metadata. +* If you have multiple sessions, the session name does not need to be included in every sequence name in the program (i.e., Program= *Patient* level mentioned above). Instead, the session can be added to a single sequence name, usually the scout (localizer) sequence e.g. ``anat-scout_ses-pre``, and *reproin* will propagate the session information to the other sequence names in the *Program*. Interestingly, *reproin* does not add the localizer to your BIDS output. +* When our scanner exports the DICOM sequences, all dashes are removed. But don't worry, *reproin* handles this just fine. +* In the UA phantom reproin data, the subject was named ``01``. Horos reports the subject number as ``01`` but exports the DICOMS into a directory ``001``. If the data are copied to an external drive at the scanner, then the subject number is reported as ``001_001`` and the images are ``*.IMA`` instead of ``*.dcm``. *Reproin* does not care, it handles all of this gracefully. Your output tree (excluding *sourcedata* and *.heudiconv*) should look like this:: + + . + |-- CHANGES + |-- README + |-- dataset_description.json + |-- participants.tsv + |-- sub-001 + | |-- anat + | | |-- sub-001_acq-MPRAGE_T1w.json + | | `-- sub-001_acq-MPRAGE_T1w.nii.gz + | |-- dwi + | | |-- sub-001_dir-AP_dwi.bval + | | |-- sub-001_dir-AP_dwi.bvec + | | |-- sub-001_dir-AP_dwi.json + | | `-- sub-001_dir-AP_dwi.nii.gz + | |-- fmap + | | |-- sub-001_acq-4mm_magnitude1.json + | | |-- sub-001_acq-4mm_magnitude1.nii.gz + | | |-- sub-001_acq-4mm_magnitude2.json + | | |-- sub-001_acq-4mm_magnitude2.nii.gz + | | |-- sub-001_acq-4mm_phasediff.json + | | |-- sub-001_acq-4mm_phasediff.nii.gz + | | |-- sub-001_dir-PA_epi.json + | | `-- sub-001_dir-PA_epi.nii.gz + | |-- func + | | |-- sub-001_task-rest_bold.json + | | |-- sub-001_task-rest_bold.nii.gz + | | `-- sub-001_task-rest_events.tsv + | `-- sub-001_scans.tsv + `-- task-rest_bold.json + +* Note that despite all the the different subject names (e.g., ``01``, ``001`` and ``001_001``), the subject is labeled ``sub-001``. + +External tutorials +================== + Luckily(?), we live in an era of plentiful information. Below are some links to other users' tutorials covering their experience with ``heudiconv``. From 481f9240925a117cc612307a18403c8f416905d6 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Thu, 18 Jan 2024 11:47:34 -0500 Subject: [PATCH 24/82] Organize left-bar --- README.rst | 14 ++ docs/commandline.rst | 12 + docs/custom-heuristic.rst | 312 +++++++++++++++++++++++ docs/heuristics.rst | 6 +- docs/index.rst | 5 +- docs/quickstart.rst | 125 ++++++++++ docs/reproin.rst | 128 ++++++++++ docs/tutorials.rst | 503 +------------------------------------- docs/usage.rst | 107 -------- 9 files changed, 605 insertions(+), 607 deletions(-) create mode 100644 docs/commandline.rst create mode 100644 docs/custom-heuristic.rst create mode 100644 docs/quickstart.rst create mode 100644 docs/reproin.rst delete mode 100644 docs/usage.rst diff --git a/README.rst b/README.rst index 7cb21498..a177af8f 100644 --- a/README.rst +++ b/README.rst @@ -115,3 +115,17 @@ Docker image preparation being found in ``.github/workflows/release.yml``. --------------------- - https://github.com/courtois-neuromod/ds_prep/blob/main/mri/convert/heuristics_unf.py + + +Support +------- + +All bugs, concerns and enhancement requests for this software can be submitted here: +https://github.com/nipy/heudiconv/issues. + +If you have a problem or would like to ask a question about how to use ``heudiconv``, +please submit a question to `NeuroStars.org `_ with a ``heudiconv`` tag. +NeuroStars.org is a platform similar to StackOverflow but dedicated to neuroinformatics. + +All previous ``heudiconv`` questions are available here: +http://neurostars.org/tags/heudiconv/ diff --git a/docs/commandline.rst b/docs/commandline.rst new file mode 100644 index 00000000..c44ff7b9 --- /dev/null +++ b/docs/commandline.rst @@ -0,0 +1,12 @@ +============= +CLI Reference +============= + +``heudiconv`` processes DICOM files and converts the output into user defined +paths. + +.. argparse:: + :ref: heudiconv.cli.run.get_parser + :prog: heudiconv + :nodefault: + :nodefaultconst: diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst new file mode 100644 index 00000000..58575ad1 --- /dev/null +++ b/docs/custom-heuristic.rst @@ -0,0 +1,312 @@ +========================= +Custom Heuristics +========================= + + +Running HeuDiConv is a 3-step process +************************************* + +* :ref:`Step1 ` By passing some path information and flags to HeuDiConv, you generate a heuristic (translation) file skeleton and some associated descriptor text files. These all get placed in a hidden directory, *.heudiconv* under the *Nifti* directory. +* :ref:`Step2 ` Copy *MRIS/Nifti/.heudiconv/heuristic.py* to *MRIS/Nifti/code/heuristic.py*. You will modify the copied *heuristic.py* to specify BIDS output names and directories, and the input DICOM characteristics. Available input DICOM characteristics are listed in *MRIS/Nifti/.heudiconv/dicominfo.tsv*. +* :ref:`Step3 ` Having revised *MRIS/Nifti/code/heuristic.py*, you can now call HeuDiConv to run on more subjects and sessions. Each time you run it, additional subdirectories are created under *.heudiconv* that record the details of each subject (and session) conversion. Detailed provenance information is retained in the *.heudiconv* hidden directory. You can rename your heuristic file, which may be useful if you have multiple heuristic files for the same dataset. + +TIPS +====== + +* **Name Directories as you wish**: You can name the project directory (e.g., **MRIS**) and the output directory (e.g., **Nifti**) as you wish (just don't put spaces in the names!). +* **Age and Sex Extraction**: Heudiconv will extract age and sex info from the DICOM header. If there is any reason to believe this information is wrong in the DICOM header (for example, it was made-up because no one knew how old the subject was, or it was considered a privacy concern), then you need to check the output. If you have Horos (or another DICOM editor), you can edit the values in the DICOM headers, otherwise you need to edit the values in the BIDS text file *participants.tsv*. +* **Separating Sessions**: If you have multiple sessions at the scanner, you should create an *Exam* folder for each session. This will help you to keep the data organized and *Exam* will be reported in the *study_description* in your *dicominfo.tsv*, so that you can use it as a criterion. +* **Don't manually combine DICOMS from different sessions**: If you combine multiple sessions in one subject DICOM folder, heudiconv will fail to run and will complain about ``conflicting study identifiers``. You can get around the problem by figuring out which DICOMs are from different sessions and separating them so you deal with one set at a time. This may mean you have to manually edit the BIDS output. + + * Why might you manually combine sessions you ask? Because you never intended to have multiple sessions, but the subject had to complete some scans the next day. Or, because the scanner had to be rebooted. +* **Don't assume all your subjects' dicoms have the same names or that the sequences were always run in the same order**: If you develop a *heuristic.py* on one subject, try it and carefully evaluate the results on your other subjects. This is especially true if you already collected the data before you started thinking about automating the output. Every time you run HeuDiConv with *heuristic.py*, a new *dicominfo.tsv* file is generated. Inspect this for differences in protocol names and series descriptions etc. +* **Decompressing DICOMS**: Decompress your data, heudiconv does not yet support compressed DICOM conversion. https://github.com/nipy/heudiconv/issues/287 +* **Create unique DICOM protocol names at the scanner** If you have the opportunity to influence the DICOM naming strategies, then try to ensure that there is a unique protocol name for every run. For example, if you repeat the fmri protocol three times, name the first one fmri_1, the next fmri_2, and the last fmri_3 (or any variation on this theme). This will make it much easier to uniquely specify the sequences when you convert and reduce your chance of errors. + +.. _heudiconv_step1: + +Step 1: Generate Skeleton +************************* + +From the *MRIS* directory, run the following Docker command to process the ``dcm`` files that you downloaded and unzipped for this tutorial. The subject number is 219:: + + docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/*/*/*.dcm -o /base/Nifti/ -f convertall -s 219 -c none + +.. Warning:: The above Docker command works in bash, but may not work in other shells, (e.g., zsh) + +* ``--rm`` means Docker should cleanup after itself +* ``-it`` means Docker should run interactively +* ``-v ${PWD}:/base`` binds your current directory to ``/base`` inside the container. You could also provide an **absolute path** to the *MRIS* directory. +* ``nipy/heudiconv:latest`` identifies the Docker container to run (the latest version of heudiconv). +* ``-d /base/dicom/{subject}/*/*/*.dcm`` identifies the path to the DICOM files and specifies that they have the extension ``.dcm`` in this case. +* ``-o /base/Nifti/`` is the output in *Nifti*. If the output directory does not exist, it will be created. +* ``-f convertall`` This creates a *heuristic.py* template from an existing heuristic module. There are `other heuristic modules `_ , e.g., banda-bids.py, bids_with_ses.py, cmrr_heuristic.py, example.py, multires_7Tbold.py, reproin.py, studyforrest_phase2.py, test_reproin.py, uc_bids.py. *heuristic.py* is a good default though. +* ``-s 219`` specifies the subject number. ``219`` will replace {subject} in the ``-d`` argument when Docker actually runs. +* ``-c none`` indicates you are not actually doing any conversion right now. +* Heudiconv generates a hidden directory *MRIS/Nifti/.heudiconv/219/info* and populates it with two files of interest: a skeleton *heuristic.py* and a *dicominfo.tsv* file. +* After Step 1, the *heuristic.py* template contains explanatory text for you to read. I have removed this from *heuristic1.py* to keep it short. + +The ``.heudiconv`` hidden directory +====================================== + + * **The Good** Every time you run conversion to create the BIDS NIfTI files and directories, a detailed record of what you did is recorded in the *.heudiconv* directory. This includes a copy of the *heuristic.py* module that you ran for each subject and session. Keep in mind that the hidden *.heudiconv* directory gets updated every time you run heudiconv. Together your *code* and *.heudiconv* directories provide valuable provenance information that should remain with your data. + * **The Bad** If you rerun *heuristic.py* for some subject and session that has already been run, heudiconv quietly uses the conversion routines it stored in *.heudiconv*. This can be really annoying if you are troubleshooting *heuristic.py*. + * **More Good** You can remove subject and session information from *.heudiconv* and run it fresh. In fact, you can entirely remove the *.heudiconv* directory and still run the *heuristic.py* you put in the *code* directory. + +* Step 1 only needs to be completed once correctly for each project. + +.. _heudiconv_step2: + +Step 2: Modify Heuristic +************************ + +* You will modify three sections in *heuristic.py*. It is okay to rename this file, or to have several versions with different names. You just don't want to mix up your new *heuristic.py* and the finished *heuristic1.py* while you are learning. +* Your goal is to produce a working *heuristic.py* that will arrange the output in a BIDS directory structure. Once you create a working *heuristic.py*, you can run it for different subjects and sessions (keep reading). +* I provide three section labels (1, 1b and 2) to facilitate exposition here. Each of these sections should be manually modified by you for your project. + +Section 1 +============== + +* This *heuristic.py* does not import all sequences in the example *Dicom* directory. This is a feature of heudiconv: You do not need to import scouts, motion corrected images or other DICOMs of no interest. +* You may wish to add, modify or remove keys from this section for your own data:: + + # Section 1: These key definitions should be revised by the user + ################################################################### + # For each sequence, define a key variables (e.g., t1w, dwi etc) and template using the create_key function: + # key = create_key(output_directory_path_and_name). + + ###### TIPS ####### + # If there are sessions, then session must be subfolder name. + # Do not prepend the ses key to the session! It will be prepended automatically for the subfolder and the filename. + # The final value in the filename should be the modality. It does not have a key, just a value. + # Otherwise, there is a key for every value. + # Filenames always start with subject, optionally followed by session, and end with modality. + + ###### Definitions ####### + # The "data" key creates sequential numbers which can be used for naming sequences. + # This is especially valuable if you run the same sequence multiple times at the scanner. + data = create_key('run-{item:03d}') + + t1w = create_key('sub-{subject}/{session}/anat/sub-{subject}_{session}_T1w') + + dwi = create_key('sub-{subject}/{session}/dwi/sub-{subject}_{session}_dir-AP_dwi') + + # Save the RPE (reverse phase-encode) B0 image as a fieldmap (fmap). It will be used to correct + # the distortion in the DWI + fmap_rev_phase = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_dir-PA_epi') + + fmap_mag = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_magnitude') + + fmap_phase = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_phasediff') + + # Even if this is resting state, you still need a task key + func_rest = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_run-01_bold') + func_rest_post = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_run-02_bold') + +* **Key** + + * Define a short informative key variable name for each image sequence you wish to export. Note that you can use any key names you want (e.g. *foo* would work as well as *fmap_phase*), but you need to be consistent. + * The ``key`` name is to the left of the ``=`` for each row in the above example. +* **Template** + + * Use the variable ``{subject}`` to make the code general purpose, so you can apply it to different subjects in Step 3. + * Use the variable ``{session}`` to make the code general purpose only if you have multiple sessions for each subject. + + * Once you use the variable ``{session}``: + * Ensure that a session gets added to the **output path**, e.g., ``sub-{subject}/{session}/anat/`` AND + * Session gets added to the **output filename**: ``sub-{subject}_{session}_T1w`` for every image in the session. + +.. TODO new link * Otherwise you will get :ref:`bids-validator errors `. + + * Define the output directories and file names according to the `BIDS specification `_ + * Note the output names for the fieldmap images (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*, *sub-219_ses-itbs_magnitude1.nii.gz*, *sub-219_ses-itbs_magnitude2.nii.gz*, *sub-219_ses-itbs_phasediff.nii.gz*). + * The reverse_phase encode dwi image (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*) is grouped with the fieldmaps because it is used to correct other images. + * Data that is not yet defined in the BIDS specification will cause the bids-validator to produce an error unless you include it in a + +.. TODO new link :ref:`.bidsignore ` file. + +* **data** + + * a key definition that creates sequential numbering + * ``03d`` means *create three slots for digits* ``3d``, *and pad with zeros* ``0``. + * This is useful if you have a scanner sequence with a single name but you run it repeatedly and need to generate separate files for each run. For example, you might define a single functional sequence at the scanner and then run it several times instead of creating separate names for each run. + + .. Note:: It is usually better to name your sequences explicitly (e.g., run-01, run-02 etc.) rather than depending on sequential numbering. There will be less confusion later. + + * If you have a sequence with the same name that you run repeatedly WITHOUT the sequential numbering, HeuDiConv will overwrite earlier sequences with later ones. + * To ensure that a sequence includes sequential numbering, you also need to add ``run-{item:03d}`` (for example) to the key-value specification for that sequence. + * Here I illustrate with the t1w key-value pair: + + * If you started with: + + * ``t1w = create_key('sub-{subject}/anat/sub-{subject}_T1w')``, + * You could add sequence numbering like this: + + * ``t1w = create_key('sub-{subject}/anat/sub-{subject}_run-{item:03d}_T1w')``. + * Now if you export several T1w images for the same subject and session, using the exact same protocol, each will get a separate run number like this: + + * *sub-219_ses_run-001_T1w.nii.gz, sub-219_ses_run-002_T1w.nii.gz* etc. + +Section 1b +==================== + +* Based on your chosen keys, create a data dictionary called *info*:: + + # Section 1b: This data dictionary (below) should be revised by the user. + ########################################################################### + # info is a Python dictionary containing the following keys from the infotodict defined above. + # This list should contain all and only the sequences you want to export from the dicom directory. + info = {t1w: [], dwi: [], fmap_rev_phase: [], fmap_mag: [], fmap_phase: [], func_rest: [], func_rest_post: []} + + # The following line does no harm, but it is not part of the dictionary. + last_run = len(seqinfo) + +* Enter each key in the dictionary in this format ``key: []``, for example, ``t1w: []``. +* Separate the entries with commas as illustrated above. + +Section 2 +=============== + +* Define the criteria for identifying each DICOM series that corresponds to one of the keys you want to export:: + + # Section 2: These criteria should be revised by the user. + ########################################################## + # Define test criteria to check that each DICOM sequence is correct + # seqinfo (s) refers to information in dicominfo.tsv. Consult that file for + # available criteria. + # Each sequence to export must have been defined in Section 1 and included in Section 1b. + # The following illustrates the use of multiple criteria: + for idx, s in enumerate(seqinfo): + # Dimension 3 must equal 176 and the string 'mprage' must appear somewhere in the protocol_name + if (s.dim3 == 176) and ('mprage' in s.protocol_name): + info[t1w].append(s.series_id) + + # Dimension 3 must equal 74 and dimension 4 must equal 32, and the string 'DTI' must appear somewhere in the protocol_name + if (s.dim3 == 74) and (s.dim4 == 32) and ('DTI' in s.protocol_name): + info[dwi].append(s.series_id) + + # The string 'verify_P-A' must appear somewhere in the protocol_name + if ('verify_P-A' in s.protocol_name): + info[fmap_rev_phase] = [s.series_id] + + # Dimension 3 must equal 64, and the string 'field_mapping' must appear somewhere in the protocol_name + if (s.dim3 == 64) and ('field_mapping' in s.protocol_name): + info[fmap_mag] = [s.series_id] + + # Dimension 3 must equal 32, and the string 'field_mapping' must appear somewhere in the protocol_name + if (s.dim3 == 32) and ('field_mapping' in s.protocol_name): + info[fmap_phase] = [s.series_id] + + # The string 'resting_state' must appear somewhere in the protocol_name and the Boolean field is_motion_corrected must be False (i.e. not motion corrected) + # This ensures I do NOT get the motion corrected MOCO series instead of the raw series! + if ('restingstate' == s.protocol_name) and (not s.is_motion_corrected): + info[func_rest].append(s.series_id) + + # The string 'Post_TMS_resting_state' must appear somewhere in the protocol_name and the Boolean field is_motion_corrected must be False (i.e. not motion corrected) + + # This ensures I do NOT get the motion corrected MOCO series instead of the raw series. + if ('Post_TMS_restingstate' == s.protocol_name) and (not s.is_motion_corrected): + info[func_rest_post].append(s.series_id) + + * To define the criteria, look at *dicominfo.tsv* in *.heudiconv/info*. This file contains tab-separated values so you can easily view it in Excel or any similar spreadsheet program. *dicominfo.tsv* is not used programmatically to run heudiconv (i.e., you could delete it with no adverse consequences), but it is very useful for defining the test criteria for Section 2 of *heuristic.py*. + * Some values in *dicominfo.tsv* might be wrong. For example, my reverse phase encode sequence with two acquisitions of 74 slices each is reported as one acquisition with 148 slices (2018_12_11). Hopefully they'll fix this. Despite the error in *dicominfo.tsv*, dcm2niix reconstructed the images correctly. + * You will be adding, removing or altering values in conditional statements based on the information you find in *dicominfo.tsv*. + * ``seqinfo`` (s) refers to the same information you can view in *dicominfo.tsv* (although seqinfo does not rely on *dicominfo.tsv*). + * Here are two types of criteria: + + * ``s.dim3 == 176`` is an **equivalence** (e.g., good for checking dimensions for a numerical data type). For our sample T1w image to be exported from DICOM, it must have 176 slices in the third dimension. + * ``'mprage' in s.protocol_name`` says the protocol name string must **include** the word *mprage* for the *T1w* image to be exported from DICOM. This criterion string is case-sensitive. + + * ``info[t1w].append(s.series_id)`` Given that the criteria are satisfied, the series should be named and organized as described in *Section 1* and referenced by the info dictionary. The information about the processing steps is saved in the *.heudiconv* subdirectory. + * Here I have organized each conditional statement so that the sequence protocol name comes first followed by other criteria if relevant. This is not necessary, though it does make the resulting code easier to read. + + +.. _heudiconv_step3: + +Step 3: +******************* + +* You have now done all the hard work for your project. When you want to add a subject or session, you only need to run this third step for that subject or session (A record of each run is kept in .heudiconv for you):: + + docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/*/*.dcm -o /base/Nifti/ -f /base/Nifti/code/heuristic.py -s 219 -ss itbs -c dcm2niix -b --minmeta --overwrite + +.. Warning:: The above Docker command WORKS IN BASH, but may not work in other shells! For example, zsh is upset by the form ``{subject}`` but bash actually doesn't mind. + +* The first time you run this step, several important text files are generated (e.g., CHANGES, dataset_description.json, participants.tsv, README etc.). On subsequent runs, information may be added (e.g., *participants.tsv* will be updated). Other files, like the *README* and *dataset_description.json* should be filled in manually after they are first generated. +* This Docker command is slightly different from the previous Docker command you ran. + + * ``-f /base/Nifti/code/heuristic.py`` now tells HeuDiConv to use your revised *heuristic.py* in the *code* directory. + * In this case, we specify the subject we wish to process ``-s 219`` and the name of the session ``-ss itbs``. + + * We could specify multiple subjects like this: ``-s 219 220 -ss itbs`` + * ``-c dcm2niix -b`` indicates that we want to use the dcm2niix converter with the -b flag (which creates BIDS). + * ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so *minmeta* provides a layer of protection against such corruption. + * ``--overwrite`` This is a peculiar option. Without it, I have found the second run of a sequence does not get generated. But with it, everything gets written again (even if it already exists). I don't know if this is my problem or the tool...but for now, I'm using ``--overwrite``. + * Step 3 should produce a tree like this:: + + Nifti + ├── CHANGES + ├── README + ├── code + │   ├── __pycache__ + │   │   └── heuristic1.cpython-36.pyc + │   ├── heuristic1.py + │   └── heuristic2.py + ├── dataset_description.json + ├── participants.json + ├── participants.tsv + ├── sub-219 + │   └── ses-itbs + │   ├── anat + │   │   ├── sub-219_ses-itbs_T1w.json + │   │   └── sub-219_ses-itbs_T1w.nii.gz + │   ├── dwi + │   │   ├── sub-219_ses-itbs_dir-AP_dwi.bval + │   │   ├── sub-219_ses-itbs_dir-AP_dwi.bvec + │   │   ├── sub-219_ses-itbs_dir-AP_dwi.json + │   │   └── sub-219_ses-itbs_dir-AP_dwi.nii.gz + │   ├── fmap + │   │   ├── sub-219_ses-itbs_dir-PA_epi.json + │   │   ├── sub-219_ses-itbs_dir-PA_epi.nii.gz + │   │   ├── sub-219_ses-itbs_magnitude1.json + │   │   ├── sub-219_ses-itbs_magnitude1.nii.gz + │   │   ├── sub-219_ses-itbs_magnitude2.json + │   │   ├── sub-219_ses-itbs_magnitude2.nii.gz + │   │   ├── sub-219_ses-itbs_phasediff.json + │   │   └── sub-219_ses-itbs_phasediff.nii.gz + │   ├── func + │   │   ├── sub-219_ses-itbs_task-rest_run-01_bold.json + │   │   ├── sub-219_ses-itbs_task-rest_run-01_bold.nii.gz + │   │   ├── sub-219_ses-itbs_task-rest_run-01_events.tsv + │   │   ├── sub-219_ses-itbs_task-rest_run-02_bold.json + │   │   ├── sub-219_ses-itbs_task-rest_run-02_bold.nii.gz + │   │   └── sub-219_ses-itbs_task-rest_run-02_events.tsv + │   ├── sub-219_ses-itbs_scans.json + │   └── sub-219_ses-itbs_scans.tsv + └── task-rest_bold.json + + +Exploring Criteria +********************** + +*dicominfo.tsv* contains a human readable version of seqinfo. Each column of data can be used as criteria for identifying the correct DICOM image. We have already provided examples of using string types, numbers, and Booleans (True-False). Tuples (immutable lists) are also available and examples of using these are provided below. To ensure that you are extracting the images you want, you need to be very careful about creating your initial *heuristic.py*. + +Why Experiment? +==================== + +* Criteria can be tricky. Ensure the NIfTI files you create are the correct ones (for example, not the derived or motion corrected if you didn't want that). In addition to looking at the images created (which tells you whether you have a fieldmap or T1w etc.), you should look at the dimensions of the image. Not only the dimensions, but the range of intensity values and the size of the image on disk should match for dcm2niix and heudiconv's *heuristic.py*. +* For really tricky cases, download and install dcm2niix on your local machine and run it for a sequence of concern (in my experience, it is usually fieldmaps that go wrong). +* Although Python does not require you to use parentheses while defining criteria, parentheses are a good idea. Parentheses will help ensure that complex criteria involving multiple logical operators ``and, or, not`` make sense and behave as expected. + +Tuples +--------- + +Suppose you want to use the values in the field ``image_type``? It is not a number or string or Boolean. To discover the data type of a column, you can add a statement like this ``print(type(s.image_type))`` to the for loop in Section 2 of *heuristic.py*. Then run *heuristic.py* (preferably without any actual conversions) and you should see an output like this ````. Here is an example of using a value from ``image_type`` as a criterion:: + + if ('ASL_3D_tra_iso' == s.protocol_name) and ('TTEST' in s.image_type): + info[asl_der].append(s.series_id) + +Note that this differs from testing for a string because you cannot test for any substring (e.g., 'TEST' would not work). String tests will not work on a tuple datatype. + +.. Note:: *image_type* is described in the `DICOM specification `_ + diff --git a/docs/heuristics.rst b/docs/heuristics.rst index b5de52df..6409f9eb 100644 --- a/docs/heuristics.rst +++ b/docs/heuristics.rst @@ -1,6 +1,6 @@ -========= -Heuristic -========= +======================== +Heuristic File Reference +======================== The heuristic file controls how information about the DICOMs is used to convert to a file system layout (e.g., BIDS). ``heudiconv`` includes some built-in diff --git a/docs/index.rst b/docs/index.rst index 5565625b..8e6a30f5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,7 +14,8 @@ Contents installation changes - usage - heuristics tutorials + heuristics + commandline api + diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 00000000..31521996 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,125 @@ +Quickstart +========== + +Heudiconv Hello World: Using `heuristic.py` + +.. TODO convert to a datalad dataset +.. TODO ``datalad install https://osf.io/mqgzh/`` +.. TODO delete any sequences of no interest prior to push, lets make the + example ds only contain what is needed for these tutorials +.. TODO create a docker/podman section explaining how to use containers + in lieu of `heudiconv`, change the tutorials to `heudiconv`, not + container. +.. TODO convert bash script to docs + +This section demonstrates how to use the heudiconv tool with `heuristic.py` to convert DICOMS into the BIDS data structure. + +* Download and unzip `sub-219_dicom.zip `_. You will see a directory called MRIS. +* Under the MRIS directory, is the *dicom* subdirectory: Under the subject number *219* the session *itbs* is nested. Each dicom sequence folder is nested under the session. You can delete sequences folders if they are of no interest:: + + dicom + └── 219 + └── itbs + ├── Bzero_verify_PA_17 + ├── DTI_30_DIRs_AP_15 + ├── Localizers_1 + ├── MoCoSeries_19 + ├── MoCoSeries_31 + ├── Post_TMS_restingstate_30 + ├── T1_mprage_1mm_13 + ├── field_mapping_20 + ├── field_mapping_21 + └── restingstate_18 + + +* Pull the HeuDiConv Docker container to your machine:: + + docker pull nipy/heudiconv + +* From a BASH shell (no, zsh will not do), navigate to the MRIS directory and run the ``hdc_run.sh`` script for subject *219*, session *itbs*, like this:: + + #!/bin/bash + + : <`_ in the BIDS specification:: + + CHANGES + README + dataset_description.json + participants.json + participants.tsv + task-rest_bold.json + +* Next, visit the `bids validator `_. +* Click `Choose File` and then select the *Nifti* directory. There should be no errors (though there are a couple of warnings). + + .. Note:: Your files are not uploaded to the BIDS validator, so there are no privacy concerns! +* Look at the directory structure and files that were generated. +* When you are ready, remove everything that was just created:: + + rm -rf Nifti/sub-* Nifti/.heudiconv Nifti/code/__pycache__ Nifti/*.json Nifti/*.tsv Nifti/README Nifti/CHANGE + +* Now you know what the results should look like. +* In the following sections, you will build *heuristic.py* yourself so you can test different options and understand how to work with your own data. + + + diff --git a/docs/reproin.rst b/docs/reproin.rst new file mode 100644 index 00000000..fc20fab6 --- /dev/null +++ b/docs/reproin.rst @@ -0,0 +1,128 @@ +================ +Reproin +================ + +If you don't want to modify a Python file as you did for *heuristic.py*, an alternative is to name your image sequences at the scanner using the *reproin* naming convention. Take some time getting the scanner protocol right, because it is the critical job for running *reproin*. Then a single Docker command converts your DICOMS to the BIDS data structure. There are more details about *reproin* in the +.. TODO new link ref:`Links ` section above. + +* You should already have Docker installed and have downloaded HeuDiConv as described in Lesson 1. +* Download and unzip the phantom dataset: `reproin_dicom.zip `_ generated here at the University of Arizona on our Siemens Skyra 3T with Syngo MR VE11c software on 2018_02_08. +* You should see a new directory *REPROIN*. This is a simple reproin-compliant dataset without sessions. Derived dwi images (ADC, FA etc.) that the scanner produced were removed. +* Change directory to *REPROIN*. The directory structure should look like this:: + + REPROIN + ├── data + └── dicom + └── 001 + └── Patterson_Coben\ -\ 1 + ├── Localizers_4 + ├── anatT1w_acqMPRAGE_6 + ├── dwi_dirAP_9 + ├── fmap_acq4mm_7 + ├── fmap_acq4mm_8 + ├── fmap_dirPA_15 + └── func_taskrest_16 + +* From the *REPROIN* directory, run this Docker command:: + + docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -f reproin --bids -o /base/data --files /base/dicom/001 --minmeta +* ``--rm`` means Docker should cleanup after itself +* ``-it`` means Docker should run interactively +* ``-v ${PWD}:/base`` binds your current directory to ``/base`` inside the container. Alternatively, you could provide an **absolute path** to the *REPROIN* directory. +* ``nipy/heudiconv:latest`` identifies the Docker container to run (the latest version of heudiconv). +* ``-f reproin`` specifies the converter file to use +* ``-o /base/data/`` specifies the output directory *data*. If the output directory does not exist, it will be created. +* ``--files /base/dicom/001`` identifies the path to the DICOM files. +* ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so minmeta provides a layer of protection against such corruption. + +That's it. Below we'll unpack what happened. + +Output Directory Structure +=============================== + +*Reproin* produces a hierarchy of BIDS directories like this:: + + data + └── Patterson + └── Coben + ├── sourcedata + │   └── sub-001 + │   ├── anat + │   ├── dwi + │   ├── fmap + │   └── func + └── sub-001 + ├── anat + ├── dwi + ├── fmap + └── func + + +* The dataset is nested under two levels in the output directory: *Region* (Patterson) and *Exam* (Coben). *Tree* is reserved for other purposes at the UA research scanner. +* Although the Program *Patient* is not visible in the output hierarchy, it is important. If you have separate sessions, then each session should have its own Program name. +* **sourcedata** contains tarred gzipped (tgz) sets of DICOM images corresponding to each NIFTI image. +* **sub-001** contains the BIDS dataset. +* The hidden directory is generated: *REPROIN/data/Patterson/Coben/.heudiconv*. + +At the Scanner +==================== + +Here is this phantom dataset displayed in the scanner dot cockpit. The directory structure is defined at the top: *Patterson >> Coben >> Patient* + +* *Region* = *Patterson* +* *Exam* = *Coben* +* *Program* = *Patient* + + + +Reproin Scanner File Names +============================== + +* For both BIDS and *reproin*, names are composed of an ordered series of key-value pairs. Each key and its value are joined with a dash ``-`` (e.g., ``acq-MPRAGE``, ``dir-AP``). These key-value pairs are joined to other key-value pairs with underscores ``_``. The exception is the modality label, which is discussed more below. +* *Reproin* scanner sequence names are simplified relative to the final BIDS output and generally conform to this scheme (but consult the `reference `_ for additional options): ``sequence type-modality label`` _ ``session-session name`` _ ``task-task name`` _ ``acquisition-acquisition detail`` _ ``run-run number`` _ ``direction-direction label``:: + + | func-bold_ses-pre_task-faces_acq-1mm_run-01_dir-AP + +* Each sequence name begins with the seqtype key. The seqtype key is the modality and corresponds to the name of the BIDS directory where the sequence belongs, e.g., ``anat``, ``dwi``, ``fmap`` or ``func``. +* The seqtype key is optionally followed by a dash ``-`` and a modality label value (e.g., ``anat-scout`` or ``anat-T2W``). Often, the modality label is not needed because there is a predictable default for most seqtypes: +* For **anat** the default modality is ``T1W``. Thus a sequence named ``anat`` will have the same output BIDS files as a sequence named ``anat-T1w``: *sub-001_T1w.nii.gz*. +* For **fmap** the default modality is ``epi``. Thus ``fmap_dir-PA`` will have the same output as ``fmap-epi_dir-PA``: *sub-001_dir-PA_epi.nii.gz*. +* For **func** the default modality is ``bold``. Thus, ``func-bold_task-rest`` will have the same output as ``func_task-rest``: *sub-001_task-rest_bold.nii.gz*. +* *Reproin* gets the subject number from the DICOM metadata. +* If you have multiple sessions, the session name does not need to be included in every sequence name in the program (i.e., Program= *Patient* level mentioned above). Instead, the session can be added to a single sequence name, usually the scout (localizer) sequence e.g. ``anat-scout_ses-pre``, and *reproin* will propagate the session information to the other sequence names in the *Program*. Interestingly, *reproin* does not add the localizer to your BIDS output. +* When our scanner exports the DICOM sequences, all dashes are removed. But don't worry, *reproin* handles this just fine. +* In the UA phantom reproin data, the subject was named ``01``. Horos reports the subject number as ``01`` but exports the DICOMS into a directory ``001``. If the data are copied to an external drive at the scanner, then the subject number is reported as ``001_001`` and the images are ``*.IMA`` instead of ``*.dcm``. *Reproin* does not care, it handles all of this gracefully. Your output tree (excluding *sourcedata* and *.heudiconv*) should look like this:: + + . + |-- CHANGES + |-- README + |-- dataset_description.json + |-- participants.tsv + |-- sub-001 + | |-- anat + | | |-- sub-001_acq-MPRAGE_T1w.json + | | `-- sub-001_acq-MPRAGE_T1w.nii.gz + | |-- dwi + | | |-- sub-001_dir-AP_dwi.bval + | | |-- sub-001_dir-AP_dwi.bvec + | | |-- sub-001_dir-AP_dwi.json + | | `-- sub-001_dir-AP_dwi.nii.gz + | |-- fmap + | | |-- sub-001_acq-4mm_magnitude1.json + | | |-- sub-001_acq-4mm_magnitude1.nii.gz + | | |-- sub-001_acq-4mm_magnitude2.json + | | |-- sub-001_acq-4mm_magnitude2.nii.gz + | | |-- sub-001_acq-4mm_phasediff.json + | | |-- sub-001_acq-4mm_phasediff.nii.gz + | | |-- sub-001_dir-PA_epi.json + | | `-- sub-001_dir-PA_epi.nii.gz + | |-- func + | | |-- sub-001_task-rest_bold.json + | | |-- sub-001_task-rest_bold.nii.gz + | | `-- sub-001_task-rest_events.tsv + | `-- sub-001_scans.tsv + `-- task-rest_bold.json + +* Note that despite all the the different subject names (e.g., ``01``, ``001`` and ``001_001``), the subject is labeled ``sub-001``. + + diff --git a/docs/tutorials.rst b/docs/tutorials.rst index a037fd70..d8c3f923 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -1,505 +1,18 @@ -============== -User Tutorials -============== +================== +Tutorials +================== -Lesson 1: Running heuristic.py -********************************* - -* Download and unzip `sub-219_dicom.zip `_. You will see a directory called MRIS. - -* Under the MRIS directory, is the *dicom* subdirectory: Under the subject number *219* the session *itbs* is nested. Each dicom sequence folder is nested under the session. You can delete sequences folders if they are of no interest:: - - dicom - └── 219 - └── itbs - ├── Bzero_verify_PA_17 - ├── DTI_30_DIRs_AP_15 - ├── Localizers_1 - ├── MoCoSeries_19 - ├── MoCoSeries_31 - ├── Post_TMS_restingstate_30 - ├── T1_mprage_1mm_13 - ├── field_mapping_20 - ├── field_mapping_21 - └── restingstate_18 - - -* Pull the HeuDiConv Docker container to your machine:: - - docker pull nipy/heudiconv - -* From a BASH shell (no, zsh will not do), navigate to the MRIS directory and run the ``hdc_run.sh`` script for subject *219*, session *itbs*, like this:: - - ./hdc_run.sh heuristic1.py 219 itbs - -* This should complete the conversion. After running, the *Nifti* directory will contain a bids-compliant subject directory:: - - - └── sub-219 - └── ses-itbs - ├── anat - ├── dwi - ├── fmap - └── func - -* The following required BIDS text files are also created in the Nifti directory. Details for filling in these skeleton text files can be found under `tabular files `_ in the BIDS specification:: - - CHANGES - README - dataset_description.json - participants.json - participants.tsv - task-rest_bold.json - -* Next, visit the `bids validator `_. -* Click `Choose File` and then select the *Nifti* directory. There should be no errors (though there are a couple of warnings). - - .. Note:: Your files are not uploaded to the BIDS validator, so there are no privacy concerns! -* Look at the directory structure and files that were generated. -* When you are ready, remove everything that was just created:: - - rm -rf Nifti/sub-* Nifti/.heudiconv Nifti/code/__pycache__ Nifti/*.json Nifti/*.tsv Nifti/README Nifti/CHANGE - -* Now you know what the results should look like. -* In the following sections, you will build *heuristic.py* yourself so you can test different options and understand how to work with your own data. - - -Running HeuDiConv is a 3-step process -********************************************* - -* :ref:`Step1 ` By passing some path information and flags to HeuDiConv, you generate a heuristic (translation) file skeleton and some associated descriptor text files. These all get placed in a hidden directory, *.heudiconv* under the *Nifti* directory. -* :ref:`Step2 ` Copy *MRIS/Nifti/.heudiconv/heuristic.py* to *MRIS/Nifti/code/heuristic.py*. You will modify the copied *heuristic.py* to specify BIDS output names and directories, and the input DICOM characteristics. Available input DICOM characteristics are listed in *MRIS/Nifti/.heudiconv/dicominfo.tsv*. -* :ref:`Step3 ` Having revised *MRIS/Nifti/code/heuristic.py*, you can now call HeuDiConv to run on more subjects and sessions. Each time you run it, additional subdirectories are created under *.heudiconv* that record the details of each subject (and session) conversion. Detailed provenance information is retained in the *.heudiconv* hidden directory. You can rename your heuristic file, which may be useful if you have multiple heuristic files for the same dataset. - -TIPS -====== - -* **Name Directories as you wish**: You can name the project directory (e.g., **MRIS**) and the output directory (e.g., **Nifti**) as you wish (just don't put spaces in the names!). -* **IMA vs dcm**: You are likely to have ``IMA`` files if you copied your images from the scanner to an external drive, but ``.dcm`` files if you exported your images from `Horos `_ or Osirix. Watch out for capitalization differences in the sequence names (``.dcm`` files are typically lower case, but ``IMA`` files are typically upper case). -* **Age and Sex Extraction**: Heudiconv will extract age and sex info from the DICOM header. If there is any reason to believe this information is wrong in the DICOM header (for example, it was made-up because no one knew how old the subject was, or it was considered a privacy concern), then you need to check the output. If you have Horos (or another DICOM editor), you can edit the values in the DICOM headers, otherwise you need to edit the values in the BIDS text file *participants.tsv*. -* **Separating Sessions**: If you have multiple sessions at the scanner, you should create an *Exam* folder for each session. This will help you to keep the data organized and *Exam* will be reported in the *study_description* in your *dicominfo.tsv*, so that you can use it as a criterion. -* **Don't manually combine DICOMS from different sessions**: If you combine multiple sessions in one subject DICOM folder, heudiconv will fail to run and will complain about ``conflicting study identifiers``. You can get around the problem by figuring out which DICOMs are from different sessions and separating them so you deal with one set at a time. This may mean you have to manually edit the BIDS output. - - * Why might you manually combine sessions you ask? Because you never intended to have multiple sessions, but the subject had to complete some scans the next day. Or, because the scanner had to be rebooted. -* **Don't assume all your subjects' dicoms have the same names or that the sequences were always run in the same order**: If you develop a *heuristic.py* on one subject, try it and carefully evaluate the results on your other subjects. This is especially true if you already collected the data before you started thinking about automating the output. Every time you run HeuDiConv with *heuristic.py*, a new *dicominfo.tsv* file is generated. Inspect this for differences in protocol names and series descriptions etc. -* **Decompressing DICOMS**: On 03/11/2019 I found that heudiconv failed if the data I exported from Horos was not decompressed. This was especially confusing because dcm2niix succeeded on this data...hmm. -* **Create unique DICOM protocol names at the scanner** If you have the opportunity to influence the DICOM naming strategies, then try to ensure that there is a unique protocol name for every run. For example, if you repeat the fmri protocol three times, name the first one fmri_1, the next fmri_2, and the last fmri_3 (or any variation on this theme). This will make it much easier to uniquely specify the sequences when you convert and reduce your chance of errors. - -.. _heudiconv_step1: - -Lesson 2: Step 1 -******************** - -From the *MRIS* directory, run the following Docker command to process the ``dcm`` files that you downloaded and unzipped for this tutorial. The subject number is 219:: - - docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/*/*/*.dcm -o /base/Nifti/ -f convertall -s 219 -c none - -.. Warning:: The above Docker command works in bash, but may not work in other shells, (e.g., zsh) - -* ``--rm`` means Docker should cleanup after itself -* ``-it`` means Docker should run interactively -* ``-v ${PWD}:/base`` binds your current directory to ``/base`` inside the container. You could also provide an **absolute path** to the *MRIS* directory. -* ``nipy/heudiconv:latest`` identifies the Docker container to run (the latest version of heudiconv). -* ``-d /base/dicom/{subject}/*/*/*.dcm`` identifies the path to the DICOM files and specifies that they have the extension ``.dcm`` in this case. -* ``-o /base/Nifti/`` is the output in *Nifti*. If the output directory does not exist, it will be created. -* ``-f convertall`` This creates a *heuristic.py* template from an existing heuristic module. There are `other heuristic modules `_ , e.g., banda-bids.py, bids_with_ses.py, cmrr_heuristic.py, example.py, multires_7Tbold.py, reproin.py, studyforrest_phase2.py, test_reproin.py, uc_bids.py. *heuristic.py* is a good default though. -* ``-s 219`` specifies the subject number. ``219`` will replace {subject} in the ``-d`` argument when Docker actually runs. -* ``-c none`` indicates you are not actually doing any conversion right now. -* Heudiconv generates a hidden directory *MRIS/Nifti/.heudiconv/219/info* and populates it with two files of interest: a skeleton *heuristic.py* and a *dicominfo.tsv* file. -* After Step 1, the *heuristic.py* template contains explanatory text for you to read. I have removed this from *heuristic1.py* to keep it short. - -The ``.heudiconv`` hidden directory -====================================== - - * **The Good** Every time you run conversion to create the BIDS NIfTI files and directories, a detailed record of what you did is recorded in the *.heudiconv* directory. This includes a copy of the *heuristic.py* module that you ran for each subject and session. Keep in mind that the hidden *.heudiconv* directory gets updated every time you run heudiconv. Together your *code* and *.heudiconv* directories provide valuable provenance information that should remain with your data. - * **The Bad** If you rerun *heuristic.py* for some subject and session that has already been run, heudiconv quietly uses the conversion routines it stored in *.heudiconv*. This can be really annoying if you are troubleshooting *heuristic.py*. - * **More Good** You can remove subject and session information from *.heudiconv* and run it fresh. In fact, you can entirely remove the *.heudiconv* directory and still run the *heuristic.py* you put in the *code* directory. +.. toctree:: -* Step 1 only needs to be completed once correctly for each project. + quickstart + custom-heuristic + reproin -.. _heudiconv_step2: -Lesson 2: Step 2 +External Tutorials ****************** -* You will modify three sections in *heuristic.py*. It is okay to rename this file, or to have several versions with different names. You just don't want to mix up your new *heuristic.py* and the finished *heuristic1.py* while you are learning. -* Your goal is to produce a working *heuristic.py* that will arrange the output in a BIDS directory structure. Once you create a working *heuristic.py*, you can run it for different subjects and sessions (keep reading). -* I provide three section labels (1, 1b and 2) to facilitate exposition here. Each of these sections should be manually modified by you for your project. - -Section 1 -============== - -* This *heuristic.py* does not import all sequences in the example *Dicom* directory. This is a feature of heudiconv: You do not need to import scouts, motion corrected images or other DICOMs of no interest. -* You may wish to add, modify or remove keys from this section for your own data:: - - # Section 1: These key definitions should be revised by the user - ################################################################### - # For each sequence, define a key variables (e.g., t1w, dwi etc) and template using the create_key function: - # key = create_key(output_directory_path_and_name). - - ###### TIPS ####### - # If there are sessions, then session must be subfolder name. - # Do not prepend the ses key to the session! It will be prepended automatically for the subfolder and the filename. - # The final value in the filename should be the modality. It does not have a key, just a value. - # Otherwise, there is a key for every value. - # Filenames always start with subject, optionally followed by session, and end with modality. - - ###### Definitions ####### - # The "data" key creates sequential numbers which can be used for naming sequences. - # This is especially valuable if you run the same sequence multiple times at the scanner. - data = create_key('run-{item:03d}') - - t1w = create_key('sub-{subject}/{session}/anat/sub-{subject}_{session}_T1w') - - dwi = create_key('sub-{subject}/{session}/dwi/sub-{subject}_{session}_dir-AP_dwi') - - # Save the RPE (reverse phase-encode) B0 image as a fieldmap (fmap). It will be used to correct - # the distortion in the DWI - fmap_rev_phase = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_dir-PA_epi') - - fmap_mag = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_magnitude') - - fmap_phase = create_key('sub-{subject}/{session}/fmap/sub-{subject}_{session}_phasediff') - - # Even if this is resting state, you still need a task key - func_rest = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_run-01_bold') - func_rest_post = create_key('sub-{subject}/{session}/func/sub-{subject}_{session}_task-rest_run-02_bold') - -* **Key** - - * Define a short informative key variable name for each image sequence you wish to export. Note that you can use any key names you want (e.g. *foo* would work as well as *fmap_phase*), but you need to be consistent. - * The ``key`` name is to the left of the ``=`` for each row in the above example. -* **Template** - - * Use the variable ``{subject}`` to make the code general purpose, so you can apply it to different subjects in Step 3. - * Use the variable ``{session}`` to make the code general purpose only if you have multiple sessions for each subject. - - * Once you use the variable ``{session}``: - * Ensure that a session gets added to the **output path**, e.g., ``sub-{subject}/{session}/anat/`` AND - * Session gets added to the **output filename**: ``sub-{subject}_{session}_T1w`` for every image in the session. - * Otherwise you will get :ref:`bids-validator errors `. - - * Define the output directories and file names according to the `BIDS specification `_ - * Note the output names for the fieldmap images (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*, *sub-219_ses-itbs_magnitude1.nii.gz*, *sub-219_ses-itbs_magnitude2.nii.gz*, *sub-219_ses-itbs_phasediff.nii.gz*). - * The reverse_phase encode dwi image (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*) is grouped with the fieldmaps because it is used to correct other images. - * Data that is not yet defined in the BIDS specification will cause the bids-validator to produce an error unless you include it in a :ref:`.bidsignore ` file. -* **data** - - * a key definition that creates sequential numbering - * ``03d`` means *create three slots for digits* ``3d``, *and pad with zeros* ``0``. - * This is useful if you have a scanner sequence with a single name but you run it repeatedly and need to generate separate files for each run. For example, you might define a single functional sequence at the scanner and then run it several times instead of creating separate names for each run. - - .. Note:: It is usually better to name your sequences explicitly (e.g., run-01, run-02 etc.) rather than depending on sequential numbering. There will be less confusion later. - - * If you have a sequence with the same name that you run repeatedly WITHOUT the sequential numbering, HeuDiConv will overwrite earlier sequences with later ones. - * To ensure that a sequence includes sequential numbering, you also need to add ``run-{item:03d}`` (for example) to the key-value specification for that sequence. - * Here I illustrate with the t1w key-value pair: - - * If you started with: - - * ``t1w = create_key('sub-{subject}/anat/sub-{subject}_T1w')``, - * You could add sequence numbering like this: - - * ``t1w = create_key('sub-{subject}/anat/sub-{subject}_run-{item:03d}_T1w')``. - * Now if you export several T1w images for the same subject and session, using the exact same protocol, each will get a separate run number like this: - - * *sub-219_ses_run-001_T1w.nii.gz, sub-219_ses_run-002_T1w.nii.gz* etc. - -Section 1b -==================== - -* Based on your chosen keys, create a data dictionary called *info*:: - - # Section 1b: This data dictionary (below) should be revised by the user. - ########################################################################### - # info is a Python dictionary containing the following keys from the infotodict defined above. - # This list should contain all and only the sequences you want to export from the dicom directory. - info = {t1w: [], dwi: [], fmap_rev_phase: [], fmap_mag: [], fmap_phase: [], func_rest: [], func_rest_post: []} - - # The following line does no harm, but it is not part of the dictionary. - last_run = len(seqinfo) - -* Enter each key in the dictionary in this format ``key: []``, for example, ``t1w: []``. -* Separate the entries with commas as illustrated above. - -Section 2 -=============== - -* Define the criteria for identifying each DICOM series that corresponds to one of the keys you want to export:: - - # Section 2: These criteria should be revised by the user. - ########################################################## - # Define test criteria to check that each DICOM sequence is correct - # seqinfo (s) refers to information in dicominfo.tsv. Consult that file for - # available criteria. - # Each sequence to export must have been defined in Section 1 and included in Section 1b. - # The following illustrates the use of multiple criteria: - for idx, s in enumerate(seqinfo): - # Dimension 3 must equal 176 and the string 'mprage' must appear somewhere in the protocol_name - if (s.dim3 == 176) and ('mprage' in s.protocol_name): - info[t1w].append(s.series_id) - - # Dimension 3 must equal 74 and dimension 4 must equal 32, and the string 'DTI' must appear somewhere in the protocol_name - if (s.dim3 == 74) and (s.dim4 == 32) and ('DTI' in s.protocol_name): - info[dwi].append(s.series_id) - - # The string 'verify_P-A' must appear somewhere in the protocol_name - if ('verify_P-A' in s.protocol_name): - info[fmap_rev_phase] = [s.series_id] - - # Dimension 3 must equal 64, and the string 'field_mapping' must appear somewhere in the protocol_name - if (s.dim3 == 64) and ('field_mapping' in s.protocol_name): - info[fmap_mag] = [s.series_id] - - # Dimension 3 must equal 32, and the string 'field_mapping' must appear somewhere in the protocol_name - if (s.dim3 == 32) and ('field_mapping' in s.protocol_name): - info[fmap_phase] = [s.series_id] - - # The string 'resting_state' must appear somewhere in the protocol_name and the Boolean field is_motion_corrected must be False (i.e. not motion corrected) - # This ensures I do NOT get the motion corrected MOCO series instead of the raw series! - if ('restingstate' == s.protocol_name) and (not s.is_motion_corrected): - info[func_rest].append(s.series_id) - - # The string 'Post_TMS_resting_state' must appear somewhere in the protocol_name and the Boolean field is_motion_corrected must be False (i.e. not motion corrected) - - # This ensures I do NOT get the motion corrected MOCO series instead of the raw series. - if ('Post_TMS_restingstate' == s.protocol_name) and (not s.is_motion_corrected): - info[func_rest_post].append(s.series_id) - - * To define the criteria, look at *dicominfo.tsv* in *.heudiconv/info*. This file contains tab-separated values so you can easily view it in Excel or any similar spreadsheet program. *dicominfo.tsv* is not used programmatically to run heudiconv (i.e., you could delete it with no adverse consequences), but it is very useful for defining the test criteria for Section 2 of *heuristic.py*. - * Some values in *dicominfo.tsv* might be wrong. For example, my reverse phase encode sequence with two acquisitions of 74 slices each is reported as one acquisition with 148 slices (2018_12_11). Hopefully they'll fix this. Despite the error in *dicominfo.tsv*, dcm2niix reconstructed the images correctly. - * You will be adding, removing or altering values in conditional statements based on the information you find in *dicominfo.tsv*. - * ``seqinfo`` (s) refers to the same information you can view in *dicominfo.tsv* (although seqinfo does not rely on *dicominfo.tsv*). - * Here are two types of criteria: - - * ``s.dim3 == 176`` is an **equivalence** (e.g., good for checking dimensions for a numerical data type). For our sample T1w image to be exported from DICOM, it must have 176 slices in the third dimension. - * ``'mprage' in s.protocol_name`` says the protocol name string must **include** the word *mprage* for the *T1w* image to be exported from DICOM. This criterion string is case-sensitive. - - * ``info[t1w].append(s.series_id)`` Given that the criteria are satisfied, the series should be named and organized as described in *Section 1* and referenced by the info dictionary. The information about the processing steps is saved in the *.heudiconv* subdirectory. - * Here I have organized each conditional statement so that the sequence protocol name comes first followed by other criteria if relevant. This is not necessary, though it does make the resulting code easier to read. - - -.. _heudiconv_step3: - -Lesson 2: Step 3 -******************* - -* You have now done all the hard work for your project. When you want to add a subject or session, you only need to run this third step for that subject or session (A record of each run is kept in .heudiconv for you):: - - docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/*/*.dcm -o /base/Nifti/ -f /base/Nifti/code/heuristic.py -s 219 -ss itbs -c dcm2niix -b --minmeta --overwrite - -.. Warning:: The above Docker command WORKS IN BASH, but may not work in other shells! For example, zsh is upset by the form ``{subject}`` but bash actually doesn't mind. - -* The first time you run this step, several important text files are generated (e.g., CHANGES, dataset_description.json, participants.tsv, README etc.). On subsequent runs, information may be added (e.g., *participants.tsv* will be updated). Other files, like the *README* and *dataset_description.json* should be filled in manually after they are first generated. -* This Docker command is slightly different from the previous Docker command you ran. - - * ``-f /base/Nifti/code/heuristic.py`` now tells HeuDiConv to use your revised *heuristic.py* in the *code* directory. - * In this case, we specify the subject we wish to process ``-s 219`` and the name of the session ``-ss itbs``. - - * We could specify multiple subjects like this: ``-s 219 220 -ss itbs`` - * ``-c dcm2niix -b`` indicates that we want to use the dcm2niix converter with the -b flag (which creates BIDS). - * ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so *minmeta* provides a layer of protection against such corruption. - * ``--overwrite`` This is a peculiar option. Without it, I have found the second run of a sequence does not get generated. But with it, everything gets written again (even if it already exists). I don't know if this is my problem or the tool...but for now, I'm using ``--overwrite``. - * Step 3 should produce a tree like this:: - - Nifti - ├── CHANGES - ├── README - ├── code - │   ├── __pycache__ - │   │   └── heuristic1.cpython-36.pyc - │   ├── heuristic1.py - │   └── heuristic2.py - ├── dataset_description.json - ├── participants.json - ├── participants.tsv - ├── sub-219 - │   └── ses-itbs - │   ├── anat - │   │   ├── sub-219_ses-itbs_T1w.json - │   │   └── sub-219_ses-itbs_T1w.nii.gz - │   ├── dwi - │   │   ├── sub-219_ses-itbs_dir-AP_dwi.bval - │   │   ├── sub-219_ses-itbs_dir-AP_dwi.bvec - │   │   ├── sub-219_ses-itbs_dir-AP_dwi.json - │   │   └── sub-219_ses-itbs_dir-AP_dwi.nii.gz - │   ├── fmap - │   │   ├── sub-219_ses-itbs_dir-PA_epi.json - │   │   ├── sub-219_ses-itbs_dir-PA_epi.nii.gz - │   │   ├── sub-219_ses-itbs_magnitude1.json - │   │   ├── sub-219_ses-itbs_magnitude1.nii.gz - │   │   ├── sub-219_ses-itbs_magnitude2.json - │   │   ├── sub-219_ses-itbs_magnitude2.nii.gz - │   │   ├── sub-219_ses-itbs_phasediff.json - │   │   └── sub-219_ses-itbs_phasediff.nii.gz - │   ├── func - │   │   ├── sub-219_ses-itbs_task-rest_run-01_bold.json - │   │   ├── sub-219_ses-itbs_task-rest_run-01_bold.nii.gz - │   │   ├── sub-219_ses-itbs_task-rest_run-01_events.tsv - │   │   ├── sub-219_ses-itbs_task-rest_run-02_bold.json - │   │   ├── sub-219_ses-itbs_task-rest_run-02_bold.nii.gz - │   │   └── sub-219_ses-itbs_task-rest_run-02_events.tsv - │   ├── sub-219_ses-itbs_scans.json - │   └── sub-219_ses-itbs_scans.tsv - └── task-rest_bold.json - - -Exploring Criteria -********************** - -*dicominfo.tsv* contains a human readable version of seqinfo. Each column of data can be used as criteria for identifying the correct DICOM image. We have already provided examples of using string types, numbers, and Booleans (True-False). Tuples (immutable lists) are also available and examples of using these are provided below. To ensure that you are extracting the images you want, you need to be very careful about creating your initial *heuristic.py*. - -Why Experiment? -==================== - -* Criteria can be tricky. Ensure the NIfTI files you create are the correct ones (for example, not the derived or motion corrected if you didn't want that). In addition to looking at the images created (which tells you whether you have a fieldmap or T1w etc.), you should look at the dimensions of the image. Not only the dimensions, but the range of intensity values and the size of the image on disk should match for dcm2niix and heudiconv's *heuristic.py*. -* For really tricky cases, download and install dcm2niix on your local machine and run it for a sequence of concern (in my experience, it is usually fieldmaps that go wrong). -* Although Python does not require you to use parentheses while defining criteria, parentheses are a good idea. Parentheses will help ensure that complex criteria involving multiple logical operators ``and, or, not`` make sense and behave as expected. - -Tuples ---------- - -Suppose you want to use the values in the field ``image_type``? It is not a number or string or Boolean. To discover the data type of a column, you can add a statement like this ``print(type(s.image_type))`` to the for loop in Section 2 of *heuristic.py*. Then run *heuristic.py* (preferably without any actual conversions) and you should see an output like this ````. Here is an example of using a value from ``image_type`` as a criterion:: - - if ('ASL_3D_tra_iso' == s.protocol_name) and ('TTEST' in s.image_type): - info[asl_der].append(s.series_id) - -Note that this differs from testing for a string because you cannot test for any substring (e.g., 'TEST' would not work). String tests will not work on a tuple datatype. - -.. Note:: *image_type* is described in the `DICOM specification `_ - -Lesson 3: reproin.py -*********************** - -If you don't want to modify a Python file as you did for *heuristic.py*, an alternative is to name your image sequences at the scanner using the *reproin* naming convention. Take some time getting the scanner protocol right, because it is the critical job for running *reproin*. Then a single Docker command converts your DICOMS to the BIDS data structure. There are more details about *reproin* in the :ref:`Links ` section above. - -* You should already have Docker installed and have downloaded HeuDiConv as described in Lesson 1. -* Download and unzip the phantom dataset: `reproin_dicom.zip `_ generated here at the University of Arizona on our Siemens Skyra 3T with Syngo MR VE11c software on 2018_02_08. -* You should see a new directory *REPROIN*. This is a simple reproin-compliant dataset without sessions. Derived dwi images (ADC, FA etc.) that the scanner produced were removed. -* Change directory to *REPROIN*. The directory structure should look like this:: - - REPROIN - ├── data - └── dicom - └── 001 - └── Patterson_Coben\ -\ 1 - ├── Localizers_4 - ├── anatT1w_acqMPRAGE_6 - ├── dwi_dirAP_9 - ├── fmap_acq4mm_7 - ├── fmap_acq4mm_8 - ├── fmap_dirPA_15 - └── func_taskrest_16 - -* From the *REPROIN* directory, run this Docker command:: - - docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -f reproin --bids -o /base/data --files /base/dicom/001 --minmeta -* ``--rm`` means Docker should cleanup after itself -* ``-it`` means Docker should run interactively -* ``-v ${PWD}:/base`` binds your current directory to ``/base`` inside the container. Alternatively, you could provide an **absolute path** to the *REPROIN* directory. -* ``nipy/heudiconv:latest`` identifies the Docker container to run (the latest version of heudiconv). -* ``-f reproin`` specifies the converter file to use -* ``-o /base/data/`` specifies the output directory *data*. If the output directory does not exist, it will be created. -* ``--files /base/dicom/001`` identifies the path to the DICOM files. -* ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so minmeta provides a layer of protection against such corruption. - -That's it. Below we'll unpack what happened. - -Output Directory Structure -=============================== - -*Reproin* produces a hierarchy of BIDS directories like this:: - - data - └── Patterson - └── Coben - ├── sourcedata - │   └── sub-001 - │   ├── anat - │   ├── dwi - │   ├── fmap - │   └── func - └── sub-001 - ├── anat - ├── dwi - ├── fmap - └── func - - -* The dataset is nested under two levels in the output directory: *Region* (Patterson) and *Exam* (Coben). *Tree* is reserved for other purposes at the UA research scanner. -* Although the Program *Patient* is not visible in the output hierarchy, it is important. If you have separate sessions, then each session should have its own Program name. -* **sourcedata** contains tarred gzipped (tgz) sets of DICOM images corresponding to each NIFTI image. -* **sub-001** contains the BIDS dataset. -* The hidden directory is generated: *REPROIN/data/Patterson/Coben/.heudiconv*. - -At the Scanner -==================== - -Here is this phantom dataset displayed in the scanner dot cockpit. The directory structure is defined at the top: *Patterson >> Coben >> Patient* - -* *Region* = *Patterson* -* *Exam* = *Coben* -* *Program* = *Patient* - -.. image:: /pictures/heudiconv_reproin_dot_cockpit.png - :alt: Dot Cockpit interface on Siemens scanner with reproin naming - - - -Reproin Scanner File Names -============================== - -* For both BIDS and *reproin*, names are composed of an ordered series of key-value pairs. Each key and its value are joined with a dash ``-`` (e.g., ``acq-MPRAGE``, ``dir-AP``). These key-value pairs are joined to other key-value pairs with underscores ``_``. The exception is the modality label, which is discussed more below. -* *Reproin* scanner sequence names are simplified relative to the final BIDS output and generally conform to this scheme (but consult the `reference `_ for additional options): ``sequence type-modality label`` _ ``session-session name`` _ ``task-task name`` _ ``acquisition-acquisition detail`` _ ``run-run number`` _ ``direction-direction label``:: - - | func-bold_ses-pre_task-faces_acq-1mm_run-01_dir-AP - -* Each sequence name begins with the seqtype key. The seqtype key is the modality and corresponds to the name of the BIDS directory where the sequence belongs, e.g., ``anat``, ``dwi``, ``fmap`` or ``func``. -* The seqtype key is optionally followed by a dash ``-`` and a modality label value (e.g., ``anat-scout`` or ``anat-T2W``). Often, the modality label is not needed because there is a predictable default for most seqtypes: -* For **anat** the default modality is ``T1W``. Thus a sequence named ``anat`` will have the same output BIDS files as a sequence named ``anat-T1w``: *sub-001_T1w.nii.gz*. -* For **fmap** the default modality is ``epi``. Thus ``fmap_dir-PA`` will have the same output as ``fmap-epi_dir-PA``: *sub-001_dir-PA_epi.nii.gz*. -* For **func** the default modality is ``bold``. Thus, ``func-bold_task-rest`` will have the same output as ``func_task-rest``: *sub-001_task-rest_bold.nii.gz*. -* *Reproin* gets the subject number from the DICOM metadata. -* If you have multiple sessions, the session name does not need to be included in every sequence name in the program (i.e., Program= *Patient* level mentioned above). Instead, the session can be added to a single sequence name, usually the scout (localizer) sequence e.g. ``anat-scout_ses-pre``, and *reproin* will propagate the session information to the other sequence names in the *Program*. Interestingly, *reproin* does not add the localizer to your BIDS output. -* When our scanner exports the DICOM sequences, all dashes are removed. But don't worry, *reproin* handles this just fine. -* In the UA phantom reproin data, the subject was named ``01``. Horos reports the subject number as ``01`` but exports the DICOMS into a directory ``001``. If the data are copied to an external drive at the scanner, then the subject number is reported as ``001_001`` and the images are ``*.IMA`` instead of ``*.dcm``. *Reproin* does not care, it handles all of this gracefully. Your output tree (excluding *sourcedata* and *.heudiconv*) should look like this:: - - . - |-- CHANGES - |-- README - |-- dataset_description.json - |-- participants.tsv - |-- sub-001 - | |-- anat - | | |-- sub-001_acq-MPRAGE_T1w.json - | | `-- sub-001_acq-MPRAGE_T1w.nii.gz - | |-- dwi - | | |-- sub-001_dir-AP_dwi.bval - | | |-- sub-001_dir-AP_dwi.bvec - | | |-- sub-001_dir-AP_dwi.json - | | `-- sub-001_dir-AP_dwi.nii.gz - | |-- fmap - | | |-- sub-001_acq-4mm_magnitude1.json - | | |-- sub-001_acq-4mm_magnitude1.nii.gz - | | |-- sub-001_acq-4mm_magnitude2.json - | | |-- sub-001_acq-4mm_magnitude2.nii.gz - | | |-- sub-001_acq-4mm_phasediff.json - | | |-- sub-001_acq-4mm_phasediff.nii.gz - | | |-- sub-001_dir-PA_epi.json - | | `-- sub-001_dir-PA_epi.nii.gz - | |-- func - | | |-- sub-001_task-rest_bold.json - | | |-- sub-001_task-rest_bold.nii.gz - | | `-- sub-001_task-rest_events.tsv - | `-- sub-001_scans.tsv - `-- task-rest_bold.json - -* Note that despite all the the different subject names (e.g., ``01``, ``001`` and ``001_001``), the subject is labeled ``sub-001``. - -External tutorials -================== - Luckily(?), we live in an era of plentiful information. Below are some links to other users' tutorials covering their experience with ``heudiconv``. diff --git a/docs/usage.rst b/docs/usage.rst deleted file mode 100644 index 8befdc99..00000000 --- a/docs/usage.rst +++ /dev/null @@ -1,107 +0,0 @@ -===== -Usage -===== - -``heudiconv`` processes DICOM files and converts the output into user defined -paths. - -CommandLine Arguments -====================== - -.. argparse:: - :ref: heudiconv.cli.run.get_parser - :prog: heudiconv - :nodefault: - :nodefaultconst: - - -Support -======= - -All bugs, concerns and enhancement requests for this software can be submitted here: -https://github.com/nipy/heudiconv/issues. - -If you have a problem or would like to ask a question about how to use ``heudiconv``, -please submit a question to `NeuroStars.org `_ with a ``heudiconv`` tag. -NeuroStars.org is a platform similar to StackOverflow but dedicated to neuroinformatics. - -All previous ``heudiconv`` questions are available here: -http://neurostars.org/tags/heudiconv/ - - -Batch jobs -========== - -``heudiconv`` can natively handle multi-subject, multi-session conversions -although it will do these conversions in a linear manner, i.e. one subject and one session at a time. -To speed up these conversions, multiple ``heudiconv`` -processes can be spawned concurrently, each converting a different subject and/or -session. - -The following example uses SLURM and Singularity to submit every subjects' -DICOMs as an independent ``heudiconv`` execution. - -The first script aggregates the DICOM directories and submits them to -``run_heudiconv.sh`` with SLURM as a job array. - -If using bids, the ``notop`` bids option suppresses creation of -top-level files in the bids directory (e.g., -``dataset_description.json``) to avoid possible race conditions. -These files may be generated later with ``populate_templates.sh`` -below (except for ``participants.tsv``, which must be created -manually). - -.. code:: shell - - #!/bin/bash - - set -eu - - # where the DICOMs are located - DCMROOT=/dicom/storage/voice - # where we want to output the data - OUTPUT=/converted/data/voice - - # find all DICOM directories that start with "voice" - DCMDIRS=(`find ${DCMROOT} -maxdepth 1 -name voice* -type d`) - - # submit to another script as a job array on SLURM - sbatch --array=0-`expr ${#DCMDIRS[@]} - 1` run_heudiconv.sh ${OUTPUT} ${DCMDIRS[@]} - - -The second script processes a DICOM directory with ``heudiconv`` using the built-in -`reproin` heuristic. - -.. code:: shell - - #!/bin/bash - set -eu - - OUTDIR=${1} - # receive all directories, and index them per job array - DCMDIRS=(${@:2}) - DCMDIR=${DCMDIRS[${SLURM_ARRAY_TASK_ID}]} - echo Submitted directory: ${DCMDIR} - - IMG="/singularity-images/heudiconv-latest-dev.sif" - CMD="singularity run -B ${DCMDIR}:/dicoms:ro -B ${OUTDIR}:/output -e ${IMG} --files /dicoms/ -o /output -f reproin -c dcm2niix -b notop --minmeta -l ." - - printf "Command:\n${CMD}\n" - ${CMD} - echo "Successful process" - -This script creates the top-level bids files (e.g., -``dataset_description.json``) - -.. code:: shell - - #!/bin/bash - set -eu - - OUTDIR=${1} - IMG="/singularity-images/heudiconv-latest-dev.sif" - CMD="singularity run -B ${OUTDIR}:/output -e ${IMG} --files /output -f reproin --command populate-templates" - - printf "Command:\n${CMD}\n" - ${CMD} - echo "Successful process" From 0f25b07ba90fee852a2af52f3c840d10cdd9be08 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Thu, 18 Jan 2024 12:26:07 -0500 Subject: [PATCH 25/82] codespell --- docs/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 31521996..da84df03 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -81,8 +81,8 @@ This section demonstrates how to use the heudiconv tool with `heuristic.py` to c # output (-o) will be placed in the directory labeled Nifti # The conversion file is in Nifti/code # dcm2niix is the engine that does the conversion - # --minmeta gaurantees that meta-information in the dcms does not get inserted into the JSON sidecar. - # This is good becuase the information is not needed but can overflow the JSON file causing some BIDS apps to crash. + # --minmeta guarantees that meta-information in the dcms does not get inserted into the JSON sidecar. + # This is good because the information is not needed but can overflow the JSON file causing some BIDS apps to crash. docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/{session}/*/*.dcm -o /base/Nifti/ -f /base/Nifti/code/${converter} -s ${subject} -ss ${session} -c dcm2niix -b --minmeta --overwrite From a104fa8f3bec71d1f3fec431b755449bce7c86b3 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 18 Jan 2024 12:58:22 -0500 Subject: [PATCH 26/82] Update Copyright years for the project --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 00e7481a..357e43b1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright [2014-2019] [Heudiconv developers] +Copyright [2014-2023] [HeuDiConv developers] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 8082b3601ff29d68afed1dcde87a66868fadb74d Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 18 Jan 2024 13:03:32 -0500 Subject: [PATCH 27/82] Add a note to LICENSE about borrowed content --- LICENSE | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/LICENSE b/LICENSE index 357e43b1..7d22fdfe 100644 --- a/LICENSE +++ b/LICENSE @@ -11,3 +11,10 @@ Copyright [2014-2023] [HeuDiConv developers] WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + + +Some parts of the codebase/documentation are borrowed from other sources: + +- HeuDiConv tutorial from https://bitbucket.org/dpat/neuroimaging_core_docs/src + + Copyright 2023 Dianne Patterson From d211a62922789c62a26613f2e67e2f6c76aa3dca Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Thu, 18 Jan 2024 14:34:47 -0500 Subject: [PATCH 28/82] Rename heuristic page title --- docs/heuristics.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/heuristics.rst b/docs/heuristics.rst index 6409f9eb..d0d8cd3e 100644 --- a/docs/heuristics.rst +++ b/docs/heuristics.rst @@ -1,6 +1,6 @@ -======================== -Heuristic File Reference -======================== +=============== +Heuristics File +=============== The heuristic file controls how information about the DICOMs is used to convert to a file system layout (e.g., BIDS). ``heudiconv`` includes some built-in From bd717decec9e6f2aaf031b9e23b4793c30727ed2 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Thu, 18 Jan 2024 14:57:13 -0500 Subject: [PATCH 29/82] Add links to original tutorials --- docs/custom-heuristic.rst | 2 ++ docs/quickstart.rst | 2 ++ docs/reproin.rst | 2 ++ 3 files changed, 6 insertions(+) diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst index 58575ad1..3654c5db 100644 --- a/docs/custom-heuristic.rst +++ b/docs/custom-heuristic.rst @@ -2,6 +2,8 @@ Custom Heuristics ========================= +This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#running-heudiconv-is-a-3-step-process + Running HeuDiConv is a 3-step process ************************************* diff --git a/docs/quickstart.rst b/docs/quickstart.rst index da84df03..b8320c8c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,6 +1,8 @@ Quickstart ========== +This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#lesson-1-running-heuristic-py + Heudiconv Hello World: Using `heuristic.py` .. TODO convert to a datalad dataset diff --git a/docs/reproin.rst b/docs/reproin.rst index fc20fab6..870d39f9 100644 --- a/docs/reproin.rst +++ b/docs/reproin.rst @@ -2,6 +2,8 @@ Reproin ================ +This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#lesson-3-reproin-py + If you don't want to modify a Python file as you did for *heuristic.py*, an alternative is to name your image sequences at the scanner using the *reproin* naming convention. Take some time getting the scanner protocol right, because it is the critical job for running *reproin*. Then a single Docker command converts your DICOMS to the BIDS data structure. There are more details about *reproin* in the .. TODO new link ref:`Links ` section above. From 5550debe818db03a242c27b2a590f930fef33ccb Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Mon, 22 Jan 2024 11:56:51 -0500 Subject: [PATCH 30/82] Minimize quickstart, format, and hand-test --- docs/quickstart.rst | 152 ++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 91 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b8320c8c..6b767017 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,23 +1,23 @@ Quickstart ========== +This section demonstrates how to use the heudiconv tool with a provided `heuristic.py` to convert DICOMS into the BIDS data structure. + This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#lesson-1-running-heuristic-py -Heudiconv Hello World: Using `heuristic.py` +Install Prerequisites +********************* + +`dcm2niix` is the engine that will do the DICOM conversion, so make sure it is installed (or use a heudiconv container TODO link):: + + pip install dcm2niix -.. TODO convert to a datalad dataset -.. TODO ``datalad install https://osf.io/mqgzh/`` -.. TODO delete any sequences of no interest prior to push, lets make the - example ds only contain what is needed for these tutorials -.. TODO create a docker/podman section explaining how to use containers - in lieu of `heudiconv`, change the tutorials to `heudiconv`, not - container. -.. TODO convert bash script to docs +Prepare Dataset +*************** -This section demonstrates how to use the heudiconv tool with `heuristic.py` to convert DICOMS into the BIDS data structure. +Download and unzip `sub-219_dicom.zip `_. -* Download and unzip `sub-219_dicom.zip `_. You will see a directory called MRIS. -* Under the MRIS directory, is the *dicom* subdirectory: Under the subject number *219* the session *itbs* is nested. Each dicom sequence folder is nested under the session. You can delete sequences folders if they are of no interest:: +We will be working from a directory called MRIS. Under the MRIS directory is the *dicom* subdirectory: Under the subject number *219* the session *itbs* is nested. Each dicom sequence folder is nested under the session:: dicom └── 219 @@ -32,96 +32,66 @@ This section demonstrates how to use the heudiconv tool with `heuristic.py` to c ├── field_mapping_20 ├── field_mapping_21 └── restingstate_18 + Nifti + └── code + └── heuristic1.py +Basic Conversion +**************** -* Pull the HeuDiConv Docker container to your machine:: +Next we will use heudiconv convert DICOMS into the BIDS data structure. +The example dataset includes an example heuristic file, `heuristic1.py`. +Typical use of heudiconv will require the creation +and/or editing of your heuristic file (TODO link), which we will cover +in the later tutorials (TODO link). - docker pull nipy/heudiconv + .. note:: Heudiconv requires you to run the command from the parent + directory of both the Dicom and Nifti directories, which is `MRIS` in + our case. -* From a BASH shell (no, zsh will not do), navigate to the MRIS directory and run the ``hdc_run.sh`` script for subject *219*, session *itbs*, like this:: +Run the following command:: - #!/bin/bash - - : <`_ in the BIDS specification:: - # This docker command assumes you are in in the bound (-v) base directory, e.g., the unzipped MRIS directory (PWD). - # dicom files are under dicom in a directory labeled with the subject number and session number (e.g. 219/itbs) - # output (-o) will be placed in the directory labeled Nifti - # The conversion file is in Nifti/code - # dcm2niix is the engine that does the conversion - # --minmeta guarantees that meta-information in the dcms does not get inserted into the JSON sidecar. - # This is good because the information is not needed but can overflow the JSON file causing some BIDS apps to crash. + CHANGES + README + dataset_description.json + participants.json + participants.tsv + task-rest_bold.json - docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/{session}/*/*.dcm -o /base/Nifti/ -f /base/Nifti/code/${converter} -s ${subject} -ss ${session} -c dcm2niix -b --minmeta --overwrite - - -.. TODO rm this command (note the args tho) - ./hdc_run.sh heuristic1.py 219 itbs - -* This should complete the conversion. After running, the *Nifti* directory will contain a bids-compliant subject directory:: - - - └── sub-219 - └── ses-itbs - ├── anat - ├── dwi - ├── fmap - └── func - -* The following required BIDS text files are also created in the Nifti directory. Details for filling in these skeleton text files can be found under `tabular files `_ in the BIDS specification:: - - CHANGES - README - dataset_description.json - participants.json - participants.tsv - task-rest_bold.json - -* Next, visit the `bids validator `_. -* Click `Choose File` and then select the *Nifti* directory. There should be no errors (though there are a couple of warnings). - - .. Note:: Your files are not uploaded to the BIDS validator, so there are no privacy concerns! -* Look at the directory structure and files that were generated. -* When you are ready, remove everything that was just created:: - - rm -rf Nifti/sub-* Nifti/.heudiconv Nifti/code/__pycache__ Nifti/*.json Nifti/*.tsv Nifti/README Nifti/CHANGE - -* Now you know what the results should look like. -* In the following sections, you will build *heuristic.py* yourself so you can test different options and understand how to work with your own data. +Validation +********** +Ensure that everything is according to spec by using `bids validator `_ +Click `Choose File` and then select the *Nifti* directory. There should be no errors (though there are a couple of warnings). + + .. Note:: Your files are not uploaded to the BIDS validator, so there are no privacy concerns! + +Next +**** +In the following sections, you will modify *heuristic.py* yourself so you can test different options and understand how to work with your own data. From f0251cc38ac32487368f8ca20dc2628204468ddd Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Tue, 23 Jan 2024 10:14:48 -0500 Subject: [PATCH 31/82] First pass custom heuristics tutorial --- docs/commandline.rst | 8 +++++ docs/custom-heuristic.rst | 70 ++++++++++++++++++++++----------------- docs/quickstart.rst | 4 +++ 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/docs/commandline.rst b/docs/commandline.rst index c44ff7b9..ea5ab299 100644 --- a/docs/commandline.rst +++ b/docs/commandline.rst @@ -10,3 +10,11 @@ paths. :prog: heudiconv :nodefault: :nodefaultconst: + +TODO +==== + +List provided heuristics + + e.g., banda-bids.py, bids_with_ses.py, cmrr_heuristic.py, example.py, multires_7Tbold.py, reproin.py, studyforrest_phase2.py, test_reproin.py, uc_bids.py. *heuristic.py* is a good default though. + diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst index 3654c5db..0ce6c1c6 100644 --- a/docs/custom-heuristic.rst +++ b/docs/custom-heuristic.rst @@ -2,66 +2,60 @@ Custom Heuristics ========================= -This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#running-heudiconv-is-a-3-step-process +In this tutorial we will: +1. :ref:`Step1 ` Generate a heuristic (translation) file skeleton and some associated descriptor text files. +2. :ref:`Step2 ` Modify the *heuristic.py* to specify BIDS output names and directories, and the input DICOM characteristics. +3. :ref:`Step3 ` Call HeuDiConv to run on more subjects and sessions. -Running HeuDiConv is a 3-step process -************************************* +This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#running-heudiconv-is-a-3-step-process -* :ref:`Step1 ` By passing some path information and flags to HeuDiConv, you generate a heuristic (translation) file skeleton and some associated descriptor text files. These all get placed in a hidden directory, *.heudiconv* under the *Nifti* directory. -* :ref:`Step2 ` Copy *MRIS/Nifti/.heudiconv/heuristic.py* to *MRIS/Nifti/code/heuristic.py*. You will modify the copied *heuristic.py* to specify BIDS output names and directories, and the input DICOM characteristics. Available input DICOM characteristics are listed in *MRIS/Nifti/.heudiconv/dicominfo.tsv*. -* :ref:`Step3 ` Having revised *MRIS/Nifti/code/heuristic.py*, you can now call HeuDiConv to run on more subjects and sessions. Each time you run it, additional subdirectories are created under *.heudiconv* that record the details of each subject (and session) conversion. Detailed provenance information is retained in the *.heudiconv* hidden directory. You can rename your heuristic file, which may be useful if you have multiple heuristic files for the same dataset. +Prerequisites +************** -TIPS -====== +1. :ref:`Prepare the dataset ` used in the quickstart. +2. Ensure :ref:`dcm2niix ` is installed. -* **Name Directories as you wish**: You can name the project directory (e.g., **MRIS**) and the output directory (e.g., **Nifti**) as you wish (just don't put spaces in the names!). -* **Age and Sex Extraction**: Heudiconv will extract age and sex info from the DICOM header. If there is any reason to believe this information is wrong in the DICOM header (for example, it was made-up because no one knew how old the subject was, or it was considered a privacy concern), then you need to check the output. If you have Horos (or another DICOM editor), you can edit the values in the DICOM headers, otherwise you need to edit the values in the BIDS text file *participants.tsv*. -* **Separating Sessions**: If you have multiple sessions at the scanner, you should create an *Exam* folder for each session. This will help you to keep the data organized and *Exam* will be reported in the *study_description* in your *dicominfo.tsv*, so that you can use it as a criterion. -* **Don't manually combine DICOMS from different sessions**: If you combine multiple sessions in one subject DICOM folder, heudiconv will fail to run and will complain about ``conflicting study identifiers``. You can get around the problem by figuring out which DICOMs are from different sessions and separating them so you deal with one set at a time. This may mean you have to manually edit the BIDS output. - - * Why might you manually combine sessions you ask? Because you never intended to have multiple sessions, but the subject had to complete some scans the next day. Or, because the scanner had to be rebooted. -* **Don't assume all your subjects' dicoms have the same names or that the sequences were always run in the same order**: If you develop a *heuristic.py* on one subject, try it and carefully evaluate the results on your other subjects. This is especially true if you already collected the data before you started thinking about automating the output. Every time you run HeuDiConv with *heuristic.py*, a new *dicominfo.tsv* file is generated. Inspect this for differences in protocol names and series descriptions etc. -* **Decompressing DICOMS**: Decompress your data, heudiconv does not yet support compressed DICOM conversion. https://github.com/nipy/heudiconv/issues/287 -* **Create unique DICOM protocol names at the scanner** If you have the opportunity to influence the DICOM naming strategies, then try to ensure that there is a unique protocol name for every run. For example, if you repeat the fmri protocol three times, name the first one fmri_1, the next fmri_2, and the last fmri_3 (or any variation on this theme). This will make it much easier to uniquely specify the sequences when you convert and reduce your chance of errors. .. _heudiconv_step1: Step 1: Generate Skeleton ************************* -From the *MRIS* directory, run the following Docker command to process the ``dcm`` files that you downloaded and unzipped for this tutorial. The subject number is 219:: +.. note:: Step 1 only needs to be completed once correctly for each project. - docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/*/*/*.dcm -o /base/Nifti/ -f convertall -s 219 -c none +From the *MRIS* directory, run the following command to process the ``dcm`` files that you downloaded and unzipped for this tutorial.:: -.. Warning:: The above Docker command works in bash, but may not work in other shells, (e.g., zsh) + heudiconv --files dicom/219/*/*/*.dcm -o Nifti/ -f convertall -s 219 -c none -* ``--rm`` means Docker should cleanup after itself -* ``-it`` means Docker should run interactively -* ``-v ${PWD}:/base`` binds your current directory to ``/base`` inside the container. You could also provide an **absolute path** to the *MRIS* directory. -* ``nipy/heudiconv:latest`` identifies the Docker container to run (the latest version of heudiconv). -* ``-d /base/dicom/{subject}/*/*/*.dcm`` identifies the path to the DICOM files and specifies that they have the extension ``.dcm`` in this case. -* ``-o /base/Nifti/`` is the output in *Nifti*. If the output directory does not exist, it will be created. -* ``-f convertall`` This creates a *heuristic.py* template from an existing heuristic module. There are `other heuristic modules `_ , e.g., banda-bids.py, bids_with_ses.py, cmrr_heuristic.py, example.py, multires_7Tbold.py, reproin.py, studyforrest_phase2.py, test_reproin.py, uc_bids.py. *heuristic.py* is a good default though. -* ``-s 219`` specifies the subject number. ``219`` will replace {subject} in the ``-d`` argument when Docker actually runs. +* ``--files dicom/{subject}/*/*/*.dcm`` identifies the path to the DICOM files and specifies that they have the extension ``.dcm`` in this case. +* ``-o Nifti/`` is the output in *Nifti*. If the output directory does not exist, it will be created. +* ``-f convertall`` This creates a *heuristic.py* template from an existing heuristic module. There are `other heuristic modules `_ , but *convertall* is a good default. +* ``-s 219`` specifies the subject number. * ``-c none`` indicates you are not actually doing any conversion right now. -* Heudiconv generates a hidden directory *MRIS/Nifti/.heudiconv/219/info* and populates it with two files of interest: a skeleton *heuristic.py* and a *dicominfo.tsv* file. -* After Step 1, the *heuristic.py* template contains explanatory text for you to read. I have removed this from *heuristic1.py* to keep it short. + +You will now have a heudiconv skeleton in the `/.heudiconv` directory, in our case `Nifti/.heudiconv` The ``.heudiconv`` hidden directory ====================================== +Take a look at *MRIS/Nifti/.heudiconv/219/info/*, heudiconv has produced two files of interest: a skeleton *heuristic.py* and a *dicominfo.tsv* file. +The generated heuristic file template contains comments explaining usage. + +.. warning:: * **The Good** Every time you run conversion to create the BIDS NIfTI files and directories, a detailed record of what you did is recorded in the *.heudiconv* directory. This includes a copy of the *heuristic.py* module that you ran for each subject and session. Keep in mind that the hidden *.heudiconv* directory gets updated every time you run heudiconv. Together your *code* and *.heudiconv* directories provide valuable provenance information that should remain with your data. * **The Bad** If you rerun *heuristic.py* for some subject and session that has already been run, heudiconv quietly uses the conversion routines it stored in *.heudiconv*. This can be really annoying if you are troubleshooting *heuristic.py*. * **More Good** You can remove subject and session information from *.heudiconv* and run it fresh. In fact, you can entirely remove the *.heudiconv* directory and still run the *heuristic.py* you put in the *code* directory. -* Step 1 only needs to be completed once correctly for each project. .. _heudiconv_step2: Step 2: Modify Heuristic ************************ +.. TODO Lets remove heuristic1 and heuristic2 and create a 2nd example + dataset? or branch? + * You will modify three sections in *heuristic.py*. It is okay to rename this file, or to have several versions with different names. You just don't want to mix up your new *heuristic.py* and the finished *heuristic1.py* while you are learning. * Your goal is to produce a working *heuristic.py* that will arrange the output in a BIDS directory structure. Once you create a working *heuristic.py*, you can run it for different subjects and sessions (keep reading). * I provide three section labels (1, 1b and 2) to facilitate exposition here. Each of these sections should be manually modified by you for your project. @@ -287,6 +281,20 @@ Step 3: │   └── sub-219_ses-itbs_scans.tsv └── task-rest_bold.json +TIPS +====== + +* **Name Directories as you wish**: You can name the project directory (e.g., **MRIS**) and the output directory (e.g., **Nifti**) as you wish (just don't put spaces in the names!). +* **Age and Sex Extraction**: Heudiconv will extract age and sex info from the DICOM header. If there is any reason to believe this information is wrong in the DICOM header (for example, it was made-up because no one knew how old the subject was, or it was considered a privacy concern), then you need to check the output. If you have Horos (or another DICOM editor), you can edit the values in the DICOM headers, otherwise you need to edit the values in the BIDS text file *participants.tsv*. +* **Separating Sessions**: If you have multiple sessions at the scanner, you should create an *Exam* folder for each session. This will help you to keep the data organized and *Exam* will be reported in the *study_description* in your *dicominfo.tsv*, so that you can use it as a criterion. +* **Don't manually combine DICOMS from different sessions**: If you combine multiple sessions in one subject DICOM folder, heudiconv will fail to run and will complain about ``conflicting study identifiers``. You can get around the problem by figuring out which DICOMs are from different sessions and separating them so you deal with one set at a time. This may mean you have to manually edit the BIDS output. + + * Why might you manually combine sessions you ask? Because you never intended to have multiple sessions, but the subject had to complete some scans the next day. Or, because the scanner had to be rebooted. +* **Don't assume all your subjects' dicoms have the same names or that the sequences were always run in the same order**: If you develop a *heuristic.py* on one subject, try it and carefully evaluate the results on your other subjects. This is especially true if you already collected the data before you started thinking about automating the output. Every time you run HeuDiConv with *heuristic.py*, a new *dicominfo.tsv* file is generated. Inspect this for differences in protocol names and series descriptions etc. +* **Decompressing DICOMS**: Decompress your data, heudiconv does not yet support compressed DICOM conversion. https://github.com/nipy/heudiconv/issues/287 +* **Create unique DICOM protocol names at the scanner** If you have the opportunity to influence the DICOM naming strategies, then try to ensure that there is a unique protocol name for every run. For example, if you repeat the fmri protocol three times, name the first one fmri_1, the next fmri_2, and the last fmri_3 (or any variation on this theme). This will make it much easier to uniquely specify the sequences when you convert and reduce your chance of errors. + + Exploring Criteria ********************** diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 6b767017..8dfab0bd 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -5,6 +5,8 @@ This section demonstrates how to use the heudiconv tool with a provided `heurist This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#lesson-1-running-heuristic-py +.. _install_prerequisites: + Install Prerequisites ********************* @@ -12,6 +14,8 @@ Install Prerequisites pip install dcm2niix +.. _prepare_dataset: + Prepare Dataset *************** From 18d5e68bbdddd3dd3a46afed723305c75ed3c775 Mon Sep 17 00:00:00 2001 From: Basile Date: Tue, 23 Jan 2024 10:58:58 -0500 Subject: [PATCH 32/82] Update heudiconv/convert.py Co-authored-by: Yaroslav Halchenko --- heudiconv/convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 486439ef..9087bf13 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -1085,7 +1085,7 @@ def bvals_are_zero(bval_file: str) -> bool: # GE hyperband multi-echo containing diffusion info if isinstance(bval_file, TraitListObject): - return all([bvals_are_zero(bvf) for bvf in bval_file]) + return all(map(bvals_are_zero, bval_file)) with open(bval_file) as f: bvals = f.read().split() From bc4ce4297735e3e53cc18e3104bd80cd7f6f9e48 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Tue, 23 Jan 2024 12:07:29 -0500 Subject: [PATCH 33/82] cast to list first, add unit test for bvals_are_zero --- heudiconv/convert.py | 29 ++++++++++++----------------- heudiconv/tests/data/non_zeros.bval | 1 + heudiconv/tests/data/zeros.bval | 1 + heudiconv/tests/test_convert.py | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 heudiconv/tests/data/non_zeros.bval create mode 100644 heudiconv/tests/data/zeros.bval diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 9087bf13..5f233fc6 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -881,24 +881,19 @@ def save_converted_files( return [] if isdefined(res.outputs.bvecs) and isdefined(res.outputs.bvals): + bvals, bvecs = res.outputs.bvals, res.outputs.bvecs + bvals = list(bvals) if isinstance(bvals, TraitListObject) else bvals + bvecs = list(bvecs) if isinstance(bvecs, TraitListObject) else bvecs if prefix_dirname.endswith("dwi"): outname_bvecs, outname_bvals = prefix + ".bvec", prefix + ".bval" - safe_movefile(res.outputs.bvecs, outname_bvecs, overwrite) - safe_movefile(res.outputs.bvals, outname_bvals, overwrite) + safe_movefile(bvecs, outname_bvecs, overwrite) + safe_movefile(bvals, outname_bvals, overwrite) else: - if bvals_are_zero(res.outputs.bvals): - to_remove = [] - if isinstance(res.outputs.bvals, str): - to_remove = [res.outputs.bvals, res.outputs.bvecs] - else: - to_remove = list(res.outputs.bvals) + list(res.outputs.bvecs) + if bvals_are_zero(bvals): + to_remove = bvals + bvecs if isinstance(bvals, list) else [bvals, bvecs] for ftr in to_remove: os.remove(ftr) - lgr.debug( - "%s and %s were removed since not dwi", - res.outputs.bvecs, - res.outputs.bvals, - ) + lgr.debug("%s and %s were removed since not dwi", bvecs, bvals) else: lgr.warning( DW_IMAGE_IN_FMAP_FOLDER_WARNING.format(folder=prefix_dirname) @@ -907,8 +902,8 @@ def save_converted_files( ".bvec and .bval files will be generated. This is NOT BIDS compliant" ) outname_bvecs, outname_bvals = prefix + ".bvec", prefix + ".bval" - safe_movefile(res.outputs.bvecs, outname_bvecs, overwrite) - safe_movefile(res.outputs.bvals, outname_bvals, overwrite) + safe_movefile(bvecs, outname_bvecs, overwrite) + safe_movefile(bvals, outname_bvals, overwrite) if isinstance(res_files, list): res_files = sorted(res_files) @@ -1070,7 +1065,7 @@ def add_taskname_to_infofile(infofiles: str | list[str]) -> None: save_json(infofile, meta_info) -def bvals_are_zero(bval_file: str) -> bool: +def bvals_are_zero(bval_file: str | list) -> bool: """Checks if all entries in a bvals file are zero (or 5, for Siemens files). Parameters @@ -1084,7 +1079,7 @@ def bvals_are_zero(bval_file: str) -> bool: """ # GE hyperband multi-echo containing diffusion info - if isinstance(bval_file, TraitListObject): + if isinstance(bval_file, list): return all(map(bvals_are_zero, bval_file)) with open(bval_file) as f: diff --git a/heudiconv/tests/data/non_zeros.bval b/heudiconv/tests/data/non_zeros.bval new file mode 100644 index 00000000..8eda1d77 --- /dev/null +++ b/heudiconv/tests/data/non_zeros.bval @@ -0,0 +1 @@ +1000 0 1000 2000 diff --git a/heudiconv/tests/data/zeros.bval b/heudiconv/tests/data/zeros.bval new file mode 100644 index 00000000..99f4fdbe --- /dev/null +++ b/heudiconv/tests/data/zeros.bval @@ -0,0 +1 @@ + 0 0 0 0 diff --git a/heudiconv/tests/test_convert.py b/heudiconv/tests/test_convert.py index 200d9c41..ec63f4e2 100644 --- a/heudiconv/tests/test_convert.py +++ b/heudiconv/tests/test_convert.py @@ -14,6 +14,7 @@ import heudiconv.convert from heudiconv.convert import ( DW_IMAGE_IN_FMAP_FOLDER_WARNING, + bvals_are_zero, update_complex_name, update_multiecho_name, update_uncombined_name, @@ -287,3 +288,16 @@ def mock_populate_intended_for( else: # If there was no heuristic, make sure populate_intended_for was not called assert not output.out + + +def test_bvals_are_zero() -> None: + """Unit testing for heudiconv.convert.bvals_are_zero(), + which checks if non-dwi bvals are all zeros and can be removed + """ + zero_bvals = op.join(TESTS_DATA_PATH, "zeros.bval") + non_zero_bvals = op.join(TESTS_DATA_PATH, "non_zeros.bval") + + assert bvals_are_zero(zero_bvals) + assert not bvals_are_zero(non_zero_bvals) + assert bvals_are_zero([zero_bvals, zero_bvals]) + assert not bvals_are_zero([non_zero_bvals, zero_bvals]) From 98a1f291d4a2990bc2c653a6e26f1d2357336325 Mon Sep 17 00:00:00 2001 From: Christian Haselgrove Date: Wed, 31 Jan 2024 14:38:55 -0500 Subject: [PATCH 34/82] Reject "Missing images" in sensor-dicoms --- utils/sensor-dicoms | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/sensor-dicoms b/utils/sensor-dicoms index c47f8442..3df288f3 100755 --- a/utils/sensor-dicoms +++ b/utils/sensor-dicoms @@ -76,6 +76,9 @@ for dir in "$@"; do if grep "Error: Check sorted order: 4D dataset has" "$TEMP/stderr"; then failed=1 fi + if grep "Error: Missing images." "$TEMP/stderr"; then + failed=1 + fi if [ -n "$failed" ]; then if [ -n "$DRY_RUN" ]; then echo mv "$dir" "$MOVE_TO_DIR" From b2f2f101214c465e9332c5d2b2545ce41c821b67 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Tue, 23 Jan 2024 10:31:07 -0500 Subject: [PATCH 35/82] second naive pass of custom heuristic --- docs/custom-heuristic.rst | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst index 0ce6c1c6..967a8d38 100644 --- a/docs/custom-heuristic.rst +++ b/docs/custom-heuristic.rst @@ -2,7 +2,7 @@ Custom Heuristics ========================= -In this tutorial we will: +In this tutorial we go more in depth, creating our own *heuristic.py* and modifying it for our needs: 1. :ref:`Step1 ` Generate a heuristic (translation) file skeleton and some associated descriptor text files. 2. :ref:`Step2 ` Modify the *heuristic.py* to specify BIDS output names and directories, and the input DICOM characteristics. @@ -22,7 +22,8 @@ Prerequisites Step 1: Generate Skeleton ************************* -.. note:: Step 1 only needs to be completed once correctly for each project. +.. note:: Step 1 only needs to be completed once for each project. + If repeating this step, ensure that the .heudiconv directory is removed. From the *MRIS* directory, run the following command to process the ``dcm`` files that you downloaded and unzipped for this tutorial.:: @@ -56,8 +57,10 @@ Step 2: Modify Heuristic .. TODO Lets remove heuristic1 and heuristic2 and create a 2nd example dataset? or branch? -* You will modify three sections in *heuristic.py*. It is okay to rename this file, or to have several versions with different names. You just don't want to mix up your new *heuristic.py* and the finished *heuristic1.py* while you are learning. -* Your goal is to produce a working *heuristic.py* that will arrange the output in a BIDS directory structure. Once you create a working *heuristic.py*, you can run it for different subjects and sessions (keep reading). +We will modify the generated *heuristic.py* so heudiconv will arrange the output in a BIDS directory structure. + +It is okay to rename this file, or to have several versions with different names, just be sure to pass the intended filename with `-f`. See :doc:`heuristics` docs for more info. + * I provide three section labels (1, 1b and 2) to facilitate exposition here. Each of these sections should be manually modified by you for your project. Section 1 @@ -117,7 +120,7 @@ Section 1 * Define the output directories and file names according to the `BIDS specification `_ * Note the output names for the fieldmap images (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*, *sub-219_ses-itbs_magnitude1.nii.gz*, *sub-219_ses-itbs_magnitude2.nii.gz*, *sub-219_ses-itbs_phasediff.nii.gz*). * The reverse_phase encode dwi image (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*) is grouped with the fieldmaps because it is used to correct other images. - * Data that is not yet defined in the BIDS specification will cause the bids-validator to produce an error unless you include it in a + * Data that is not yet defined in the BIDS specification will cause the bids-validator to produce an error unless you include it in a .. TODO new link :ref:`.bidsignore ` file. @@ -219,22 +222,21 @@ Section 2 .. _heudiconv_step3: -Step 3: +Step 3: ******************* * You have now done all the hard work for your project. When you want to add a subject or session, you only need to run this third step for that subject or session (A record of each run is kept in .heudiconv for you):: - docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -d /base/dicom/{subject}/*/*.dcm -o /base/Nifti/ -f /base/Nifti/code/heuristic.py -s 219 -ss itbs -c dcm2niix -b --minmeta --overwrite - -.. Warning:: The above Docker command WORKS IN BASH, but may not work in other shells! For example, zsh is upset by the form ``{subject}`` but bash actually doesn't mind. + heudiconv --files dicom/{subject}/*/*.dcm -o Nifti/ -f Nifti/code/heuristic.py -s 219 -ss itbs -c dcm2niix -b --minmeta --overwrite -* The first time you run this step, several important text files are generated (e.g., CHANGES, dataset_description.json, participants.tsv, README etc.). On subsequent runs, information may be added (e.g., *participants.tsv* will be updated). Other files, like the *README* and *dataset_description.json* should be filled in manually after they are first generated. +* The first time you run this step, several important text files are generated (e.g., CHANGES, dataset_description.json, participants.tsv, README etc.). + On subsequent runs, information may be added (e.g., *participants.tsv* will be updated). + Other files, like the *README* and *dataset_description.json* should be updated manually. * This Docker command is slightly different from the previous Docker command you ran. - * ``-f /base/Nifti/code/heuristic.py`` now tells HeuDiConv to use your revised *heuristic.py* in the *code* directory. + * ``-f Nifti/code/heuristic.py`` now tells HeuDiConv to use your revised *heuristic.py* in the *code* directory. * In this case, we specify the subject we wish to process ``-s 219`` and the name of the session ``-ss itbs``. - - * We could specify multiple subjects like this: ``-s 219 220 -ss itbs`` + * We could specify multiple subjects like this: ``-s 219 220 -ss itbs`` * ``-c dcm2niix -b`` indicates that we want to use the dcm2niix converter with the -b flag (which creates BIDS). * ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so *minmeta* provides a layer of protection against such corruption. * ``--overwrite`` This is a peculiar option. Without it, I have found the second run of a sequence does not get generated. But with it, everything gets written again (even if it already exists). I don't know if this is my problem or the tool...but for now, I'm using ``--overwrite``. @@ -295,7 +297,6 @@ TIPS * **Create unique DICOM protocol names at the scanner** If you have the opportunity to influence the DICOM naming strategies, then try to ensure that there is a unique protocol name for every run. For example, if you repeat the fmri protocol three times, name the first one fmri_1, the next fmri_2, and the last fmri_3 (or any variation on this theme). This will make it much easier to uniquely specify the sequences when you convert and reduce your chance of errors. - Exploring Criteria ********************** From 40b388043f8347723b22ce8794db566244ee3863 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Wed, 31 Jan 2024 15:05:30 -0500 Subject: [PATCH 36/82] Cleanup quickstart and use install link --- docs/custom-heuristic.rst | 5 +++-- docs/installation.rst | 1 + docs/quickstart.rst | 14 +++----------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst index 967a8d38..b4d0e0e9 100644 --- a/docs/custom-heuristic.rst +++ b/docs/custom-heuristic.rst @@ -2,14 +2,15 @@ Custom Heuristics ========================= +This tutorial is based on `Dianne Patterson's University of Arizona tutorials `_ + + In this tutorial we go more in depth, creating our own *heuristic.py* and modifying it for our needs: 1. :ref:`Step1 ` Generate a heuristic (translation) file skeleton and some associated descriptor text files. 2. :ref:`Step2 ` Modify the *heuristic.py* to specify BIDS output names and directories, and the input DICOM characteristics. 3. :ref:`Step3 ` Call HeuDiConv to run on more subjects and sessions. -This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#running-heudiconv-is-a-3-step-process - Prerequisites ************** diff --git a/docs/installation.rst b/docs/installation.rst index 1b9599ee..ae4b7ec5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -4,6 +4,7 @@ Installation ``Heudiconv`` is packaged and available from many different sources. +.. _install_local: Local ===== diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 8dfab0bd..9ceef999 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,18 +1,10 @@ Quickstart ========== -This section demonstrates how to use the heudiconv tool with a provided `heuristic.py` to convert DICOMS into the BIDS data structure. +This tutorial is based on `Dianne Patterson's University of Arizona tutorials `_ -This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#lesson-1-running-heuristic-py - -.. _install_prerequisites: - -Install Prerequisites -********************* - -`dcm2niix` is the engine that will do the DICOM conversion, so make sure it is installed (or use a heudiconv container TODO link):: - - pip install dcm2niix +This guide assumes you have already :ref:`installed heudiconv and dcm2niix ` and +demonstrates how to use the heudiconv tool with a provided `heuristic.py` to convert DICOMS into the BIDS data structure. .. _prepare_dataset: From 5de5e71baf8c2dc0541f7863eb037344cbe464f1 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Wed, 31 Jan 2024 15:06:46 -0500 Subject: [PATCH 37/82] First pass Reproin --- docs/custom-heuristic.rst | 3 +- docs/reproin.rst | 89 +++++++++++++++++++++------------------ 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst index b4d0e0e9..cb5c1cb3 100644 --- a/docs/custom-heuristic.rst +++ b/docs/custom-heuristic.rst @@ -14,9 +14,8 @@ In this tutorial we go more in depth, creating our own *heuristic.py* and modify Prerequisites ************** +1. Ensure :ref:`heudiconv and dcm2niix ` is installed. 1. :ref:`Prepare the dataset ` used in the quickstart. -2. Ensure :ref:`dcm2niix ` is installed. - .. _heudiconv_step1: diff --git a/docs/reproin.rst b/docs/reproin.rst index 870d39f9..a925ee18 100644 --- a/docs/reproin.rst +++ b/docs/reproin.rst @@ -2,47 +2,55 @@ Reproin ================ -This tutorial is based on https://neuroimaging-core-docs.readthedocs.io/en/latest/pages/heudiconv.html#lesson-3-reproin-py - -If you don't want to modify a Python file as you did for *heuristic.py*, an alternative is to name your image sequences at the scanner using the *reproin* naming convention. Take some time getting the scanner protocol right, because it is the critical job for running *reproin*. Then a single Docker command converts your DICOMS to the BIDS data structure. There are more details about *reproin* in the -.. TODO new link ref:`Links ` section above. - -* You should already have Docker installed and have downloaded HeuDiConv as described in Lesson 1. -* Download and unzip the phantom dataset: `reproin_dicom.zip `_ generated here at the University of Arizona on our Siemens Skyra 3T with Syngo MR VE11c software on 2018_02_08. -* You should see a new directory *REPROIN*. This is a simple reproin-compliant dataset without sessions. Derived dwi images (ADC, FA etc.) that the scanner produced were removed. -* Change directory to *REPROIN*. The directory structure should look like this:: - - REPROIN - ├── data - └── dicom - └── 001 - └── Patterson_Coben\ -\ 1 - ├── Localizers_4 - ├── anatT1w_acqMPRAGE_6 - ├── dwi_dirAP_9 - ├── fmap_acq4mm_7 - ├── fmap_acq4mm_8 - ├── fmap_dirPA_15 - └── func_taskrest_16 - -* From the *REPROIN* directory, run this Docker command:: - - docker run --rm -it -v ${PWD}:/base nipy/heudiconv:latest -f reproin --bids -o /base/data --files /base/dicom/001 --minmeta -* ``--rm`` means Docker should cleanup after itself -* ``-it`` means Docker should run interactively -* ``-v ${PWD}:/base`` binds your current directory to ``/base`` inside the container. Alternatively, you could provide an **absolute path** to the *REPROIN* directory. -* ``nipy/heudiconv:latest`` identifies the Docker container to run (the latest version of heudiconv). +This tutorial is based on `Dianne Patterson's University of Arizona tutorials `_ + +`Reproin `_ is a setup for +automatic generation of sharable, version-controlled BIDS datasets from +MR scanners. + +If can control how your image sequences are named at the scanner you can +use the *reproin* naming convention. + +Get Example Dataset +------------------- + +This example uses a phantom dataset: `reproin_dicom.zip `_ generated by the University of Arizona on their Siemens Skyra 3T with Syngo MR VE11c software on 2018_02_08. + +The ``REPROIN`` directory is a simple reproin-compliant DICOM (.dcm) dataset without sessions. +(Derived dwi images (ADC, FA etc.) that the scanner produced have been removed.:: + + [user@local ~/reproin_dicom/REPROIN]$ tree -I "*.dcm" + + REPROIN + ├── data + └── dicom + └── 001 + └── Patterson_Coben\ -\ 1 + ├── Localizers_4 + ├── anatT1w_acqMPRAGE_6 + ├── dwi_dirAP_9 + ├── fmap_acq4mm_7 + ├── fmap_acq4mm_8 + ├── fmap_dirPA_15 + └── func_taskrest_16 + +Convert and organize +-------------------- + +From the ``REPROIN`` directory:: + + heudiconv -f reproin --bids -o data --files dicom/001 --minmeta + * ``-f reproin`` specifies the converter file to use -* ``-o /base/data/`` specifies the output directory *data*. If the output directory does not exist, it will be created. -* ``--files /base/dicom/001`` identifies the path to the DICOM files. +* ``-o data/`` specifies the output directory *data*. If the output directory does not exist, it will be created. +* ``--files dicom/001`` identifies the path to the DICOM files. * ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so minmeta provides a layer of protection against such corruption. -That's it. Below we'll unpack what happened. Output Directory Structure -=============================== +-------------------------- -*Reproin* produces a hierarchy of BIDS directories like this:: +Heudiconv's Reproin converter produces a hierarchy of BIDS directories:: data └── Patterson @@ -59,6 +67,7 @@ Output Directory Structure ├── fmap └── func +**TODO --- WHAT IS A REGION AND EXAM???** * The dataset is nested under two levels in the output directory: *Region* (Patterson) and *Exam* (Coben). *Tree* is reserved for other purposes at the UA research scanner. * Although the Program *Patient* is not visible in the output hierarchy, it is important. If you have separate sessions, then each session should have its own Program name. @@ -67,7 +76,9 @@ Output Directory Structure * The hidden directory is generated: *REPROIN/data/Patterson/Coben/.heudiconv*. At the Scanner -==================== +************** + +**TODO --- Is this generally useful or should be cut???** Here is this phantom dataset displayed in the scanner dot cockpit. The directory structure is defined at the top: *Patterson >> Coben >> Patient* @@ -78,10 +89,10 @@ Here is this phantom dataset displayed in the scanner dot cockpit. The director Reproin Scanner File Names -============================== +**************************** * For both BIDS and *reproin*, names are composed of an ordered series of key-value pairs. Each key and its value are joined with a dash ``-`` (e.g., ``acq-MPRAGE``, ``dir-AP``). These key-value pairs are joined to other key-value pairs with underscores ``_``. The exception is the modality label, which is discussed more below. -* *Reproin* scanner sequence names are simplified relative to the final BIDS output and generally conform to this scheme (but consult the `reference `_ for additional options): ``sequence type-modality label`` _ ``session-session name`` _ ``task-task name`` _ ``acquisition-acquisition detail`` _ ``run-run number`` _ ``direction-direction label``:: +* *Reproin* scanner sequence names are simplified relative to the final BIDS output and generally conform to this scheme (but consult the `reproin heuristics file `_ for additional options): ``sequence type-modality label`` _ ``session-session name`` _ ``task-task name`` _ ``acquisition-acquisition detail`` _ ``run-run number`` _ ``direction-direction label``:: | func-bold_ses-pre_task-faces_acq-1mm_run-01_dir-AP @@ -126,5 +137,3 @@ Reproin Scanner File Names `-- task-rest_bold.json * Note that despite all the the different subject names (e.g., ``01``, ``001`` and ``001_001``), the subject is labeled ``sub-001``. - - From 7353d34d682d5a2a442673fd932311a56320445d Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Wed, 31 Jan 2024 15:26:29 -0500 Subject: [PATCH 38/82] Fix rendering of prereqs --- docs/custom-heuristic.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst index cb5c1cb3..1fd60c56 100644 --- a/docs/custom-heuristic.rst +++ b/docs/custom-heuristic.rst @@ -11,11 +11,10 @@ In this tutorial we go more in depth, creating our own *heuristic.py* and modify 2. :ref:`Step2 ` Modify the *heuristic.py* to specify BIDS output names and directories, and the input DICOM characteristics. 3. :ref:`Step3 ` Call HeuDiConv to run on more subjects and sessions. -Prerequisites -************** +**Prerequisites**: 1. Ensure :ref:`heudiconv and dcm2niix ` is installed. -1. :ref:`Prepare the dataset ` used in the quickstart. +2. :ref:`Prepare the dataset ` used in the quickstart. .. _heudiconv_step1: From 050749b709ec1dc34885576644724f139bda1a51 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Wed, 31 Jan 2024 15:26:41 -0500 Subject: [PATCH 39/82] Fix links --- docs/quickstart.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 9ceef999..c43dee55 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -37,9 +37,8 @@ Basic Conversion Next we will use heudiconv convert DICOMS into the BIDS data structure. The example dataset includes an example heuristic file, `heuristic1.py`. -Typical use of heudiconv will require the creation -and/or editing of your heuristic file (TODO link), which we will cover -in the later tutorials (TODO link). +Typical use of heudiconv will require the creation and editing of your :doc:`heuristics file `, which we will cover +in a :doc:`later tutorial `. .. note:: Heudiconv requires you to run the command from the parent directory of both the Dicom and Nifti directories, which is `MRIS` in From a6486dfe48c21042f30e331cc48218929fe425b0 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Thu, 1 Feb 2024 13:18:43 -0500 Subject: [PATCH 40/82] Add container useage instructions --- docs/container.rst | 49 +++++++++++++++++++++++++++++++++++++++ docs/custom-heuristic.rst | 9 +++---- docs/index.rst | 1 + docs/installation.rst | 19 ++++++++------- 4 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 docs/container.rst diff --git a/docs/container.rst b/docs/container.rst new file mode 100644 index 00000000..740f60ba --- /dev/null +++ b/docs/container.rst @@ -0,0 +1,49 @@ +============================== +Using heudiconv in a Container +============================== + +If heudiconv is :ref:`installed via container `, you +can run the commands in the following format:: + + docker run nipy/heudiconv:latest [heudiconv options] + +So a user running via container would check the version with this command:: + + docker run nipy/heudiconv:latest --version + +Which is equivalent to the locally installed command:: + + heudiconv --version + +Bind mount +---------- + +Typically, users of heudiconv will be operating on data that is on their local machine. We can give heudiconv access to that data via a ``bind mount``, which is the ``-v`` syntax. + +Once common pattern is to share the working directory with ``-v $PWD:$PWD``, so heudiconv will behave as though it is installed on your system. However, you should be aware of how permissions work depending on your container toolset. + + +Docker Permissions +****************** + +When you run a container with docker without specifying a user, it will be run as root. +This isn't ideal if you are operating on data owned by your local user, so for ``Docker`` it is recommended to specify that the container will run as your user.:: + + docker run --user=$(id -u):$(id -g) -e "UID=$(id -u)" -e "GID=$(id -g)" --rm -t -v $PWD:$PWD nipy/heudiconv:latest --version + +Podman Permissions +****************** + +When running Podman without specifying a user, the container is run as root inside the container, but your user outside of the container. +This default behavior usually works for heudiconv users:: + + docker run -v $PWD:PWD nipy/heudiconv:latest --version + +Other Common Options +-------------------- + +We typically recommend users make use of the following flags to Docker and Podman + +* ``-it`` Interactive terminal +* ``--rm`` Remove the changes to the container when it completes + diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst index 1fd60c56..bfef79db 100644 --- a/docs/custom-heuristic.rst +++ b/docs/custom-heuristic.rst @@ -113,15 +113,12 @@ Section 1 * Once you use the variable ``{session}``: * Ensure that a session gets added to the **output path**, e.g., ``sub-{subject}/{session}/anat/`` AND * Session gets added to the **output filename**: ``sub-{subject}_{session}_T1w`` for every image in the session. + * Otherwise you will get `bids validator errors `_ -.. TODO new link * Otherwise you will get :ref:`bids-validator errors `. - - * Define the output directories and file names according to the `BIDS specification `_ + * Define the output directories and file names according to the `BIDS specification `_ * Note the output names for the fieldmap images (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*, *sub-219_ses-itbs_magnitude1.nii.gz*, *sub-219_ses-itbs_magnitude2.nii.gz*, *sub-219_ses-itbs_phasediff.nii.gz*). * The reverse_phase encode dwi image (e.g., *sub-219_ses-itbs_dir-PA_epi.nii.gz*) is grouped with the fieldmaps because it is used to correct other images. - * Data that is not yet defined in the BIDS specification will cause the bids-validator to produce an error unless you include it in a - -.. TODO new link :ref:`.bidsignore ` file. + * Data that is not yet defined in the BIDS specification will cause the bids-validator to produce an error unless you include it in a `.bidsignore `_ file. * **data** diff --git a/docs/index.rst b/docs/index.rst index 8e6a30f5..f17be802 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,5 +17,6 @@ Contents tutorials heuristics commandline + container api diff --git a/docs/installation.rst b/docs/installation.rst index ae4b7ec5..03b80766 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -22,23 +22,24 @@ subsequently it would be able to download and install dcm2niix binary. On Debian-based systems, we recommend using `NeuroDebian `_, which provides the `heudiconv package `_. +.. _install_container: -Docker -====== -If `Docker `_ is available on your system, you -can visit `our page on Docker Hub `_ -to view available releases. To pull the latest release, run:: +Containers +========== - $ docker pull nipy/heudiconv:latest +Our container image releases are availe on `our Docker Hub `_ -Note that when using HeuDiConv via ``docker run``, you might need to provide your user and group IDs so they map correspondingly -within the container, i.e.:: +If `Docker `_ is available on your system, you can pull the latest release:: - $ docker run --user=$(id -u):$(id -g) -e "UID=$(id -u)" -e "GID=$(id -g)" --rm -t -v $PWD:$PWD nipy/heudiconv:latest [OPTIONS TO FOLLOW] + $ docker pull nipy/heudiconv:latest Additionally, HeuDiConv is available through the Docker image at `repronim/reproin `_ provided by `ReproIn heuristic project `_, which develops the ``reproin`` heuristic. +To maintain provenance, it is recommended that you use the ``latest`` tag only when testing out heudiconv. +Otherwise, it is recommended that you use an explicit version and record that information alongside the produced data. + + Singularity =========== If `Singularity `_ is available on your system, From 688be6e7ff2538a99428f6d50c9d141dba02675e Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Thu, 1 Feb 2024 13:37:49 -0500 Subject: [PATCH 41/82] Document provided heuristics --- docs/commandline.rst | 8 -------- docs/heuristics.rst | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/commandline.rst b/docs/commandline.rst index ea5ab299..c44ff7b9 100644 --- a/docs/commandline.rst +++ b/docs/commandline.rst @@ -10,11 +10,3 @@ paths. :prog: heudiconv :nodefault: :nodefaultconst: - -TODO -==== - -List provided heuristics - - e.g., banda-bids.py, bids_with_ses.py, cmrr_heuristic.py, example.py, multires_7Tbold.py, reproin.py, studyforrest_phase2.py, test_reproin.py, uc_bids.py. *heuristic.py* is a good default though. - diff --git a/docs/heuristics.rst b/docs/heuristics.rst index d0d8cd3e..b2f13fc5 100644 --- a/docs/heuristics.rst +++ b/docs/heuristics.rst @@ -12,6 +12,14 @@ covered by the existing heuristics. This section will outline what makes up a heuristic file, and some useful functions available when making one. +Provided Heuristics +------------------- + +Running ``heudiconv`` without a heuristic file results in the generation of a skeleton for the user to customize to their needs. + +``heudiconv`` also provides more than 10 additional heuristics, which can be seen `here `_ +These heuristic files are documented in their code comments. + Components ========== From 79e0cc221e66ba1a46ee8294ea149d4c500cac6f Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Sat, 3 Feb 2024 12:02:11 -0500 Subject: [PATCH 42/82] Update LICENSE Co-authored-by: Yaroslav Halchenko --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 7d22fdfe..34648045 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright [2014-2023] [HeuDiConv developers] +Copyright [2014-2024] [HeuDiConv developers] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From b7be1c95e84fd81c0907fbc15c6c8588bdb8ee38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 05:53:58 +0000 Subject: [PATCH 43/82] [gh-actions](deps): Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef3a9c6d..4b59355e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,7 @@ jobs: run: coverage run `which pytest` -s -v heudiconv - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: fail_ci_if_error: false From c91ba587b9783a080154c3750950d22ac35f0735 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Fri, 9 Feb 2024 11:01:02 -0500 Subject: [PATCH 44/82] Apply suggestions from code review Co-authored-by: Yaroslav Halchenko --- docs/container.rst | 2 +- docs/reproin.rst | 34 ++++++++++++++-------------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/docs/container.rst b/docs/container.rst index 740f60ba..8ad96729 100644 --- a/docs/container.rst +++ b/docs/container.rst @@ -2,7 +2,7 @@ Using heudiconv in a Container ============================== -If heudiconv is :ref:`installed via container `, you +If heudiconv is :ref:`installed via a Docker container `, you can run the commands in the following format:: docker run nipy/heudiconv:latest [heudiconv options] diff --git a/docs/reproin.rst b/docs/reproin.rst index a925ee18..1bf8ed38 100644 --- a/docs/reproin.rst +++ b/docs/reproin.rst @@ -8,8 +8,9 @@ This tutorial is based on `Dianne Patterson's University of Arizona tutorials `_ for a brief HOWTO. Get Example Dataset ------------------- @@ -44,13 +45,13 @@ From the ``REPROIN`` directory:: * ``-f reproin`` specifies the converter file to use * ``-o data/`` specifies the output directory *data*. If the output directory does not exist, it will be created. * ``--files dicom/001`` identifies the path to the DICOM files. -* ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. fmriprep and mriqc are very sensitive to this information overload and will crash, so minmeta provides a layer of protection against such corruption. +* ``--minmeta`` ensures that only the minimum necessary amount of data gets added to the JSON file when created. On the off chance that there is a LOT of meta-information in the DICOM header, the JSON file will not get swamped by it. Rumors are that fMRIPrep and MRIQC might be sensitive to excess of metadata and might crash crash, so minmeta provides a layer of protection against such corruption. Output Directory Structure -------------------------- -Heudiconv's Reproin converter produces a hierarchy of BIDS directories:: +Heudiconv's Reproin converter produces a hierarchy of directories with the BIDS dataset (here - `Cohen`) at the bottom:: data └── Patterson @@ -67,31 +68,24 @@ Heudiconv's Reproin converter produces a hierarchy of BIDS directories:: ├── fmap └── func -**TODO --- WHAT IS A REGION AND EXAM???** +The specific value for the hierarchy can be specified to HeuDiConv via `--locator PATH` option. +If not, ReproIn heuristic bases it on the value of the DICOM "Study Description" field which is populated when user selects a specific *Exam* card located within some *Region* (see `ReproIn Walkthrough "Organization" `_). * The dataset is nested under two levels in the output directory: *Region* (Patterson) and *Exam* (Coben). *Tree* is reserved for other purposes at the UA research scanner. * Although the Program *Patient* is not visible in the output hierarchy, it is important. If you have separate sessions, then each session should have its own Program name. -* **sourcedata** contains tarred gzipped (tgz) sets of DICOM images corresponding to each NIFTI image. -* **sub-001** contains the BIDS dataset. -* The hidden directory is generated: *REPROIN/data/Patterson/Coben/.heudiconv*. - -At the Scanner -************** - -**TODO --- Is this generally useful or should be cut???** - -Here is this phantom dataset displayed in the scanner dot cockpit. The directory structure is defined at the top: *Patterson >> Coben >> Patient* - -* *Region* = *Patterson* -* *Exam* = *Coben* -* *Program* = *Patient* +* **sourcedata** contains tarred gzipped (`.tgz`) sets of DICOM images corresponding to NIfTI images. +* **sub-001/** contains a single subject data within this BIDS dataset. +* The hidden directory is generated: *REPROIN/data/Patterson/Coben/.heudiconv* to contain derived mapping data, which could potentially be inspected or adjusted/used for re-conversion. Reproin Scanner File Names **************************** -* For both BIDS and *reproin*, names are composed of an ordered series of key-value pairs. Each key and its value are joined with a dash ``-`` (e.g., ``acq-MPRAGE``, ``dir-AP``). These key-value pairs are joined to other key-value pairs with underscores ``_``. The exception is the modality label, which is discussed more below. +* For both BIDS and *reproin*, names are composed of an ordered series of key-value pairs, called [*entities*](https://github.com/bids-standard/bids-specification/blob/master/src/schema/objects/entities.yaml). + Each key and its value are joined with a dash ``-`` (e.g., ``acq-MPRAGE``, ``dir-AP``). + These key-value pairs are joined to other key-value pairs with underscores ``_``. + The exception is the modality label, which is discussed more below. * *Reproin* scanner sequence names are simplified relative to the final BIDS output and generally conform to this scheme (but consult the `reproin heuristics file `_ for additional options): ``sequence type-modality label`` _ ``session-session name`` _ ``task-task name`` _ ``acquisition-acquisition detail`` _ ``run-run number`` _ ``direction-direction label``:: | func-bold_ses-pre_task-faces_acq-1mm_run-01_dir-AP From e0f9ce2694f375cc43a60c61b403e8e831fcaa4d Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Fri, 9 Feb 2024 11:10:49 -0500 Subject: [PATCH 45/82] Update links to our datasets --- docs/quickstart.rst | 2 +- docs/reproin.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.rst b/docs/quickstart.rst index c43dee55..118c77dc 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -11,7 +11,7 @@ demonstrates how to use the heudiconv tool with a provided `heuristic.py` to con Prepare Dataset *************** -Download and unzip `sub-219_dicom.zip `_. +Download and unzip `sub-219_dicom.zip `_. We will be working from a directory called MRIS. Under the MRIS directory is the *dicom* subdirectory: Under the subject number *219* the session *itbs* is nested. Each dicom sequence folder is nested under the session:: diff --git a/docs/reproin.rst b/docs/reproin.rst index 1bf8ed38..871d8e4f 100644 --- a/docs/reproin.rst +++ b/docs/reproin.rst @@ -15,7 +15,7 @@ That will be a topic for another tutorial but meanwhile you can checkout `reproi Get Example Dataset ------------------- -This example uses a phantom dataset: `reproin_dicom.zip `_ generated by the University of Arizona on their Siemens Skyra 3T with Syngo MR VE11c software on 2018_02_08. +This example uses a phantom dataset: `reproin_dicom.zip `_ generated by the University of Arizona on their Siemens Skyra 3T with Syngo MR VE11c software on 2018_02_08. The ``REPROIN`` directory is a simple reproin-compliant DICOM (.dcm) dataset without sessions. (Derived dwi images (ADC, FA etc.) that the scanner produced have been removed.:: From 9530dd72bcd6aa683e282f9527f97ddd01fb54a8 Mon Sep 17 00:00:00 2001 From: Austin Macdonald Date: Thu, 15 Feb 2024 10:52:07 -0500 Subject: [PATCH 46/82] Document how to release and add changelog entries Fixes #732 --- CONTRIBUTING.rst | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 772b17f3..d22f875f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -51,11 +51,33 @@ If you are unsure what that means, here is a set-up workflow you may wish to fol git push -u origin topic_of_your_contribution - (If any of the above seems overwhelming, you can look up the `Git documentation `_ on the web.) +Releases and Changelog +---------------------- + +Heudiconv uses the `auto `_ tool to generate the changelog and automatically release the project. + +`auto` is used in the heudiconv github actions, which monitors the labels on the pull request. +Heudiconv automation can add entries to the changelog, cut releases, and +push new images to `dockerhub `_. + +The following pull request labels are respected: + + * major: Increment the major version when merged + * minot: Increment the minot version when merged + * patch: Increment the patch version when merged + * skip-release: Preserve the current version when merged + * release: Create a release when this pr is merged + * internal: Changes only affect the internal API + * documentation: Changes only affect the documentation + * tests: Add or improve existing tests + * dependencies: Update one or more dependencies version + * performance: Improve performance of an existing feature + + Development environment ----------------------- From 709b104a9e1f43145d468e5f10e475d72e7955f3 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 15 Feb 2024 14:59:35 -0500 Subject: [PATCH 47/82] Consistent casing for HeuDiConv and GitHub project names --- CONTRIBUTING.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d22f875f..850bae79 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -58,10 +58,10 @@ If you are unsure what that means, here is a set-up workflow you may wish to fol Releases and Changelog ---------------------- -Heudiconv uses the `auto `_ tool to generate the changelog and automatically release the project. +HeuDiConv uses the `auto `_ tool to generate the changelog and automatically release the project. -`auto` is used in the heudiconv github actions, which monitors the labels on the pull request. -Heudiconv automation can add entries to the changelog, cut releases, and +`auto` is used in the HeuDiConv GitHub actions, which monitors the labels on the pull request. +HeuDiConv automation can add entries to the changelog, cut releases, and push new images to `dockerhub `_. The following pull request labels are respected: From 6ba6cbad1891c635fdf504f6adc02ff00b52f501 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Tue, 20 Feb 2024 13:50:27 -0500 Subject: [PATCH 48/82] mark sensitive only files added in the commit. metadata add rather than init to fix reruns issue --- heudiconv/external/dlad.py | 30 ++++++++++++++++++--------- heudiconv/external/tests/test_dlad.py | 20 ++++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/heudiconv/external/dlad.py b/heudiconv/external/dlad.py index 2d65c2b2..5e1b305a 100644 --- a/heudiconv/external/dlad.py +++ b/heudiconv/external/dlad.py @@ -153,16 +153,16 @@ def add_to_datalad( # annex_add_opts=['--include-dotfiles'] ) - # TODO: filter for only changed files? # Provide metadata for sensitive information - mark_sensitive(ds, "sourcedata") - mark_sensitive(ds, "*_scans.tsv") # top level - mark_sensitive(ds, "*/*_scans.tsv") # within subj - mark_sensitive(ds, "*/*/*_scans.tsv") # within sess/subj - mark_sensitive(ds, "*/anat") # within subj - mark_sensitive(ds, "*/*/anat") # within ses/subj + last_commit = "HEAD" + mark_sensitive(ds, "sourcedata", last_commit) + mark_sensitive(ds, "*_scans.tsv", last_commit) # top level + mark_sensitive(ds, "*/*_scans.tsv", last_commit) # within subj + mark_sensitive(ds, "*/*/*_scans.tsv", last_commit) # within sess/subj + mark_sensitive(ds, "*/anat", last_commit) # within subj + mark_sensitive(ds, "*/*/anat", last_commit) # within ses/subj if dsh_path: - mark_sensitive(ds, ".heudiconv") # entire .heudiconv! + mark_sensitive(ds, ".heudiconv", last_commit) # entire .heudiconv! superds.save(path=ds.path, message=msg, recursive=True) assert not ds.repo.dirty @@ -178,7 +178,7 @@ def add_to_datalad( """ -def mark_sensitive(ds: Dataset, path_glob: str) -> None: +def mark_sensitive(ds: Dataset, path_glob: str, commit: str = None) -> None: """ Parameters @@ -186,18 +186,28 @@ def mark_sensitive(ds: Dataset, path_glob: str) -> None: ds : Dataset to operate on path_glob : str glob of the paths within dataset to work on + commit : str + commit which files to mark Returns ------- None """ paths = glob(op.join(ds.path, path_glob)) + if commit: + paths_in_commit = [ + op.join(ds.path, nf) + for nf in ds.repo.call_git( + ["show", "--name-only", commit, "--format=oneline"] + ).split("\n")[1:] + ] + paths = [p for p in paths if p in paths_in_commit] if not paths: return lgr.debug("Marking %d files with distribution-restrictions field", len(paths)) # set_metadata can be a bloody generator res = ds.repo.set_metadata( - paths, init=dict([("distribution-restrictions", "sensitive")]), recursive=True + paths, add=dict([("distribution-restrictions", "sensitive")]), recursive=True ) if inspect.isgenerator(res): res = list(res) diff --git a/heudiconv/external/tests/test_dlad.py b/heudiconv/external/tests/test_dlad.py index 6518d648..5900311a 100644 --- a/heudiconv/external/tests/test_dlad.py +++ b/heudiconv/external/tests/test_dlad.py @@ -28,3 +28,23 @@ def test_mark_sensitive(tmp_path: Path) -> None: # g2 since the same content assert not all_meta.pop("g1", None) # nothing or empty record assert all_meta == {"f1": target_rec, "f2": target_rec, "g2": target_rec} + + +def test_mark_sensitive_last_commit(tmp_path: Path) -> None: + ds = dl.Dataset(tmp_path).create(force=True) + create_tree( + str(tmp_path), + { + "f1": "d1", + "f2": "d2", + "g1": "d3", + "g2": "d1", + }, + ) + ds.save(".") + mark_sensitive(ds, "f*", "HEAD") + all_meta = dict(ds.repo.get_metadata(".")) + target_rec = {"distribution-restrictions": ["sensitive"]} + # g2 since the same content + assert not all_meta.pop("g1", None) # nothing or empty record + assert all_meta == {"f1": target_rec, "f2": target_rec, "g2": target_rec} From 2844d5413d5089126de66fbf2e05b9427c3715b2 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Wed, 21 Feb 2024 10:19:37 -0500 Subject: [PATCH 49/82] filter mark_sensitive based on save output --- heudiconv/external/dlad.py | 38 +++++++++++++-------------- heudiconv/external/tests/test_dlad.py | 7 ++--- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/heudiconv/external/dlad.py b/heudiconv/external/dlad.py index 5e1b305a..5ea57aa6 100644 --- a/heudiconv/external/dlad.py +++ b/heudiconv/external/dlad.py @@ -146,23 +146,27 @@ def add_to_datalad( message="Added gitattributes to place all .heudiconv content" " under annex", ) - ds.save( + save_res = ds.save( ".", recursive=True # not in effect! ? # annex_add_opts=['--include-dotfiles'] ) + annexed_files = [sr["path"] for sr in save_res if sr["key"]] # Provide metadata for sensitive information - last_commit = "HEAD" - mark_sensitive(ds, "sourcedata", last_commit) - mark_sensitive(ds, "*_scans.tsv", last_commit) # top level - mark_sensitive(ds, "*/*_scans.tsv", last_commit) # within subj - mark_sensitive(ds, "*/*/*_scans.tsv", last_commit) # within sess/subj - mark_sensitive(ds, "*/anat", last_commit) # within subj - mark_sensitive(ds, "*/*/anat", last_commit) # within ses/subj + sensitive_patterns = [ + "sourcedata", + "*_scans.tsv", # top level + "*/*_scans.tsv", # within subj + "*/*/*_scans.tsv", # within sess/subj + "*/anat", # within subj + "*/*/anat", # within ses/subj + ] + for sp in sensitive_patterns: + mark_sensitive(ds, sp, annexed_files) if dsh_path: - mark_sensitive(ds, ".heudiconv", last_commit) # entire .heudiconv! + mark_sensitive(ds, ".heudiconv") # entire .heudiconv! superds.save(path=ds.path, message=msg, recursive=True) assert not ds.repo.dirty @@ -178,7 +182,7 @@ def add_to_datalad( """ -def mark_sensitive(ds: Dataset, path_glob: str, commit: str = None) -> None: +def mark_sensitive(ds: Dataset, path_glob: str, files: list[str] = None) -> None: """ Parameters @@ -186,22 +190,16 @@ def mark_sensitive(ds: Dataset, path_glob: str, commit: str = None) -> None: ds : Dataset to operate on path_glob : str glob of the paths within dataset to work on - commit : str - commit which files to mark + files : list[str] + subset of files to mark Returns ------- None """ paths = glob(op.join(ds.path, path_glob)) - if commit: - paths_in_commit = [ - op.join(ds.path, nf) - for nf in ds.repo.call_git( - ["show", "--name-only", commit, "--format=oneline"] - ).split("\n")[1:] - ] - paths = [p for p in paths if p in paths_in_commit] + if files: + paths = [p for p in paths if p in files] if not paths: return lgr.debug("Marking %d files with distribution-restrictions field", len(paths)) diff --git a/heudiconv/external/tests/test_dlad.py b/heudiconv/external/tests/test_dlad.py index 5900311a..325744a7 100644 --- a/heudiconv/external/tests/test_dlad.py +++ b/heudiconv/external/tests/test_dlad.py @@ -30,7 +30,7 @@ def test_mark_sensitive(tmp_path: Path) -> None: assert all_meta == {"f1": target_rec, "f2": target_rec, "g2": target_rec} -def test_mark_sensitive_last_commit(tmp_path: Path) -> None: +def test_mark_sensitive_subset(tmp_path: Path) -> None: ds = dl.Dataset(tmp_path).create(force=True) create_tree( str(tmp_path), @@ -42,9 +42,10 @@ def test_mark_sensitive_last_commit(tmp_path: Path) -> None: }, ) ds.save(".") - mark_sensitive(ds, "f*", "HEAD") + mark_sensitive(ds, "f*", [str(tmp_path / "f1")]) all_meta = dict(ds.repo.get_metadata(".")) target_rec = {"distribution-restrictions": ["sensitive"]} # g2 since the same content assert not all_meta.pop("g1", None) # nothing or empty record - assert all_meta == {"f1": target_rec, "f2": target_rec, "g2": target_rec} + assert not all_meta.pop("f2", None) # nothing or empty record + assert all_meta == {"f1": target_rec, "g2": target_rec} From 328aae8a60fc3f11d0e78685b733f19d38dcee3c Mon Sep 17 00:00:00 2001 From: bpinsard Date: Wed, 21 Feb 2024 10:23:35 -0500 Subject: [PATCH 50/82] fix typing --- heudiconv/external/dlad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heudiconv/external/dlad.py b/heudiconv/external/dlad.py index 5ea57aa6..660f02f0 100644 --- a/heudiconv/external/dlad.py +++ b/heudiconv/external/dlad.py @@ -182,7 +182,7 @@ def add_to_datalad( """ -def mark_sensitive(ds: Dataset, path_glob: str, files: list[str] = None) -> None: +def mark_sensitive(ds: Dataset, path_glob: str, files: list[str] | None = None) -> None: """ Parameters From 78db99eb8b5c2cb5436475a57326e990aebcab38 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Wed, 21 Feb 2024 10:32:43 -0500 Subject: [PATCH 51/82] fix: get key properly for git-files --- heudiconv/external/dlad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heudiconv/external/dlad.py b/heudiconv/external/dlad.py index 660f02f0..dd9daeae 100644 --- a/heudiconv/external/dlad.py +++ b/heudiconv/external/dlad.py @@ -152,7 +152,7 @@ def add_to_datalad( # not in effect! ? # annex_add_opts=['--include-dotfiles'] ) - annexed_files = [sr["path"] for sr in save_res if sr["key"]] + annexed_files = [sr["path"] for sr in save_res if sr.get("key", None)] # Provide metadata for sensitive information sensitive_patterns = [ From bdbd8cbc977d43b7101ebc0f6d2cd804f9ba39a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Feb 2024 00:20:15 +0000 Subject: [PATCH 52/82] [gh-actions](deps): Bump actions/setup-python from 4 to 5 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/typing.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index aeaf4f42..1d270e42 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b720c2d6..2fcc8532 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,7 +34,7 @@ jobs: - name: Set up Python if: steps.auto-version.outputs.version != '' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '^3.8' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ef3a9c6d..69d9bda7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/typing.yml b/.github/workflows/typing.yml index 82a24486..c01c56cb 100644 --- a/.github/workflows/typing.yml +++ b/.github/workflows/typing.yml @@ -14,7 +14,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' From fd1f838a42607dbc3fc985e8f7750589a664daa9 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 23 Feb 2024 20:00:14 -0500 Subject: [PATCH 53/82] Adjust wording on heuristics page -- do not claim creating some skeleton --- docs/heuristics.rst | 21 ++++++++++++--------- docs/tutorials.rst | 5 +++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/heuristics.rst b/docs/heuristics.rst index b2f13fc5..8c9a68d1 100644 --- a/docs/heuristics.rst +++ b/docs/heuristics.rst @@ -3,22 +3,25 @@ Heuristics File =============== The heuristic file controls how information about the DICOMs is used to convert -to a file system layout (e.g., BIDS). ``heudiconv`` includes some built-in -heuristics, including `ReproIn `_ -(which is great to adopt if you will be starting your data collection!). - -However, there is a large variety of data out there, and not all DICOMs will be -covered by the existing heuristics. This section will outline what makes up a -heuristic file, and some useful functions available when making one. +to a file system layout (e.g., BIDS). Provided Heuristics ------------------- -Running ``heudiconv`` without a heuristic file results in the generation of a skeleton for the user to customize to their needs. +``heudiconv`` provides over 10 pre-created heuristics, which can be seen `here `_ . -``heudiconv`` also provides more than 10 additional heuristics, which can be seen `here `_ These heuristic files are documented in their code comments. +Some of them, like `convertall `_ or `ReproIn `__ could be immediately reused and represent two ends of the spectrum in heuristics: + +- ``convertall`` is very simple and does not automate anything -- it is for a user to modify filenames in the prepared conversion table, and then rerun with ``-c dcm2niix``. +- ``reproin`` can be used fully automated, if original sequences were named according to its ReproIn convention. + +Discover more on their user in the :ref:`Tutorials` section. + +However, there is a large variety of data out there, and not all DICOMs +will be covered by the existing heuristics. This section will outline what +makes up a heuristic file, and some useful functions available when making one. Components ========== diff --git a/docs/tutorials.rst b/docs/tutorials.rst index d8c3f923..516d7d9a 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -1,7 +1,8 @@ +.. _Tutorials: -================== +========= Tutorials -================== +========= .. toctree:: From 1aa7774e20871a777e0958cc6db6014e49181cac Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 23 Feb 2024 20:02:35 -0500 Subject: [PATCH 54/82] Provide CODECOV_TOKEN --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b59355e..4d361407 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,5 +56,6 @@ jobs: uses: codecov/codecov-action@v4 with: fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} # vim:set et sts=2: From f99594d9e91f3b6843195b783f23a0a6a566e289 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 26 Feb 2024 09:18:36 -0500 Subject: [PATCH 55/82] Show auto version -v (to be able to debug) and then take last line for the actual version boost --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2fcc8532..a6b68469 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,9 @@ jobs: - name: Check whether a release is due id: auto-version run: | - version="$(~/auto version)" + # to be able to debug if something goes wrong + auto version -v | tee /tmp/auto-version + version="$(tail -n 1 /tmp/auto-version)" echo "version=$version" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c41a49b5049fca5a3bec0d241434dc3db116731e Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 26 Feb 2024 09:20:11 -0500 Subject: [PATCH 56/82] Fix the incorrect label on invocation of auto version --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a6b68469..15d9c233 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: wget -O- https://github.com/intuit/auto/releases/download/v10.16.1/auto-linux.gz | gunzip > ~/auto chmod a+x ~/auto - - name: Check whether a release is due + - name: Query 'auto' on type of the release id: auto-version run: | # to be able to debug if something goes wrong From e9fe9605c24e17be4573e36eb13cd67df3699167 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 26 Feb 2024 10:38:16 -0500 Subject: [PATCH 57/82] set -o pipefail + do not rely on bump being the last line -- parse from debug msg --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15d9c233..66a2e29b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,8 +28,9 @@ jobs: id: auto-version run: | # to be able to debug if something goes wrong + set -o pipefail auto version -v | tee /tmp/auto-version - version="$(tail -n 1 /tmp/auto-version)" + version=$(sed -ne '/Calculated SEMVER bump:/s,.*: *,,p' < /tmp/auto-version) echo "version=$version" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d26e901e8b491b4c472dcbc09b560f6071af8588 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 26 Feb 2024 12:54:25 -0500 Subject: [PATCH 58/82] Be more verbose while invoking auto version Co-authored-by: John T. Wodder II --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 66a2e29b..97a59f3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: run: | # to be able to debug if something goes wrong set -o pipefail - auto version -v | tee /tmp/auto-version + auto version -vv | tee /tmp/auto-version version=$(sed -ne '/Calculated SEMVER bump:/s,.*: *,,p' < /tmp/auto-version) echo "version=$version" >> "$GITHUB_OUTPUT" env: From 17aad35a5d2cfe487fd24e5685e82a2d56525c26 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 26 Feb 2024 12:54:51 -0500 Subject: [PATCH 59/82] Do not bother with < + quote just in case Co-authored-by: John T. Wodder II --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 97a59f3f..cb3af600 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: # to be able to debug if something goes wrong set -o pipefail auto version -vv | tee /tmp/auto-version - version=$(sed -ne '/Calculated SEMVER bump:/s,.*: *,,p' < /tmp/auto-version) + version="$(sed -ne '/Calculated SEMVER bump:/s,.*: *,,p' /tmp/auto-version)" echo "version=$version" >> "$GITHUB_OUTPUT" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d6ed9b0a6dce14a8a3c0472e517fe4d238bc0b98 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 26 Feb 2024 16:01:18 -0500 Subject: [PATCH 60/82] Fix - auto is in ~/, not in the PATH --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb3af600..9d0d7881 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: run: | # to be able to debug if something goes wrong set -o pipefail - auto version -vv | tee /tmp/auto-version + ~/auto version -vv | tee /tmp/auto-version version="$(sed -ne '/Calculated SEMVER bump:/s,.*: *,,p' /tmp/auto-version)" echo "version=$version" >> "$GITHUB_OUTPUT" env: From 5c3ce088140b7a28c49999f47a4a557e424bf46f Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 26 Feb 2024 17:33:26 -0500 Subject: [PATCH 61/82] auto 11.0.5 is needed to avoid hitting some "Error: fatal: ... not an integer" bug --- .github/workflows/release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d0d7881..199015fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,9 +19,10 @@ jobs: - name: Download auto run: | #curl -vL -o - "$(curl -fsSL https://api.github.com/repos/intuit/auto/releases/latest | jq -r '.assets[] | select(.name == "auto-linux.gz") | .browser_download_url')" | gunzip > ~/auto - # Pin to 10.16.1 so we don't break if & when + # Pin so we don't break if & when # is fixed. - wget -O- https://github.com/intuit/auto/releases/download/v10.16.1/auto-linux.gz | gunzip > ~/auto + # 11.0.5 is needed for + wget -O- https://github.com/intuit/auto/releases/download/v11.0.5/auto-linux.gz | gunzip > ~/auto chmod a+x ~/auto - name: Query 'auto' on type of the release From a38a001865a97050af0cba8671fc863fb5591835 Mon Sep 17 00:00:00 2001 From: auto Date: Mon, 26 Feb 2024 22:42:41 +0000 Subject: [PATCH 62/82] Update CHANGELOG.md [skip ci] --- CHANGELOG.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 937478a1..e469cb35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ +# v1.0.2 (Mon Feb 26 2024) + +#### 🐛 Bug Fix + +- properly remove GE multiecho bvals/bvecs [#728](https://github.com/nipy/heudiconv/pull/728) ([@bpinsard](https://github.com/bpinsard)) +- datalad sensitive marking fixes [#739](https://github.com/nipy/heudiconv/pull/739) ([@bpinsard](https://github.com/bpinsard)) +- Reject "Missing images" in sensor-dicoms [#735](https://github.com/nipy/heudiconv/pull/735) ([@chaselgrove](https://github.com/chaselgrove)) + +#### ⚠️ Pushed to `master` + +- Adding workflow figure ([@TheChymera](https://github.com/TheChymera)) +- Added figures to master branch ([@TheChymera](https://github.com/TheChymera)) + +#### 🏠 Internal + +- auto 11.0.5 is needed to avoid hitting some "Error: fatal: ... not an integer" bug [#746](https://github.com/nipy/heudiconv/pull/746) ([@yarikoptic](https://github.com/yarikoptic)) +- Fix - auto is in ~/, not in the PATH [#745](https://github.com/nipy/heudiconv/pull/745) ([@yarikoptic](https://github.com/yarikoptic)) +- Make it possible to review auto version -v output during release + adjust that workflow step description [#743](https://github.com/nipy/heudiconv/pull/743) ([@yarikoptic](https://github.com/yarikoptic)) +- [gh-actions](deps): Bump codecov/codecov-action from 3 to 4 [#736](https://github.com/nipy/heudiconv/pull/736) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@yarikoptic](https://github.com/yarikoptic)) +- [gh-actions](deps): Bump actions/setup-python from 4 to 5 [#723](https://github.com/nipy/heudiconv/pull/723) ([@dependabot[bot]](https://github.com/dependabot[bot])) + +#### 📝 Documentation + +- Adjust wording on heuristics page -- do not claim creating some skeleton [#741](https://github.com/nipy/heudiconv/pull/741) ([@yarikoptic](https://github.com/yarikoptic)) +- Document how to release and add changelog entries [#737](https://github.com/nipy/heudiconv/pull/737) ([@asmacdo](https://github.com/asmacdo) [@yarikoptic](https://github.com/yarikoptic)) +- Add dianne tutorials [#734](https://github.com/nipy/heudiconv/pull/734) ([@asmacdo](https://github.com/asmacdo) [@yarikoptic](https://github.com/yarikoptic)) +- Add documentation building instructions [#730](https://github.com/nipy/heudiconv/pull/730) ([@asmacdo](https://github.com/asmacdo)) +- Allowing RTD to access images under the same path as README [#734](https://github.com/nipy/heudiconv/pull/734) ([@TheChymera](https://github.com/TheChymera)) +- Using environment figure in about section [#730](https://github.com/nipy/heudiconv/pull/730) ([@TheChymera](https://github.com/TheChymera)) +- Make README more concrete [#724](https://github.com/nipy/heudiconv/pull/724) ([@asmacdo](https://github.com/asmacdo)) + +#### Authors: 6 + +- [@chaselgrove](https://github.com/chaselgrove) +- [@dependabot[bot]](https://github.com/dependabot[bot]) +- Austin Macdonald ([@asmacdo](https://github.com/asmacdo)) +- Basile ([@bpinsard](https://github.com/bpinsard)) +- Horea Christian ([@TheChymera](https://github.com/TheChymera)) +- Yaroslav Halchenko ([@yarikoptic](https://github.com/yarikoptic)) + +--- + # v1.0.1 (Fri Dec 08 2023) #### 🐛 Bug Fix From 153d5229f2418ae073e110a6964f602c5152d550 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Wed, 28 Feb 2024 09:43:54 -0500 Subject: [PATCH 63/82] codespell: ignore "build" folder which might be on the system --- .codespellrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codespellrc b/.codespellrc index 4d5c4658..e3483843 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,4 +1,4 @@ [codespell] -skip = .git,.venv,venvs,*.svg,_build +skip = .git,.venv,venvs,*.svg,_build,build # te -- TE as codespell is case insensitive ignore-words-list = bu,nd,te From 44124bd7722f96aea3b17b469cdacf235c455a6c Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 19 Jul 2022 16:10:08 -0400 Subject: [PATCH 64/82] ENH: custom_seqinfo - provide a way for heuristics to extract/add arbitrary value Just a draft implementation --- heudiconv/convert.py | 3 +++ heudiconv/dicoms.py | 29 +++++++++++++++++++++++------ heudiconv/utils.py | 2 ++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 5f233fc6..40a575e7 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -221,6 +221,9 @@ def prep_conversion( dcmfilter=getattr(heuristic, "filter_dicom", None), flatten=True, custom_grouping=getattr(heuristic, "grouping", None), + # callable which will be provided dcminfo and returned + # structure extend seqinfo + custom_seqinfo = getattr(heuristic, 'custom_seqinfo', None), ) elif seqinfo is None: raise ValueError("Neither 'dicoms' nor 'seqinfo' is given") diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index ef51086a..7cdade8c 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -9,7 +9,8 @@ from pathlib import Path import sys import tarfile -from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, Optional, Union, overload +from typing import TYPE_CHECKING, Any, Dict, Hashable, List, NamedTuple, Optional, Union, overload +from typing_extensions import Protocol from unittest.mock import patch import warnings @@ -42,7 +43,16 @@ compresslevel = 9 -def create_seqinfo(mw: dw.Wrapper, series_files: list[str], series_id: str) -> SeqInfo: +class CustomSeqinfoT(Protocol): + def __call__(self, wrapper: dw.Wrapper, series_files: list[str]) -> Hashable: ... + + +def create_seqinfo( + mw: dw.Wrapper, + series_files: list[str], + series_id: str, + custom_seqinfo: CustomSeqinfoT | None = None, +) -> SeqInfo: """Generate sequence info Parameters @@ -109,6 +119,9 @@ def create_seqinfo(mw: dw.Wrapper, series_files: list[str], series_id: str) -> S date=dcminfo.get("AcquisitionDate"), series_uid=dcminfo.get("SeriesInstanceUID"), time=dcminfo.get("AcquisitionTime"), + custom = + custom_seqinfo(wrapper=mw, series_files=series_files) + if custom_seqinfo else None, ) @@ -199,6 +212,7 @@ def group_dicoms_into_seqinfos( dict[SeqInfo, list[str]], ] | None = None, + custom_seqinfo: CustomSeqinfoT | None = None, ) -> dict[SeqInfo, list[str]]: ... @@ -215,6 +229,7 @@ def group_dicoms_into_seqinfos( dict[SeqInfo, list[str]], ] | None = None, + custom_seqinfo: CustomSeqinfoT | None = None, ) -> dict[Optional[str], dict[SeqInfo, list[str]]] | dict[SeqInfo, list[str]]: """Process list of dicoms and return seqinfo and file group `seqinfo` contains per-sequence extract of fields from DICOMs which @@ -236,9 +251,11 @@ def group_dicoms_into_seqinfos( Creates a flattened `seqinfo` with corresponding DICOM files. True when invoked with `dicom_dir_template`. custom_grouping: str or callable, optional - grouping key defined within heuristic. Can be a string of a - DICOM attribute, or a method that handles more complex groupings. - + grouping key defined within heuristic. Can be a string of a + DICOM attribute, or a method that handles more complex groupings. + custom_seqinfo: callable, optional + A callable which will be provided MosaicWrapper giving possibility to + extract any custom DICOM metadata of interest. Returns ------- @@ -358,7 +375,7 @@ def group_dicoms_into_seqinfos( else: # nothing to see here, just move on continue - seqinfo = create_seqinfo(mw, series_files, series_id_str) + seqinfo = create_seqinfo(mw, series_files, series_id_str, custom_seqinfo) key: Optional[str] if per_studyUID: diff --git a/heudiconv/utils.py b/heudiconv/utils.py index 9f988267..f7bf16a7 100644 --- a/heudiconv/utils.py +++ b/heudiconv/utils.py @@ -24,6 +24,7 @@ from typing import ( Any, AnyStr, + Hashable, Mapping, NamedTuple, Optional, @@ -69,6 +70,7 @@ class SeqInfo(NamedTuple): date: Optional[str] # 24 series_uid: Optional[str] # 25 time: Optional[str] # 26 + custom: Optional[Hashable] # 27 class StudySessionInfo(NamedTuple): From a699e4d75d7cad82d9736e06f80181bcd9e1cefc Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 19 Jul 2022 16:10:25 -0400 Subject: [PATCH 65/82] TMP: just a demonstration on how custom_seqinfo could/should be used --- heudiconv/heuristics/convertall.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/heudiconv/heuristics/convertall.py b/heudiconv/heuristics/convertall.py index 8e1edee6..1ce783fa 100644 --- a/heudiconv/heuristics/convertall.py +++ b/heudiconv/heuristics/convertall.py @@ -1,7 +1,8 @@ from __future__ import annotations -from typing import Optional +from typing import Any, Optional +from heudiconv.dicoms import dw from heudiconv.utils import SeqInfo @@ -15,6 +16,14 @@ def create_key( return (template, outtype, annotation_classes) +def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str], **kw: Any) -> tuple[str, str]: + # Just a dummy demo for what custom_seqinfo could get/do + # for already loaded DICOM data, and including storing/returning + # the sample series file as was requested + # in https://github.com/nipy/heudiconv/pull/333 + return wrapper.affine, series_files[0] + + def infotodict( seqinfo: list[SeqInfo], ) -> dict[tuple[str, tuple[str, ...], None], list[str]]: From 48f14bfd1ffb222be8f58d02a2fef0fe8682c430 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 28 Apr 2023 21:24:55 -0400 Subject: [PATCH 66/82] fixup typing in dicoms.py --- heudiconv/dicoms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index 7cdade8c..ba21c214 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -194,6 +194,7 @@ def group_dicoms_into_seqinfos( dict[SeqInfo, list[str]], ] | None = None, + custom_seqinfo: CustomSeqinfoT | None = None, ) -> dict[Optional[str], dict[SeqInfo, list[str]]]: ... @@ -212,7 +213,7 @@ def group_dicoms_into_seqinfos( dict[SeqInfo, list[str]], ] | None = None, - custom_seqinfo: CustomSeqinfoT | None = None, + custom_seqinfo: CustomSeqinfoT | None = None, ) -> dict[SeqInfo, list[str]]: ... From 4afc7dfec8169da98c833a7fb08888429bde475e Mon Sep 17 00:00:00 2001 From: basile Date: Wed, 15 Feb 2023 12:00:32 -0500 Subject: [PATCH 67/82] add custom_info to group_dicoms_into_seqinfos call in parser --- heudiconv/parser.py | 1 + 1 file changed, 1 insertion(+) diff --git a/heudiconv/parser.py b/heudiconv/parser.py index 27d822e1..d2d75084 100644 --- a/heudiconv/parser.py +++ b/heudiconv/parser.py @@ -224,6 +224,7 @@ def get_study_sessions( file_filter=getattr(heuristic, "filter_files", None), dcmfilter=getattr(heuristic, "filter_dicom", None), custom_grouping=getattr(heuristic, "grouping", None), + custom_seqinfo=getattr(heuristic, 'custom_seqinfo', None), ) if sids: From 115b2274b47356578020b8f0997cc38080e93572 Mon Sep 17 00:00:00 2001 From: basile Date: Thu, 16 Feb 2023 10:30:49 -0500 Subject: [PATCH 68/82] fix test, custom_seqinfo has to be hashable --- heudiconv/heuristics/convertall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heudiconv/heuristics/convertall.py b/heudiconv/heuristics/convertall.py index 1ce783fa..2bab26b6 100644 --- a/heudiconv/heuristics/convertall.py +++ b/heudiconv/heuristics/convertall.py @@ -21,7 +21,7 @@ def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str], **kw: Any) -> t # for already loaded DICOM data, and including storing/returning # the sample series file as was requested # in https://github.com/nipy/heudiconv/pull/333 - return wrapper.affine, series_files[0] + return wrapper.affine.tostring(), series_files[0] def infotodict( From a37e276c7546ef61f309e4aaf4bc69acede671e9 Mon Sep 17 00:00:00 2001 From: basile Date: Thu, 16 Feb 2023 10:45:16 -0500 Subject: [PATCH 69/82] test data include messed-up dicoms with no affine --- heudiconv/heuristics/convertall.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/heudiconv/heuristics/convertall.py b/heudiconv/heuristics/convertall.py index 2bab26b6..20f52019 100644 --- a/heudiconv/heuristics/convertall.py +++ b/heudiconv/heuristics/convertall.py @@ -21,7 +21,12 @@ def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str], **kw: Any) -> t # for already loaded DICOM data, and including storing/returning # the sample series file as was requested # in https://github.com/nipy/heudiconv/pull/333 - return wrapper.affine.tostring(), series_files[0] + from nibabel.nicom.dicomwrappers import WrapperError + try: + affine = wrapper.affine.tostring() + except WrapperError: + affine = None + return affine, series_files[0] def infotodict( From 803bbafd87a6c2e562a8826ba1927bfd8f0b406d Mon Sep 17 00:00:00 2001 From: basile Date: Thu, 16 Feb 2023 15:21:59 -0500 Subject: [PATCH 70/82] add a check for hashable custom_info, link to new doc section --- docs/heuristics.rst | 13 +++++++++++++ heudiconv/dicoms.py | 13 ++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/docs/heuristics.rst b/docs/heuristics.rst index 8c9a68d1..f7940d43 100644 --- a/docs/heuristics.rst +++ b/docs/heuristics.rst @@ -119,6 +119,19 @@ or:: ... return seqinfos # ordered dict containing seqinfo objects: list of DICOMs +--------------------------------------------------------------- +``custom_seqinfo(series_files, wrapper)`` +--------------------------------------------------------------- +If present this function will be called on eacg group of dicoms with +a sample nibabel dicom wrapper to extract additional information +to be used in ``infotodict``. + +Importantly the return value of that function needs to be hashable. +For instance the following non-hashable types can be converted to an alternative +hashable type: +- list > tuple +- dict > frozendict +- arrays > bytes (tobytes(), or pickle.dumps), str or tuple of tuples. ------------------------------- ``POPULATE_INTENDED_FOR_OPTS`` diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index ba21c214..67da0a75 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -90,6 +90,15 @@ def create_seqinfo( global total_files total_files += len(series_files) + custom_seqinfo_data = custom_seqinfo(wrapper=mw, series_files=series_files) \ + if custom_seqinfo else None + try: + hash(custom_seqinfo_data) + except TypeError: + raise RuntimeError("Data returned by the heuristics custom_seqinfo is not hashable. " + "See https://heudiconv.readthedocs.io/en/latest/heuristics.html#custom_seqinfo for more " + "details.") + return SeqInfo( total_files_till_now=total_files, example_dcm_file=op.basename(series_files[0]), @@ -119,9 +128,7 @@ def create_seqinfo( date=dcminfo.get("AcquisitionDate"), series_uid=dcminfo.get("SeriesInstanceUID"), time=dcminfo.get("AcquisitionTime"), - custom = - custom_seqinfo(wrapper=mw, series_files=series_files) - if custom_seqinfo else None, + custom=custom_seqinfo_data, ) From 678f8168b84bf45e60d5d33561cb546a7da123a4 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 3 Mar 2023 12:43:38 -0500 Subject: [PATCH 71/82] Typo fix --- docs/heuristics.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/heuristics.rst b/docs/heuristics.rst index f7940d43..3f32a55a 100644 --- a/docs/heuristics.rst +++ b/docs/heuristics.rst @@ -122,7 +122,7 @@ or:: --------------------------------------------------------------- ``custom_seqinfo(series_files, wrapper)`` --------------------------------------------------------------- -If present this function will be called on eacg group of dicoms with +If present this function will be called on each group of dicoms with a sample nibabel dicom wrapper to extract additional information to be used in ``infotodict``. From 0f5846a2753528addf80ff1b9c4d3a82a08217d7 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 3 Mar 2023 12:48:48 -0500 Subject: [PATCH 72/82] Log exception (as error) if we fail to obtain affine in convertall --- heudiconv/heuristics/convertall.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/heudiconv/heuristics/convertall.py b/heudiconv/heuristics/convertall.py index 20f52019..9afede7d 100644 --- a/heudiconv/heuristics/convertall.py +++ b/heudiconv/heuristics/convertall.py @@ -1,10 +1,13 @@ from __future__ import annotations +import logging from typing import Any, Optional from heudiconv.dicoms import dw from heudiconv.utils import SeqInfo +lgr = logging.getLogger('heudiconv') + def create_key( template: Optional[str], @@ -25,6 +28,7 @@ def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str], **kw: Any) -> t try: affine = wrapper.affine.tostring() except WrapperError: + lgr.exception("Errored out while obtaining/converting affine") affine = None return affine, series_files[0] From c640ffe0fa827f1b50323d5c773ca262ac1b3fb4 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 20 Jul 2023 11:26:30 -0400 Subject: [PATCH 73/82] fix the order of args in the custom_seqinfo doc --- docs/heuristics.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/heuristics.rst b/docs/heuristics.rst index 3f32a55a..b55f6040 100644 --- a/docs/heuristics.rst +++ b/docs/heuristics.rst @@ -120,7 +120,7 @@ or:: return seqinfos # ordered dict containing seqinfo objects: list of DICOMs --------------------------------------------------------------- -``custom_seqinfo(series_files, wrapper)`` +``custom_seqinfo(wrapper, series_files)`` --------------------------------------------------------------- If present this function will be called on each group of dicoms with a sample nibabel dicom wrapper to extract additional information From 517ffdcec29eb982e5f0dc1f12be90c42d96ead2 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 19 Jul 2022 16:10:08 -0400 Subject: [PATCH 74/82] ENH: custom_seqinfo - provide a way for heuristics to extract/add arbitrary value Just a draft implementation --- heudiconv/dicoms.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index 67da0a75..d2c10f89 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -9,8 +9,7 @@ from pathlib import Path import sys import tarfile -from typing import TYPE_CHECKING, Any, Dict, Hashable, List, NamedTuple, Optional, Union, overload -from typing_extensions import Protocol +from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, Optional, Union, Protocol, cast, overload from unittest.mock import patch import warnings From 2afe136ba0033a28e9817e980229a79769a91399 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Tue, 16 Jan 2024 09:16:11 -0500 Subject: [PATCH 75/82] change tostr -> tobytes for deprecation, add basic test --- heudiconv/heuristics/convertall.py | 2 +- heudiconv/tests/test_dicoms.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/heudiconv/heuristics/convertall.py b/heudiconv/heuristics/convertall.py index 9afede7d..d9ada9f7 100644 --- a/heudiconv/heuristics/convertall.py +++ b/heudiconv/heuristics/convertall.py @@ -26,7 +26,7 @@ def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str], **kw: Any) -> t # in https://github.com/nipy/heudiconv/pull/333 from nibabel.nicom.dicomwrappers import WrapperError try: - affine = wrapper.affine.tostring() + affine = wrapper.affine.tobytes() except WrapperError: lgr.exception("Errored out while obtaining/converting affine") affine = None diff --git a/heudiconv/tests/test_dicoms.py b/heudiconv/tests/test_dicoms.py index dc03790a..678ff2e2 100644 --- a/heudiconv/tests/test_dicoms.py +++ b/heudiconv/tests/test_dicoms.py @@ -99,6 +99,26 @@ def test_group_dicoms_into_seqinfos() -> None: ] +def test_custom_seqinfo() -> None: + """Tests for custom seqinfo extraction""" + + from heudiconv.heuristics.convertall import custom_seqinfo + + dcmfiles = glob(op.join(TESTS_DATA_PATH, "phantom.dcm")) + + seqinfos = group_dicoms_into_seqinfos( + dcmfiles, + "studyUID", + flatten=True, + custom_seqinfo=custom_seqinfo) + + seqinfo = list(seqinfos.keys())[0] + + assert hasattr(seqinfo, 'custom') + assert isinstance(seqinfo.custom, tuple) + assert len(seqinfo.custom) == 2 + assert seqinfo.custom[1] == dcmfiles[0] + def test_get_datetime_from_dcm_from_acq_date_time() -> None: typical_dcm = dcm.dcmread( op.join(TESTS_DATA_PATH, "phantom.dcm"), stop_before_pixels=True From b3193f678a4ef99348b7853821d85973d864e522 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Tue, 16 Jan 2024 09:27:00 -0500 Subject: [PATCH 76/82] linting/typing --- heudiconv/convert.py | 2 +- heudiconv/dicoms.py | 3 ++- heudiconv/heuristics/convertall.py | 4 ++-- heudiconv/tests/test_dicoms.py | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 40a575e7..05534dc9 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -223,7 +223,7 @@ def prep_conversion( custom_grouping=getattr(heuristic, "grouping", None), # callable which will be provided dcminfo and returned # structure extend seqinfo - custom_seqinfo = getattr(heuristic, 'custom_seqinfo', None), + custom_seqinfo=getattr(heuristic, 'custom_seqinfo', None), ) elif seqinfo is None: raise ValueError("Neither 'dicoms' nor 'seqinfo' is given") diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index d2c10f89..1dc11c7d 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -43,7 +43,8 @@ class CustomSeqinfoT(Protocol): - def __call__(self, wrapper: dw.Wrapper, series_files: list[str]) -> Hashable: ... + def __call__(self, wrapper: dw.Wrapper, series_files: list[str]) -> Hashable: + ... def create_seqinfo( diff --git a/heudiconv/heuristics/convertall.py b/heudiconv/heuristics/convertall.py index d9ada9f7..44cd5ede 100644 --- a/heudiconv/heuristics/convertall.py +++ b/heudiconv/heuristics/convertall.py @@ -1,7 +1,7 @@ from __future__ import annotations import logging -from typing import Any, Optional +from typing import Optional from heudiconv.dicoms import dw from heudiconv.utils import SeqInfo @@ -19,7 +19,7 @@ def create_key( return (template, outtype, annotation_classes) -def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str], **kw: Any) -> tuple[str, str]: +def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str]) -> tuple[str, str]: # Just a dummy demo for what custom_seqinfo could get/do # for already loaded DICOM data, and including storing/returning # the sample series file as was requested diff --git a/heudiconv/tests/test_dicoms.py b/heudiconv/tests/test_dicoms.py index 678ff2e2..8fab01be 100644 --- a/heudiconv/tests/test_dicoms.py +++ b/heudiconv/tests/test_dicoms.py @@ -119,6 +119,7 @@ def test_custom_seqinfo() -> None: assert len(seqinfo.custom) == 2 assert seqinfo.custom[1] == dcmfiles[0] + def test_get_datetime_from_dcm_from_acq_date_time() -> None: typical_dcm = dcm.dcmread( op.join(TESTS_DATA_PATH, "phantom.dcm"), stop_before_pixels=True From 419ed4824c915f06c7b0ff9d0f6805a39188e368 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Tue, 16 Jan 2024 10:13:50 -0500 Subject: [PATCH 77/82] fix py3.11 typing import --- heudiconv/dicoms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index 1dc11c7d..b7574c29 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -9,7 +9,7 @@ from pathlib import Path import sys import tarfile -from typing import TYPE_CHECKING, Any, Dict, List, NamedTuple, Optional, Union, Protocol, cast, overload +from typing import TYPE_CHECKING, Any, Dict, Hashable, List, NamedTuple, Optional, Union, Protocol, overload from unittest.mock import patch import warnings From cfdd3d70bf76e1c97565aabc5f29391efea9ccb1 Mon Sep 17 00:00:00 2001 From: bpinsard Date: Tue, 16 Jan 2024 16:08:11 -0500 Subject: [PATCH 78/82] run pre-commit magic! --- README.rst | 2 +- heudiconv/convert.py | 2 +- heudiconv/dicoms.py | 28 ++++++++++++++++++++++------ heudiconv/heuristics/convertall.py | 3 ++- heudiconv/parser.py | 2 +- heudiconv/tests/test_dicoms.py | 8 +++----- 6 files changed, 30 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index a177af8f..02a33c40 100644 --- a/README.rst +++ b/README.rst @@ -71,7 +71,7 @@ You can run your conversion automatically (which will produce a ``.heudiconv`` d .. image:: figs/workflow.png -``heudiconv`` comes with `existing heuristics `_ which can be used as is, or as examples. +``heudiconv`` comes with `existing heuristics `_ which can be used as is, or as examples. For instance, the Heuristic `convertall `_ extracts standard metadata from all matching DICOMs. ``heudiconv`` creates mapping files, ``.edit.text`` which lets researchers simply establish their own conversion mapping. diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 05534dc9..aecc70dc 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -223,7 +223,7 @@ def prep_conversion( custom_grouping=getattr(heuristic, "grouping", None), # callable which will be provided dcminfo and returned # structure extend seqinfo - custom_seqinfo=getattr(heuristic, 'custom_seqinfo', None), + custom_seqinfo=getattr(heuristic, "custom_seqinfo", None), ) elif seqinfo is None: raise ValueError("Neither 'dicoms' nor 'seqinfo' is given") diff --git a/heudiconv/dicoms.py b/heudiconv/dicoms.py index b7574c29..f504af18 100644 --- a/heudiconv/dicoms.py +++ b/heudiconv/dicoms.py @@ -9,7 +9,18 @@ from pathlib import Path import sys import tarfile -from typing import TYPE_CHECKING, Any, Dict, Hashable, List, NamedTuple, Optional, Union, Protocol, overload +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Hashable, + List, + NamedTuple, + Optional, + Protocol, + Union, + overload, +) from unittest.mock import patch import warnings @@ -90,14 +101,19 @@ def create_seqinfo( global total_files total_files += len(series_files) - custom_seqinfo_data = custom_seqinfo(wrapper=mw, series_files=series_files) \ - if custom_seqinfo else None + custom_seqinfo_data = ( + custom_seqinfo(wrapper=mw, series_files=series_files) + if custom_seqinfo + else None + ) try: hash(custom_seqinfo_data) except TypeError: - raise RuntimeError("Data returned by the heuristics custom_seqinfo is not hashable. " - "See https://heudiconv.readthedocs.io/en/latest/heuristics.html#custom_seqinfo for more " - "details.") + raise RuntimeError( + "Data returned by the heuristics custom_seqinfo is not hashable. " + "See https://heudiconv.readthedocs.io/en/latest/heuristics.html#custom_seqinfo for more " + "details." + ) return SeqInfo( total_files_till_now=total_files, diff --git a/heudiconv/heuristics/convertall.py b/heudiconv/heuristics/convertall.py index 44cd5ede..c232415a 100644 --- a/heudiconv/heuristics/convertall.py +++ b/heudiconv/heuristics/convertall.py @@ -6,7 +6,7 @@ from heudiconv.dicoms import dw from heudiconv.utils import SeqInfo -lgr = logging.getLogger('heudiconv') +lgr = logging.getLogger("heudiconv") def create_key( @@ -25,6 +25,7 @@ def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str]) -> tuple[str, s # the sample series file as was requested # in https://github.com/nipy/heudiconv/pull/333 from nibabel.nicom.dicomwrappers import WrapperError + try: affine = wrapper.affine.tobytes() except WrapperError: diff --git a/heudiconv/parser.py b/heudiconv/parser.py index d2d75084..cd891ac9 100644 --- a/heudiconv/parser.py +++ b/heudiconv/parser.py @@ -224,7 +224,7 @@ def get_study_sessions( file_filter=getattr(heuristic, "filter_files", None), dcmfilter=getattr(heuristic, "filter_dicom", None), custom_grouping=getattr(heuristic, "grouping", None), - custom_seqinfo=getattr(heuristic, 'custom_seqinfo', None), + custom_seqinfo=getattr(heuristic, "custom_seqinfo", None), ) if sids: diff --git a/heudiconv/tests/test_dicoms.py b/heudiconv/tests/test_dicoms.py index 8fab01be..b1ad0038 100644 --- a/heudiconv/tests/test_dicoms.py +++ b/heudiconv/tests/test_dicoms.py @@ -107,14 +107,12 @@ def test_custom_seqinfo() -> None: dcmfiles = glob(op.join(TESTS_DATA_PATH, "phantom.dcm")) seqinfos = group_dicoms_into_seqinfos( - dcmfiles, - "studyUID", - flatten=True, - custom_seqinfo=custom_seqinfo) + dcmfiles, "studyUID", flatten=True, custom_seqinfo=custom_seqinfo + ) seqinfo = list(seqinfos.keys())[0] - assert hasattr(seqinfo, 'custom') + assert hasattr(seqinfo, "custom") assert isinstance(seqinfo.custom, tuple) assert len(seqinfo.custom) == 2 assert seqinfo.custom[1] == dcmfiles[0] From 4688912a7c1121f3d889c32f8cdb1f926f5074a9 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 13 Feb 2024 11:29:46 -0500 Subject: [PATCH 79/82] Create convertall_custom.py as a demonstration for a derived heuristic with custom_seqinfo --- heudiconv/heuristics/convertall.py | 16 --------------- heudiconv/heuristics/convertall_custom.py | 25 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 heudiconv/heuristics/convertall_custom.py diff --git a/heudiconv/heuristics/convertall.py b/heudiconv/heuristics/convertall.py index c232415a..dc5ef073 100644 --- a/heudiconv/heuristics/convertall.py +++ b/heudiconv/heuristics/convertall.py @@ -3,7 +3,6 @@ import logging from typing import Optional -from heudiconv.dicoms import dw from heudiconv.utils import SeqInfo lgr = logging.getLogger("heudiconv") @@ -19,21 +18,6 @@ def create_key( return (template, outtype, annotation_classes) -def custom_seqinfo(wrapper: dw.Wrapper, series_files: list[str]) -> tuple[str, str]: - # Just a dummy demo for what custom_seqinfo could get/do - # for already loaded DICOM data, and including storing/returning - # the sample series file as was requested - # in https://github.com/nipy/heudiconv/pull/333 - from nibabel.nicom.dicomwrappers import WrapperError - - try: - affine = wrapper.affine.tobytes() - except WrapperError: - lgr.exception("Errored out while obtaining/converting affine") - affine = None - return affine, series_files[0] - - def infotodict( seqinfo: list[SeqInfo], ) -> dict[tuple[str, tuple[str, ...], None], list[str]]: diff --git a/heudiconv/heuristics/convertall_custom.py b/heudiconv/heuristics/convertall_custom.py new file mode 100644 index 00000000..abc4f812 --- /dev/null +++ b/heudiconv/heuristics/convertall_custom.py @@ -0,0 +1,25 @@ +"""A demo convertall heuristic with custom_seqinfo extracting affine and sample DICOM path + +This heuristic also demonstrates on how to create a "derived" heuristic which would augment +behavior of an already existing heuristic without complete rewrite. Such approach could be +useful for heuristic like reproin to overload mapping etc. +""" + +from .convertall import * # noqa: F403 + + +def custom_seqinfo(series_files, wrapper, **kw): # noqa: U100 + """Demo for extracting custom header fields into custom_seqinfo field + + Operates on already loaded DICOM data. + Origin: https://github.com/nipy/heudiconv/pull/333 + """ + + from nibabel.nicom.dicomwrappers import WrapperError + + try: + affine = wrapper.affine.tostring() + except WrapperError: + lgr.exception("Errored out while obtaining/converting affine") # noqa: F405 + affine = None + return affine, series_files[0] From ca48c66bffb894eaba08822e1bf46cdda5a8fece Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 13 Feb 2024 11:46:42 -0500 Subject: [PATCH 80/82] Fix import in the test due to move + use str() since now tostring is gone --- heudiconv/heuristics/convertall_custom.py | 2 +- heudiconv/tests/test_dicoms.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/heudiconv/heuristics/convertall_custom.py b/heudiconv/heuristics/convertall_custom.py index abc4f812..780ecc76 100644 --- a/heudiconv/heuristics/convertall_custom.py +++ b/heudiconv/heuristics/convertall_custom.py @@ -18,7 +18,7 @@ def custom_seqinfo(series_files, wrapper, **kw): # noqa: U100 from nibabel.nicom.dicomwrappers import WrapperError try: - affine = wrapper.affine.tostring() + affine = str(wrapper.affine) except WrapperError: lgr.exception("Errored out while obtaining/converting affine") # noqa: F405 affine = None diff --git a/heudiconv/tests/test_dicoms.py b/heudiconv/tests/test_dicoms.py index b1ad0038..12ac29c6 100644 --- a/heudiconv/tests/test_dicoms.py +++ b/heudiconv/tests/test_dicoms.py @@ -102,7 +102,7 @@ def test_group_dicoms_into_seqinfos() -> None: def test_custom_seqinfo() -> None: """Tests for custom seqinfo extraction""" - from heudiconv.heuristics.convertall import custom_seqinfo + from heudiconv.heuristics.convertall_custom import custom_seqinfo dcmfiles = glob(op.join(TESTS_DATA_PATH, "phantom.dcm")) From 41b22d7b8b8ff1a1e066515b17d5da4baebd6c56 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Wed, 28 Feb 2024 09:59:07 -0500 Subject: [PATCH 81/82] Provide types for custom_seqinfo but disable type checking in the test -- I think mypy has deficiency since we do use kwargs and thus should be ok --- heudiconv/heuristics/convertall_custom.py | 9 ++++++++- heudiconv/tests/test_dicoms.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/heudiconv/heuristics/convertall_custom.py b/heudiconv/heuristics/convertall_custom.py index 780ecc76..26f0ca05 100644 --- a/heudiconv/heuristics/convertall_custom.py +++ b/heudiconv/heuristics/convertall_custom.py @@ -4,11 +4,18 @@ behavior of an already existing heuristic without complete rewrite. Such approach could be useful for heuristic like reproin to overload mapping etc. """ +from __future__ import annotations + +from typing import Any + +import nibabel.nicom.dicomwrappers as dw from .convertall import * # noqa: F403 -def custom_seqinfo(series_files, wrapper, **kw): # noqa: U100 +def custom_seqinfo( + series_files: list[str], wrapper: dw.Wrapper, **kw: Any # noqa: U100 +) -> tuple[str | None, str]: """Demo for extracting custom header fields into custom_seqinfo field Operates on already loaded DICOM data. diff --git a/heudiconv/tests/test_dicoms.py b/heudiconv/tests/test_dicoms.py index 12ac29c6..4cb28290 100644 --- a/heudiconv/tests/test_dicoms.py +++ b/heudiconv/tests/test_dicoms.py @@ -108,7 +108,7 @@ def test_custom_seqinfo() -> None: seqinfos = group_dicoms_into_seqinfos( dcmfiles, "studyUID", flatten=True, custom_seqinfo=custom_seqinfo - ) + ) # type: ignore seqinfo = list(seqinfos.keys())[0] From 7caff37cf040d0ba12967b41b0385548ce6a51bd Mon Sep 17 00:00:00 2001 From: auto Date: Wed, 28 Feb 2024 15:22:04 +0000 Subject: [PATCH 82/82] Update CHANGELOG.md [skip ci] --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e469cb35..84c24155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +# v1.1.0 (Wed Feb 28 2024) + +#### 🚀 Enhancement + +- Add support for a custom seqinfo to extract from DICOMs any additional metadata desired for a heuristic [#581](https://github.com/nipy/heudiconv/pull/581) ([@yarikoptic](https://github.com/yarikoptic) [@bpinsard](https://github.com/bpinsard)) +- codespell: ignore "build" folder which might be on the system [#581](https://github.com/nipy/heudiconv/pull/581) ([@yarikoptic](https://github.com/yarikoptic)) + +#### Authors: 2 + +- Basile ([@bpinsard](https://github.com/bpinsard)) +- Yaroslav Halchenko ([@yarikoptic](https://github.com/yarikoptic)) + +--- + # v1.0.2 (Mon Feb 26 2024) #### 🐛 Bug Fix