Skip to content

Commit 317d72d

Browse files
committed
[Feature] <Allow reading of DICOM images> #68
updates to the CLI and conversion error handling
1 parent 0e496b6 commit 317d72d

File tree

2 files changed

+73
-29
lines changed

2 files changed

+73
-29
lines changed

Docker/README.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ Before running the Docker container, here are the available options for the `Doc
6565

6666
Replace `brain.nii.gz`, `brain.bvec`, and `brain.bval` with the actual file names you want to use.
6767

68+
69+
6870
## Running the Docker container for reading in DICOM Images
6971

7072
- You can run the dicom2nifti Docker container using the `docker run` command. This command runs the Docker image with the specified input files:
@@ -77,15 +79,28 @@ Before running the Docker container, here are the available options for the `Doc
7779
/usr/src/app/dicom_folder /usr/app/output
7880
```
7981

80-
- You can also run the command in an interactive mode if you want to convert multiple folders of data and run them consecutively.
82+
- You can run the dicom2niix_wrapper.py script either directly or from inside a Docker container (non-interactive only). Here are the available options:
8183

82-
```sh
83-
python ./WrapImage/dicom2niix_wrapper.py --prompt-user
84-
```
84+
#### Required (CLI Mode Only)
85+
input: Path to the input DICOM directory.
86+
87+
output: Path to the output directory for the converted NIfTI files.
88+
89+
#### Optional Flags
90+
-n, --series-number:
91+
Convert only the specified DICOM series number.
92+
Example: --series-number 5
93+
94+
-m, --merge-2d:
95+
Merge 2D slices into a 3D or 4D volume if supported.
96+
Example: --merge-2d
8597

86-
It then prompts for inquiring information of DICOM Images in your terminal.
98+
-s, --single-file:
99+
Use single file mode (convert only one series per folder).
100+
Example: --single-file
87101

88-
The interactive version of this script is not suited for use in container.
102+
-pu, --prompt-user:
103+
Run the tool in interactive mode. This launches a terminal-based wizard where you can select DICOM folders and configure conversion interactively.
89104

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

WrapImage/dicom2niix_wrapper.py

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,33 +47,45 @@ def prompt_output_directory(input_dir):
4747
return os.path.abspath(os.path.join(input_dir, answer.strip("./")))
4848

4949

50-
def dicom_to_niix(vol_dir: Path, out_dir: Path, merge_2d: bool = False):
50+
def dicom_to_niix(vol_dir: Path, out_dir: Path, merge_2d: bool = False, series_num: int = -1, is_single_file: bool = False):
5151
"""
5252
For converting DICOM images to a (compresssed) 4d nifti image
5353
"""
54+
5455
os.makedirs(out_dir, exist_ok=True)
5556

57+
cmd = [
58+
"dcm2niix",
59+
"-f", "%s_%p", # dcm2niix attempts to provide a sensible file naming scheme
60+
"-o", out_dir, # output directory
61+
"-z", "y", #specifying compressed nii.gz file
62+
"-m", "y" if merge_2d else "n", # Add merge option
63+
"-s", "y" if is_single_file else "n",
64+
# https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage
65+
# for further configuration for general usage see page above
66+
]
67+
if series_num >= 0:
68+
cmd.extend(["-n", str(series_num)])
69+
cmd.append(str(vol_dir)) # input directory
70+
5671
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+
res = subprocess.run(cmd, capture_output=True, text=True, check=True)
7273

7374
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.")
7675

76+
if (is_single_file or merge_2d or series_num >= 0):
77+
if len(nifti_files) != 1:
78+
raise RuntimeError(
79+
f"Expected a single .nii.gz output due to flags "
80+
f"{'-s' if is_single_file else ''} "
81+
f"{'-m' if merge_2d else ''} "
82+
f"{f'-n {series_num}' if series_num >= 0 else ''}, "
83+
f"but found {len(nifti_files)} files."
84+
)
85+
else:
86+
if len(nifti_files) < 1:
87+
raise RuntimeError("No NIfTI (.nii.gz) files were generated.")
88+
7789
bval_files = list(out_dir.glob("*.bval"))
7890
bvec_files = list(out_dir.glob("*.bvec"))
7991
bval_path = str(bval_files[0]) if bval_files else None
@@ -106,29 +118,42 @@ def run_interactive():
106118
if not add_more:
107119
break
108120

121+
series_num = inquirer.prompt([
122+
inquirer.Text("series", message="🔢 Enter series number to convert (-n <num>) [Leave blank for all]", default="")
123+
])["series"]
124+
series_num = int(series_num) if series_num.isdigit() else -1
125+
109126
merge_answer = inquirer.prompt([
110127
inquirer.Confirm("merge", message="🧩 Merge 2D slices into a single NIfTI (-m y)?", default=True)
111128
])
112129
merge_2d = merge_answer["merge"]
113130

131+
single_file = inquirer.prompt([
132+
inquirer.Confirm("single", message="📦 Force single file output (-s y)?", default=False)
133+
])["single"]
134+
114135
for in_dir, out_dir in zip(input_dirs, output_dirs):
115136
vol_dir = Path(in_dir)
116137
out_path = Path(out_dir)
117138

118139
print(f"Converting:\n → Input: {vol_dir}\n → Output: {out_path}")
119140
try:
120-
nifti, bval, bvec = dicom_to_niix(vol_dir, out_path, merge_2d)
121-
print(f"✅ Created: {nifti}")
141+
nifti, bval, bvec = dicom_to_niix(vol_dir, out_path, merge_2d, series_num, single_file)
142+
print(f" Conversion succeeded: {nifti}")
122143
except RuntimeError as err:
123144
print(f"❌ Conversion failed: {err}")
124145

125-
def run_cli(input_path: str, output_path: str):
146+
def run_cli(input_path: str, output_path: str, **kwargs):
126147
vol_dir = Path(input_path)
127148
out_dir = Path(output_path)
128149

150+
merge_2d = kwargs.get("merge_2d", False)
151+
series_num = kwargs.get("series_number", -1)
152+
single_file = kwargs.get("single_file", False)
153+
129154
print(f" Converting:\n → Input: {vol_dir}\n → Output: {out_dir}")
130155
try:
131-
nifti, bval, bvec = dicom_to_niix(vol_dir, out_dir, merge_2d=False)
156+
nifti, bval, bvec = dicom_to_niix(vol_dir, out_dir, merge_2d, series_num, single_file)
132157
print(f" Created NIfTI: {nifti}")
133158

134159
if bval and bvec:
@@ -148,14 +173,18 @@ def run_cli(input_path: str, output_path: str):
148173
parser = argparse.ArgumentParser(description="DICOM to NIfTI converter with optional IVIM processing")
149174
parser.add_argument("input", nargs="?", help="Path to input DICOM directory")
150175
parser.add_argument("output", nargs="?", help="Path to output directory for NIfTI files")
176+
parser.add_argument("-n", "--series-number", type=int, default=-1, help="Only convert this series number (-n <num>)")
177+
parser.add_argument("-m", "--merge-2d", action="store_true", help="Merge 2D slices (-m y)")
178+
parser.add_argument("-s", "--single-file", action="store_true", help="Enable single file mode (-s y)")
151179
parser.add_argument("-pu", "--prompt-user", action="store_true", help="Run in interactive mode")
152180

181+
153182
args = parser.parse_args()
154183

155184
if args.prompt_user:
156185
run_interactive()
157186
elif args.input and args.output:
158-
run_cli(args.input, args.output)
187+
run_cli(args.input, args.output, **vars(args))
159188
else:
160189
print("❗ You must provide input and output paths OR use --prompt-user for interactive mode.")
161190
parser.print_help()

0 commit comments

Comments
 (0)