Repository: Project-MONAI/MONAI

Issue:
SegmentationSaver fails if dtype is np.uint8
SegmentationSaver fails with RuntimeError: "inverse_cpu" not implemented for 'Byte' if dtype is np.uint8 in monai.networks.utils line 158. It hapens when the output image that should be written by the Segmentation Saver should be uint8 (which in my eyes is the most meaningfull datatype for saving labelmaps) and an affine transformation is to be applied to rescale the output to the same spacing as the inputimage was. 

It debugged it down to monai.data.nifty_writer.py line 135 wehre the dtype of the SegmentationSaver is set before the transormation is done. This is happening since monai 3.0 and is still the case in monai 4.0. In monai 2.0 the dtype before the call off affine_xform was hardcoded to np.float32 so that the transformation worked out and the desired output dtype was just set at the end of nifti_writer.py. The workaround for now is to not set the output datatype for the SegmentationSaver so it defaults to np.float32, however the output labelmaps get much larger compared to using np.unit8.

**To Reproduce**
Use the SegmentationSaver with some integer dtype, and make it use an affine transformation (eg. rescale to the original spacing of the inputfile with batch_transform=lambda batch: batch["somedictkey_meta_dict"])

**Expected behavior**
Should write uint8 labelmaps

**Environment**
monai 4.0 and 3.0,
works in monai 2.0

Thank you guys once more for the amazing work :-)
Best Eli



Discussion:
thanks for the detailed feedback, there are options of `dtype` and `output_dtype` in nifti_writer 0.4.0 to address this issue: https://github.com/Project-MONAI/MONAI/blob/1608b10fa9ad2ced77d7fc66373c513017f89fea/monai/data/nifti_writer.py#L34-L35 
the options should be made available for `NiftiSaver` `SegmentationSaver`
Hi @elitap and @wyli ,

Thanks for raising this up, I will try to enhance it ASAP.


Resolving patch:
diff --git a/monai/data/nifti_saver.py b/monai/data/nifti_saver.py
--- a/monai/data/nifti_saver.py
+++ b/monai/data/nifti_saver.py
@@ -37,6 +37,7 @@ def __init__(
         padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.BORDER,
         align_corners: bool = False,
         dtype: Optional[np.dtype] = np.float64,
+        output_dtype: Optional[np.dtype] = np.float32,
     ) -> None:
         """
         Args:
@@ -57,6 +58,7 @@ def __init__(
             dtype: data type for resampling computation. Defaults to ``np.float64`` for best precision.
                 If None, use the data type of input data. To be compatible with other modules,
                 the output data type is always ``np.float32``.
+            output_dtype: data type for saving data. Defaults to ``np.float32``.
         """
         self.output_dir = output_dir
         self.output_postfix = output_postfix
@@ -66,6 +68,7 @@ def __init__(
         self.padding_mode: GridSamplePadMode = GridSamplePadMode(padding_mode)
         self.align_corners = align_corners
         self.dtype = dtype
+        self.output_dtype = output_dtype
         self._data_index = 0
 
     def save(self, data: Union[torch.Tensor, np.ndarray], meta_data: Optional[Dict] = None) -> None:
@@ -118,6 +121,7 @@ def save(self, data: Union[torch.Tensor, np.ndarray], meta_data: Optional[Dict]
             padding_mode=self.padding_mode,
             align_corners=self.align_corners,
             dtype=self.dtype,
+            output_dtype=self.output_dtype,
         )
 
     def save_batch(self, batch_data: Union[torch.Tensor, np.ndarray], meta_data: Optional[Dict] = None) -> None:
diff --git a/monai/handlers/segmentation_saver.py b/monai/handlers/segmentation_saver.py
--- a/monai/handlers/segmentation_saver.py
+++ b/monai/handlers/segmentation_saver.py
@@ -38,7 +38,8 @@ def __init__(
         mode: Union[GridSampleMode, InterpolateMode, str] = "nearest",
         padding_mode: Union[GridSamplePadMode, str] = GridSamplePadMode.BORDER,
         scale: Optional[int] = None,
-        dtype: Optional[np.dtype] = None,
+        dtype: Optional[np.dtype] = np.float64,
+        output_dtype: Optional[np.dtype] = np.float32,
         batch_transform: Callable = lambda x: x,
         output_transform: Callable = lambda x: x,
         name: Optional[str] = None,
@@ -69,8 +70,10 @@ def __init__(
             scale: {``255``, ``65535``} postprocess data by clipping to [0, 1] and scaling
                 [0, 255] (uint8) or [0, 65535] (uint16). Default is None to disable scaling.
                 It's used for PNG format only.
-            dtype: convert the image data to save to this data type.
-                If None, keep the original type of data. It's used for Nifti format only.
+            dtype: data type for resampling computation. Defaults to ``np.float64`` for best precision.
+                If None, use the data type of input data. To be compatible with other modules,
+                the output data type is always ``np.float32``, it's used for Nifti format only.
+            output_dtype: data type for saving data. Defaults to ``np.float32``, it's used for Nifti format only.
             batch_transform: a callable that is used to transform the
                 ignite.engine.batch into expected format to extract the meta_data dictionary.
             output_transform: a callable that is used to transform the
@@ -90,6 +93,7 @@ def __init__(
                 mode=GridSampleMode(mode),
                 padding_mode=padding_mode,
                 dtype=dtype,
+                output_dtype=output_dtype,
             )
         elif output_ext == ".png":
             self.saver = PNGSaver(


Test patch: