From 1b701671cbf71b0f30e50589aeef56fafb93b23f Mon Sep 17 00:00:00 2001 From: Joshua Newton Date: Sat, 23 Mar 2024 11:16:42 -0400 Subject: [PATCH 1/4] `predict_single_npy_array`: Add doc notes re: SITK axis order This will hopefully help any users who try to call `predict_single_npy_array` on a npy array derived from a `nibabel`-loaded image. --- nnunetv2/inference/predict_from_raw_data.py | 3 +++ nnunetv2/inference/readme.md | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/nnunetv2/inference/predict_from_raw_data.py b/nnunetv2/inference/predict_from_raw_data.py index 8015ed77d..e9d6f3966 100644 --- a/nnunetv2/inference/predict_from_raw_data.py +++ b/nnunetv2/inference/predict_from_raw_data.py @@ -422,6 +422,9 @@ def predict_single_npy_array(self, input_image: np.ndarray, image_properties: di output_file_truncated: str = None, save_or_return_probabilities: bool = False): """ + input_image must use SimpleITK axis ordering! + (NB: if array comes from a nibabel-loaded image, you must swap the + axes of both the array *and* the spacing from [x,y,z] to [z,y,x]!) image_properties must only have a 'spacing' key! """ ppa = PreprocessAdapterFromNpy([input_image], [segmentation_previous_stage], [image_properties], diff --git a/nnunetv2/inference/readme.md b/nnunetv2/inference/readme.md index 4f832a158..a9d6d65d8 100644 --- a/nnunetv2/inference/readme.md +++ b/nnunetv2/inference/readme.md @@ -147,6 +147,7 @@ cons: tldr: - you give one image as npy array +- array must use [SimpleITK axis order](http://insightsoftwareconsortium.github.io/SimpleITK-Notebooks/Python_html/03_Image_Details.html#Conversion-between-numpy-and-SimpleITK) (see examples below) - everything is done in the main process: preprocessing, prediction, resampling, (export) - no interlacing, slowest variant! - ONLY USE THIS IF YOU CANNOT GIVE NNUNET MULTIPLE IMAGES AT ONCE FOR SOME REASON @@ -160,9 +161,15 @@ cons: - never the right choice unless you can only give a single image at a time to nnU-Net ```python - # predict a single numpy array + # predict a single numpy array (SimpleITK) img, props = SimpleITKIO().read_images([join(nnUNet_raw, 'Dataset003_Liver/imagesTr/liver_63_0000.nii.gz')]) ret = predictor.predict_single_npy_array(img, props, None, None, False) + + # predict a single numpy array (Nibabel with axes swapped) + img_nii = nib.load('Dataset003_Liver/imagesTr/liver_63_0000.nii.gz') + img = np.asanyarray(img_nii.dataobj).transpose([2, 1, 0]) # reverse axis order to match SITK + props = {'spacing': img_nii.header.get_zooms()[::-1]} # reverse axis order to match SITK + ret = predictor.predict_single_npy_array(img, props, None, None, False) ``` ## Predicting with a custom data iterator From e9ea09f351ad56b8fadf687eb2f6f558cd102c5f Mon Sep 17 00:00:00 2001 From: Joshua Newton Date: Sat, 23 Mar 2024 11:19:35 -0400 Subject: [PATCH 2/4] `predict_single_npy_array`: Copy warning from inference/readme.md --- nnunetv2/inference/predict_from_raw_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nnunetv2/inference/predict_from_raw_data.py b/nnunetv2/inference/predict_from_raw_data.py index e9d6f3966..daeead75e 100644 --- a/nnunetv2/inference/predict_from_raw_data.py +++ b/nnunetv2/inference/predict_from_raw_data.py @@ -422,6 +422,8 @@ def predict_single_npy_array(self, input_image: np.ndarray, image_properties: di output_file_truncated: str = None, save_or_return_probabilities: bool = False): """ + WARNING: SLOW. ONLY USE THIS IF YOU CANNOT GIVE NNUNET MULTIPLE IMAGES AT ONCE FOR SOME REASON. + input_image must use SimpleITK axis ordering! (NB: if array comes from a nibabel-loaded image, you must swap the axes of both the array *and* the spacing from [x,y,z] to [z,y,x]!) From 6ef34761d1b8d5fb94565105ed0f8eae7390fa71 Mon Sep 17 00:00:00 2001 From: Fabian Isensee Date: Wed, 27 Mar 2024 10:08:57 +0100 Subject: [PATCH 3/4] remove dict assertion --- nnunetv2/training/dataloading/base_data_loader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nnunetv2/training/dataloading/base_data_loader.py b/nnunetv2/training/dataloading/base_data_loader.py index 6a6a49f1f..21bddecf8 100644 --- a/nnunetv2/training/dataloading/base_data_loader.py +++ b/nnunetv2/training/dataloading/base_data_loader.py @@ -19,7 +19,6 @@ def __init__(self, pad_sides: Union[List[int], Tuple[int, ...], np.ndarray] = None, probabilistic_oversampling: bool = False): super().__init__(data, batch_size, 1, None, True, False, True, sampling_probabilities) - assert isinstance(data, nnUNetDataset), 'nnUNetDataLoaderBase only supports dictionaries as data' self.indices = list(data.keys()) self.oversample_foreground_percent = oversample_foreground_percent From c7f85b729145d8b8ebf1786aa87c45398f7fbc76 Mon Sep 17 00:00:00 2001 From: Fabian Isensee Date: Wed, 27 Mar 2024 15:59:42 +0100 Subject: [PATCH 4/4] documentation: further clarify the need for consistency of axes ordering between training and inference --- nnunetv2/inference/predict_from_raw_data.py | 11 ++++++++--- nnunetv2/inference/readme.md | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/nnunetv2/inference/predict_from_raw_data.py b/nnunetv2/inference/predict_from_raw_data.py index daeead75e..2ac6600c2 100644 --- a/nnunetv2/inference/predict_from_raw_data.py +++ b/nnunetv2/inference/predict_from_raw_data.py @@ -424,9 +424,14 @@ def predict_single_npy_array(self, input_image: np.ndarray, image_properties: di """ WARNING: SLOW. ONLY USE THIS IF YOU CANNOT GIVE NNUNET MULTIPLE IMAGES AT ONCE FOR SOME REASON. - input_image must use SimpleITK axis ordering! - (NB: if array comes from a nibabel-loaded image, you must swap the - axes of both the array *and* the spacing from [x,y,z] to [z,y,x]!) + + input_image: Make sure to load the image in the way nnU-Net expects! nnU-Net is trained on a certain axis + ordering which cannot be disturbed in inference, + otherwise you will get bad results. The easiest way to achieve that is to use the same I/O class + for loading images as was used during nnU-Net preprocessing! You can find that class in your + plans.json file under the key "image_reader_writer". If you decide to freestyle, know that the + default axis ordering for medical images is the one from SimpleITK. If you load with nibabel, + you need to transpose your axes AND your spacing from [x,y,z] to [z,y,x]! image_properties must only have a 'spacing' key! """ ppa = PreprocessAdapterFromNpy([input_image], [segmentation_previous_stage], [image_properties], diff --git a/nnunetv2/inference/readme.md b/nnunetv2/inference/readme.md index a9d6d65d8..d0acc6b2a 100644 --- a/nnunetv2/inference/readme.md +++ b/nnunetv2/inference/readme.md @@ -147,7 +147,11 @@ cons: tldr: - you give one image as npy array -- array must use [SimpleITK axis order](http://insightsoftwareconsortium.github.io/SimpleITK-Notebooks/Python_html/03_Image_Details.html#Conversion-between-numpy-and-SimpleITK) (see examples below) +- axes ordering must match the corresponding training data. The easiest way to achieve that is to use the same I/O class + for loading images as was used during nnU-Net preprocessing! You can find that class in your + plans.json file under the key "image_reader_writer". If you decide to freestyle, know that the + default axis ordering for medical images is the one from SimpleITK. If you load with nibabel, + you need to transpose your axes AND your spacing from [x,y,z] to [z,y,x]! - everything is done in the main process: preprocessing, prediction, resampling, (export) - no interlacing, slowest variant! - ONLY USE THIS IF YOU CANNOT GIVE NNUNET MULTIPLE IMAGES AT ONCE FOR SOME REASON @@ -161,12 +165,17 @@ cons: - never the right choice unless you can only give a single image at a time to nnU-Net ```python - # predict a single numpy array (SimpleITK) + # predict a single numpy array (SimpleITKIO) img, props = SimpleITKIO().read_images([join(nnUNet_raw, 'Dataset003_Liver/imagesTr/liver_63_0000.nii.gz')]) ret = predictor.predict_single_npy_array(img, props, None, None, False) - # predict a single numpy array (Nibabel with axes swapped) - img_nii = nib.load('Dataset003_Liver/imagesTr/liver_63_0000.nii.gz') + # predict a single numpy array (NibabelIO) + img, props = NibabelIO().read_images([join(nnUNet_raw, 'Dataset003_Liver/imagesTr/liver_63_0000.nii.gz')]) + ret = predictor.predict_single_npy_array(img, props, None, None, False) + + # The following IS NOT RECOMMENDED. Use nnunetv2.imageio! + # nibabel, we need to transpose axes and spacing to match the training axes ordering for the nnU-Net default: + nib.load('Dataset003_Liver/imagesTr/liver_63_0000.nii.gz') img = np.asanyarray(img_nii.dataobj).transpose([2, 1, 0]) # reverse axis order to match SITK props = {'spacing': img_nii.header.get_zooms()[::-1]} # reverse axis order to match SITK ret = predictor.predict_single_npy_array(img, props, None, None, False)