Source code for unravel.image_tools.invert

#!/usr/bin/env python3

"""
Use ``img_invert`` (``invert``) from UNRAVEL to invert a 3D image and save it.

Inputs:
    - path/img<.nii.gz|.tif|.czi|.zarr> or glob pattern(s)
    
Outputs:
    - path/img_inv.<ext>

Note:
    - Inversion is performed as max(image) - x, independent of data type.
    - A warning is issued if the input contains negative values.

Usage:
------
    invert [-i path/img<.nii.gz|.tif|.czi|.zarr> or glob pattern(s)] [-c channel] [-dt dtype] [-v]
"""

from pathlib import Path
import warnings
import numpy as np
from rich import print
from rich.traceback import install

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


[docs] def parse_args(): parser = RichArgumentParser(formatter_class=SuppressMetavar, add_help=False, docstring=__doc__) 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('-c', '--channel', help='.czi channel index. Default: 1', default=1, type=int, action=SM) opts.add_argument('-dt', '--dtype', help='Desired dtype for output (e.g., uint8, uint16). Default: uint16', default="uint16", action=SM) 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] @print_func_name_args_times() def invert_img(ndarray, dtype=None): """ Invert an image using data-range inversion. Behavior: inv = max(image) - x Notes: - Inversion is based on the image maximum, not the data type range. - If the input contains negative values, a warning is issued. """ if not np.issubdtype(ndarray.dtype, np.number): raise TypeError(f"invert_img() expects a numeric array. Got {ndarray.dtype}") work = ndarray.astype(np.float64, copy=False) img_min = float(work.min()) img_max = float(work.max()) if img_min < 0: warnings.warn( f"Input image contains negative values (min={img_min:.3g}); " "inversion uses max(image) - x.", RuntimeWarning ) inv = img_max - work if dtype is not None: out_dtype = np.dtype(dtype) if np.issubdtype(out_dtype, np.integer): info = np.iinfo(out_dtype) inv = np.clip(inv, info.min, info.max) inv = inv.astype(out_dtype, copy=False) return inv
[docs] @log_command def main(): install() args = parse_args() Configuration.verbose = args.verbose verbose_start_msg() img_paths = match_files(args.input) for img_path in img_paths: print(f'\n Processing image: {img_path}\n') img = load_3D_img(img_path, channel=args.channel) # Invert the image (max(input_dtype) - x) img_inv = invert_img(img, dtype=args.dtype) # Save the inverted image ext = get_extension(img_path) output_path = img_path.parent / str(img_path.name).replace(ext, f'_inv{ext}') save_3D_img(img_inv, output_path=output_path, data_type=args.dtype, reference_img=img_path, verbose=args.verbose) verbose_end_msg()
if __name__ == '__main__': main()