diff --git a/brainprep/scripts/brainprep b/brainprep/scripts/brainprep index 720eb692..e3a942ac 100755 --- a/brainprep/scripts/brainprep +++ b/brainprep/scripts/brainprep @@ -22,6 +22,7 @@ fire.Fire({ "cat12vbm-qc": wf.brainprep_cat12vbm_qc, "cat12vbm-roi": wf.brainprep_cat12vbm_roi, "quasiraw": wf.brainprep_quasiraw, + "quasiraw-sitk": wf.brainprep_quasiraw_sitk, "quasiraw-qc": wf.brainprep_quasiraw_qc, "fmriprep": wf.brainprep_fmriprep, "fmriprep-conn": wf.brainprep_fmriprep_conn, diff --git a/brainprep/spatial.py b/brainprep/spatial.py index dcc16e45..bf51cd7d 100644 --- a/brainprep/spatial.py +++ b/brainprep/spatial.py @@ -15,11 +15,12 @@ import os import nibabel import numpy as np -from .utils import check_version, check_command, execute_command +import SimpleITK as sitk +from .utils import check_version, check_command, execute_command, print_error, print_command def scale(imfile, scaledfile, scale, check_pkg_version=False): - """ Scale the MRI image. + """ Resample the MRI image to a new isotropic voxel size. .. note:: This function is based on FSL. @@ -30,7 +31,7 @@ def scale(imfile, scaledfile, scale, check_pkg_version=False): scaledfile: str the path to the scaled input image. scale: int - the scale factor in all directions. + Desired isotropic voxel size in mm. check_pkg_version: bool, default False optionally check the package version using dpkg. @@ -48,7 +49,7 @@ def scale(imfile, scaledfile, scale, check_pkg_version=False): return scaledfile, trffile -def bet2(imfile, brainfile, frac=0.5, cleanup=True, check_pkg_version=False): +def bet2(imfile, brainfile, frac=0.5, cleanup=True, save_brain_mask=True, check_pkg_version=False): """ Skull stripped the MRI image. .. note:: This function is based on FSL. @@ -58,29 +59,68 @@ def bet2(imfile, brainfile, frac=0.5, cleanup=True, check_pkg_version=False): imfile: str the input image. brainfile: str - the path to the brain image file. - frac: float, default 0.5 + the path to the output brain image file (with masked applied). + frac: float, default=0.5 fractional intensity threshold (0->1);smaller values give larger brain outline estimates - cleanup: bool, default True + cleanup: bool, default=True optionnally add bias field & neck cleanup. - check_pkg_version: bool, default False + save_brain_mask: bool, default=True + optionnally save the brain mask with suffix "_mask.nii.gz". + check_pkg_version: bool, default=False optionally check the package version using dpkg. Returns ------- - brainfile, maskfile: str - the generated files. + brainfile, maskfile: str, str or None + the generated files. + If `save_brain_mask` is False, the maskfile will be None """ check_version("fsl", check_pkg_version) check_command("bet") - maskfile = brainfile.split(".")[0] + "_mask.nii.gz" - cmd = ["bet", imfile, brainfile, "-f", str(frac), "-R", "-m"] + cmd = ["bet", imfile, brainfile, "-f", str(frac), "-R"] + maskfile = None + if save_brain_mask: + cmd.append("-m") + maskfile = brainfile.split(".")[0] + "_mask.nii.gz" if cleanup: cmd.append("-B") execute_command(cmd) return brainfile, maskfile +def synthstrip(imfile, brainfile, save_brain_mask=True): + """ + Skull strip the MRI image using SynthStrip (FreeSurfer). + + Parameters + ---------- + imfile: str + Input image (T1, T2, FLAIR, etc.) + brainfile: str + Output skull-stripped brain image file path. + save_brain_mask: bool, default=True + Optionally save the brain mask with suffix '_mask.nii.gz'. + + Returns + ------- + brainfile, maskfile: str, str or None + The skull-stripped image and mask file paths. + If `save_brain_mask` is False, maskfile is None. + """ + check_command("mri_synthstrip") + + cmd = [ + "mri_synthstrip", + "-i", imfile, + "-o", brainfile, + "--no-csf" + ] + maskfile = None + if save_brain_mask: + maskfile = brainfile.split(".")[0] + "_mask.nii.gz" + cmd.extend(["-m", maskfile]) + execute_command(cmd) + return brainfile, maskfile def reorient2std(imfile, stdfile, check_pkg_version=False): """ Reorient the MRI image to match the approximate orientation of the @@ -108,7 +148,6 @@ def reorient2std(imfile, stdfile, check_pkg_version=False): execute_command(cmd) return stdfile - def biasfield(imfile, bfcfile, maskfile=None, nb_iterations=50, convergence_threshold=0.001, bspline_grid=(1, 1, 1), shrink_factor=1, bspline_order=3, @@ -172,17 +211,99 @@ def biasfield(imfile, bfcfile, maskfile=None, nb_iterations=50, "-d", str(ndim), "-i", imfile, "-s", str(shrink_factor), - "-b", "[{0}, {1}]".format("x".join(bspline_grid), bspline_order), - "-c", "[{0}, {1}]".format( + "-b", "[{0},{1}]".format("x".join(bspline_grid), bspline_order), + "-c", "[{0},{1}]".format( "x".join([str(nb_iterations)] * 4), convergence_threshold), "-t", "[{0}]".format(", ".join(histogram_sharpening)), - "-o", "[{0}, {1}]".format(bfcfile, bffile), + "-o", "[{0},{1}]".format(bfcfile, bffile), "-v"] if maskfile is not None: cmd += ["-x", maskfile] - execute_command(cmd) + try: + execute_command(cmd) + except Exception as e: + print(f"Error occurred while executing command: {e}") + cmd = ["cp", imfile, bfcfile] + execute_command(cmd) + print("Using the original image as the bias field corrected image.") return bfcfile, bffile +def biasfield_sitk(imfile, bfcfile, maskfile=None, nb_iterations=50, + convergence_threshold=0.001, + shrink_factor=1, bspline_order=3, + histogram_sharpening=(0.15, 0.01, 200)): + """ + Perform MRI bias field correction using SimpleITK's N4 algorithm. + It is the SITK-equivalent of ANTS' N4 algorithm. + + Parameters + ---------- + imfile: str + the input image. + bfcfile: str + the bias fieled corrected file. + maskfile: str, default None + the brain mask image. + nb_iterations: int, default 50 + Maximum number of iterations at each level of resolution. Larger + values will increase execution time, but may lead to better results. + convergence_threshold: float, default 0.001 + Stopping criterion for the iterative bias estimation. Larger values + will lead to smaller execution time. + shrink_factor: int, default 1 + Defines how much the image should be upsampled before estimating the + inhomogeneity field. Increase if you want to reduce the execution + time. 1 corresponds to the original resolution. Larger values will + significantly reduce the computation time. + bspline_order: int, default 3 + Order of B-spline used in the approximation. Larger values will lead + to longer execution times, may result in overfitting and poor result. + histogram_sharpening: 3-uplate, default (0.15, 0.01, 200) + A vector of up to three values. Non-zero values correspond to Bias + Field Full Width at Half Maximum, Wiener filter noise, and Number of + histogram bins. + + """ + # Read input image + image = sitk.ReadImage(imfile, sitk.sitkFloat32) + + # Read mask + if maskfile is not None: + mask = sitk.ReadImage(maskfile, sitk.sitkUInt8) + else: + mask = None + + # Optionally shrink image and mask for faster processing + if shrink_factor > 1: + image = sitk.Shrink(image, [shrink_factor]*image.GetDimension()) + if mask is not None: + mask = sitk.Shrink(mask, [shrink_factor]*mask.GetDimension()) + + # Set up the corrector + corrector = sitk.N4BiasFieldCorrectionImageFilter() + corrector.SetMaximumNumberOfIterations([nb_iterations]*4) + corrector.SetConvergenceThreshold(convergence_threshold) + corrector.SetSplineOrder(bspline_order) + corrector.SetBiasFieldFullWidthAtHalfMaximum(histogram_sharpening[0]) + corrector.SetWienerFilterNoise(histogram_sharpening[1]) + corrector.SetNumberOfHistogramBins(histogram_sharpening[2]) + + # Run correction + print_command([str(corrector)]) + if mask is None: + corrected = corrector.Execute(image) + else: + corrected = corrector.Execute(image, mask) + + # Write output + sitk.WriteImage(corrected, bfcfile) + + # Get bias field as image + log_bias_field = corrector.GetLogBiasFieldAsImage(image) + bias_field = sitk.Exp(log_bias_field) + bffile = bfcfile.replace(".nii", "_field.nii") + sitk.WriteImage(bias_field, bffile) + return bfcfile, bffile def register_affine(imfile, targetfile, regfile, mask=None, cost="normmi", bins=256, interp="spline", dof=9, check_pkg_version=False): @@ -243,6 +364,116 @@ def register_affine(imfile, targetfile, regfile, mask=None, cost="normmi", return regfile, trffile +def register_affine_sitk(imfile, targetfile, regfile, maskfile=None, regmaskfile=None, + cost="mattesmi", bins=50, interp="spline"): + """ + Register MRI image to target using an affine transform via SimpleITK. + + Parameters + ---------- + imfile: str + the input image. + targetfile: str + the target image. + regfile: str + the registered file. + maskfile: str, default=None + Brain mask to eventually register using the same transformation. + regmaskfile: str, default=None + Registered brain mask file name (only used if maskfile is given). + cost: str, default 'mattesmi' + Choose the most appropriate metric: 'mi', 'mattesmi', 'correlation', + 'mse'. + bins: int, default 50 + Number of histogram bins + interp: str, default 'spline' + Choose the most appropriate interpolation method: 'trilinear', + 'nearestneighbour', 'spline'. + + Returns + ------- + regfile, trffile: str + the generated files. + """ + + # Read fixed (target) and moving (input) images + fixed = sitk.ReadImage(targetfile, sitk.sitkFloat32) + moving = sitk.ReadImage(imfile, sitk.sitkFloat32) + + # Set up initial transform: Affine with 12 DOF + transform = sitk.CenteredTransformInitializer( + fixed, + moving, + sitk.AffineTransform(fixed.GetDimension()), + sitk.CenteredTransformInitializerFilter.GEOMETRY + ) + + # Configure registration method + registration = sitk.ImageRegistrationMethod() + + # Cost SimpleITK metrics + if cost.lower() == "mi": + registration.SetMetricAsJointHistogramMutualInformation( + numberOfHistogramBins=bins, varianceForJointPDFSmoothing=1.5) + elif cost.lower() == "mattesmi": + registration.SetMetricAsMattesMutualInformation(numberOfHistogramBins=bins) + elif cost.lower() == "correlation": + registration.SetMetricAsCorrelation() + elif cost.lower() == "mse": + registration.SetMetricAsMeanSquares() + else: + raise ValueError(f"Unsupported cost function '{cost}' for SimpleITK.") + + registration.SetMetricSamplingStrategy(registration.RANDOM) + registration.SetMetricSamplingPercentage(0.01) + + # Map interpolation methods + interp = interp.lower() + if interp == "trilinear": + registration.SetInterpolator(sitk.sitkLinear) + elif interp == "nearestneighbour": + registration.SetInterpolator(sitk.sitkNearestNeighbor) + elif interp == "spline": + registration.SetInterpolator(sitk.sitkBSpline) + else: + raise ValueError(f"Unsupported interpolation method '{interp}'.") + + # Optimizer settings + registration.SetOptimizerAsGradientDescent( + learningRate=1.0, + numberOfIterations=100, + convergenceMinimumValue=1e-6, + convergenceWindowSize=10) + registration.SetOptimizerScalesFromPhysicalShift() + + registration.SetInitialTransform(transform) + registration.SetShrinkFactorsPerLevel([4, 2, 1]) + registration.SetSmoothingSigmasPerLevel([2, 1, 0]) + registration.SmoothingSigmasAreSpecifiedInPhysicalUnitsOn() + + # Execute registration + print_command([str(registration)]) + final_transform = registration.Execute(fixed, moving) + + # Resample and write output image + resampled = sitk.Resample(moving, fixed, final_transform, + sitk.sitkLinear, 0.0, sitk.sitkFloat32) + sitk.WriteImage(resampled, regfile) + + # Save transform to text file (ITK format) + trffile = regfile.rsplit(".", 1)[0] + ".txt" + sitk.WriteTransform(final_transform, trffile) + + # Eventuall apply the transformation to mask + if maskfile is not None: + mask_img = sitk.ReadImage(maskfile, sitk.sitkUInt8) + mask_resampled = sitk.Resample(mask_img, fixed, final_transform, + sitk.sitkNearestNeighbor, 0, sitk.sitkUInt8) + sitk.WriteImage(mask_resampled, regmaskfile) + + return regfile, trffile + + def apply_affine(imfile, targetfile, regfile, affines, interp="spline", check_pkg_version=False): """ Apply affine transformations to an image. diff --git a/brainprep/utils.py b/brainprep/utils.py index f59b9c48..a4cc5eeb 100644 --- a/brainprep/utils.py +++ b/brainprep/utils.py @@ -305,3 +305,21 @@ def listify(obj): return obj.split(",") else: return obj + + +def cp_file(src, dst): + """ Copy a file from src to dst. + + Parameters + ---------- + src: str + the source file. + dst: str + the destination file. + """ + if not os.path.isfile(src): + raise ValueError("Source file '{}' does not exist.".format(src)) + if not os.path.isdir(os.path.dirname(dst)): + raise ValueError("Destination directory '{}' does not exist." + .format(os.path.dirname(dst))) + execute_command(["cp", src, dst]) diff --git a/brainprep/workflow/__init__.py b/brainprep/workflow/__init__.py index f5b66459..0dd6d26a 100644 --- a/brainprep/workflow/__init__.py +++ b/brainprep/workflow/__init__.py @@ -18,6 +18,7 @@ from .cat12vbm import (brainprep_cat12vbm, brainprep_cat12vbm_qc, brainprep_cat12vbm_roi) from .quasiraw import brainprep_quasiraw, brainprep_quasiraw_qc +from .quasiraw_sitk import brainprep_quasiraw_sitk from .fmriprep import brainprep_fmriprep, brainprep_fmriprep_conn from .mriqc import brainprep_mriqc, brainprep_mriqc_summary from .deface import brainprep_deface, brainprep_deface_qc diff --git a/brainprep/workflow/quasiraw.py b/brainprep/workflow/quasiraw.py index 143d9cfe..fc1e16ef 100644 --- a/brainprep/workflow/quasiraw.py +++ b/brainprep/workflow/quasiraw.py @@ -18,28 +18,48 @@ import numpy as np from html import unescape import brainprep -from brainprep.utils import load_images, create_clickable, listify +from brainprep.utils import load_images, create_clickable, listify, cp_file from brainprep.color_utils import print_title, print_result from brainprep.qc import plot_pca, compute_mean_correlation, check_files from brainprep.plotting import plot_images, plot_hists +from brainprep.spatial import reorient2std, apply_mask, scale, biasfield, register_affine, apply_affine, synthstrip -def brainprep_quasiraw(anatomical, mask, outdir, target=None, no_bids=False): +def brainprep_quasiraw(anatomical, outdir, mask=None, + target=None, no_bids=False, cleanup=True): """ Define quasi-raw pre-processing workflow. + This includes: + + 1) Reorient the anatomical image to standard space (MNI152 by default). + 2) Reorient the mask to standard space (if provided). + 3) Apply the mask to the anatomical image (if provided). + 4) Resample the image to 1mm isotropic voxel size. + 5) Bias field correction. + 6) Linearly register the image to a standard template (default MNI152 T1 1mm). + 7) Apply the registration to the mask. + 8) Apply the mask to the registered image. + 9) Save the final image as a Nifti file with the suffix "_preproc-quasiraw_T1w" + and the mask with the suffix "_preproc-quasiraw_T1w_mask" (if not provided). + Parameters ---------- anatomical: str path to the anatomical T1w Nifti file. - mask: str - a binary mask to be applied. outdir: str the destination folder. - target: str + mask: str, default=None + a binary mask to be applied. + If None, the mask is computed using SynthStrip (deep learning based). + target: str, default=None a custom target image for the registration. - no_bids: bool + If None, the default MNI152 T1 1mm template is used from ..resources/MNI152_T1_1mm_brain.nii.gz + no_bids: bool, default=False set this option if the input files are not named following the BIDS hierarchy. + cleanup: bool, default=True + if True, the temporary files are removed after the workflow is completed. + If False, the temporary files are kept for further inspection. """ print_title("Set outputs and default target if applicable...") if not os.path.isdir(outdir): @@ -55,11 +75,17 @@ def brainprep_quasiraw(anatomical, mask, outdir, target=None, no_bids=False): targetfile = target if no_bids: basename = os.path.basename(imfile).split(".")[0] + "_desc-{0}_T1w" + outfile = os.path.join(outdir, os.path.basename(imfile).split(".")[0] + \ + "_preproc-quasiraw_T1w.nii.gz") + outmaskfile = os.path.join(outdir, os.path.basename(imfile).split(".")[0] + \ + "_preproc-quasiraw_T1w_mask.nii.gz") else: basename = os.path.basename(imfile).split(".")[0] if not basename.endswith("_T1w"): raise ValueError("The input file is not formatted in BIDS! " "Please use the --no-bids parameter.") + outfile = os.path.join(outdir, basename.replace("_T1w", "_preproc-quasiraw_T1w.nii.gz")) + outmaskfile = os.path.join(outdir, basename.replace("_T1w", "_preproc-quasiraw_T1w_mask.nii.gz")) basename = basename.replace("_T1w", "_desc-{0}_T1w") basefile = os.path.join(outdir, basename + ".nii.gz") print("use base file name:", basefile) @@ -73,24 +99,33 @@ def brainprep_quasiraw(anatomical, mask, outdir, target=None, no_bids=False): applyfile = basefile.format("6apply") print_title("Launch quasi-raw pre-processing...") - brainprep.reorient2std(imfile, stdfile) - brainprep.reorient2std(maskfile, stdmaskfile) - brainprep.apply_mask(stdfile, stdmaskfile, brainfile) - brainprep.scale(brainfile, scaledfile, scale=1) - brainprep.biasfield(scaledfile, bfcfile) - _, trffile = brainprep.register_affine(bfcfile, targetfile, regfile) - brainprep.apply_affine(stdmaskfile, regfile, regmaskfile, trffile, - interp="nearestneighbour") - brainprep.apply_mask(regfile, regmaskfile, applyfile) - - print_title("Make datasets...") - if not os.path.exists(applyfile): - raise ValueError("{0} file doesn't exists".format(applyfile)) - nii_img = nibabel.load(applyfile) - nii_arr = nii_img.get_fdata() - nii_arr = nii_arr.astype(np.float32) - npy_mat = applyfile.replace(".nii.gz", ".npy") - np.save(npy_mat, nii_arr) + reorient2std(imfile, stdfile) + if maskfile is not None: + reorient2std(maskfile, stdmaskfile) + apply_mask(stdfile, stdmaskfile, brainfile) + else: + print_title("No mask provided, use SynthStrip to compute it...") + brainfile, stdmaskfile = synthstrip(stdfile, brainfile, save_brain_mask=True) + + _, trfscalefile = scale(brainfile, scaledfile, scale=1) + _, bffile = biasfield(scaledfile, bfcfile) + _, trffile = register_affine(bfcfile, targetfile, regfile) + _, trfmaskfile = apply_affine(stdmaskfile, regfile, regmaskfile, trffile, + interp="nearestneighbour") + apply_mask(regfile, regmaskfile, applyfile) + + if maskfile is None: + cp_file(regmaskfile, outmaskfile) + cp_file(applyfile, outfile) + + if cleanup: + print_title("Cleanup temporary files...") + for item in [stdfile, stdmaskfile, brainfile, scaledfile, bfcfile, + regfile, regmaskfile, trffile, trfscalefile, trfmaskfile, + bffile, applyfile]: + if os.path.isfile(item): + os.remove(item) + def brainprep_quasiraw_qc(img_regex, outdir, brainmask_regex=None, diff --git a/brainprep/workflow/quasiraw_sitk.py b/brainprep/workflow/quasiraw_sitk.py new file mode 100644 index 00000000..d148c5bd --- /dev/null +++ b/brainprep/workflow/quasiraw_sitk.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +########################################################################## +# NSAp - Copyright (C) CEA, 2021 - 2022 +# Distributed under the terms of the CeCILL-B license, as published by +# the CEA-CNRS-INRIA. Refer to the LICENSE file or to +# http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html +# for details. +########################################################################## + +""" +Interface for quasi-raw. +""" + +# System import +import os +import brainprep +from brainprep.utils import cp_file +from brainprep.color_utils import print_title +from brainprep.spatial import reorient2std, apply_mask, scale, biasfield_sitk, register_affine_sitk, synthstrip + + +def brainprep_quasiraw_sitk(anatomical, outdir, contrast, mask=None, + target=None, no_bids=False, cleanup=True): + """ Define quasi-raw-sitk pre-processing workflow. + + It is similar to quasi-raw pipeline but it uses SimpleITK (faster) for all steps + except reorient (FSL) and skull-stripping (DL performed with FreeSurfer) and it works + for T1w, T2w and FLAIR. + + This includes: + + 1) Reorient the anatomical image to standard space (MNI152 by default). + 2) Reorient the mask to standard space (if provided). + 3) Apply the mask to the anatomical image (if provided). + 4) Resample the image to 1mm isotropic voxel size. + 5) Bias field correction. + 6) Linearly register the image to a standard template (default MNI152 T1 1mm). + 7) Apply the registration to the mask. + 8) Apply the mask to the registered image. + 9) Save the final image as a Nifti file with the suffix "_preproc-quasiraw-sitk_{contrast}" + and the mask with the suffix "_preproc-quasiraw-sitk_{contrast}_mask" (if not provided). + + Parameters + ---------- + anatomical: str + path to the anatomical T1w, T2w or FLAIR Nifti file. + outdir: str + the destination folder. + contrast: str + Contrast used (T1w, T2w or FLAIR) + mask: str, default=None + a binary mask to be applied. + If None, the mask is computed using SynthStrip (deep learning based). + target: str, default=None + a custom target image for the registration. + If None, the default MNI152 T1 1mm template is used from ..resources/MNI152_T1_1mm_brain.nii.gz + no_bids: bool, default=False + set this option if the input files are not named following the + BIDS hierarchy. + cleanup: bool, default=True + if True, the temporary files are removed after the workflow is completed. + If False, the temporary files are kept for further inspection. + """ + print_title("Set outputs and default target if applicable...") + if not os.path.isdir(outdir): + raise ValueError("{0} does not exist".format(outdir)) + if contrast not in ["T1w", "T2w", "FLAIR"]: + raise ValueError("{0} is not handled".format(contrast)) + if target is None: + resource_dir = os.path.join( + os.path.dirname(brainprep.__file__), "resources") + target = os.path.join( + resource_dir, "MNI152_T1_1mm_brain.nii.gz") + print("set target:", target) + imfile = anatomical + maskfile = mask + targetfile = target + if no_bids: + basename = os.path.basename(imfile).split(".")[0] + "_desc-{0}_%s"%contrast + outfile = os.path.join(outdir, os.path.basename(imfile).split(".")[0] + \ + f"_preproc-quasiraw_{contrast}.nii.gz") + outmaskfile = os.path.join(outdir, os.path.basename(imfile).split(".")[0] + \ + f"_preproc-quasiraw_{contrast}_mask.nii.gz") + else: + basename = os.path.basename(imfile).split(".")[0] + if not basename.endswith(f"_{contrast}"): + raise ValueError("The input file is not formatted in BIDS! " + "Please use the --no-bids parameter.") + outfile = os.path.join(outdir, basename.replace(f"_{contrast}", f"_preproc-quasiraw-sitk_{contrast}.nii.gz")) + outmaskfile = os.path.join(outdir, basename.replace(f"_{contrast}", f"_preproc-quasiraw-sitk_{contrast}_mask.nii.gz")) + basename = basename.replace(f"_{contrast}", "_desc-{0}_%s"%contrast) + basefile = os.path.join(outdir, basename + ".nii.gz") + print("use base file name:", basefile) + stdfile = basefile.format("1std") + stdmaskfile = basefile.format("1maskstd") + brainfile = basefile.format("2brain") + scaledfile = basefile.format("3scaled") + bfcfile = basefile.format("4bfc") + regfile = basefile.format("5reg") + regmaskfile = basefile.format("5maskreg") + applyfile = basefile.format("6apply") + + print_title("Launch quasi-raw-sitk pre-processing...") + reorient2std(imfile, stdfile) + if maskfile is not None: + reorient2std(maskfile, stdmaskfile) + apply_mask(stdfile, stdmaskfile, brainfile) + else: + print_title("No mask provided, use SynthStrip to compute it...") + brainfile, stdmaskfile = synthstrip(stdfile, brainfile, save_brain_mask=True) + + _, trfscalefile = scale(brainfile, scaledfile, scale=1) + _, bffile = biasfield_sitk(scaledfile, bfcfile) + _, trfmaskfile = register_affine_sitk(bfcfile, targetfile, regfile, stdmaskfile, regmaskfile) + apply_mask(regfile, regmaskfile, applyfile) + + if maskfile is None: + cp_file(regmaskfile, outmaskfile) + cp_file(applyfile, outfile) + + if cleanup: + print_title("Cleanup temporary files...") + for item in [stdfile, stdmaskfile, brainfile, scaledfile, bfcfile, + regfile, regmaskfile, trfscalefile, trfmaskfile, + bffile, applyfile]: + if os.path.isfile(item): + os.remove(item) diff --git a/containers/Dockerfile.anat b/containers/Dockerfile.anat index 61f299c8..e4c80109 100644 --- a/containers/Dockerfile.anat +++ b/containers/Dockerfile.anat @@ -1,6 +1,7 @@ # Use Ubuntu 20.04 LTS FROM ubuntu:focal-20210416 +ENV DEBIAN_FRONTEND=noninteractive # Install dependencies and prereqs RUN apt-get update \ @@ -14,12 +15,17 @@ RUN apt-get update \ locales \ curl \ bzip2 \ - python3.8 \ + python3.9 \ + python3.9-dev \ + python3.9-distutils \ python3-pip \ - python3.8-dev \ git \ tcsh \ locales \ + && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.9 1 \ + && curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py \ + && python3 get-pip.py \ + && rm get-pip.py \ && apt-get clean RUN locale-gen "en_US.UTF-8" \ @@ -34,7 +40,7 @@ ENV DEBIAN_FRONTEND="noninteractive" \ # Installing brainprep -RUN pip install --no-cache-dir git+https://github.com/neurospin-deepinsight/brainprep.git +RUN pip install --no-cache-dir git+https://github.com/Duplums/brainprep.git@extension # FSL-6.0.5.1 @@ -86,12 +92,13 @@ ENV FSLDIR="/opt/fsl-6.0.5.1" \ LD_LIBRARY_PATH="/opt/fsl-6.0.5.1:$LD_LIBRARY_PATH" -# Installing ANTs 2.3.4 (NeuroDocker build) +# Installing ANTs 2.6.1 (Github release) ENV ANTSPATH="/usr/lib/ants" \ - PATH="/usr/lib/ants:$PATH" + PATH="/usr/lib/ants/ants-2.6.1/bin:$PATH" WORKDIR $ANTSPATH -RUN curl -sSL "https://dl.dropbox.com/s/gwf51ykkk5bifyj/ants-Linux-centos6_x86_64-v2.3.4.tar.gz" \ - | tar -xzC $ANTSPATH --strip-components 1 +RUN curl -sSL -o /tmp/ants.zip "https://github.com/ANTsX/ANTs/releases/download/v2.6.1/ants-2.6.1-ubuntu20.04-X64-gcc.zip" && \ + unzip -q /tmp/ants.zip -d $ANTSPATH && \ + rm /tmp/ants.zip # Install Matlab MCR at /opt/mcr @@ -127,9 +134,10 @@ ENV SPMROOT="/opt/spm" -# Installing FreeSurfer v7.1.1 -RUN curl -sSL https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/7.1.1/freesurfer-linux-centos6_x86_64-7.1.1.tar.gz \ - | tar zxv --no-same-owner -C /opt \ +# Installing FreeSurfer v7.4.1 +RUN curl -sSL https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/7.4.1/freesurfer-linux-ubuntu20_amd64-7.4.1.tar.gz \ + -o /tmp/freesurfer.tar.gz && \ + tar zxv --no-same-owner -C /opt -f /tmp/freesurfer.tar.gz \ --exclude="freesurfer/diffusion" \ --exclude="freesurfer/docs" \ --exclude="freesurfer/fsfast" \ @@ -182,7 +190,7 @@ ENV HOME="/home/brainprep" # Define labels MAINTAINER Antoine Grigis -LABEL description="FSL 6.0.5.1, ANTS 2.3.4, FreeSurfer v7.1.1, MATLAB Compiler Runtime R2017b v9.3 and CAT12.8.2 r2166 standalone docker image for brainprep 0.0.0" +LABEL description="FSL 6.0.5.1, ANTS 2.6.1, FreeSurfer v7.4.1, MATLAB Compiler Runtime R2017b v9.3 and CAT12.8.2 r2166 standalone docker image for brainprep 0.0.0" LABEL maintainer="antoine.grigis@cea.fr" LABEL org.label-schema.build-date=$BUILD_DATE \ org.label-schema.name="brainprep" \ diff --git a/pyproject.toml b/pyproject.toml index 1654b960..cc7d2618 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,8 @@ dependencies = [ "seaborn", "requests", "progressbar2", - "fire" + "fire", + "simpleitk" ] dynamic = ["version"]