Skip to content

Commit

Permalink
5998 improves error messages in monai/transforms (#5961)
Browse files Browse the repository at this point in the history
Signed-off-by: Wenqi Li <wenqil@nvidia.com>

part of #5898


### Types of changes
<!--- Put an `x` in all the boxes that apply, and remove the not
applicable items -->
- [x] Non-breaking change (fix or new feature that would not break
existing functionality).
- [ ] Breaking change (fix or new feature that would cause existing
functionality to change).
- [ ] New tests added to cover the changes.
- [ ] Integration tests passed locally by running `./runtests.sh -f -u
--net --coverage`.
- [ ] Quick tests passed locally by running `./runtests.sh --quick
--unittests --disttests`.
- [ ] In-line docstrings updated.
- [ ] Documentation updated, tested `make html` command in the `docs/`
folder.

---------

Signed-off-by: Wenqi Li <wenqil@nvidia.com>
Signed-off-by: Wenqi Li <831580+wyli@users.noreply.github.com>
Co-authored-by: YunLiu <55491388+KumoLiu@users.noreply.github.com>
  • Loading branch information
wyli and KumoLiu authored Feb 9, 2023
1 parent 82cd668 commit 94feae5
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 36 deletions.
17 changes: 12 additions & 5 deletions monai/transforms/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,17 +221,20 @@ def __init__(
elif weights is None or isinstance(weights, float):
weights = [1.0 / len(self.transforms)] * len(self.transforms)
if len(weights) != len(self.transforms):
raise AssertionError("transforms and weights should be same size if both specified as sequences.")
raise ValueError(
"transforms and weights should be same size if both specified as sequences, "
f"got {len(weights)} and {len(self.transforms)}."
)
self.weights = ensure_tuple(self._normalize_probabilities(weights))

def _normalize_probabilities(self, weights):
if len(weights) == 0:
return weights
weights = np.array(weights)
if np.any(weights < 0):
raise AssertionError("Probabilities must be greater than or equal to zero.")
raise ValueError(f"Probabilities must be greater than or equal to zero, got {weights}.")
if np.all(weights == 0):
raise AssertionError("At least one probability must be greater than zero.")
raise ValueError(f"At least one probability must be greater than zero, got {weights}.")
weights = weights / weights.sum()
return list(weights)

Expand Down Expand Up @@ -278,7 +281,9 @@ def inverse(self, data):
if isinstance(data[key], monai.data.MetaTensor) or self.trace_key(key) in data:
index = self.pop_transform(data, key)[TraceKeys.EXTRA_INFO]["index"]
else:
raise RuntimeError("Inverse only implemented for Mapping (dictionary) or MetaTensor data.")
raise RuntimeError(
f"Inverse only implemented for Mapping (dictionary) or MetaTensor data, got type {type(data)}."
)
if index is None:
# no invertible transforms have been applied
return data
Expand Down Expand Up @@ -342,7 +347,9 @@ def inverse(self, data):
if isinstance(data[key], monai.data.MetaTensor) or self.trace_key(key) in data:
applied_order = self.pop_transform(data, key)[TraceKeys.EXTRA_INFO]["applied_order"]
else:
raise RuntimeError("Inverse only implemented for Mapping (dictionary) or MetaTensor data.")
raise RuntimeError(
f"Inverse only implemented for Mapping (dictionary) or MetaTensor data, got type {type(data)}."
)
if applied_order is None:
# no invertible transforms have been applied
return data
Expand Down
2 changes: 1 addition & 1 deletion monai/transforms/croppad/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def __call__(self, batch: Any):
@staticmethod
def inverse(data: dict) -> dict[Hashable, np.ndarray]:
if not isinstance(data, Mapping):
raise RuntimeError("Inverse can only currently be applied on dictionaries.")
raise RuntimeError(f"Inverse can only currently be applied on dictionaries, got type {type(data)}.")

d = dict(data)
for key in d:
Expand Down
20 changes: 10 additions & 10 deletions monai/transforms/intensity/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,12 +206,12 @@ def __call__(self, img: NdarrayOrTensor, randomize: bool = True) -> NdarrayOrTen
img[i] = self._add_noise(d, mean=_mean[i], std=_std[i] * d.std() if self.relative else _std[i])
else:
if not isinstance(self.mean, (int, float)):
raise RuntimeError("If channel_wise is False, mean must be a float or int number.")
raise RuntimeError(f"If channel_wise is False, mean must be a float or int, got {type(self.mean)}.")
if not isinstance(self.std, (int, float)):
raise RuntimeError("If channel_wise is False, std must be a float or int number.")
raise RuntimeError(f"If channel_wise is False, std must be a float or int, got {type(self.std)}.")
std = self.std * img.std().item() if self.relative else self.std
if not isinstance(std, (int, float)):
raise RuntimeError("std must be a float or int number.")
raise RuntimeError(f"std must be a float or int number, got {type(std)}.")
img = self._add_noise(img, mean=self.mean, std=std)
return img

Expand Down Expand Up @@ -265,7 +265,7 @@ def __init__(self, offsets: tuple[float, float] | float, safe: bool = False, pro
if isinstance(offsets, (int, float)):
self.offsets = (min(-offsets, offsets), max(-offsets, offsets))
elif len(offsets) != 2:
raise ValueError("offsets should be a number or pair of numbers.")
raise ValueError(f"offsets should be a number or pair of numbers, got {offsets}.")
else:
self.offsets = (min(offsets), max(offsets))
self._offset = self.offsets[0]
Expand Down Expand Up @@ -381,7 +381,7 @@ def __init__(
if isinstance(factors, (int, float)):
self.factors = (min(-factors, factors), max(-factors, factors))
elif len(factors) != 2:
raise ValueError("factors should be a number or pair of numbers.")
raise ValueError(f"factors should be a number or pair of numbers, got {factors}.")
else:
self.factors = (min(factors), max(factors))
self.factor = self.factors[0]
Expand Down Expand Up @@ -488,7 +488,7 @@ def __init__(self, factors: tuple[float, float] | float, prob: float = 0.1, dtyp
if isinstance(factors, (int, float)):
self.factors = (min(-factors, factors), max(-factors, factors))
elif len(factors) != 2:
raise ValueError("factors should be a number or pair of numbers.")
raise ValueError(f"factors should be a number or pair of numbers, got {factors}.")
else:
self.factors = (min(factors), max(factors))
self.factor = self.factors[0]
Expand Down Expand Up @@ -545,7 +545,7 @@ def __init__(
) -> None:
RandomizableTransform.__init__(self, prob)
if degree < 1:
raise ValueError("degree should be no less than 1.")
raise ValueError(f"degree should be no less than 1, got {degree}.")
self.degree = degree
self.coeff_range = coeff_range
self.dtype = dtype
Expand Down Expand Up @@ -725,7 +725,7 @@ class ThresholdIntensity(Transform):

def __init__(self, threshold: float, above: bool = True, cval: float = 0.0) -> None:
if not isinstance(threshold, (int, float)):
raise ValueError("threshold must be a float or int number.")
raise ValueError(f"threshold must be a float or int number, got {type(threshold)} {threshold}.")
self.threshold = threshold
self.above = above
self.cval = cval
Expand Down Expand Up @@ -812,7 +812,7 @@ class AdjustContrast(Transform):

def __init__(self, gamma: float) -> None:
if not isinstance(gamma, (int, float)):
raise ValueError("gamma must be a float or int number.")
raise ValueError(f"gamma must be a float or int number, got {type(gamma)} {gamma}.")
self.gamma = gamma

def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
Expand Down Expand Up @@ -847,7 +847,7 @@ def __init__(self, prob: float = 0.1, gamma: Sequence[float] | float = (0.5, 4.5
if isinstance(gamma, (int, float)):
if gamma <= 0.5:
raise ValueError(
"if gamma is single number, must greater than 0.5 and value is picked from (0.5, gamma)"
f"if gamma is a number, must greater than 0.5 and value is picked from (0.5, gamma), got {gamma}"
)
self.gamma = (0.5, gamma)
elif len(gamma) != 2:
Expand Down
2 changes: 1 addition & 1 deletion monai/transforms/io/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ def __call__(self, filename: Sequence[PathLike] | PathLike, reader: ImageReader
img_array, meta_data = reader.get_data(img)
img_array = convert_to_dst_type(img_array, dst=img_array, dtype=self.dtype)[0]
if not isinstance(meta_data, dict):
raise ValueError("`meta_data` must be a dict.")
raise ValueError(f"`meta_data` must be a dict, got type {type(meta_data)}.")
# make sure all elements in metadata are little endian
meta_data = switch_endianness(meta_data, "<")

Expand Down
10 changes: 7 additions & 3 deletions monai/transforms/io/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ def __init__(
raise TypeError(f"meta_key_postfix must be a str but is {type(meta_key_postfix).__name__}.")
self.meta_keys = ensure_tuple_rep(None, len(self.keys)) if meta_keys is None else ensure_tuple(meta_keys)
if len(self.keys) != len(self.meta_keys):
raise ValueError("meta_keys should have the same length as keys.")
raise ValueError(
f"meta_keys should have the same length as keys, got {len(self.keys)} and {len(self.meta_keys)}."
)
self.meta_key_postfix = ensure_tuple_rep(meta_key_postfix, len(self.keys))
self.overwriting = overwriting

Expand All @@ -162,10 +164,12 @@ def __call__(self, data, reader: ImageReader | None = None):
d[key] = data
else:
if not isinstance(data, (tuple, list)):
raise ValueError("loader must return a tuple or list (because image_only=False was used).")
raise ValueError(
f"loader must return a tuple or list (because image_only=False was used), got {type(data)}."
)
d[key] = data[0]
if not isinstance(data[1], dict):
raise ValueError("metadata must be a dict.")
raise ValueError(f"metadata must be a dict, got {type(data[1])}.")
meta_key = meta_key or f"{key}_{meta_key_postfix}"
if meta_key in d and not self.overwriting:
raise KeyError(f"Metadata with key {meta_key} already exists and overwriting=False.")
Expand Down
2 changes: 1 addition & 1 deletion monai/transforms/post/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def __call__(
to_onehot = self.to_onehot if to_onehot is None else to_onehot
if to_onehot is not None:
if not isinstance(to_onehot, int):
raise ValueError("the number of classes for One-Hot must be an integer.")
raise ValueError(f"the number of classes for One-Hot must be an integer, got {type(to_onehot)}.")
img_t = one_hot(
img_t, num_classes=to_onehot, dim=self.kwargs.get("dim", 0), dtype=self.kwargs.get("dtype", torch.float)
)
Expand Down
4 changes: 3 additions & 1 deletion monai/transforms/spatial/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1639,7 +1639,9 @@ def __init__(
self.min_zoom = ensure_tuple(min_zoom)
self.max_zoom = ensure_tuple(max_zoom)
if len(self.min_zoom) != len(self.max_zoom):
raise AssertionError("min_zoom and max_zoom must have same length.")
raise ValueError(
f"min_zoom and max_zoom must have same length, got {len(self.min_zoom)} and {len(self.max_zoom)}."
)
self.mode: InterpolateMode = look_up_option(mode, InterpolateMode)
self.padding_mode = padding_mode
self.align_corners = align_corners
Expand Down
2 changes: 1 addition & 1 deletion monai/transforms/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def lazy_evaluation(self):
@lazy_evaluation.setter
def lazy_evaluation(self, lazy_evaluation: bool):
if not isinstance(lazy_evaluation, bool):
raise TypeError("'lazy_evaluation must be a bool but is of " f"type {type(lazy_evaluation)}'")
raise TypeError(f"lazy_evaluation must be a bool but is of type {type(lazy_evaluation)}")
self.lazy_evaluation = lazy_evaluation


Expand Down
12 changes: 6 additions & 6 deletions monai/transforms/utility/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class AsChannelFirst(Transform):

def __init__(self, channel_dim: int = -1) -> None:
if not (isinstance(channel_dim, int) and channel_dim >= -1):
raise AssertionError("invalid channel dimension.")
raise ValueError(f"invalid channel dimension ({channel_dim}).")
self.channel_dim = channel_dim

def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
Expand Down Expand Up @@ -191,7 +191,7 @@ class AsChannelLast(Transform):

def __init__(self, channel_dim: int = 0) -> None:
if not (isinstance(channel_dim, int) and channel_dim >= -1):
raise AssertionError("invalid channel dimension.")
raise ValueError(f"invalid channel dimension ({channel_dim}).")
self.channel_dim = channel_dim

def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
Expand Down Expand Up @@ -303,7 +303,7 @@ class RepeatChannel(Transform):

def __init__(self, repeats: int) -> None:
if repeats <= 0:
raise AssertionError("repeats count must be greater than 0.")
raise ValueError(f"repeats count must be greater than 0, got {repeats}.")
self.repeats = repeats

def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
Expand All @@ -328,7 +328,7 @@ class RemoveRepeatedChannel(Transform):

def __init__(self, repeats: int) -> None:
if repeats <= 0:
raise AssertionError("repeats count must be greater than 0.")
raise ValueError(f"repeats count must be greater than 0, got {repeats}.")

self.repeats = repeats

Expand All @@ -337,7 +337,7 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor:
Apply the transform to `img`, assuming `img` is a "channel-first" array.
"""
if img.shape[0] < 2:
raise AssertionError("Image must have more than one channel")
raise ValueError(f"Image must have more than one channel, got {img.shape[0]} channels.")

out: NdarrayOrTensor = convert_to_tensor(img[:: self.repeats, :], track_meta=get_track_meta())
return out
Expand Down Expand Up @@ -718,7 +718,7 @@ def __init__(
"""
if not isinstance(prefix, str):
raise AssertionError("prefix must be a string.")
raise ValueError(f"prefix must be a string, got {type(prefix)}.")
self.prefix = prefix
self.data_type = data_type
self.data_shape = data_shape
Expand Down
19 changes: 12 additions & 7 deletions monai/transforms/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def weighted_patch_samples(
"""
if w is None:
raise ValueError("w must be an ND array.")
raise ValueError("w must be an ND array, got None.")
if r_state is None:
r_state = np.random.RandomState()
img_size = np.asarray(w.shape, dtype=int)
Expand Down Expand Up @@ -447,7 +447,10 @@ def correct_crop_centers(
spatial_size = fall_back_tuple(spatial_size, default=label_spatial_shape)
if any(np.subtract(label_spatial_shape, spatial_size) < 0):
if not allow_smaller:
raise ValueError("The size of the proposed random crop ROI is larger than the image size.")
raise ValueError(
"The size of the proposed random crop ROI is larger than the image size, "
f"got ROI size {spatial_size} and label image size {label_spatial_shape} respectively."
)
spatial_size = tuple(min(l, s) for l, s in zip(label_spatial_shape, spatial_size))

# Select subregion to assure valid roi
Expand Down Expand Up @@ -555,12 +558,14 @@ def generate_label_classes_crop_centers(
rand_state = np.random.random.__self__ # type: ignore

if num_samples < 1:
raise ValueError("num_samples must be an int number and greater than 0.")
raise ValueError(f"num_samples must be an int number and greater than 0, got {num_samples}.")
ratios_: list[float | int] = ([1] * len(indices)) if ratios is None else ratios
if len(ratios_) != len(indices):
raise ValueError("random crop ratios must match the number of indices of classes.")
raise ValueError(
f"random crop ratios must match the number of indices of classes, got {len(ratios_)} and {len(indices)}."
)
if any(i < 0 for i in ratios_):
raise ValueError("ratios should not contain negative number.")
raise ValueError(f"ratios should not contain negative number, got {ratios_}.")

for i, array in enumerate(indices):
if len(array) == 0:
Expand Down Expand Up @@ -925,7 +930,7 @@ def generate_spatial_bounding_box(
margin = ensure_tuple_rep(margin, ndim)
for m in margin:
if m < 0:
raise ValueError("margin value should not be negative number.")
raise ValueError(f"margin value should not be negative number, got {margin}.")

box_start = [0] * ndim
box_end = [0] * ndim
Expand Down Expand Up @@ -1065,7 +1070,7 @@ def get_unique_labels(img: NdarrayOrTensor, is_onehot: bool, discard: int | Iter
applied_labels = {i for i, s in enumerate(img) if s.sum() > 0}
else:
if n_channels != 1:
raise ValueError("If input not one-hotted, should only be 1 channel.")
raise ValueError(f"If input not one-hotted, should only be 1 channel, got {n_channels}.")
applied_labels = set(unique(img).tolist())
if discard is not None:
for i in ensure_tuple(discard):
Expand Down

0 comments on commit 94feae5

Please sign in to comment.