Skip to content

Commit

Permalink
Use Pathlib in path_to_bids and remove real and imaginary images gene…
Browse files Browse the repository at this point in the history
…rated by dcm2niix
  • Loading branch information
NicolasGensollen committed Dec 4, 2023
1 parent 943b742 commit 193e97b
Showing 1 changed file with 167 additions and 136 deletions.
303 changes: 167 additions & 136 deletions clinica/iotools/converters/adni_to_bids/adni_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1217,21 +1217,44 @@ def find_image_path(images, source_dir, modality, prefix, id_field):


def paths_to_bids(
images, bids_dir, modality, mod_to_update=False, n_procs: Optional[int] = 1
):
images: pd.DataFrame,
bids_dir: str,
modality: str,
mod_to_update: bool = False,
n_procs: Optional[int] = 1,
) -> List[Path]:
"""Images in the list are converted and copied to directory in BIDS format.
Args:
images: List of images metadata and paths
bids_dir: Path to the output BIDS directory
modality: Imaging modality
mod_to_update: If True, pre-existing images in the BIDS directory will be erased and extracted agai n.
n_procs: int, optional. The number of processes to use for conversion. Default=1.
Parameters
----------
images : pd.DataFrame
List of images metadata and paths.
bids_dir : str
Path to the output BIDS directory.
modality : str
Imaging modality.
mod_to_update : bool
If True, pre-existing images in the BIDS directory will be erased
and extracted again.
n_procs : int, optional
The number of processes to use for conversion.
Default=1.
Returns
-------
output_file_treated : list of paths
List of path to image files created.
This list contains None values for files where the
conversion wasn't successful.
"""
from functools import partial
from multiprocessing import Pool

if modality.lower() not in [
if modality.lower() not in (
"t1",
"dwi",
"flair",
Expand All @@ -1241,12 +1264,12 @@ def paths_to_bids(
"pib",
"av45_fbb",
"tau",
]:
# This should never be reached
):
raise RuntimeError(
modality.lower() + " is not supported for conversion in paths_to_bids"
f"{modality.lower()} is not supported for conversion in paths_to_bids."
)

bids_dir = Path(bids_dir)
images_list = list([data for _, data in images.iterrows()])

with Pool(processes=n_procs) as pool:
Expand All @@ -1261,28 +1284,37 @@ def paths_to_bids(
return output_file_treated


def create_file(image, modality, bids_dir, mod_to_update):
"""
Image file is created at the corresponding output folder
as result of image conversion (DICOM to NIFTI) and centering,
as specified for each modality
def create_file(
image, modality: str, bids_dir: Path, mod_to_update: bool
) -> Optional[Path]:
"""Creates an image file at the corresponding output folder.
Args:
image: Image metadata
modality: Imaging modality
bids_dir: Path to the output BIDS directory
mod_to_update: If True, pre-existing images in the BIDS directory will be erased and extracted again.
The image file is the result of image conversion (DICOM to NIFTI)
and centering, as specified for each modality
Returns:
[Returns]
Parameters
----------
image: Image metadata
modality : str
Imaging modality.
bids_dir : Path
The path to the output BIDS directory.
mod_to_update : bool
If True, pre-existing images in the BIDS directory will be
erased and extracted again.
Returns
-------
output_image : Path
The path to the output image created.
If the conversion wasn't successful, this function returns None.
"""
import os
import re
import shutil
from glob import glob
from os import path

from numpy import nan

from clinica.iotools.bids_utils import run_dcm2niix
from clinica.iotools.converter_utils import viscode_to_session
Expand Down Expand Up @@ -1361,141 +1393,140 @@ def create_file(image, modality, bids_dir, mod_to_update):

if not image.Path:
cprint(
msg=(
f"[{modality.upper()}] No path specified for {subject} "
f"in session {viscode}"
),
lvl="info",
)
return nan
else:
cprint(
msg=(
f"[{modality.upper()}] Processing subject {subject}"
f"in session {viscode}"
),
f"[{modality.upper()}] No path specified for {subject} in session {viscode}",
lvl="info",
)
return None
cprint(
f"[{modality.upper()}] Processing subject {subject} in session {viscode}",
lvl="info",
)

session = viscode_to_session(viscode)

image_path = image.Path
image_id = image.Image_ID
# If the original image is a DICOM, check if contains two DICOM
# inside the same folder
# If the original image is a DICOM, check if contains two DICOM inside the same folder
if image.Is_Dicom:
image_path = check_two_dcm_folder(image_path, bids_dir, image_id)
image_path = check_two_dcm_folder(image_path, str(bids_dir), image_id)
bids_subj = subject.replace("_", "")
output_path = path.join(
bids_dir,
"sub-ADNI" + bids_subj,
session,
modality_specific[modality]["output_path"],
output_path = (
bids_dir
/ f"sub-ADNI{bids_subj}"
/ session
/ modality_specific[modality]["output_path"]
)
output_filename = (
"sub-ADNI"
+ bids_subj
+ "_"
+ session
+ modality_specific[modality]["output_filename"]
f"sub-ADNI{bids_subj}_{session}{modality_specific[modality]['output_filename']}"
)
output_path.mkdir(parents=True, exist_ok=True)

# If updated mode is selected, check if an old image is existing and remove it
existing_im = glob(path.join(output_path, output_filename + "*"))
if mod_to_update and len(existing_im) > 0:
print("Removing the old image...")
for im in existing_im:
os.remove(im)
for image_to_remove in output_path.glob(f"{output_filename}*"):
if not mod_to_update:
cprint(
f"There exists already an image at {image_to_remove}. "
"The parameter 'mod_to_update' is set to False such that "
"images cannot be overwritten.",
lvl="warning",
)
return None
cprint(f"Removing old image {image_to_remove}...", lvl="info")
image_to_remove.unlink()

if mod_to_update or not len(existing_im) > 0:
try:
os.makedirs(output_path)
except OSError:
# Folder already created with previous instance
pass
generate_json = modality_specific[modality]["json"]
zip_image = "n" if modality_specific[modality]["to_center"] else "y"
file_without_extension = output_path / output_filename
output_image = file_without_extension.with_suffix(".nii.gz")

generate_json = modality_specific[modality]["json"]
if modality_specific[modality]["to_center"]:
zip_image = "n"
else:
zip_image = "y"

if image.Is_Dicom:
run_dcm2niix(
input_dir=image_path,
output_dir=output_path,
output_fmt=output_filename,
compress=True if zip_image == "y" else False,
bids_sidecar=True if generate_json == "y" else False,
if image.Is_Dicom:
success = run_dcm2niix(
input_dir=image_path,
output_dir=output_path,
output_fmt=output_filename,
compress=True if zip_image == "y" else False,
bids_sidecar=True if generate_json == "y" else False,
)
if not success:
cprint(
f"Error converting image {image_path} for subject {subject} and session {session}",
lvl="warning",
)

# If "_t" - the trigger delay time - exists in dcm2niix output filename, we remove it
exception_t = glob(path.join(output_path, output_filename + "_t[0-9]*"))
for trigger_time in exception_t:
res = re.search(r"_t\d+\.", trigger_time)
no_trigger_time = trigger_time.replace(
trigger_time[res.start() : res.end()], "."
)
os.rename(trigger_time, no_trigger_time)

# Removing ADC images if one is generated
adc_image = path.join(output_path, output_filename + "_ADC.nii.gz")
if os.path.exists(adc_image):
os.remove(adc_image)

nifti_file = path.join(output_path, output_filename + ".nii")
output_image = nifti_file + ".gz"

# Conditions to check if output NIFTI files exists,
# and, if DWI, if .bvec and .bval files are also present
nifti_exists = path.isfile(nifti_file) or path.isfile(output_image)
dwi_bvec_and_bval_exist = not (modality == "dwi") or (
path.isfile(path.join(output_path, output_filename + ".bvec"))
and path.isfile(path.join(output_path, output_filename + ".bval"))
# If "_t" - the trigger delay time - exists in dcm2niix output filename, we remove it
for trigger_time in output_path.glob(f"{output_filename}_t[0-9]*"):
res = re.search(r"_t\d+\.", str(trigger_time))
no_trigger_time = str(trigger_time).replace(
trigger_time[res.start() : res.end()], "."
)
os.rename(trigger_time, no_trigger_time)

# Check if conversion worked (output files exist)
if not nifti_exists or not dwi_bvec_and_bval_exist:
cprint(
msg=f"Conversion with dcm2niix failed for subject {subject} and session {session}",
lvl="warning",
)
return nan

# Case when JSON file was expected, but not generated by dcm2niix
elif generate_json == "y" and not os.path.exists(
path.join(output_path, output_filename + ".json")
):
# Removing images with unsupported suffixes if generated by dcm2niix
for suffix in ("ADC", "real", "imaginary"):
file_with_bad_suffix = output_path / f"{output_filename}_{suffix}"
if file_with_bad_suffix.with_suffix(".nii").exists():
cprint(
msg=f"JSON file not generated by dcm2niix for subject {subject} and session {session}",
f"Image with bad suffix {suffix} was generated by dcm2niix "
f"for subject {subject} and session {session} : "
f"{file_with_bad_suffix.with_suffix('.nii.gz')}. This image will NOT "
"be converted as the suffix is not supported by Clinica.",
lvl="warning",
)
for file_to_delete in output_path.glob(f"{output_filename}_{suffix}*"):
cprint(f"Deleting file {file_to_delete}.", lvl="info")
os.remove(file_to_delete)

# Conditions to check if output NIFTI files exists,
# and, if DWI, if .bvec and .bval files are also present
nifti_exists = (
file_without_extension.with_suffix(".nii").is_file()
or output_image.is_file()
)
dwi_bvec_and_bval_exist = not (modality == "dwi") or (
file_without_extension.with_suffix(".bvec").is_file()
and file_without_extension.with_suffix(".bval").is_file()
)

if modality_specific[modality]["to_center"]:
center_nifti_origin(nifti_file, output_image)
os.remove(nifti_file)
# Check if conversion worked (output files exist)
if not nifti_exists or not dwi_bvec_and_bval_exist:
cprint(
msg=f"Conversion with dcm2niix failed for subject {subject} and session {session}",
lvl="warning",
)
return None

else:
output_image = path.join(output_path, output_filename + ".nii.gz")
if modality_specific[modality]["to_center"]:
center_nifti_origin(image_path, output_image)
if not output_image:
cprint(
msg=(
f"For subject {subject} in session {session}, "
f"an error occurred whilst recentering Nifti image: {image_path}"
),
lvl="error",
)
else:
shutil.copy(image_path, output_image)
# Case when JSON file was expected, but not generated by dcm2niix
elif (
generate_json == "y"
and not file_without_extension.with_suffix(".json").exists()
):
cprint(
msg=f"JSON file not generated by dcm2niix for subject {subject} and session {session}",
lvl="warning",
)

# Check if there is still the folder tmp_dcm_folder and remove it
remove_tmp_dmc_folder(bids_dir, image_id)
return output_image
if modality_specific[modality]["to_center"]:
center_nifti_origin(
file_without_extension.with_suffix(".nii"), output_image
)
file_without_extension.with_suffix(".nii").unlink()

else:
return nan
if modality_specific[modality]["to_center"]:
center_nifti_origin(image_path, output_image)
if not output_image:
cprint(
msg=(
f"For subject {subject} in session {session}, "
f"an error occurred whilst recentering Nifti image: {image_path}"
),
lvl="error",
)
else:
shutil.copy(image_path, output_image)

# Check if there is still the folder tmp_dcm_folder and remove it
remove_tmp_dmc_folder(bids_dir, image_id)
return output_image


def session_label_to_viscode(session_name: str) -> str:
Expand Down

0 comments on commit 193e97b

Please sign in to comment.