bids-manager 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bids_manager-0.1.0/LICENSE +21 -0
- bids_manager-0.1.0/MANIFEST.in +3 -0
- bids_manager-0.1.0/PKG-INFO +142 -0
- bids_manager-0.1.0/README.md +111 -0
- bids_manager-0.1.0/bids_manager/__init__.py +11 -0
- bids_manager-0.1.0/bids_manager/build_heuristic_from_tsv.py +214 -0
- bids_manager-0.1.0/bids_manager/dicom_inventory.py +463 -0
- bids_manager-0.1.0/bids_manager/fill_bids_ignore.py +51 -0
- bids_manager-0.1.0/bids_manager/gui.py +4115 -0
- bids_manager-0.1.0/bids_manager/miscellaneous/images/ANCP_lab.png +0 -0
- bids_manager-0.1.0/bids_manager/miscellaneous/images/Icon.png +0 -0
- bids_manager-0.1.0/bids_manager/miscellaneous/images/Jochem.jpg +0 -0
- bids_manager-0.1.0/bids_manager/miscellaneous/images/Karel.jpeg +0 -0
- bids_manager-0.1.0/bids_manager/miscellaneous/images/Logo.png +0 -0
- bids_manager-0.1.0/bids_manager/post_conv_renamer.py +224 -0
- bids_manager-0.1.0/bids_manager/run_heudiconv_from_heuristic.py +248 -0
- bids_manager-0.1.0/bids_manager/scans_utils.py +80 -0
- bids_manager-0.1.0/bids_manager.egg-info/PKG-INFO +142 -0
- bids_manager-0.1.0/bids_manager.egg-info/SOURCES.txt +23 -0
- bids_manager-0.1.0/bids_manager.egg-info/dependency_links.txt +1 -0
- bids_manager-0.1.0/bids_manager.egg-info/entry_points.txt +7 -0
- bids_manager-0.1.0/bids_manager.egg-info/requires.txt +12 -0
- bids_manager-0.1.0/bids_manager.egg-info/top_level.txt +1 -0
- bids_manager-0.1.0/pyproject.toml +57 -0
- bids_manager-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 BIDS Manager
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bids-manager
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GUI application to manage BIDS datasets
|
|
5
|
+
Author-email: Karel López Vilaret <karel.mauricio.lopez.vilaret@uni-oldenburg.de>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ANCPLabOldenburg/BIDS-Manager
|
|
8
|
+
Project-URL: Documentation, https://github.com/ANCPLabOldenburg/BIDS-Manager
|
|
9
|
+
Project-URL: Source, https://github.com/ANCPLabOldenburg/BIDS-Manager
|
|
10
|
+
Project-URL: Tracker, https://github.com/ANCPLabOldenburg/BIDS-Manager/issues
|
|
11
|
+
Keywords: BIDS,DICOM,GUI,Neuroimaging
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pydicom==3.0.1
|
|
19
|
+
Requires-Dist: pandas==2.3.1
|
|
20
|
+
Requires-Dist: PyQt5==5.15.11
|
|
21
|
+
Requires-Dist: PyQtWebEngine==5.15.7
|
|
22
|
+
Requires-Dist: heudiconv-ancp
|
|
23
|
+
Requires-Dist: nipype-ancp
|
|
24
|
+
Requires-Dist: dcm2niix==1.0.20250506
|
|
25
|
+
Requires-Dist: nibabel==5.3.2
|
|
26
|
+
Requires-Dist: numpy==2.2.6
|
|
27
|
+
Requires-Dist: psutil==7.0.0
|
|
28
|
+
Requires-Dist: matplotlib==3.10.3
|
|
29
|
+
Requires-Dist: joblib==1.4.2
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# BIDS Manager
|
|
34
|
+
|
|
35
|
+
**BIDS Manager** is a **PyQt-based** GUI that converts **DICOM** folders into **BIDS**-compliant datasets and allows easy metadata editing.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Requirements
|
|
40
|
+
|
|
41
|
+
| Software | Minimum Version | Notes |
|
|
42
|
+
|----------|-----------------|---------------------------------------------------------|
|
|
43
|
+
| **Python** | 3.10 | Installed automatically if you use the one-click installers |
|
|
44
|
+
| **Git** | — | Must be installed manually (see below) |
|
|
45
|
+
|
|
46
|
+
### Installing Git
|
|
47
|
+
|
|
48
|
+
| Platform | Command / Action |
|
|
49
|
+
|-------------------|----------------------------------------------------|
|
|
50
|
+
| **Windows** | Download and run the installer: <https://git-scm.com/download/win> |
|
|
51
|
+
| **Ubuntu/Debian** | `sudo apt-get update && sudo apt-get install git` |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
You can install BIDS Manager in two ways:
|
|
58
|
+
|
|
59
|
+
### 1. One-click installers <sup>(recommended)</sup>
|
|
60
|
+
|
|
61
|
+
1. **Download** the ZIP package:
|
|
62
|
+
**[📦 One-click Installers](https://github.com/ANCPLabOldenburg/BIDS-Manager/raw/main/Installers/Installers.zip
|
|
63
|
+
)**
|
|
64
|
+
2. **Extract** the ZIP file and run the script for your operating system:
|
|
65
|
+
|
|
66
|
+
| OS | Script | How to Run | Duration |
|
|
67
|
+
|------------------|-------------------------------|------------------------------------|---------|
|
|
68
|
+
| **Windows 10/11**| `install_bids_manager.bat` | Double-click | ≈ 5 min |
|
|
69
|
+
| **Linux** | `install_bids_manager.sh` | `./install_bids_manager.sh` | ≈ 5 min |
|
|
70
|
+
|
|
71
|
+
3. After the installation finishes, you will find two shortcuts on your desktop:
|
|
72
|
+
|
|
73
|
+
| OS | Launch | Uninstall |
|
|
74
|
+
|-------------|---------------------------|--------------------------------|
|
|
75
|
+
| **Windows** | `run_bidsmanager.bat` | `uninstall_bidsmanager.bat` |
|
|
76
|
+
| **Linux** | **BIDS Manager** (launcher)| `uninstall_bidsmanager.sh` |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### 2. Install in a virtual environment (advanced)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# 1. Create a virtual environment
|
|
84
|
+
python3 -m venv <env_name>
|
|
85
|
+
|
|
86
|
+
# 2. Activate it
|
|
87
|
+
source <env_name>/bin/activate # On Windows: <env_name>\Scripts\activate
|
|
88
|
+
|
|
89
|
+
# 3. Install BIDS Manager from GitHub
|
|
90
|
+
pip install git+https://github.com/ANCPLabOldenburg/BIDS-Manager.git
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The package declares all dependencies including `heudiconv`, so installation
|
|
94
|
+
pulls everything required to run the GUI and helper scripts.
|
|
95
|
+
All core requirements are version pinned in `pyproject.toml` to ensure
|
|
96
|
+
consistent installations.
|
|
97
|
+
|
|
98
|
+
After installation the following commands become available:
|
|
99
|
+
|
|
100
|
+
- `bids-manager` – main GUI combining conversion and editing tools
|
|
101
|
+
- `dicom-inventory` – generate `subject_summary.tsv` from a DICOM directory
|
|
102
|
+
- `build-heuristic` – create a HeuDiConv heuristic from the TSV
|
|
103
|
+
- `run-heudiconv` – run HeuDiConv using the generated heuristic
|
|
104
|
+
- `post-conv-renamer` – rename fieldmap files after conversion
|
|
105
|
+
- `bids-editor` – standalone metadata editor
|
|
106
|
+
- `fill-bids-ignore` – interactively update `.bidsignore`
|
|
107
|
+
|
|
108
|
+
All utilities provide `-h/--help` for details.
|
|
109
|
+
|
|
110
|
+
### Recent updates
|
|
111
|
+
|
|
112
|
+
- The TSV produced by `dicom-inventory` can now be loaded directly in the GUI and
|
|
113
|
+
its file name customised before generation.
|
|
114
|
+
- The Batch Rename tool previews changes and allows restricting the scope to
|
|
115
|
+
specific subjects.
|
|
116
|
+
- A "Set Intended For" dialog lets you manually edit fieldmap IntendedFor lists
|
|
117
|
+
if the automatic matching needs adjustment.
|
|
118
|
+
- `run-heudiconv` now keeps a copy of `subject_summary.tsv` under `.bids_manager`
|
|
119
|
+
and generates a clean `participants.tsv` using demographics from that file.
|
|
120
|
+
- Re-running `run-heudiconv` on the same dataset now appends new subjects to
|
|
121
|
+
the existing `.bids_manager` records and updates `participants.tsv` instead of
|
|
122
|
+
overwriting them.
|
|
123
|
+
- `dicom-inventory` distinguishes repeated sequences by adding `series_uid` and `rep`
|
|
124
|
+
columns and records `acq_time` for each series in `subject_summary.tsv`.
|
|
125
|
+
- Fieldmap rows for magnitude and phase images are now merged so each acquisition
|
|
126
|
+
appears once with the combined file count, and their `series_uid` values are
|
|
127
|
+
stored as a pipe-separated list so both sequences are converted.
|
|
128
|
+
- `post-conv-renamer` now adds an `IntendedFor` list to each fieldmap JSON so
|
|
129
|
+
fMRI preprocessing tools can automatically match fieldmaps with the relevant
|
|
130
|
+
functional runs.
|
|
131
|
+
- The GUI's Tools menu gained actions to refresh `_scans.tsv` files and edit
|
|
132
|
+
`.bidsignore` entries.
|
|
133
|
+
- The DPI scale dialog now adjusts values in 25% increments and the DPI button
|
|
134
|
+
appears between the CPU and Authorship buttons.
|
|
135
|
+
- On startup the GUI detects the system DPI and applies the matching scale.
|
|
136
|
+
- The scanned data table now provides a "Generate unique IDs" button that
|
|
137
|
+
assigns random 3‑letter/3‑digit identifiers to subjects. If an entry already
|
|
138
|
+
exists for the same study in an existing `.bids_manager/subject_summary.tsv`,
|
|
139
|
+
you are prompted to reuse its identifier.
|
|
140
|
+
- A "Detect repeats" button can recompute repetition numbers based on
|
|
141
|
+
acquisition time when all BIDS and given names are filled.
|
|
142
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
|
|
2
|
+
# BIDS Manager
|
|
3
|
+
|
|
4
|
+
**BIDS Manager** is a **PyQt-based** GUI that converts **DICOM** folders into **BIDS**-compliant datasets and allows easy metadata editing.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
| Software | Minimum Version | Notes |
|
|
11
|
+
|----------|-----------------|---------------------------------------------------------|
|
|
12
|
+
| **Python** | 3.10 | Installed automatically if you use the one-click installers |
|
|
13
|
+
| **Git** | — | Must be installed manually (see below) |
|
|
14
|
+
|
|
15
|
+
### Installing Git
|
|
16
|
+
|
|
17
|
+
| Platform | Command / Action |
|
|
18
|
+
|-------------------|----------------------------------------------------|
|
|
19
|
+
| **Windows** | Download and run the installer: <https://git-scm.com/download/win> |
|
|
20
|
+
| **Ubuntu/Debian** | `sudo apt-get update && sudo apt-get install git` |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
You can install BIDS Manager in two ways:
|
|
27
|
+
|
|
28
|
+
### 1. One-click installers <sup>(recommended)</sup>
|
|
29
|
+
|
|
30
|
+
1. **Download** the ZIP package:
|
|
31
|
+
**[📦 One-click Installers](https://github.com/ANCPLabOldenburg/BIDS-Manager/raw/main/Installers/Installers.zip
|
|
32
|
+
)**
|
|
33
|
+
2. **Extract** the ZIP file and run the script for your operating system:
|
|
34
|
+
|
|
35
|
+
| OS | Script | How to Run | Duration |
|
|
36
|
+
|------------------|-------------------------------|------------------------------------|---------|
|
|
37
|
+
| **Windows 10/11**| `install_bids_manager.bat` | Double-click | ≈ 5 min |
|
|
38
|
+
| **Linux** | `install_bids_manager.sh` | `./install_bids_manager.sh` | ≈ 5 min |
|
|
39
|
+
|
|
40
|
+
3. After the installation finishes, you will find two shortcuts on your desktop:
|
|
41
|
+
|
|
42
|
+
| OS | Launch | Uninstall |
|
|
43
|
+
|-------------|---------------------------|--------------------------------|
|
|
44
|
+
| **Windows** | `run_bidsmanager.bat` | `uninstall_bidsmanager.bat` |
|
|
45
|
+
| **Linux** | **BIDS Manager** (launcher)| `uninstall_bidsmanager.sh` |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
### 2. Install in a virtual environment (advanced)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 1. Create a virtual environment
|
|
53
|
+
python3 -m venv <env_name>
|
|
54
|
+
|
|
55
|
+
# 2. Activate it
|
|
56
|
+
source <env_name>/bin/activate # On Windows: <env_name>\Scripts\activate
|
|
57
|
+
|
|
58
|
+
# 3. Install BIDS Manager from GitHub
|
|
59
|
+
pip install git+https://github.com/ANCPLabOldenburg/BIDS-Manager.git
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The package declares all dependencies including `heudiconv`, so installation
|
|
63
|
+
pulls everything required to run the GUI and helper scripts.
|
|
64
|
+
All core requirements are version pinned in `pyproject.toml` to ensure
|
|
65
|
+
consistent installations.
|
|
66
|
+
|
|
67
|
+
After installation the following commands become available:
|
|
68
|
+
|
|
69
|
+
- `bids-manager` – main GUI combining conversion and editing tools
|
|
70
|
+
- `dicom-inventory` – generate `subject_summary.tsv` from a DICOM directory
|
|
71
|
+
- `build-heuristic` – create a HeuDiConv heuristic from the TSV
|
|
72
|
+
- `run-heudiconv` – run HeuDiConv using the generated heuristic
|
|
73
|
+
- `post-conv-renamer` – rename fieldmap files after conversion
|
|
74
|
+
- `bids-editor` – standalone metadata editor
|
|
75
|
+
- `fill-bids-ignore` – interactively update `.bidsignore`
|
|
76
|
+
|
|
77
|
+
All utilities provide `-h/--help` for details.
|
|
78
|
+
|
|
79
|
+
### Recent updates
|
|
80
|
+
|
|
81
|
+
- The TSV produced by `dicom-inventory` can now be loaded directly in the GUI and
|
|
82
|
+
its file name customised before generation.
|
|
83
|
+
- The Batch Rename tool previews changes and allows restricting the scope to
|
|
84
|
+
specific subjects.
|
|
85
|
+
- A "Set Intended For" dialog lets you manually edit fieldmap IntendedFor lists
|
|
86
|
+
if the automatic matching needs adjustment.
|
|
87
|
+
- `run-heudiconv` now keeps a copy of `subject_summary.tsv` under `.bids_manager`
|
|
88
|
+
and generates a clean `participants.tsv` using demographics from that file.
|
|
89
|
+
- Re-running `run-heudiconv` on the same dataset now appends new subjects to
|
|
90
|
+
the existing `.bids_manager` records and updates `participants.tsv` instead of
|
|
91
|
+
overwriting them.
|
|
92
|
+
- `dicom-inventory` distinguishes repeated sequences by adding `series_uid` and `rep`
|
|
93
|
+
columns and records `acq_time` for each series in `subject_summary.tsv`.
|
|
94
|
+
- Fieldmap rows for magnitude and phase images are now merged so each acquisition
|
|
95
|
+
appears once with the combined file count, and their `series_uid` values are
|
|
96
|
+
stored as a pipe-separated list so both sequences are converted.
|
|
97
|
+
- `post-conv-renamer` now adds an `IntendedFor` list to each fieldmap JSON so
|
|
98
|
+
fMRI preprocessing tools can automatically match fieldmaps with the relevant
|
|
99
|
+
functional runs.
|
|
100
|
+
- The GUI's Tools menu gained actions to refresh `_scans.tsv` files and edit
|
|
101
|
+
`.bidsignore` entries.
|
|
102
|
+
- The DPI scale dialog now adjusts values in 25% increments and the DPI button
|
|
103
|
+
appears between the CPU and Authorship buttons.
|
|
104
|
+
- On startup the GUI detects the system DPI and applies the matching scale.
|
|
105
|
+
- The scanned data table now provides a "Generate unique IDs" button that
|
|
106
|
+
assigns random 3‑letter/3‑digit identifiers to subjects. If an entry already
|
|
107
|
+
exists for the same study in an existing `.bids_manager/subject_summary.tsv`,
|
|
108
|
+
you are prompted to reuse its identifier.
|
|
109
|
+
- A "Detect repeats" button can recompute repetition numbers based on
|
|
110
|
+
acquisition time when all BIDS and given names are filled.
|
|
111
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""BIDS Manager package."""
|
|
2
|
+
|
|
3
|
+
from importlib import metadata
|
|
4
|
+
|
|
5
|
+
__all__ = ["__version__"]
|
|
6
|
+
|
|
7
|
+
try: # pragma: no cover - version resolution
|
|
8
|
+
__version__ = metadata.version("bids-manager")
|
|
9
|
+
except metadata.PackageNotFoundError: # pragma: no cover
|
|
10
|
+
__version__ = "0.0.0"
|
|
11
|
+
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
build_heuristic_from_tsv.py — **v10**
|
|
4
|
+
====================================
|
|
5
|
+
Simple heuristic that:
|
|
6
|
+
1. **Keeps every sequence**, including SBRef.
|
|
7
|
+
2. **Uses the raw SeriesDescription** (cleaned) as the filename stem – no
|
|
8
|
+
added `rep-*`, task, or echo logic.
|
|
9
|
+
3. Skips only modalities listed in `SKIP_BY_DEFAULT` (`report`,
|
|
10
|
+
`physio`, `refscan`).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from textwrap import dedent
|
|
16
|
+
import pandas as pd
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
# -----------------------------------------------------------------------------
|
|
20
|
+
# Configuration
|
|
21
|
+
# -----------------------------------------------------------------------------
|
|
22
|
+
SKIP_BY_DEFAULT = {"report", "physio", "refscan"}
|
|
23
|
+
|
|
24
|
+
# -----------------------------------------------------------------------------
|
|
25
|
+
# Helper functions
|
|
26
|
+
# -----------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
def clean(text: str) -> str:
|
|
29
|
+
"""Return alphanumerics only (for variable names)."""
|
|
30
|
+
return re.sub(r"[^0-9A-Za-z]+", "", str(text))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def safe_stem(seq: str) -> str:
|
|
34
|
+
"""Clean SeriesDescription for use in a filename."""
|
|
35
|
+
return re.sub(r"[^0-9A-Za-z_-]+", "_", seq.strip()).strip("_")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def dedup_parts(*parts: str) -> str:
|
|
39
|
+
"""Return underscore-joined *parts* with consecutive repeats removed."""
|
|
40
|
+
tokens: list[str] = []
|
|
41
|
+
for part in parts:
|
|
42
|
+
for t in str(part).split("_"):
|
|
43
|
+
if t and (not tokens or t != tokens[-1]):
|
|
44
|
+
tokens.append(t)
|
|
45
|
+
return "_".join(tokens)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# -----------------------------------------------------------------------------
|
|
49
|
+
# Core writer
|
|
50
|
+
# -----------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
def write_heuristic(df: pd.DataFrame, dst: Path) -> None:
|
|
53
|
+
"""Write a HeuDiConv heuristic from ``df`` to ``dst``.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
df : pandas.DataFrame
|
|
58
|
+
Table generated by :mod:`dicom_inventory` describing the DICOM series.
|
|
59
|
+
dst : Path
|
|
60
|
+
Destination ``heuristic_<name>.py`` file.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
print("Building heuristic (v10)…")
|
|
64
|
+
buf: list[str] = []
|
|
65
|
+
|
|
66
|
+
# 1 ─ header -----------------------------------------------------------
|
|
67
|
+
buf.append(
|
|
68
|
+
dedent(
|
|
69
|
+
'''\
|
|
70
|
+
"""AUTO-GENERATED HeuDiConv heuristic (v10)."""
|
|
71
|
+
from typing import Tuple
|
|
72
|
+
|
|
73
|
+
def create_key(template: str,
|
|
74
|
+
outtype: Tuple[str, ...] = ("nii.gz",),
|
|
75
|
+
annotation_classes=None):
|
|
76
|
+
if not template:
|
|
77
|
+
raise ValueError("Template must be non-empty")
|
|
78
|
+
return template, outtype, annotation_classes
|
|
79
|
+
'''
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# 2 ─ SID_MAP ----------------------------------------------------------
|
|
84
|
+
sid_pairs = {(clean(str(r.source_folder)) or clean(Path(r.source_folder or '.').name), r.BIDS_name) for r in df.itertuples()}
|
|
85
|
+
buf.append("\nSID_MAP = {\n")
|
|
86
|
+
for folder, bids in sorted(sid_pairs):
|
|
87
|
+
buf.append(f" '{folder}': '{bids}',\n")
|
|
88
|
+
buf.append("}\n\n")
|
|
89
|
+
|
|
90
|
+
# 3 ─ template keys ----------------------------------------------------
|
|
91
|
+
# Include series UID (or rep) in the key to handle repeated sequences
|
|
92
|
+
seq2key: dict[tuple[str, str, str, str, str], str] = {}
|
|
93
|
+
key_defs: list[tuple[str, str]] = []
|
|
94
|
+
|
|
95
|
+
rep_counts = (
|
|
96
|
+
df.groupby(["BIDS_name", "session", "sequence"], dropna=False)["sequence"].transform("count")
|
|
97
|
+
)
|
|
98
|
+
rep_index = (
|
|
99
|
+
df.groupby(["BIDS_name", "session", "sequence"], dropna=False).cumcount() + 1
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
key_def_set = set()
|
|
103
|
+
for idx, row in df.iterrows():
|
|
104
|
+
ses_raw = row.get("session", "")
|
|
105
|
+
ses = "" if pd.isna(ses_raw) else str(ses_raw).strip()
|
|
106
|
+
folder = Path(str(row.get("source_folder", "."))).name
|
|
107
|
+
rep_num = rep_index.loc[idx]
|
|
108
|
+
uid_field = str(row.get("series_uid", ""))
|
|
109
|
+
bids = row["BIDS_name"]
|
|
110
|
+
container = row.get("modality_bids", "misc") or "misc"
|
|
111
|
+
stem = safe_stem(row["sequence"])
|
|
112
|
+
|
|
113
|
+
base_parts = [bids, ses, stem]
|
|
114
|
+
if rep_counts.loc[idx] > 1:
|
|
115
|
+
base_parts.append(f"rep-{rep_num}")
|
|
116
|
+
base = dedup_parts(*base_parts)
|
|
117
|
+
path = "/".join(p for p in [bids, ses, container] if p)
|
|
118
|
+
template = f"{path}/{base}"
|
|
119
|
+
|
|
120
|
+
key_parts = [bids, ses, stem]
|
|
121
|
+
if rep_counts.loc[idx] > 1:
|
|
122
|
+
key_parts.append(f"rep-{rep_num}")
|
|
123
|
+
key_var = "key_" + clean("_".join(p for p in key_parts if p))
|
|
124
|
+
if key_var not in key_def_set:
|
|
125
|
+
key_defs.append((key_var, template))
|
|
126
|
+
key_def_set.add(key_var)
|
|
127
|
+
|
|
128
|
+
uid_list = [u for u in uid_field.split("|") if u] or [""]
|
|
129
|
+
for uid in uid_list:
|
|
130
|
+
key_id = (row["sequence"], row["BIDS_name"], ses, folder, uid)
|
|
131
|
+
if key_id in seq2key:
|
|
132
|
+
continue
|
|
133
|
+
seq2key[key_id] = key_var
|
|
134
|
+
|
|
135
|
+
for var, tpl in key_defs:
|
|
136
|
+
buf.append(f"{var} = create_key('{tpl}')\n")
|
|
137
|
+
buf.append("\n")
|
|
138
|
+
|
|
139
|
+
# 4 ─ infotodict() ----------------------------------------------------
|
|
140
|
+
buf.append("def infotodict(seqinfo):\n \"\"\"Return mapping SeriesDescription → key list.\"\"\"\n")
|
|
141
|
+
for var in seq2key.values():
|
|
142
|
+
buf.append(f" {var}_list = []\n")
|
|
143
|
+
buf.append(" info = {\n")
|
|
144
|
+
for var in seq2key.values():
|
|
145
|
+
buf.append(f" {var}: {var}_list,\n")
|
|
146
|
+
buf.append(" }\n\n")
|
|
147
|
+
|
|
148
|
+
buf.append(" for s in seqinfo:\n")
|
|
149
|
+
for (seq, _b, _s, folder, uid), var in seq2key.items():
|
|
150
|
+
seq_esc = seq.replace("'", "\\'")
|
|
151
|
+
fol_esc = folder.replace("'", "\\'")
|
|
152
|
+
uid_esc = str(uid).replace("'", "\\'")
|
|
153
|
+
buf.append(
|
|
154
|
+
f" if s.series_description == '{seq_esc}' and s.dcm_dir_name == '{fol_esc}' and getattr(s, 'series_uid', '') == '{uid_esc}':\n"
|
|
155
|
+
)
|
|
156
|
+
buf.append(f" {var}_list.append(s.series_id)\n")
|
|
157
|
+
buf.append(" return info\n")
|
|
158
|
+
|
|
159
|
+
dst.write_text("".join(buf), encoding="utf-8")
|
|
160
|
+
print("Heuristic written →", dst.resolve())
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# -----------------------------------------------------------------------------
|
|
164
|
+
# Driver
|
|
165
|
+
# -----------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
def generate(tsv: Path, out_dir: Path) -> None:
|
|
168
|
+
"""Generate heuristic files for each study described in ``tsv``.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
tsv : Path
|
|
173
|
+
Path to ``subject_summary.tsv`` produced by :mod:`dicom_inventory`.
|
|
174
|
+
out_dir : Path
|
|
175
|
+
Directory where the heuristic files will be written.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
df = pd.read_csv(tsv, sep="\t", keep_default_na=False)
|
|
179
|
+
|
|
180
|
+
# Drop rows with unwanted modalities
|
|
181
|
+
mask = df.modality.isin(SKIP_BY_DEFAULT)
|
|
182
|
+
if mask.any():
|
|
183
|
+
df.loc[mask, "include"] = 0
|
|
184
|
+
print(f"Auto‑skipped {mask.sum()} rows ({', '.join(SKIP_BY_DEFAULT)})")
|
|
185
|
+
|
|
186
|
+
df = df[df.include == 1]
|
|
187
|
+
|
|
188
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
189
|
+
|
|
190
|
+
for study, sub_df in df.groupby("StudyDescription"):
|
|
191
|
+
fname = safe_stem(study or "unknown")
|
|
192
|
+
heur = out_dir / f"heuristic_{fname}.py"
|
|
193
|
+
write_heuristic(sub_df, heur)
|
|
194
|
+
folders = " ".join(sorted({clean(f) or clean(Path(f or '.').name) for f in sub_df.source_folder.unique()}))
|
|
195
|
+
print(dedent(f"""
|
|
196
|
+
heudiconv -d "<RAW_ROOT>/{{subject}}/**/*.*" -s {folders} -f {heur.name} -c dcm2niix -o <BIDS_OUT>/{fname} -b --minmeta --overwrite"""))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def main() -> None:
|
|
200
|
+
"""Entry point for the ``build-heuristic`` command line utility."""
|
|
201
|
+
|
|
202
|
+
import argparse
|
|
203
|
+
|
|
204
|
+
parser = argparse.ArgumentParser(description="Generate HeuDiConv heuristic(s) from TSV")
|
|
205
|
+
parser.add_argument("tsv", help="Path to subject_summary.tsv file")
|
|
206
|
+
parser.add_argument("out_dir", help="Directory to write heuristic files")
|
|
207
|
+
args = parser.parse_args()
|
|
208
|
+
|
|
209
|
+
generate(Path(args.tsv), Path(args.out_dir))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
main()
|
|
214
|
+
|