nnUNet icon indicating copy to clipboard operation
nnUNet copied to clipboard

Clarify that the default `.nii.gz` reader (`SimpleITKIO`) results in an axis swap when loading numpy arrays

Open joshuacwnewton opened this issue 1 year ago • 0 comments

Hi there! I come from a lab that primarily uses nibabel over SimpleITK for image processing.

When using nnUNetV2, we did the following:

  • Training: nnunetv2_train on .nii.gz images (no setting of overwrite_image_reader_writer in the dataset.json file)
    • SimpleITKIO was implicitly used as the image reader/writer by default
  • Inference: Load images in our Python script using nibabel, then pass data array to predict_single_npy_array

When following this approach, we learned of a quirk in SimpleITK's behavior:

SimpleITK and numpy indexing access is in opposite order!

SimpleITK: image[x,y,z] numpy: image_numpy_array[z,y,x]

Because of this, the x and z axes get swapped within nnUNet at this call:

https://github.com/MIC-DKFZ/nnUNet/blob/997804c7510634dc8fd83f1194b434c60815a93e/nnunetv2/imageio/simpleitk_reader_writer.py#L41

As a result, when loading a data array using nibabel (for inference purposes), the array (and the spacings!) must be transposed ([2, 1, 0]) to match the format of the training data. In fact, we had no idea that the training data had been transposed by SimpleITK in the first place! (In essence, even though our training data was in LPI orientation, the model was effectively trained using IPL orientation due to the axis swap!)

It might be good to emphasize in the relevant documentation (1, 2) ~the priority difference for .nii.gz image readers (SimpleITK > nibabel), as well as the data loading differences between the SimpleITKIO loader and the NibabelIO loader~. (EDIT: I now realize that NibabelIO will result in the same axis-swapping, making the choice of reader not as important as I thought.)

The axis-swapping behavior is largely invisible to the user (due to the use of npy arrays being internal to nnUNet)... until they try using predict_single_npy_array and get an empty prediction, at which point the training has already been completed. (Quite the "gotcha!")

joshuacwnewton avatar Feb 13 '24 17:02 joshuacwnewton