Skip to content

Commit 5347a31

Browse files
committed
[Feature] <Allow reading of DICOM images> #68
Fixed up readme and enabled the output to buffered line by line for our subprocess output
1 parent 783e2f3 commit 5347a31

File tree

2 files changed

+92
-12
lines changed

2 files changed

+92
-12
lines changed

Docker/README.md

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,32 +81,66 @@ Before running the Docker container, here are the available options for the `Doc
8181

8282
- You can run the dicom2niix_wrapper.py script either directly or from inside a Docker container (non-interactive only). Here are the available options:
8383

84-
#### Required (CLI Mode Only)
84+
##### example usage:
85+
86+
```sh
87+
sudo docker run -it --rm --name TF2.4_IVIM-MRI_CodeCollection \
88+
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/src/app \
89+
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/app/output \
90+
tf2.4_ivim-mri_codecollection \
91+
/usr/src/app/dicom_folder /usr/app/output -n -1
92+
```
93+
94+
```sh
95+
sudo docker run -it --rm --name TF2.4_IVIM-MRI_CodeCollection \
96+
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/src/app \
97+
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/app/output \
98+
tf2.4_ivim-mri_codecollection \
99+
/usr/src/app/dicom_folder /usr/app/output -m
100+
```
101+
102+
```sh
103+
sudo docker run -it --rm --name TF2.4_IVIM-MRI_CodeCollection \
104+
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/src/app \
105+
-v ~/TF2.4_IVIM-MRI_CodeCollection:/usr/app/output \
106+
tf2.4_ivim-mri_codecollection \
107+
/usr/src/app/dicom_file /usr/app/output -s
108+
```
109+
110+
#### Required parameters
111+
85112
input: Path to the input DICOM directory.
86113

87114
output: Path to the output directory for the converted NIfTI files.
88115

89116
#### Optional Flags
90-
-n, --series-number:
117+
118+
#### -n, --series-number
119+
91120
Convert only the specified DICOM series number.
92-
Example: --series-number 5
121+
A special feature of this option is unleashed when you provide a negative series number ("-n -1"): in this case the software will report the series numbers available in the input folder, but will convert nothing.
122+
123+
#### -m, --merge-2d
124+
125+
Merge 2D slices into a 3D or 4D NIfTI image regardless of study time, echo, coil, orientation, etc.
126+
Depending on your vendor, you may want to keep images segmented based on these attributes or merge/combine them.
93127

94-
-m, --merge-2d:
95-
Merge 2D slices into a 3D or 4D volume if supported.
96-
Example: --merge-2d
128+
#### -s, --single-file
97129

98-
-s, --single-file:
99130
Use single file mode (convert only one series per folder).
100-
Example: --single-file
101131

102-
-pu, --prompt-user:
132+
For example, if the input path "~/dir/001.dcm" will only convert the file 001.dcm.
133+
134+
#### -pu, --prompt-user
135+
103136
Run the tool in interactive mode. This launches a terminal-based wizard where you can select DICOM folders and configure conversion interactively.
104137

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

107140
![image](https://github.com/user-attachments/assets/8ea21692-36ac-4773-aec7-6cb3a6838055)
108141

109-
##### The goal of dcm2niix is to create FSL format bvec/bval files for processing. A crucial concern is ensuring that the gradient directions are reported in the frame of reference expected by the software you use to fit your tractography. [dicom2niix should generate a ".bvec" file that reports the tensors as expected](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging) by FSL's dtifit, where vectors are reported relative to image frame of reference (rather than relative to the scanner bore).
142+
##### The goal of dcm2niix is to create FSL format bvec/bval files for processing. A crucial concern is ensuring that the gradient directions are reported in the frame of reference expected by the software you use to fit your tractography. [dicom2niix should generate a ".bvec" file that reports the tensors as expected](https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage#Diffusion_Tensor_Imaging) by FSL's dtifit, where vectors are reported relative to image frame of reference (rather than relative to the scanner bore)
110143

111144
#### It is strongly recommend that users check validate the b-vector directions for their hardware and sequence as [described in a dedicated document](https://www.nitrc.org/docman/?group_id=880)
145+
112146
---

WrapImage/dicom2niix_wrapper.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import argparse
2+
import io
23
import os
34
from pathlib import Path
5+
import selectors
46
import subprocess
57
import sys
68
import inquirer
@@ -69,7 +71,10 @@ def dicom_to_niix(vol_dir: Path, out_dir: Path, merge_2d: bool = False, series_n
6971
cmd.append(str(vol_dir)) # input directory
7072

7173
try:
72-
res = subprocess.run(cmd, capture_output=True, text=True, check=True)
74+
success, output = capture_subprocess_output(cmd)
75+
print(output)
76+
if not success:
77+
raise RuntimeError(f"dcm2niix failed: {output}")
7378

7479
nifti_files = list(Path(out_dir).glob("*.nii.gz"))
7580

@@ -98,6 +103,48 @@ def dicom_to_niix(vol_dir: Path, out_dir: Path, merge_2d: bool = False, series_n
98103

99104
except subprocess.CalledProcessError as e:
100105
raise RuntimeError(f"dcm2niix failed: {e.stderr}")
106+
107+
108+
def capture_subprocess_output(subprocess_args):
109+
process = subprocess.Popen(
110+
subprocess_args,
111+
bufsize=1, # output is line buffered
112+
stdout=subprocess.PIPE,
113+
stderr=subprocess.STDOUT,
114+
universal_newlines=True # for line buffering
115+
)
116+
117+
buf = io.StringIO() # callback for output
118+
def handle_output(stream, mask):
119+
line = stream.readline()
120+
buf.write(line)
121+
sys.stdout.write(line)
122+
123+
selector = selectors.DefaultSelector() # register callback
124+
selector.register(process.stdout, selectors.EVENT_READ, handle_output) # for 'read' event from subprocess stdout stream
125+
126+
while process.poll() is None:
127+
events = selector.select()
128+
for key, mask in events:
129+
callback = key.data
130+
callback(key.fileobj, mask)
131+
132+
# ensure all remaining output is processed
133+
while True:
134+
line = process.stdout.readline()
135+
if not line:
136+
break
137+
buf.write(line)
138+
sys.stdout.write(line)
139+
140+
return_code = process.wait()
141+
selector.close()
142+
143+
success = (return_code == 0)
144+
output = buf.getvalue()
145+
buf.close()
146+
147+
return success, output
101148

102149
def run_interactive():
103150

@@ -178,7 +225,6 @@ def run_cli(input_path: str, output_path: str, **kwargs):
178225
parser.add_argument("-s", "--single-file", action="store_true", help="Enable single file mode (-s y)")
179226
parser.add_argument("-pu", "--prompt-user", action="store_true", help="Run in interactive mode")
180227

181-
182228
args = parser.parse_args()
183229

184230
if args.prompt_user:

0 commit comments

Comments
 (0)