Repository: mlcommons/GaNDLF

Issue:
Expose image rescaling as a preprocessing API
**Is your feature request related to a problem? Please describe.**
Currently, the rescaling operation is not exposed to users, but it can done easily via https://torchio.readthedocs.io/_modules/torchio/transforms/preprocessing/intensity/rescale.html

**Describe the solution you'd like**
Include `rescale` as a preprocessing option.

**Describe alternatives you've considered**
N.A.

**Additional context**
Very useful for CT images, where this potentially could work better compared to clipping + normalization [[ref](https://doi.org/10.1148/ryai.2019180021)].


Discussion:

Resolving patch:
diff --git a/GANDLF/data/preprocessing/__init__.py b/GANDLF/data/preprocessing/__init__.py
--- a/GANDLF/data/preprocessing/__init__.py
+++ b/GANDLF/data/preprocessing/__init__.py
@@ -24,6 +24,7 @@
     Resize,
     Resample,
     Compose,
+    RescaleIntensity,
 )
 
 
@@ -69,6 +70,17 @@ def centercrop_transform(patch_size):
     return CropOrPad(target_shape=generic_3d_check(patch_size))
 
 
+def rescale_transform(parameters=None):
+    if parameters is None:
+        parameters = {}
+    # get defaults from torchio
+    rescaler = RescaleIntensity()
+    rescaler.out_min_max = parameters.get("out_min_max", rescaler.out_min_max)
+    rescaler.percentiles = parameters.get("percentiles", rescaler.percentiles)
+    rescaler.in_min_max = parameters.get("in_min_max", None)
+    return rescaler
+
+
 # defining dict for pre-processing - key is the string and the value is the transform object
 global_preprocessing_dict = {
     "to_canonical": to_canonical_transform,
@@ -88,6 +100,7 @@ def centercrop_transform(patch_size):
     "normalize_nonzero": ZNormalization(masking_method=nonzero_voxel_mask),
     "normalize_nonZero_masked": NonZeroNormalizeOnMaskedRegion(),
     "normalize_nonzero_masked": NonZeroNormalizeOnMaskedRegion(),
+    "rescale": rescale_transform,
     "rgba2rgb": rgba2rgb_transform,
     "rgbatorgb": rgba2rgb_transform,
     "rgba_to_rgb": rgba2rgb_transform,
diff --git a/GANDLF/utils/tensor.py b/GANDLF/utils/tensor.py
--- a/GANDLF/utils/tensor.py
+++ b/GANDLF/utils/tensor.py
@@ -96,23 +96,18 @@ def reverse_one_hot(predmask_tensor, class_list):
             if isinstance(_class, str):
                 if case in _class:  # check if any of the special cases are present
                     special_case_detected = True
+                    break
 
     final_mask = np.zeros(predmask_array[0, ...].shape).astype(np.int16)
     predmask_array_bool = predmask_array >= 0.5
 
-    # in case special case is detected, if 0 is absent from
-    # class_list do not use 0 to initialize any value in final_mask
+    # for special case, do not use '0' to initialize any value in final_mask in case it is absent
     zero_present = False
     if special_case_detected:
         for _class in class_list:
-            if isinstance(_class, str):
-                if _class == "0":
-                    zero_present = True
-                    break
-            else:
-                if _class == 0:
-                    zero_present = True
-                    break
+            if (_class == "0") or (_class == 0):
+                zero_present = True
+                break
     for idx, i in enumerate(class_list):
         output_value = i
         # for special case, use the index as value


Test patch:
diff --git a/testing/test_full.py b/testing/test_full.py
--- a/testing/test_full.py
+++ b/testing/test_full.py
@@ -1466,6 +1466,24 @@ def test_generic_preprocess_functions():
     exception_raised = exc_info.value
     print("Exception raised: ", exception_raised)
 
+    ## image rescaling test
+    input_tensor = torch.randint(0, 256, (1, 64, 64, 64))
+    # try out different options
+    for params in [
+        {},
+        None,
+        {"in_min_max": [5, 250], "out_min_max": [-1, 2]},
+        {"out_min_max": [0, 1], "percentiles": [5, 95]},
+    ]:
+        rescaler = global_preprocessing_dict["rescale"](params)
+        input_transformed = rescaler(input_tensor)
+        assert (
+            input_transformed.min() >= rescaler.out_min_max[0]
+        ), "Rescaling should work for min"
+        assert (
+            input_transformed.max() <= rescaler.out_min_max[1]
+        ), "Rescaling should work for max"
+
     print("passed")