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:
Fernando Pérez-García 2022-06-13 14:47:27 +02:00 коммит произвёл GitHub
Родитель edc72eda8c
Коммит a9fd52c2c9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 33 добавлений и 17 удалений

Просмотреть файл

@ -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)