-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathdefault_normalization_schemes.py
98 lines (76 loc) · 4.05 KB
/
default_normalization_schemes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from abc import ABC, abstractmethod
from typing import Type
import numpy as np
from numpy import number
class ImageNormalization(ABC):
leaves_pixels_outside_mask_at_zero_if_use_mask_for_norm_is_true = None
def __init__(self, use_mask_for_norm: bool = None, intensityproperties: dict = None,
target_dtype: Type[number] = np.float32):
assert use_mask_for_norm is None or isinstance(use_mask_for_norm, bool)
self.use_mask_for_norm = use_mask_for_norm
assert isinstance(intensityproperties, dict)
self.intensityproperties = intensityproperties
self.target_dtype = target_dtype
@abstractmethod
def run(self, image: np.ndarray, seg: np.ndarray = None) -> np.ndarray:
"""
Image and seg must have the same shape. Seg is not always used
"""
pass
class ZScoreNormalization(ImageNormalization):
leaves_pixels_outside_mask_at_zero_if_use_mask_for_norm_is_true = True
def run(self, image: np.ndarray, seg: np.ndarray = None) -> np.ndarray:
"""
here seg is used to store the zero valued region. The value for that region in the segmentation is -1 by
default.
"""
image = image.astype(self.target_dtype, copy=False)
if self.use_mask_for_norm is not None and self.use_mask_for_norm:
# negative values in the segmentation encode the 'outside' region (think zero values around the brain as
# in BraTS). We want to run the normalization only in the brain region, so we need to mask the image.
# The default nnU-net sets use_mask_for_norm to True if cropping to the nonzero region substantially
# reduced the image size.
mask = seg >= 0
mean = image[mask].mean()
std = image[mask].std()
image[mask] = (image[mask] - mean) / (max(std, 1e-8))
else:
mean = image.mean()
std = image.std()
image -= mean
image /= (max(std, 1e-8))
return image
class CTNormalization(ImageNormalization):
leaves_pixels_outside_mask_at_zero_if_use_mask_for_norm_is_true = False
def run(self, image: np.ndarray, seg: np.ndarray = None) -> np.ndarray:
assert self.intensityproperties is not None, "CTNormalization requires intensity properties"
mean_intensity = self.intensityproperties['mean']
std_intensity = self.intensityproperties['std']
lower_bound = self.intensityproperties['percentile_00_5']
upper_bound = self.intensityproperties['percentile_99_5']
image = image.astype(self.target_dtype, copy=False)
np.clip(image, lower_bound, upper_bound, out=image)
image -= mean_intensity
image /= max(std_intensity, 1e-8)
return image
class NoNormalization(ImageNormalization):
leaves_pixels_outside_mask_at_zero_if_use_mask_for_norm_is_true = False
def run(self, image: np.ndarray, seg: np.ndarray = None) -> np.ndarray:
return image.astype(self.target_dtype, copy=False)
class RescaleTo01Normalization(ImageNormalization):
leaves_pixels_outside_mask_at_zero_if_use_mask_for_norm_is_true = False
def run(self, image: np.ndarray, seg: np.ndarray = None) -> np.ndarray:
image = image.astype(self.target_dtype, copy=False)
image -= image.min()
image /= np.clip(image.max(), a_min=1e-8, a_max=None)
return image
class RGBTo01Normalization(ImageNormalization):
leaves_pixels_outside_mask_at_zero_if_use_mask_for_norm_is_true = False
def run(self, image: np.ndarray, seg: np.ndarray = None) -> np.ndarray:
assert image.min() >= 0, "RGB images are uint 8, for whatever reason I found pixel values smaller than 0. " \
"Your images do not seem to be RGB images"
assert image.max() <= 255, "RGB images are uint 8, for whatever reason I found pixel values greater than 255" \
". Your images do not seem to be RGB images"
image = image.astype(self.target_dtype, copy=False)
image /= 255.
return image