Source code for miblab.dlsegkidneyfatwater

import os
import sys
import shutil

import numpy as np

from miblab.data import zenodo_fetch
from miblab.data import clear_cache_datafiles

if sys.version_info < (3, 9):
    # importlib.resources either doesn't exist or lacks the files()
    # function, so use the PyPI version:
    import importlib_resources
else:
    # importlib.resources has files(), so use that:
    import importlib.resources as importlib_resources

try:
    from nnunetv2.inference.predict_from_raw_data import nnUNetPredictor
    nnunetv2 = True
except ImportError:
    nnunetv2 = False

try:
    import torch
    torch_installed = True
except ImportError:
    torch_installed = False

try:
    import nibabel as nib
    nib_installed = True
except ImportError:
    nib_installed = False

MODEL = 'nnunet_kidney_fatwater_v1.zip'
MODEL_DOI = "15356746"

[docs] def kidney_dixon_fat_water(input_array, clear_cache =False, verbose=False): """ Calculate fat/water maps on post-contrast Dixon images. This requires 2-channel input data with out-phase images, in-phase images. This uses a pretrained nnunet based model, hosted on `Zenodo <https://zenodo.org/records/15356746>`_ under the hood this runs nnUNetPredictor (for more details `MIC-DKFZ Wiki <https://deepwiki.com/MIC-DKFZ/nnUNet>`_) Args: input_array (numpy.ndarray): A 4D numpy array of shape [x, y, z, contrast] representing the input medical image volume. The last index must contain out-phase, in-phase, in that order. clear_cache: If True, the downloaded pth file is removed again after running the inference. verbose (bool): If True, prints logging messages. Returns: dict: A dictionary with the keys 'fat' and 'water', each containing a binary NumPy array representing the respective map. Example: >>> import numpy as np >>> import miblab >>> data = np.random.rand(128, 128, 30, 2) >>> fatwatermap = miblab.kidney_dixon_fat_water(data) >>> print(fatwatermap['fat'].shape) [128, 128, 30] """ if not torch_installed: raise ImportError( 'torch is not installed. Please install it with "pip install torch".' 'To install all dlseg options at once, install miblab as pip install miblab[dlseg].' ) if not nnunetv2: raise ImportError( 'nnunetv2 is not installed. Please install it with "pip install nnunetv2".' 'To install all dlseg options at once, install miblab as pip install miblab[dlseg].' ) if not nib_installed: raise ImportError( 'nibabel is not installed. Please install it with "pip install nibabel".' 'To install all dlseg options at once, install miblab as pip install miblab[dlseg].' ) if verbose: print('Downloading model..') temp_dir = importlib_resources.files('miblab.datafiles') weights_path = zenodo_fetch(MODEL, temp_dir, MODEL_DOI,extract=True) if verbose: print('Applying model to data..') # Check is device has cuda (to speed up inference) os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" device_str = "cuda" if torch.cuda.is_available() else "cpu" # Setup device predictor = nnUNetPredictor( tile_step_size=0.75, use_gaussian=True, use_mirroring=False, perform_everything_on_device=True, device=torch.device(device_str), verbose=False, verbose_preprocessing=False, allow_tqdm=True, ) # direct to "nnUNetTrainer__nnUNetPlans__3d_fullres" nested_folder = os.path.join(weights_path, "nnUNetTrainer__nnUNetPlans__3d_fullres") # Setup the predictor predictor.initialize_from_trained_model_folder( nested_folder, use_folds='3', checkpoint_name='checkpoint_best.pth' ) # Generate temp folder for intermediate data temp_folder_results = os.path.join(temp_dir,"temp_results") temp_folder_data_to_test = os.path.join(temp_dir,"temp_results",'data_to_test') os.makedirs(temp_folder_data_to_test, exist_ok=True) # Save arrays as nifti (.nii) affine = np.eye(4) nii_out_ph = nib.Nifti1Image(input_array[...,0], affine) nib.save(nii_out_ph, os.path.join(temp_folder_data_to_test, 'Dixon_999_0000.nii.gz')) nii_in_ph = nib.Nifti1Image(input_array[...,1], affine) nib.save(nii_in_ph, os.path.join(temp_folder_data_to_test, 'Dixon_999_0001.nii.gz')) # Infere water dominant map predictor.predict_from_files( temp_folder_data_to_test, temp_folder_results, num_processes_preprocessing=1, # Limit RAM usage num_processes_segmentation_export=1, # Also reduce RAM save_probabilities=False, overwrite=True, ) # Load the NIfTI file nifti_file = nib.load(os.path.join(temp_folder_results,'Dixon_999.nii.gz')) array_Dixon_water_dom = nifti_file.get_fdata() # Calculate Fat/ Water maps from in/out-phase images (using predicted water dominant map) array_water_calculated = np.zeros_like(input_array[...,0]) # Ensure same shape array_water_calculated = np.where(array_Dixon_water_dom == 1, input_array[...,1] + input_array[...,0], array_water_calculated) array_water_calculated = np.where(array_Dixon_water_dom == 0, input_array[...,1] - input_array[...,0], array_water_calculated) array_fat_calculated = np.zeros_like(input_array[...,0]) # Ensure same shape array_fat_calculated = np.where(array_Dixon_water_dom == 0, input_array[...,1] + input_array[...,0], array_fat_calculated) array_fat_calculated = np.where(array_Dixon_water_dom == 1, input_array[...,1] - input_array[...,0], array_fat_calculated) if clear_cache: if verbose: print('Deleting downloaded files...') clear_cache_datafiles(temp_dir) fatwatermap = { "fat": array_fat_calculated, "water": array_water_calculated } return fatwatermap