diff --git a/CerebNet/run_prediction.py b/CerebNet/run_prediction.py index d37c31830..649727204 100644 --- a/CerebNet/run_prediction.py +++ b/CerebNet/run_prediction.py @@ -20,14 +20,14 @@ from pathlib import Path from FastSurferCNN.utils import logging, parser_defaults, Plane, PLANES -from CerebNet.utils.load_config import get_config -from CerebNet.inference import Inference from FastSurferCNN.utils.checkpoint import ( get_checkpoints, load_checkpoint_config_defaults, - YAML_DEFAULT as CHECKPOINT_PATHS_FILE, ) from FastSurferCNN.utils.common import assert_no_root, SubjectList +from CerebNet.inference import Inference +from CerebNet.utils.checkpoint import YAML_DEFAULT as CHECKPOINT_PATHS_FILE +from CerebNet.utils.load_config import get_config logger = logging.get_logger(__name__) DEFAULT_CEREBELLUM_STATSFILE = Path("stats/cerebellum.CerebNet.stats") diff --git a/CerebNet/utils/load_config.py b/CerebNet/utils/load_config.py index 87bea4393..ac54f0ba8 100644 --- a/CerebNet/utils/load_config.py +++ b/CerebNet/utils/load_config.py @@ -14,14 +14,16 @@ # IMPORTS -import argparse -import sys +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import yacs.config from CerebNet.config import get_cfg_cerebnet from FastSurferCNN.utils import PLANES -def get_config(args) -> "yacs.CfgNode": +def get_config(args) -> "yacs.config.CfgNode": """ Given the arguments, load and initialize the config_files. """ @@ -35,8 +37,8 @@ def get_config(args) -> "yacs.CfgNode": if hasattr(args, "rng_seed"): cfg.RNG_SEED = args.rng_seed if hasattr(args, "out_dir"): + cfg.LOG_DIR = str(args.out_dir) - cfg.LOG_DIR = args.out_dir path_ax, path_sag, path_cor = [ getattr(args, name) for name in ["ckpt_ax", "ckpt_sag", "ckpt_cor"] ] @@ -50,32 +52,3 @@ def get_config(args) -> "yacs.CfgNode": cfg.TEST.BATCH_SIZE = batch_size return cfg - - -def setup_options(): - """ - Set up the command-line options for the segmentation. - - Returns - ------- - argparse.Namespace - The configured argument parser. - """ - parser = argparse.ArgumentParser(description="Segmentation") - parser.add_argument( - "--cfg", - dest="cfg_file", - help="Path to the config file", - default="config_files/CerebNet.yaml", - type=str, - ) - parser.add_argument( - "opts", - help="See CerebNet/config/cerebnet.py for all options", - default=None, - nargs=argparse.REMAINDER, - ) - - if len(sys.argv) == 1: - parser.print_help() - return parser.parse_args() diff --git a/FastSurferCNN/run_prediction.py b/FastSurferCNN/run_prediction.py index 922e92c6e..5a5eba22b 100644 --- a/FastSurferCNN/run_prediction.py +++ b/FastSurferCNN/run_prediction.py @@ -603,14 +603,13 @@ def main( *, orig_name: Path | str, out_dir: Path, - segfile: str, + pred_name: str, ckpt_ax: Path, ckpt_sag: Path, ckpt_cor: Path, cfg_ax: Path, cfg_sag: Path, cfg_cor: Path, - seg_log: Path, qc_log: str = "", log_name: str = "", allow_root: bool = False, @@ -631,7 +630,7 @@ def main( threads: int = -1, conform_to_1mm_threshold: float = 0.95, **kwargs, -): +) -> Literal[0] | str: # Warning if run as root user allow_root or assert_no_root() @@ -658,7 +657,7 @@ def main( config = SubjectDirectoryConfig( orig_name=orig_name, - pred_name=segfile, + pred_name=pred_name, conf_name=conf_name, in_dir=in_dir, csv_file=csv_file, @@ -668,29 +667,36 @@ def main( remove_suffix=remove_suffix, out_dir=out_dir, ) - config.copy_org_name = "mri/orig/001.mgz" - - # Get all subjects of interest - subjects = SubjectList(config, segfile="pred_name", copy_orig_name="copy_orig_name") - subjects.make_subjects_dir() - - # Set Up Model - eval = RunModelOnData( - lut=lut, - ckpt_ax=ckpt_ax, - ckpt_sag=ckpt_sag, - ckpt_cor=ckpt_cor, - cfg_ax=cfg_ax, - cfg_sag=cfg_sag, - cfg_cor=cfg_cor, - device=device, - viewagg_device=viewagg_device, - threads=threads, - batch_size=batch_size, - vox_size=vox_size, - async_io=async_io, - conform_to_1mm_threshold=conform_to_1mm_threshold, - ) + config.copy_orig_name = "mri/orig/001.mgz" + + try: + # Get all subjects of interest + subjects = SubjectList( + config, + segfile="pred_name", + copy_orig_name="copy_orig_name", + ) + subjects.make_subjects_dir() + + # Set Up Model + eval = RunModelOnData( + lut=lut, + ckpt_ax=ckpt_ax, + ckpt_sag=ckpt_sag, + ckpt_cor=ckpt_cor, + cfg_ax=cfg_ax, + cfg_sag=cfg_sag, + cfg_cor=cfg_cor, + device=device, + viewagg_device=viewagg_device, + threads=threads, + batch_size=batch_size, + vox_size=vox_size, + async_io=async_io, + conform_to_1mm_threshold=conform_to_1mm_threshold, + ) + except RuntimeError as e: + return e.args[0] qc_failed_subject_count = 0 diff --git a/FastSurferCNN/utils/parser_defaults.py b/FastSurferCNN/utils/parser_defaults.py index 966784a31..b6a5430be 100644 --- a/FastSurferCNN/utils/parser_defaults.py +++ b/FastSurferCNN/utils/parser_defaults.py @@ -178,7 +178,7 @@ class SubjectDirectoryConfig: flags=("--csv_file",), default=None, help="Csv-file with subjects to analyze (alternative to --tag)", - ), + ) sid: Optional[str] = field( flags=("--sid",), default=None, @@ -191,7 +191,7 @@ class SubjectDirectoryConfig: default="*", help="Search tag to process only certain subjects. If a single image should be " "analyzed, set the tag with its id. Default: processes all.", - ), + ) brainmask_name: str = field( default="mri/mask.mgz", help="Name under which the brainmask image will be saved, in the same " @@ -271,7 +271,12 @@ class SubjectDirectoryConfig: "(no memory check will be done).", ), "in_dir": __arg("--in_dir", dc=SubjectDirectoryConfig, fieldname="in_dir"), - "tag": __arg("--tag", type=unquote_str, dc=SubjectDirectoryConfig, fieldname="search_tag"), + "tag": __arg( + "--tag", + type=unquote_str, + dc=SubjectDirectoryConfig, + fieldname="search_tag", + ), "csv_file": __arg("--csv_file", dc=SubjectDirectoryConfig), "batch_size": __arg( "--batch_size", diff --git a/doc/overview/OUTPUT_FILES.md b/doc/overview/OUTPUT_FILES.md index 3f857ade0..0ef19454c 100644 --- a/doc/overview/OUTPUT_FILES.md +++ b/doc/overview/OUTPUT_FILES.md @@ -4,26 +4,26 @@ The segmentation module outputs the files shown in the table below. The two primary output files are the `aparc.DKTatlas+aseg.deep.mgz` file, which contains the FastSurfer segmentation of cortical and subcortical structures based on the DKT atlas, and the `aseg+DKT.stats` file, which contains summary statistics for these structures. Note, that the surface model (downstream) corrects these segmentations along the cortex with the created surfaces. So if the surface model is used, it is recommended to use the updated segmentations and stats (see below). -| directory | filename | module | description | -|:------------|-------------------------------|-----------|-------------| -| mri | aparc.DKTatlas+aseg.deep.mgz | asegdkt | cortical and subcortical segmentation| -| mri | aseg.auto_noCCseg.mgz | asegdkt | simplified subcortical segmentation without corpus callosum labels| -| mri | mask.mgz | asegdkt | brainmask| -| mri | orig.mgz | asegdkt | conformed image| -| mri | orig_nu.mgz | asegdkt | biasfield-corrected image| -| mri/orig | 001.mgz | asegdkt | original image| -| scripts | deep-seg.log | asegdkt | logfile| -| stats | aseg+DKT.stats | asegdkt | table of cortical and subcortical segmentation statistics| +| directory | filename | module | description | +|:----------|------------------------------|---------|--------------------------------------------------------------------| +| mri | aparc.DKTatlas+aseg.deep.mgz | asegdkt | cortical and subcortical segmentation | +| mri | aseg.auto_noCCseg.mgz | asegdkt | simplified subcortical segmentation without corpus callosum labels | +| mri | mask.mgz | asegdkt | brainmask | +| mri | orig.mgz | asegdkt | conformed image | +| mri | orig_nu.mgz | asegdkt | biasfield-corrected image | +| mri/orig | 001.mgz | asegdkt | original image | +| scripts | deep-seg.log | asegdkt | logfile | +| stats | aseg+DKT.stats | asegdkt | table of cortical and subcortical segmentation statistics | ## Cerebnet module The cerebellum module outputs the files in the table shown below. Unless switched off by the `--no_cereb` argument, this module is automatically run whenever the segmentation module is run. It adds two files, an image with the sub-segmentation of the cerebellum and a text file with summary statistics. -| directory | filename | module | description | -|:------------|-------------------------------|-----------|-------------| -| mri | cerebellum.CerebNet.nii.gz | cerebnet | cerebellum sub-segmentation| -| stats | cerebellum.CerebNet.stats | cerebnet | table of cerebellum segmentation statistics| +| directory | filename | module | description | +|:----------|----------------------------|----------|---------------------------------------------| +| mri | cerebellum.CerebNet.nii.gz | cerebnet | cerebellum sub-segmentation | +| stats | cerebellum.CerebNet.stats | cerebnet | table of cerebellum segmentation statistics | ## HypVINN module @@ -51,26 +51,26 @@ After running this module, some of the initial segmentations and corresponding v The primary output files are pial, white, and inflated surface files, the thickness overlay files, and the cortical parcellation (annotation) files. The preferred way of assessing this output is the [FreeView](https://surfer.nmr.mgh.harvard.edu/fswiki/FreeviewGuide) software. Summary statistics for volume and thickness estimates per anatomical structure are reported in the stats files, in particular the `aseg.stats`, and the left and right `aparc.DKTatlas.mapped.stats` files. -| directory | filename | module | description | -|:------------|-------------------------------|-----------|-------------| -| mri | aparc.DKTatlas+aseg.deep.withCC.mgz| surface | cortical and subcortical segmentation incl. corpus callosum after running the surface module| -| mri | aparc.DKTatlas+aseg.mapped.mgz| surface | cortical and subcortical segmentation after running the surface module| -| mri | aparc.DKTatlas+aseg.mgz | surface | symlink to aparc.DKTatlas+aseg.mapped.mgz| -| mri | aparc+aseg.mgz | surface | symlink to aparc.DKTatlas+aseg.mapped.mgz| -| mri | aseg.mgz | surface | subcortical segmentation after running the surface module| -| mri | wmparc.DKTatlas.mapped.mgz | surface | white matter parcellation| -| mri | wmparc.mgz | surface | symlink to wmparc.DKTatlas.mapped.mgz| -| surf | lh.area, rh.area | surface | surface area overlay file| -| surf | lh.curv, rh.curv | surface | curvature overlay file| -| surf | lh.inflated, rh.inflated | surface | inflated cortical surface| -| surf | lh.pial, rh.pial | surface | pial surface| -| surf | lh.thickness, rh.thickness | surface | cortical thickness overlay file| -| surf | lh.volume, rh.volume | surface | gray matter volume overlay file| -| surf | lh.white, rh.white | surface | white matter surface| -| label | lh.aparc.DKTatlas.annot, rh.aparc.DKTatlas.annot| surface | symlink to lh.aparc.DKTatlas.mapped.annot| -| label | lh.aparc.DKTatlas.mapped.annot, rh.aparc.DKTatlas.mapped.annot| surface | annotation file for cortical parcellations, mapped from ASEGDKT segmentation to the surface| -| stats | aseg.stats | surface | table of cortical and subcortical segmentation statistics after running the surface module| -| stats | lh.aparc.DKTatlas.mapped.stats, rh.aparc.DKTatlas.mapped.stats| surface | table of cortical parcellation statistics, mapped from ASEGDKT segmentation to the surface| -| stats | lh.curv.stats, rh.curv.stats | surface | table of curvature statistics| -| stats | wmparc.DKTatlas.mapped.stats | surface | table of white matter segmentation statistics| -| scripts | recon-all.log | surface | logfile| \ No newline at end of file +| directory | filename | module | description | +|:----------|----------------------------------------------------------------|---------|----------------------------------------------------------------------------------------------| +| mri | aparc.DKTatlas+aseg.deep.withCC.mgz | surface | cortical and subcortical segmentation incl. corpus callosum after running the surface module | +| mri | aparc.DKTatlas+aseg.mapped.mgz | surface | cortical and subcortical segmentation after running the surface module | +| mri | aparc.DKTatlas+aseg.mgz | surface | symlink to aparc.DKTatlas+aseg.mapped.mgz | +| mri | aparc+aseg.mgz | surface | symlink to aparc.DKTatlas+aseg.mapped.mgz | +| mri | aseg.mgz | surface | subcortical segmentation after running the surface module | +| mri | wmparc.DKTatlas.mapped.mgz | surface | white matter parcellation | +| mri | wmparc.mgz | surface | symlink to wmparc.DKTatlas.mapped.mgz | +| surf | lh.area, rh.area | surface | surface area overlay file | +| surf | lh.curv, rh.curv | surface | curvature overlay file | +| surf | lh.inflated, rh.inflated | surface | inflated cortical surface | +| surf | lh.pial, rh.pial | surface | pial surface | +| surf | lh.thickness, rh.thickness | surface | cortical thickness overlay file | +| surf | lh.volume, rh.volume | surface | gray matter volume overlay file | +| surf | lh.white, rh.white | surface | white matter surface | +| label | lh.aparc.DKTatlas.annot, rh.aparc.DKTatlas.annot | surface | symlink to lh.aparc.DKTatlas.mapped.annot | +| label | lh.aparc.DKTatlas.mapped.annot, rh.aparc.DKTatlas.mapped.annot | surface | annotation file for cortical parcellations, mapped from ASEGDKT segmentation to the surface | +| stats | aseg.stats | surface | table of cortical and subcortical segmentation statistics after running the surface module | +| stats | lh.aparc.DKTatlas.mapped.stats, rh.aparc.DKTatlas.mapped.stats | surface | table of cortical parcellation statistics, mapped from ASEGDKT segmentation to the surface | +| stats | lh.curv.stats, rh.curv.stats | surface | table of curvature statistics | +| stats | wmparc.DKTatlas.mapped.stats | surface | table of white matter segmentation statistics | +| scripts | recon-all.log | surface | logfile | \ No newline at end of file diff --git a/recon_surf/N4_bias_correct.py b/recon_surf/N4_bias_correct.py index 31e26a018..cddacd2af 100644 --- a/recon_surf/N4_bias_correct.py +++ b/recon_surf/N4_bias_correct.py @@ -20,7 +20,7 @@ import logging import sys from pathlib import Path -from typing import Optional, cast, Literal +from typing import Optional, cast, Literal, TypeVar, Callable, get_args, Union # Group 2: External modules import SimpleITK as sitk @@ -30,7 +30,6 @@ # Group 3: Internal modules import image_io as iio -logger = logging.getLogger(__name__) HELPTEXT = """ Script to call SITK N4 Bias Correction @@ -88,20 +87,36 @@ Date: Feb-27-2024 """ -h_verbosity = "Logging verbosity: 0 (none), 1 (normal), 2 (debug)" -h_invol = "path to input.nii.gz" -h_outvol = "path to corrected.nii.gz" -h_uchar = ("sets the output dtype to uchar (only applies to outvol, rescalevol " - "is uchar by default.)") -h_rescaled = "path to rescaled.nii.gz" -h_mask = "optional: path to mask.nii.gz" -h_aseg = "optional: path to aseg or aseg+dkt image to find the white matter mask" -h_shrink = " shrink factor, default: 4" -h_levels = " number of fitting levels, default: 4" -h_numiter = " max number of iterations per level, default: 50" -h_thres = " convergence threshold, default: 0.0" -h_tal = " file name of talairach.xfm if using this for finding origin" -h_threads = " number of threads, default: 1" +HELP_VERBOSITY = "Logging verbosity: 0 (none), 1 (normal), 2 (debug)" +HELP_INVOL = "path to input.nii.gz" +HELP_OUTVOL = "path to corrected.nii.gz" +HELP_UCHAR = ("sets the output dtype to uchar (only applies to outvol, rescalevol " + "is uchar by default.)") +HELP_RESCALED = "path to rescaled.nii.gz" +HELP_MASK = "optional: path to mask.nii.gz" +HELP_ASEG = "optional: path to aseg or aseg+dkt image to find the white matter mask" +HELP_SHRINK_FACTOR = " shrink factor, default: 4" +HELP_LEVELS = " number of fitting levels, default: 4" +HELP_NUM_ITER = " max number of iterations per level, default: 50" +HELP_THRESHOLD = " convergence threshold, default: 0.0" +HELP_TALAIRACH = " file name of talairach.xfm if using this for finding origin" +HELP_THREADS = " number of threads, default: 1" +LiteralSkipRescaling = Literal["skip rescaling"] +SKIP_RESCALING: LiteralSkipRescaling = "skip rescaling" +LiteralDoNotSave = Literal["do not save"] +DO_NOT_SAVE: LiteralDoNotSave = "do not save" + +logger = logging.getLogger(__name__) + +_T = TypeVar("_T", bound=str) + + +def path_or_(*constants: _T) -> Callable[[str], _T]: + def wrapper(a: str) -> Path | _T: + if a in constants: + return a + return Path(a) + return wrapper def options_parse(): @@ -121,91 +136,91 @@ def options_parse(): dest="verbosity", choices=(0, 1, 2), default=-1, - help=h_verbosity, + help=HELP_VERBOSITY, type=int, ) parser.add_argument( "--in", dest="invol", type=Path, - help=h_invol, + help=HELP_INVOL, required=True, ) parser.add_argument( "--out", dest="outvol", - help=h_outvol, - default="do not save", - type=Path, + help=HELP_OUTVOL, + default=DO_NOT_SAVE, + type=path_or_(DO_NOT_SAVE), ) parser.add_argument( "--uchar", dest="dtype", action="store_const", const="uint8", - help=h_uchar, + help=HELP_UCHAR, default="keep", ) parser.add_argument( "--rescale", dest="rescalevol", - help=h_rescaled, - default="skip rescaling", - type=Path, + help=HELP_RESCALED, + default=SKIP_RESCALING, + type=path_or_(SKIP_RESCALING), ) parser.add_argument( "--mask", dest="mask", - help=h_mask, + help=HELP_MASK, default=None, type=Path, ) parser.add_argument( "--aseg", dest="aseg", - help=h_aseg, + help=HELP_ASEG, default=None, type=Path, ) parser.add_argument( "--shrink", dest="shrink", - help=h_shrink, + help=HELP_SHRINK_FACTOR, default=4, type=int, ) parser.add_argument( "--levels", dest="levels", - help=h_levels, + help=HELP_LEVELS, default=4, type=int, ) parser.add_argument( "--numiter", dest="numiter", - help=h_numiter, + help=HELP_NUM_ITER, default=50, type=int, ) parser.add_argument( "--thres", dest="thres", - help=h_thres, + help=HELP_THRESHOLD, default=0.0, type=float, ) parser.add_argument( "--tal", dest="tal", - help=h_tal, + help=HELP_TALAIRACH, default=None, type=Path, ) parser.add_argument( "--threads", dest="threads", - help=h_threads, + help=HELP_THREADS, default=1, type=int, ) @@ -340,14 +355,19 @@ def get_distance(axis): # get ndarray from sitk image image = sitk.GetArrayFromImage(itk_image) # get 90th percentiles of intensities in ball (to identify WM intensity) - source_intensity = np.percentile(image[ball], [1, 90]) + source_intensity_bg, source_intensity_wm = np.percentile(image[ball], [1, 90]) _logger.info( - f"- source background intensity: {source_intensity[0]:.2f}" - f"- source white matter intensity: {source_intensity[1]:.2f}" + f"- source background intensity: {source_intensity_bg:.2f}" + f"- source white matter intensity: {source_intensity_wm:.2f}" ) - return normalize_img(itk_image, itk_mask, tuple(source_intensity.tolist()), (target_bg, target_wm)) + return normalize_img( + itk_image, + itk_mask, + (source_intensity_bg, source_intensity_wm), + (target_bg, target_wm), + ) def normalize_wm_aseg( @@ -358,8 +378,8 @@ def normalize_wm_aseg( target_bg: float = 3. ) -> sitk.Image: """ - Normalize WM image so the white matter has a mean - intensity of target_wm and the backgroud has intensity target_bg. + Normalize WM image so the white matter has a mean intensity of target_wm and the + background has intensity target_bg. Parameters ---------- @@ -369,10 +389,6 @@ def normalize_wm_aseg( Image mask. itk_aseg : sitk.Image Aseg-like segmentation image to find WM. - radius : float | int - Defaults to 50 [MISSING]. - centroid : Optional[np.ndarray] - Image centroid. Defaults to None. target_wm : float | int Target white matter intensity. Defaults to 110. target_bg : float | int @@ -399,7 +415,12 @@ def normalize_wm_aseg( f"- source white matter intensity: {source_wm_intensity:.2f}" ) - return normalize_img(itk_image, itk_mask, (source_bg, source_wm_intensity), (target_bg, target_wm)) + return normalize_img( + itk_image, + itk_mask, + (source_bg, source_wm_intensity), + (target_bg, target_wm), + ) def normalize_img( @@ -430,7 +451,10 @@ def normalize_img( """ _logger = logging.getLogger(__name__ + ".normalize_wm") # compute intensity transformation - m = (target_intensity[0] - target_intensity[1]) / (source_intensity[0] - source_intensity[1]) + m = ( + (target_intensity[0] - target_intensity[1]) + / (source_intensity[0] - source_intensity[1]) + ) _logger.info(f"- m: {m:.4f}") # itk_image already is Float32 and output should be also Float32, we clamp outside @@ -469,15 +493,22 @@ def read_talairach_xfm(fname: Path | str) -> np.ndarray: lines = f.readlines() try: - transf_start = [ln.lower().startswith("linear_") for ln in lines].index(True) + 1 - tal_str = [ln.replace(";", " ") for ln in lines[transf_start:transf_start + 3]] + transform_iter = iter(lines) + # advance transform_iter to linear header + _ = next(l for l in transform_iter if l.lower().startswith("linear_")) + # return the next 3 lines in transform_lines + transform_lines = (l for l, _ in zip(transform_iter, range(3))) + tal_str = [ln.replace(";", " ") for ln in transform_lines] tal = np.genfromtxt(tal_str) tal = np.vstack([tal, [0, 0, 0, 1]]) _logger.info(f"- tal: {tal}") return tal - except Exception as e: + except StopIteration: + _logger.error(msg := f"Could not find 'linear_' in {fname}.") + raise ValueError(msg) + except (Exception, StopIteration) as e: err = ValueError(f"Could not find taiairach transform in {fname}.") _logger.exception(err) raise err from e @@ -583,9 +614,8 @@ def get_brain_centroid(itk_mask: sitk.Image) -> np.ndarray: def main( invol: Path, - outvol: Literal["do not save"] | Path = "do not save", - rescalevol: Literal["skip rescaling"] | Path = "skip rescaling", - /, + outvol: LiteralDoNotSave | Path = DO_NOT_SAVE, + rescalevol: LiteralSkipRescaling | Path = SKIP_RESCALING, dtype: str = "keep", threads: int = 1, mask: Optional[Path] = None, @@ -595,9 +625,12 @@ def main( numiter: int = 50, thres: float = 0.0, tal: Optional[Path] = None, + verbosity: int = -1, ) -> int | str: - if rescalevol == "skip rescaling" and outvol == "do not save": - logger.error("Neither the rescaled nor the unrescaled volume are saved, aborting.") + if rescalevol == "skip rescaling" and outvol == DO_NOT_SAVE: + logger.error( + "Neither the rescaled nor the unrescaled volume are saved, aborting." + ) sys.exit(1) # set number of threads @@ -637,7 +670,7 @@ def main( thres, ) - if outvol != "do not save": + if outvol != DO_NOT_SAVE: logger.info("Skipping WM normalization, ignoring talairach and aseg inputs") # normalize to average input intensity @@ -674,10 +707,10 @@ def main( itk_outvol = sitk.Cast(itk_outvol, sitk_dtype) # write image - logger.info(f"writing: {outvol}") + logger.info(f"writing {type(outvol).__name__}: {outvol}") iio.writeITKimage(itk_outvol, str(outvol), image_header) - if rescalevol == "skip rescaling": + if rescalevol == SKIP_RESCALING: logger.info("Skipping WM normalization, ignoring talairach and aseg inputs") else: target_wm = 110. @@ -724,7 +757,7 @@ def main( ) # write image - logger.info(f"writing: {rescalevol}") + logger.info(f"writing {type(rescalevol).__name__}: {rescalevol}") iio.writeITKimage(itk_bfcorr_image, str(rescalevol), image_header) return 0