BUG: Fix missing channels dimension in normalization (#701)
* Fix missing channels dimension in normalization * Update CHANGELOG * Add test for 3D and 4D input images * Move conversion to NumPy array
This commit is contained in:
Родитель
edc72eda8c
Коммит
a9fd52c2c9
|
@ -98,6 +98,7 @@ gets uploaded to AzureML, by skipping all test folders.
|
|||
|
||||
#### Fixed
|
||||
|
||||
- ([#701](https://github.com/microsoft/InnerEye-DeepLearning/pull/701)) Fix 3D images expected to be 4D for intensity normalization.
|
||||
- ([#704](https://github.com/microsoft/InnerEye-DeepLearning/pull/704)) Add submodules to sys.path to fix autodoc's warning.
|
||||
- ([#699](https://github.com/microsoft/InnerEye-DeepLearning/pull/699)) Fix Sphinx warnings.
|
||||
- ([#682](https://github.com/microsoft/InnerEye-DeepLearning/pull/682)) Ensure the shape of input patches is compatible with model constraints.
|
||||
|
|
|
@ -84,6 +84,10 @@ class PhotometricNormalization(Transform3D[Sample]):
|
|||
else:
|
||||
mask = np.ones_like(image)
|
||||
|
||||
is3d = image.ndim == 3
|
||||
if is3d:
|
||||
image = image[np.newaxis]
|
||||
|
||||
self.status_of_most_recent_call = None
|
||||
if self.norm_method == PhotometricNormalizationMethod.Unchanged:
|
||||
image_out = image
|
||||
|
@ -116,7 +120,10 @@ class PhotometricNormalization(Transform3D[Sample]):
|
|||
raise ValueError("Unknown normalization method {}".format(self.norm_method))
|
||||
if patient_id is not None and self.status_of_most_recent_call is not None:
|
||||
logging.debug(f"Photonorm patient {patient_id}: {self.status_of_most_recent_call}")
|
||||
check_array_range(image_out, error_prefix="Normalized image")
|
||||
check_array_range(np.asarray(image_out), error_prefix="Normalized image")
|
||||
|
||||
if is3d:
|
||||
image_out = image_out[0]
|
||||
|
||||
return image_out
|
||||
|
||||
|
|
|
@ -389,16 +389,16 @@ def get_center_crop(image: NumpyOrTorch, crop_shape: TupleInt3) -> NumpyOrTorch:
|
|||
|
||||
|
||||
def check_array_range(data: np.ndarray, expected_range: Optional[Range] = None,
|
||||
error_prefix: str = None) -> None:
|
||||
error_prefix: Optional[str] = None) -> None:
|
||||
"""
|
||||
Checks if all values in the given array fall into the expected range. If not, raises a
|
||||
ValueError, and prints out statistics about the values that fell outside the expected range.
|
||||
``ValueError``, and prints out statistics about the values that fell outside the expected range.
|
||||
If no range is provided, it checks that all values in the array are finite (that is, they are not
|
||||
infinity and not np.nan
|
||||
infinity and not ``np.nan``).
|
||||
|
||||
:param data: The array to check. It can have any size.
|
||||
:param expected_range: The interval that all array elements must fall into. The first entry is the lower
|
||||
bound, the second entry is the upper bound.
|
||||
bound, the second entry is the upper bound.
|
||||
:param error_prefix: A string to use as the prefix for the error message.
|
||||
"""
|
||||
if expected_range is None:
|
||||
|
|
|
@ -36,14 +36,14 @@ use_gpu = not no_gpu_available
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def image_rand_pos() -> Union[torch.Tensor, np.ndarray]:
|
||||
def image_rand_pos() -> np.ndarray:
|
||||
torch.random.manual_seed(1)
|
||||
np.random.seed(0)
|
||||
return (np.random.rand(3, 4, 4, 4) * 1000.0).astype(ImageDataType.IMAGE.value)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def image_rand_pos_gpu(image_rand_pos: Union[torch.Tensor, np.ndarray]) -> Union[torch.Tensor, np.ndarray]:
|
||||
def image_rand_pos_gpu(image_rand_pos: np.ndarray) -> Union[torch.Tensor, np.ndarray]:
|
||||
return torch.tensor(image_rand_pos) if use_gpu else image_rand_pos
|
||||
|
||||
|
||||
|
@ -56,7 +56,7 @@ def assert_image_out_datatype(image_out: np.ndarray) -> None:
|
|||
"datatype that we force images to have."
|
||||
|
||||
|
||||
def test_simplenorm_half(image_rand_pos: Union[torch.Tensor, np.ndarray]) -> None:
|
||||
def test_simplenorm_half(image_rand_pos: np.ndarray) -> None:
|
||||
image_out = photometric_normalization.simple_norm(image_rand_pos, mask_half, debug_mode=True)
|
||||
assert np.mean(image_out, dtype=np.float) == approx(-0.05052318)
|
||||
for c in range(image_out.shape[0]):
|
||||
|
@ -64,34 +64,42 @@ def test_simplenorm_half(image_rand_pos: Union[torch.Tensor, np.ndarray]) -> Non
|
|||
assert_image_out_datatype(image_out)
|
||||
|
||||
|
||||
def test_simplenorm_ones(image_rand_pos: Union[torch.Tensor, np.ndarray]) -> None:
|
||||
def test_simplenorm_ones(image_rand_pos: np.ndarray) -> None:
|
||||
image_out = photometric_normalization.simple_norm(image_rand_pos, mask_ones, debug_mode=True)
|
||||
assert np.mean(image_out) == approx(0, abs=1e-7)
|
||||
assert_image_out_datatype(image_out)
|
||||
|
||||
|
||||
def test_mriwindowhalf(image_rand_pos: Union[torch.Tensor, np.ndarray]) -> None:
|
||||
image_out, status = photometric_normalization.mri_window(image_rand_pos, mask_half, (0, 1), sharpen, tail)
|
||||
def test_3d_4d(image_rand_pos: np.ndarray) -> None:
|
||||
normalization = photometric_normalization.PhotometricNormalization()
|
||||
shape = image_rand_pos.shape
|
||||
spatial_shape = shape[1:]
|
||||
assert normalization.transform(image_rand_pos).shape == shape
|
||||
assert normalization.transform(image_rand_pos[0]).shape == spatial_shape
|
||||
|
||||
|
||||
def test_mriwindowhalf(image_rand_pos: np.ndarray) -> None:
|
||||
image_out, _ = photometric_normalization.mri_window(image_rand_pos, mask_half, (0, 1), sharpen, tail)
|
||||
assert np.mean(image_out) == approx(0.2748852)
|
||||
assert_image_out_datatype(image_out)
|
||||
|
||||
|
||||
def test_mriwindowones(image_rand_pos: Union[torch.Tensor, np.ndarray]) -> None:
|
||||
image_out, status = photometric_normalization.mri_window(image_rand_pos, mask_ones, (0.0, 1.0), sharpen, tail3)
|
||||
def test_mriwindowones(image_rand_pos: np.ndarray) -> None:
|
||||
image_out, _ = photometric_normalization.mri_window(image_rand_pos, mask_ones, (0.0, 1.0), sharpen, tail3)
|
||||
assert np.mean(image_out) == approx(0.2748852)
|
||||
assert_image_out_datatype(image_out)
|
||||
|
||||
|
||||
def test_trimmed_norm_full(image_rand_pos: Union[torch.Tensor, np.ndarray]) -> None:
|
||||
image_out, status = photometric_normalization.normalize_trim(image_rand_pos, mask_ones,
|
||||
def test_trimmed_norm_full(image_rand_pos: np.ndarray) -> None:
|
||||
image_out, _ = photometric_normalization.normalize_trim(image_rand_pos, mask_ones,
|
||||
output_range=(-1, 1), sharpen=1,
|
||||
trim_percentiles=(1, 99))
|
||||
assert np.mean(image_out, dtype=np.float) == approx(-0.08756259549409151)
|
||||
assert_image_out_datatype(image_out)
|
||||
|
||||
|
||||
def test_trimmed_norm_half(image_rand_pos: Union[torch.Tensor, np.ndarray]) -> None:
|
||||
image_out, status = photometric_normalization.normalize_trim(image_rand_pos, mask_half,
|
||||
def test_trimmed_norm_half(image_rand_pos: np.ndarray) -> None:
|
||||
image_out, _ = photometric_normalization.normalize_trim(image_rand_pos, mask_half,
|
||||
output_range=(-1, 1), sharpen=1,
|
||||
trim_percentiles=(1, 99))
|
||||
assert np.mean(image_out, dtype=np.float) == approx(-0.4862089517215888)
|
||||
|
|
Загрузка…
Ссылка в новой задаче