Source code for unravel.image_tools.smooth

#!/usr/bin/env python3

"""
Use ``img_smooth`` (``smooth``) from UNRAVEL to smooth an image.nii.gz and save it.

Note:
    - Sigma is the Gaussian standard deviation.
    - By default, sigma is interpreted in voxels (scipy.ndimage.gaussian_filter).
    - With --mm, sigma is interpreted in millimeters (FSL fslmaths -s).
    - This matches conventions used elsewhere in UNRAVEL (e.g., ``reg``: voxels; ``vstats``: mm).
    - Smoothing is performed in 3D.

Inputs:
    - path/img.nii.gz or glob pattern(s)

Outputs:
    - path/img_s<sigma><mm|vx>.nii.gz

Usage:
------
    img_smooth [-i path/img.nii.gz or glob pattern(s)] [-s sigma] [-m] [-v]
"""

from concurrent.futures import ThreadPoolExecutor
from fsl.wrappers import fslmaths
from pathlib import Path
from rich import print
from rich.traceback import install
from scipy.ndimage import gaussian_filter

from unravel.core.help_formatter import RichArgumentParser, SuppressMetavar, SM
from unravel.core.config import Configuration
from unravel.core.img_io import load_nii, save_3D_img
from unravel.core.utils import log_command, match_files, verbose_start_msg, verbose_end_msg


[docs] def parse_args(): parser = RichArgumentParser(formatter_class=SuppressMetavar, add_help=False, docstring=__doc__) reqs = parser.add_argument_group('Required arguments') reqs.add_argument('-s', '--sigma', help='Gaussian sigma (standard deviation). Default units: voxels; with --mm: millimeters.', required=True, type=float, action=SM) opts = parser.add_argument_group('Optional arguments') opts.add_argument('-i', '--input', help="path/img.nii.gz or glob pattern(s). Default: '*.nii.gz'", default='*.nii.gz', action=SM) opts.add_argument('-m', '--mm', help='Interpret --sigma in millimeters (uses FSL fslmaths -s).', default=False, action='store_true') general = parser.add_argument_group('General arguments') general.add_argument('-v', '--verbose', help='Increase verbosity. Default: False', action='store_true', default=False) return parser.parse_args()
[docs] def smooth_w_sigma_in_mm(img_path, sigma_mm, output_path): """ Smooth an image with a Gaussian kernel using FSL's fslmaths -s, which expects sigma in millimeters. Parameters: ----------- img_path : str or Path Path to the input image (e.g., .nii.gz). sigma_mm : float Standard deviation of the Gaussian kernel in millimeters. output_path : str or Path Path to save the smoothed image (e.g., .nii.gz). """ fslmaths(str(img_path)).s(sigma_mm).run(output=str(output_path))
[docs] def smooth_w_sigma_in_voxels(img_path, sigma_vx, output_path): """ Smooth an image with a Gaussian kernel using scipy.ndimage.gaussian_filter, which expects sigma in voxels. Parameters: ----------- img_path : str or Path Path to the input image (e.g., .nii.gz). sigma_vx : float Standard deviation of the Gaussian kernel in voxels. output_path : str or Path Path to save the smoothed image (e.g., .nii.gz). """ img = load_nii(img_path) smoothed_img = gaussian_filter(img, sigma=sigma_vx) save_3D_img(smoothed_img, output_path, reference_img=img_path, verbose=Configuration.verbose)
[docs] def smooth_image(img_path, sigma, mm=False): """ Smooth an image with a Gaussian kernel, interpreting sigma in either millimeters or voxels. Parameters: ----------- img_path : str or Path Path to the input image (e.g., .nii.gz). sigma : float Standard deviation of the Gaussian kernel. mm : bool, optional If True, interpret sigma in millimeters and use FSL's fslmaths -s. If False, interpret sigma in voxels and use scipy.ndimage.gaussian_filter. Default is False. """ output_path = str(Path(img_path).parent / (str(Path(img_path).name).replace('.nii.gz', f'_s{sigma:g}{"mm" if mm else "vx"}.nii.gz'))) if mm: smooth_w_sigma_in_mm(img_path, sigma, output_path) else: smooth_w_sigma_in_voxels(img_path, sigma, output_path)
[docs] @log_command def main(): install() args = parse_args() Configuration.verbose = args.verbose verbose_start_msg() img_paths = match_files(args.input) with ThreadPoolExecutor() as executor: executor.map(lambda img_path: smooth_image(img_path, args.sigma, mm=args.mm), img_paths) verbose_end_msg()
if __name__ == '__main__': main()