Skip to content

Commit 0e496b6

Browse files
committed
[Feature] <Allow reading of DICOM images> #68
Updating Dockerfile and refactored inquire to function more like a wrapper to the cli and writing a one-call script for easy streamlined use, but also preserving the interactives for debugging Removed dcm2niibatch as its easier to focus on one command that is better maintained. Also updated the readmee
1 parent a5f63e6 commit 0e496b6

File tree

4 files changed

+183
-241
lines changed

4 files changed

+183
-241
lines changed

Docker/README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ This project is designed to run the `nifti_wrapper` script using a Docker contai
1313
1414
├── Docker/
1515
│ └── Dockerfile
16+
│ └── dicom2nifti/
17+
│ └── Dockerfile
1618
1719
├── WrapImage/
1820
│ └── nifti_wrapper.py
21+
│ └── dicom2niix_wrapper.py
1922
2023
└── requirements.txt
2124
```
@@ -64,16 +67,25 @@ Before running the Docker container, here are the available options for the `Doc
6467

6568
## Running the Docker container for reading in DICOM Images
6669

67-
1. you can run the same Docker container using the `docker run` command. This command runs the Docker image with the specified input files:
70+
- You can run the dicom2nifti Docker container using the `docker run` command. This command runs the Docker image with the specified input files:
6871

6972
```sh
7073
sudo docker run -it --rm --name TF2.4_IVIM-MRI_CodeCollection \
7174
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/src/app \
72-
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/app/output \
73-
tf2.4_ivim-mri_codecollection
75+
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/app/output \
76+
tf2.4_ivim-mri_codecollection \
77+
/usr/src/app/dicom_folder /usr/app/output
78+
```
79+
80+
- You can also run the command in an interactive mode if you want to convert multiple folders of data and run them consecutively.
81+
82+
```sh
83+
python ./WrapImage/dicom2niix_wrapper.py --prompt-user
7484
```
7585

76-
Then answer the prompts for inquiring information of DICOM Images in your terminal.
86+
It then prompts for inquiring information of DICOM Images in your terminal.
87+
88+
The interactive version of this script is not suited for use in container.
7789

7890
[Note that NIfTI and DICOM encode space differently](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Spatial_Coordinates)
7991

Docker/dicom2nifti/Dockerfile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
FROM ubuntu:jammy AS build
1+
FROM debian:stable-slim AS build
2+
3+
ARG DCM2NIIX_VERSION=v1.0.20241211
24

35
RUN apt-get update && apt-get install -y --no-install-recommends \
46
build-essential \
@@ -9,7 +11,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
911
pigz \
1012
ca-certificates \
1113
&& update-ca-certificates \
12-
&& wget https://github.com/rordenlab/dcm2niix/archive/refs/tags/v1.0.20241211.tar.gz -O /tmp/dcm2niix.tar.gz \
14+
&& wget https://github.com/rordenlab/dcm2niix/archive/refs/tags/${DCM2NIIX_VERSION}.tar.gz -O /tmp/dcm2niix.tar.gz \
1315
&& mkdir -p /tmp/dcm2niix && tar -xzf /tmp/dcm2niix.tar.gz -C /tmp/dcm2niix --strip-components=1 \
1416
&& mkdir /tmp/dcm2niix/build && cd /tmp/dcm2niix/build \
1517
&& cmake -DBATCH_VERSION=ON -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON .. \
@@ -20,7 +22,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
2022
&& apt-get clean \
2123
&& rm -rf /var/lib/apt/lists/*
2224

23-
2425
FROM python:3.11-slim
2526

2627
WORKDIR /usr/src/app
@@ -38,4 +39,4 @@ RUN pip install --no-cache-dir -r requirements.txt
3839

3940
COPY ../.. .
4041

41-
ENTRYPOINT ["python3", "-m", "WrapImage.inquire_dicom2niix"]
42+
ENTRYPOINT ["python3", "-m", "WrapImage.dicom2niix_wrapper"]

WrapImage/dicom2niix_wrapper.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import argparse
2+
import os
3+
from pathlib import Path
4+
import subprocess
5+
import sys
6+
import inquirer
7+
8+
def prompt_input_directory():
9+
return inquirer.prompt([
10+
inquirer.Path(
11+
"path",
12+
message="📂 Select an input directory containing DICOM image files:",
13+
path_type=inquirer.Path.DIRECTORY,
14+
exists=True,
15+
)
16+
])["path"]
17+
18+
19+
def prompt_output_directory(input_dir):
20+
# List subfolders of input_dir
21+
subdirs = [
22+
name for name in os.listdir(input_dir)
23+
if os.path.isdir(os.path.join(input_dir, name))
24+
]
25+
26+
choices = [f"./{name}" for name in subdirs]
27+
choices.append("📥 Enter a custom output path...")
28+
29+
answer = inquirer.prompt([
30+
inquirer.List(
31+
"choice",
32+
message=f"📁 Choose an output directory for NIfTI files from:\n{input_dir}",
33+
choices=choices
34+
)
35+
])["choice"]
36+
37+
if answer == "📥 Enter a custom output path...":
38+
return inquirer.prompt([
39+
inquirer.Path(
40+
"custom_path",
41+
message="📥 Enter custom output directory path:",
42+
path_type=inquirer.Path.DIRECTORY,
43+
exists=True
44+
)
45+
])["custom_path"]
46+
else:
47+
return os.path.abspath(os.path.join(input_dir, answer.strip("./")))
48+
49+
50+
def dicom_to_niix(vol_dir: Path, out_dir: Path, merge_2d: bool = False):
51+
"""
52+
For converting DICOM images to a (compresssed) 4d nifti image
53+
"""
54+
os.makedirs(out_dir, exist_ok=True)
55+
56+
try:
57+
res = subprocess.run(
58+
[
59+
"dcm2niix",
60+
"-f", "%s_%p", # dcm2niix attempts to provide a sensible file naming scheme
61+
"-o", out_dir, # output directory
62+
"-z", "y", #specifying compressed nii.gz file
63+
"-m", "y" if merge_2d else "n", # Add merge option
64+
# https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage
65+
# for further configuration for general usage see page above
66+
vol_dir # input directory
67+
],
68+
capture_output=True,
69+
text=True,
70+
check=True
71+
)
72+
73+
nifti_files = list(Path(out_dir).glob("*.nii.gz"))
74+
if len(nifti_files) != 1:
75+
raise RuntimeError("Only one NIfTI (.nii.gz) should be in this output directory.")
76+
77+
bval_files = list(out_dir.glob("*.bval"))
78+
bvec_files = list(out_dir.glob("*.bvec"))
79+
bval_path = str(bval_files[0]) if bval_files else None
80+
bvec_path = str(bvec_files[0]) if bvec_files else None
81+
82+
if not bval_path or not bvec_path:
83+
raise RuntimeError("No bvec or bval files were generated.")
84+
85+
return nifti_files[0], bval_path, bvec_path
86+
87+
except subprocess.CalledProcessError as e:
88+
raise RuntimeError(f"dcm2niix failed: {e.stderr}")
89+
90+
def run_interactive():
91+
92+
input_dirs = []
93+
output_dirs = []
94+
95+
while True:
96+
input_dir = prompt_input_directory()
97+
output_dir = prompt_output_directory(input_dir)
98+
99+
input_dirs.append(input_dir)
100+
output_dirs.append(output_dir)
101+
102+
add_more = inquirer.prompt([
103+
inquirer.Confirm("more", message="➕ Add another input/output pair?", default=False)
104+
])["more"]
105+
106+
if not add_more:
107+
break
108+
109+
merge_answer = inquirer.prompt([
110+
inquirer.Confirm("merge", message="🧩 Merge 2D slices into a single NIfTI (-m y)?", default=True)
111+
])
112+
merge_2d = merge_answer["merge"]
113+
114+
for in_dir, out_dir in zip(input_dirs, output_dirs):
115+
vol_dir = Path(in_dir)
116+
out_path = Path(out_dir)
117+
118+
print(f"Converting:\n → Input: {vol_dir}\n → Output: {out_path}")
119+
try:
120+
nifti, bval, bvec = dicom_to_niix(vol_dir, out_path, merge_2d)
121+
print(f"✅ Created: {nifti}")
122+
except RuntimeError as err:
123+
print(f"❌ Conversion failed: {err}")
124+
125+
def run_cli(input_path: str, output_path: str):
126+
vol_dir = Path(input_path)
127+
out_dir = Path(output_path)
128+
129+
print(f" Converting:\n → Input: {vol_dir}\n → Output: {out_dir}")
130+
try:
131+
nifti, bval, bvec = dicom_to_niix(vol_dir, out_dir, merge_2d=False)
132+
print(f" Created NIfTI: {nifti}")
133+
134+
if bval and bvec:
135+
print(" Running IVIM fitting algorithm...")
136+
subprocess.run([
137+
"python3", "-m", "WrapImage.nifti_wrapper",
138+
str(nifti), str(bvec), str(bval)
139+
], check=True)
140+
print(" IVIM fitting complete.")
141+
else:
142+
print("⚠️ bvec/bval missing, skipping IVIM post-processing.")
143+
except RuntimeError as err:
144+
print(f"❌ Conversion failed: {err}")
145+
sys.exit(1)
146+
147+
if __name__ == "__main__":
148+
parser = argparse.ArgumentParser(description="DICOM to NIfTI converter with optional IVIM processing")
149+
parser.add_argument("input", nargs="?", help="Path to input DICOM directory")
150+
parser.add_argument("output", nargs="?", help="Path to output directory for NIfTI files")
151+
parser.add_argument("-pu", "--prompt-user", action="store_true", help="Run in interactive mode")
152+
153+
args = parser.parse_args()
154+
155+
if args.prompt_user:
156+
run_interactive()
157+
elif args.input and args.output:
158+
run_cli(args.input, args.output)
159+
else:
160+
print("❗ You must provide input and output paths OR use --prompt-user for interactive mode.")
161+
parser.print_help()
162+
sys.exit(1)

0 commit comments

Comments
 (0)