-
Notifications
You must be signed in to change notification settings - Fork 36
Added the feature to read DICOM images into a 4D Nifti image #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jph6366
wants to merge
28
commits into
OSIPI:main
Choose a base branch
from
jph6366:dicom2niix
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
4bc40f2
Added the feature to read DICOM images into a 4D Nifti image utilizin…
jph6366 1e060cd
Update README.md
jph6366 a4ce220
[Feature] <Allow reading of DICOM images> #68
jph6366 e331dfc
Merge branch 'dicom2niix' of https://github.com/jph6366/TF2.4_IVIM-MR…
jph6366 1247c2c
Update README.md
jph6366 a5f63e6
[Feature] <Allow reading of DICOM images> #68
jph6366 0e496b6
[Feature] <Allow reading of DICOM images> #68
jph6366 ae56831
Update README.md
jph6366 317d72d
[Feature] <Allow reading of DICOM images> #68
jph6366 783e2f3
Merge branch 'dicom2niix' of https://github.com/jph6366/TF2.4_IVIM-MR…
jph6366 5347a31
[Feature] <Allow reading of DICOM images> #68
jph6366 22df73a
[Feature] <Allow reading of DICOM images> #68
jph6366 d18c2e6
Update README.md
jph6366 3dbb64c
[Feature] <Allow reading of DICOM images> #68
jph6366 606b667
Merge branch 'dicom2niix' of https://github.com/jph6366/TF2.4_IVIM-MR…
jph6366 9a17fb1
Update README.md
jph6366 614c8f8
[Feature] <Allow reading of DICOM images> #68
jph6366 dda0e88
Merge branch 'dicom2niix' of https://github.com/jph6366/TF2.4_IVIM-MR…
jph6366 ac752cb
Update pyproject.toml
jph6366 92abf87
Update generate_signal_docker_test.py
jph6366 201f495
[Feature] <Allow reading of DICOM images> #68
jph6366 d32460b
Merge branch 'dicom2niix' of https://github.com/jph6366/TF2.4_IVIM-MR…
jph6366 9cd090f
Update pyproject.toml
jph6366 30a0603
Update README.md
jph6366 64dc1bc
updated job to be fixed or otherwise log error to help fix
jph6366 682d655
Merge branch 'dicom2niix' of https://github.com/jph6366/TF2.4_IVIM-MR…
jph6366 814cdc9
#68
jph6366 015f7dc
tested locally successfully
jph6366 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
FROM ubuntu:jammy AS build | ||
jph6366 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
build-essential \ | ||
cmake \ | ||
git \ | ||
libssl-dev \ | ||
wget \ | ||
pigz \ | ||
ca-certificates \ | ||
&& update-ca-certificates \ | ||
&& wget https://github.com/rordenlab/dcm2niix/archive/refs/tags/v1.0.20241211.tar.gz -O /tmp/dcm2niix.tar.gz \ | ||
jph6366 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
&& mkdir -p /tmp/dcm2niix && tar -xzf /tmp/dcm2niix.tar.gz -C /tmp/dcm2niix --strip-components=1 \ | ||
&& mkdir /tmp/dcm2niix/build && cd /tmp/dcm2niix/build \ | ||
&& cmake -DBATCH_VERSION=ON -DZLIB_IMPLEMENTATION=Cloudflare -DUSE_JPEGLS=ON -DUSE_OPENJPEG=ON .. \ | ||
&& make && make install \ | ||
&& rm -rf /tmp/dcm2niix* \ | ||
&& apt-get remove -y wget git cmake \ | ||
&& apt-get autoremove -y \ | ||
&& apt-get clean \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
||
|
||
FROM python:3.11-slim | ||
|
||
WORKDIR /usr/src/app | ||
|
||
RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
pigz \ | ||
&& apt-get clean \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
||
COPY --from=build /usr/local/bin/dcm2niix /usr/local/bin/dcm2niix | ||
|
||
COPY ../../requirements.txt ./ | ||
|
||
RUN pip install --no-cache-dir -r requirements.txt | ||
|
||
COPY ../.. . | ||
|
||
ENTRYPOINT ["python3", "-m", "WrapImage.inquire_dicom2niix"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
import os | ||
from pathlib import Path | ||
import subprocess | ||
import yaml | ||
import inquirer | ||
|
||
def prompt_input_directory(): | ||
return inquirer.prompt([ | ||
inquirer.Path( | ||
"path", | ||
message="📂 Select an input directory containing DICOM image files:", | ||
path_type=inquirer.Path.DIRECTORY, | ||
exists=True, | ||
) | ||
])["path"] | ||
|
||
|
||
def prompt_output_directory(input_dir): | ||
# List subfolders of input_dir | ||
subdirs = [ | ||
name for name in os.listdir(input_dir) | ||
if os.path.isdir(os.path.join(input_dir, name)) | ||
] | ||
|
||
choices = [f"./{name}" for name in subdirs] | ||
choices.append("📥 Enter a custom output path...") | ||
|
||
answer = inquirer.prompt([ | ||
inquirer.List( | ||
"choice", | ||
message=f"📁 Choose an output directory for NIfTI files from:\n → {input_dir}", | ||
choices=choices | ||
) | ||
])["choice"] | ||
|
||
if answer == "📥 Enter a custom output path...": | ||
return inquirer.prompt([ | ||
inquirer.Path( | ||
"custom_path", | ||
message="📥 Enter custom output directory path:", | ||
path_type=inquirer.Path.DIRECTORY, | ||
exists=True | ||
) | ||
])["custom_path"] | ||
else: | ||
return os.path.abspath(os.path.join(input_dir, answer.strip("./"))) | ||
|
||
|
||
def generate_batch_config( | ||
input_dirs, | ||
output_dirs, | ||
config_path="batch_config.yaml" | ||
): | ||
""" | ||
input_dirs: List of input directory paths | ||
output_dirs: Single output directory (string) OR list of output directories | ||
""" | ||
|
||
# Normalize inputs | ||
if isinstance(output_dirs, str): | ||
output_dirs = [output_dirs] * len(input_dirs) | ||
elif isinstance(output_dirs, list): | ||
if len(input_dirs) != 1 and len(output_dirs) != len(input_dirs): | ||
raise ValueError("Number of output directories must match number of input directories, unless only one output directory is provided.") | ||
|
||
config = { | ||
"Options": { | ||
"isGz": True, # compressed nii.gz | ||
"isFlipY": False, # flip Y-axis of images | ||
"isVerbose": False, # by default is verbose; this value does not change anything | ||
"isCreateBIDS": True, # create BIDS-compatible NIfTI and JSON files | ||
"isOnlySingleFile": False | ||
}, | ||
"Files": [] | ||
} | ||
|
||
for in_dir, out_dir in zip(input_dirs, output_dirs): | ||
if not os.path.isdir(in_dir): | ||
print(f"Warning: {in_dir} is not a valid directory.") | ||
continue | ||
|
||
files = os.listdir(in_dir) | ||
if not files: | ||
print(f"No files found in {in_dir}") | ||
continue | ||
|
||
files = [f for f in os.listdir(in_dir) if os.path.isfile(os.path.join(in_dir, f))] | ||
if not files: | ||
print(f"No files found in {in_dir}") | ||
continue | ||
|
||
for f in files: | ||
filename = os.path.splitext(f)[0].replace(" ", "_").lower() | ||
|
||
config["Files"].append({ | ||
"in_dir": os.path.abspath(in_dir), | ||
"out_dir": os.path.abspath(out_dir), | ||
"filename": filename | ||
}) | ||
|
||
with open(config_path, 'w') as f: | ||
yaml.dump(config, f, sort_keys=False, default_flow_style=False) | ||
|
||
print(f"Config written to {config_path}") | ||
|
||
def dicom_to_niix(vol_dir: Path, out_dir: Path, merge_2d: bool = False): | ||
""" | ||
For converting DICOM images to a (compresssed) 4d nifti image | ||
""" | ||
os.makedirs(out_dir, exist_ok=True) | ||
|
||
try: | ||
res = subprocess.run( | ||
[ | ||
"dcm2niix", | ||
"-f", "%s_%p", # dcm2niix attempts to provide a sensible file naming scheme | ||
"-o", out_dir, # output directory | ||
"-z", "y", #specifying compressed nii.gz file | ||
"-m", "y" if merge_2d else "n", # Add merge option | ||
# https://www.nitrc.org/plugins/mwiki/index.php/dcm2nii:MainPage | ||
# for further configuration for general usage see page above | ||
vol_dir # input directory | ||
], | ||
capture_output=True, | ||
text=True, | ||
check=True | ||
) | ||
|
||
nifti_files = list(Path(out_dir).glob("*.nii.gz")) | ||
if not nifti_files: | ||
raise RuntimeError("No NIfTI (.nii.gz) files were generated.") | ||
|
||
bval_files = list(out_dir.glob("*.bval")) | ||
bvec_files = list(out_dir.glob("*.bvec")) | ||
bval_path = str(bval_files[0]) if bval_files else None | ||
jph6366 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bvec_path = str(bvec_files[0]) if bvec_files else None | ||
|
||
|
||
return nifti_files[0], bval_path, bvec_path | ||
jph6366 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
except subprocess.CalledProcessError as e: | ||
raise RuntimeError(f"dcm2niix failed: {e.stderr}") | ||
|
||
if __name__ == "__main__": | ||
input_dirs = [] | ||
output_dirs = [] | ||
|
||
print("Inquiring to convert your DICOM Images to NIfTI Image(s) \n") | ||
|
||
while True: | ||
input_dir = prompt_input_directory() | ||
jph6366 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
output_dir = prompt_output_directory(input_dir) | ||
|
||
input_dirs.append(input_dir) | ||
output_dirs.append(output_dir) | ||
|
||
add_more = inquirer.prompt([ | ||
inquirer.Confirm( | ||
"more", | ||
message="➕ Do you want to add another input/output pair?", | ||
default=False | ||
) | ||
])["more"] | ||
|
||
if not add_more: | ||
break | ||
|
||
if len(input_dirs) == 1: | ||
print("📥 Single input/output pair detected — using `dcm2niix`...") | ||
vol_dir = Path(input_dirs[0]) | ||
out_dir = Path(output_dirs[0]) | ||
|
||
merge_answer = inquirer.prompt([ | ||
inquirer.Confirm( | ||
"merge", | ||
message="🧩 Do you want to merge 2D slices into a single NIfTI (-m y)?", | ||
default=True | ||
) | ||
]) | ||
merge_2d = merge_answer["merge"] | ||
|
||
try: | ||
nifti, bval, bvec = dicom_to_niix(vol_dir, out_dir, merge_2d=merge_2d) | ||
print(f"\n✅ NIfTI file created: {nifti}") | ||
if bval: | ||
print(f" 📈 bval: {bval}") | ||
jph6366 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if bvec: | ||
print(f" 📊 bvec: {bvec}") | ||
|
||
post_process = inquirer.prompt([ | ||
inquirer.Confirm( | ||
"run_post", | ||
message="🧩 Do you want to run IVIM fit algorithm on the NIfTI file now?", | ||
default=True | ||
) | ||
])["run_post"] | ||
|
||
if bvec and bval and post_process: | ||
print("\n🧩 Running post-processing: OSIPI IVIM fitting...\n") | ||
subprocess.run([ | ||
"python3", "-m", "WrapImage.nifti_wrapper", | ||
str(nifti), | ||
str(bvec), | ||
str(bval), | ||
], check=True) | ||
print("✅ NIfTI post-processing completed.") | ||
except RuntimeError as err: | ||
print(f"❌ Conversion failed: {err}") | ||
|
||
else: | ||
print("📥📥📥 Multiple inputs detected — generating batch config and using `dcm2niibatch`...") | ||
config_path = "batch_config.yaml" | ||
generate_batch_config(input_dirs, output_dirs, config_path=config_path) | ||
|
||
try: | ||
with subprocess.Popen( | ||
jph6366 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
["dcm2niibatch", config_path], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.STDOUT, | ||
text=True, | ||
bufsize=1 | ||
) as proc: | ||
# for line in proc.stdout: # uncomment this to see verbose logs | ||
# print(line.strip()) | ||
proc.wait() | ||
if proc.returncode != 0: | ||
raise subprocess.CalledProcessError(proc.returncode, proc.args) | ||
|
||
print("✅ Batch conversion completed successfully.") | ||
|
||
except subprocess.CalledProcessError as err: | ||
print(f"❌ Batch conversion failed:\n{err}") | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
numpy<2 | ||
nibabel | ||
inquirer | ||
pyyaml | ||
scipy | ||
torchio | ||
torch | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.