From 1c2f1448f4170d7275a3dd380f2a904953ab9cd8 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Mon, 15 Nov 2021 12:00:49 +0800 Subject: [PATCH 1/8] [DLMED] optimize astype Signed-off-by: Nic Ma --- monai/apps/deepedit/transforms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index ab2d4b40fa..98201955c6 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -158,7 +158,7 @@ def _apply(self, guidance, discrepancy): guidance[0].append([-1] * len(neg)) guidance[1].append(neg) - return json.dumps(np.asarray(guidance).astype(int).tolist()) + return json.dumps(np.asarray(guidance, dtype=int).tolist()) def __call__(self, data): d = dict(data) From 0550d77ff778cc376b7959b6ad0bb88dd6fbd3cf Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Wed, 17 Nov 2021 19:04:08 +0800 Subject: [PATCH 2/8] [DLMED] improve astype Signed-off-by: Nic Ma --- monai/apps/deepedit/transforms.py | 4 ++-- monai/apps/deepgrow/transforms.py | 22 +++++++++---------- .../apps/pathology/transforms/stain/array.py | 6 ++--- monai/data/nifti_writer.py | 15 +++++++------ monai/data/png_writer.py | 6 ++--- monai/data/synthetic.py | 4 ++-- monai/data/test_time_augmentation.py | 4 +++- monai/data/utils.py | 4 ++-- monai/transforms/intensity/array.py | 7 +++--- monai/transforms/io/array.py | 2 +- monai/transforms/spatial/array.py | 4 ++-- monai/transforms/utility/array.py | 2 +- monai/utils/type_conversion.py | 3 ++- monai/visualize/img2tensorboard.py | 3 ++- monai/visualize/utils.py | 3 ++- 15 files changed, 48 insertions(+), 41 deletions(-) diff --git a/monai/apps/deepedit/transforms.py b/monai/apps/deepedit/transforms.py index 98201955c6..33054f4a13 100644 --- a/monai/apps/deepedit/transforms.py +++ b/monai/apps/deepedit/transforms.py @@ -71,8 +71,8 @@ def __call__(self, data): factor = np.divide(current_shape, d["image_meta_dict"]["dim"][1:4]) pos_clicks, neg_clicks = d["foreground"], d["background"] - pos = np.multiply(pos_clicks, factor).astype(int).tolist() if len(pos_clicks) else [] - neg = np.multiply(neg_clicks, factor).astype(int).tolist() if len(neg_clicks) else [] + pos = np.multiply(pos_clicks, factor).astype(int, copy=False).tolist() if len(pos_clicks) else [] + neg = np.multiply(neg_clicks, factor).astype(int, copy=False).tolist() if len(neg_clicks) else [] d[self.guidance] = [pos, neg] return d diff --git a/monai/apps/deepgrow/transforms.py b/monai/apps/deepgrow/transforms.py index 1a3c035083..4954c1c5fb 100644 --- a/monai/apps/deepgrow/transforms.py +++ b/monai/apps/deepgrow/transforms.py @@ -145,7 +145,7 @@ def _apply(self, label, sid): def __call__(self, data): d = dict(data) self.randomize(data) - d[self.guidance] = json.dumps(self._apply(d[self.label], self.sid).astype(int).tolist()) + d[self.guidance] = json.dumps(self._apply(d[self.label], self.sid).astype(int, copy=False).tolist()) return d @@ -323,7 +323,7 @@ def _apply(self, guidance, discrepancy): guidance[0].append([-1] * len(neg)) guidance[1].append(neg) - return json.dumps(np.asarray(guidance).astype(int).tolist()) + return json.dumps(np.asarray(guidance, dtype=int).tolist()) def __call__(self, data): d = dict(data) @@ -420,8 +420,8 @@ def __call__(self, data): d[self.source_key], self.select_fn, self.channel_indices, self.margin ) - center = list(np.mean([box_start, box_end], axis=0).astype(int)) - current_size = list(np.subtract(box_end, box_start).astype(int)) + center = list(np.mean([box_start, box_end], axis=0).astype(int, copy=False)) + current_size = list(np.subtract(box_end, box_start).astype(int, copy=False)) if np.all(np.less(current_size, self.spatial_size)): cropper = SpatialCrop(roi_center=center, roi_size=self.spatial_size) @@ -528,9 +528,9 @@ def _apply(self, pos_clicks, neg_clicks, factor, slice_num): guidance = [pos, neg, slice_idx] else: if len(pos_clicks): - pos = np.multiply(pos_clicks, factor).astype(int).tolist() + pos = np.multiply(pos_clicks, factor).astype(int, copy=False).tolist() if len(neg_clicks): - neg = np.multiply(neg_clicks, factor).astype(int).tolist() + neg = np.multiply(neg_clicks, factor).astype(int, copy=False).tolist() guidance = [pos, neg] return guidance @@ -555,7 +555,7 @@ def __call__(self, data): fg_bg_clicks = [] for key in [self.foreground, self.background]: clicks = d[key] - clicks = list(np.array(clicks).astype(int)) + clicks = list(np.array(clicks, dtype=int)) if self.depth_first: for i in range(len(clicks)): clicks[i] = list(np.roll(clicks[i], 1)) @@ -651,10 +651,10 @@ def __call__(self, data): guidance = d[self.guidance] original_spatial_shape = d[first_key].shape[1:] # type: ignore box_start, box_end = self.bounding_box(np.array(guidance[0] + guidance[1]), original_spatial_shape) - center = list(np.mean([box_start, box_end], axis=0).astype(int)) + center = list(np.mean([box_start, box_end], axis=0).astype(int, copy=False)) spatial_size = self.spatial_size - box_size = list(np.subtract(box_end, box_start).astype(int)) + box_size = list(np.subtract(box_end, box_start).astype(int, copy=False)) spatial_size = spatial_size[-len(box_size) :] if len(spatial_size) < len(box_size): @@ -738,8 +738,8 @@ def __call__(self, data): factor = np.divide(current_shape, cropped_shape) pos_clicks, neg_clicks = guidance[0], guidance[1] - pos = np.multiply(pos_clicks, factor).astype(int).tolist() if len(pos_clicks) else [] - neg = np.multiply(neg_clicks, factor).astype(int).tolist() if len(neg_clicks) else [] + pos = np.multiply(pos_clicks, factor).astype(int, copy=False).tolist() if len(pos_clicks) else [] + neg = np.multiply(neg_clicks, factor).astype(int, copy=False).tolist() if len(neg_clicks) else [] d[self.guidance] = [pos, neg] return d diff --git a/monai/apps/pathology/transforms/stain/array.py b/monai/apps/pathology/transforms/stain/array.py index bd644a77c2..6c27df3b5e 100644 --- a/monai/apps/pathology/transforms/stain/array.py +++ b/monai/apps/pathology/transforms/stain/array.py @@ -67,7 +67,7 @@ def _deconvolution_extract_stain(self, image: np.ndarray) -> np.ndarray: # reshape image and calculate absorbance image = image.reshape((-1, 3)) - image = image.astype(np.float32) + 1.0 + image = image.astype(np.float32, copy=False) + 1.0 absorbance = -np.log(image.clip(max=self.tli) / self.tli) # remove transparent pixels @@ -76,7 +76,7 @@ def _deconvolution_extract_stain(self, image: np.ndarray) -> np.ndarray: raise ValueError("All pixels of the input image are below the absorbance threshold.") # compute eigenvectors - _, eigvecs = np.linalg.eigh(np.cov(absorbance_hat.T).astype(np.float32)) + _, eigvecs = np.linalg.eigh(np.cov(absorbance_hat.T).astype(np.float32, copy=False)) # project on the plane spanned by the eigenvectors corresponding to the two largest eigenvalues t_hat = absorbance_hat.dot(eigvecs[:, 1:3]) @@ -186,7 +186,7 @@ def __call__(self, image: np.ndarray) -> np.ndarray: conc = np.linalg.lstsq(he, y, rcond=None)[0] # normalize stain concentrations - max_conc = np.array([np.percentile(conc[0, :], 99), np.percentile(conc[1, :], 99)], dtype=np.float32) + max_conc = np.asarray([np.percentile(conc[0, :], 99), np.percentile(conc[1, :], 99)], dtype=np.float32) tmp = np.divide(max_conc, self.max_cref, dtype=np.float32) image_c = np.divide(conc, tmp[:, np.newaxis], dtype=np.float32) diff --git a/monai/data/nifti_writer.py b/monai/data/nifti_writer.py index 210321daca..130830dd53 100644 --- a/monai/data/nifti_writer.py +++ b/monai/data/nifti_writer.py @@ -9,6 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from enum import Flag from typing import Optional, Sequence, Union import numpy as np @@ -116,7 +117,7 @@ def write_nifti( if np.allclose(affine, target_affine, atol=1e-3): # no affine changes, save (data, affine) - results_img = nib.Nifti1Image(data.astype(output_dtype), to_affine_nd(3, target_affine)) + results_img = nib.Nifti1Image(data.astype(output_dtype, copy=False), to_affine_nd(3, target_affine)) nib.save(results_img, file_name) return @@ -128,7 +129,7 @@ def write_nifti( data = nib.orientations.apply_orientation(data, ornt_transform) _affine = affine @ nib.orientations.inv_ornt_aff(ornt_transform, data_shape) if np.allclose(_affine, target_affine, atol=1e-3) or not resample: - results_img = nib.Nifti1Image(data.astype(output_dtype), to_affine_nd(3, _affine)) # type: ignore + results_img = nib.Nifti1Image(data.astype(output_dtype, copy=False), to_affine_nd(3, _affine)) # type: ignore nib.save(results_img, file_name) return @@ -147,8 +148,8 @@ def write_nifti( data_np: np.ndarray = data.reshape(list(spatial_shape) + [-1]) # type: ignore data_np = np.moveaxis(data_np, -1, 0) # channel first for pytorch data_torch = affine_xform( - torch.as_tensor(np.ascontiguousarray(data_np).astype(dtype)).unsqueeze(0), - torch.as_tensor(np.ascontiguousarray(transform).astype(dtype)), + torch.as_tensor(np.ascontiguousarray(data_np).astype(dtype, copy=False)).unsqueeze(0), + torch.as_tensor(np.ascontiguousarray(transform).astype(dtype, copy=False)), spatial_size=output_spatial_shape_[:3], ) data_np = data_torch.squeeze(0).detach().cpu().numpy() @@ -158,12 +159,12 @@ def write_nifti( while len(output_spatial_shape_) < len(data.shape): output_spatial_shape_ = output_spatial_shape_ + [1] data_torch = affine_xform( - torch.as_tensor(np.ascontiguousarray(data).astype(dtype)[None, None]), - torch.as_tensor(np.ascontiguousarray(transform).astype(dtype)), + torch.as_tensor(np.ascontiguousarray(data).astype(dtype, copy=False)[None, None]), + torch.as_tensor(np.ascontiguousarray(transform).astype(dtype, copy=False)), spatial_size=output_spatial_shape_[: len(data.shape)], ) data_np = data_torch.squeeze(0).squeeze(0).detach().cpu().numpy() - results_img = nib.Nifti1Image(data_np.astype(output_dtype), to_affine_nd(3, target_affine)) + results_img = nib.Nifti1Image(data_np.astype(output_dtype, copy=False), to_affine_nd(3, target_affine)) nib.save(results_img, file_name) return diff --git a/monai/data/png_writer.py b/monai/data/png_writer.py index 52163e40ac..ea028a07be 100644 --- a/monai/data/png_writer.py +++ b/monai/data/png_writer.py @@ -70,15 +70,15 @@ def write_png( if scale is not None: data = np.clip(data, 0.0, 1.0) # type: ignore # png writer only can scale data in range [0, 1] if scale == np.iinfo(np.uint8).max: - data = (scale * data).astype(np.uint8) + data = (scale * data).astype(np.uint8, copy=False) elif scale == np.iinfo(np.uint16).max: - data = (scale * data).astype(np.uint16) + data = (scale * data).astype(np.uint16, copy=False) else: raise ValueError(f"Unsupported scale: {scale}, available options are [255, 65535]") # PNG data must be int number if data.dtype not in (np.uint8, np.uint16): # type: ignore - data = data.astype(np.uint8) + data = data.astype(np.uint8, copy=False) data = np.moveaxis(data, 0, 1) img = Image.fromarray(data) diff --git a/monai/data/synthetic.py b/monai/data/synthetic.py index 6eec9fd277..0a1c179a7f 100644 --- a/monai/data/synthetic.py +++ b/monai/data/synthetic.py @@ -73,7 +73,7 @@ def create_test_image_2d( else: image[circle] = rs.random() * 0.5 + 0.5 - labels = np.ceil(image).astype(np.int32) + labels = np.ceil(image).astype(np.int32, copy=False) norm = rs.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage: np.ndarray = rescale_array(np.maximum(image, norm)) # type: ignore @@ -148,7 +148,7 @@ def create_test_image_3d( else: image[circle] = rs.random() * 0.5 + 0.5 - labels = np.ceil(image).astype(np.int32) + labels = np.ceil(image).astype(np.int32, copy=False) norm = rs.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage: np.ndarray = rescale_array(np.maximum(image, norm)) # type: ignore diff --git a/monai/data/test_time_augmentation.py b/monai/data/test_time_augmentation.py index 948e85e131..a87bf9b7cf 100644 --- a/monai/data/test_time_augmentation.py +++ b/monai/data/test_time_augmentation.py @@ -26,6 +26,7 @@ from monai.transforms.utils import allow_missing_keys_mode, convert_inverse_interp_mode from monai.utils.enums import CommonKeys, TraceKeys from monai.utils.module import optional_import +from monai.utils.type_conversion import convert_data_type if TYPE_CHECKING: from tqdm import tqdm @@ -215,7 +216,8 @@ def __call__( return output # calculate metrics - mode = np.array(torch.mode(torch.Tensor(output.astype(np.int64)), dim=0).values) + output_t, *_ = convert_data_type(output, output_type=torch.Tensor, dtype=np.int64) + mode = np.array(torch.mode(output_t, dim=0).values) mean: np.ndarray = np.mean(output, axis=0) # type: ignore std: np.ndarray = np.std(output, axis=0) # type: ignore vvc: float = (np.std(output) / np.mean(output)).item() diff --git a/monai/data/utils.py b/monai/data/utils.py index 2e5efce7cd..29dbeeda24 100644 --- a/monai/data/utils.py +++ b/monai/data/utils.py @@ -631,7 +631,7 @@ def compute_shape_offset( # different orientation, the min is the origin corners = corners[:-1] / corners[-1] offset = np.min(corners, 1) - return out_shape.astype(int), offset + return out_shape.astype(int, copy=False), offset def to_affine_nd(r: Union[np.ndarray, int], affine: np.ndarray) -> np.ndarray: @@ -1143,7 +1143,7 @@ def convert_tables_to_dicts( # convert data types types = {k: v["type"] for k, v in col_types.items() if v is not None and "type" in v} if types: - data_ = data_.astype(dtype=types) + data_ = data_.astype(dtype=types, copy=False) data: List[Dict] = data_.to_dict(orient="records") # group columns to generate new column diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index cffbc27d19..131bc19f29 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -15,6 +15,7 @@ from abc import abstractmethod from collections.abc import Iterable +from copy import copy from functools import partial from typing import Any, Callable, List, Optional, Sequence, Tuple, Union from warnings import warn @@ -168,8 +169,8 @@ def _add_noise(self, img: NdarrayTensor, mean: float, std: float): dtype_np = get_equivalent_dtype(img.dtype, np.ndarray) im_shape = img.shape _std = self.R.uniform(0, std) if self.sample_std else std - self._noise1 = self.R.normal(mean, _std, size=im_shape).astype(dtype_np) - self._noise2 = self.R.normal(mean, _std, size=im_shape).astype(dtype_np) + self._noise1 = self.R.normal(mean, _std, size=im_shape).astype(dtype_np, copy=False) + self._noise2 = self.R.normal(mean, _std, size=im_shape).astype(dtype_np, copy=False) if isinstance(img, torch.Tensor): n1 = torch.tensor(self._noise1, device=img.device) n2 = torch.tensor(self._noise2, device=img.device) @@ -1942,7 +1943,7 @@ def _transform_holes(self, img: np.ndarray): ret = img else: if isinstance(fill_value, (tuple, list)): - ret = self.R.uniform(fill_value[0], fill_value[1], size=img.shape).astype(img.dtype) + ret = self.R.uniform(fill_value[0], fill_value[1], size=img.shape).astype(img.dtype, copy=False) else: ret = np.full_like(img, fill_value) for h in self.hole_coords: diff --git a/monai/transforms/io/array.py b/monai/transforms/io/array.py index 170482f504..cd2d93a0c7 100644 --- a/monai/transforms/io/array.py +++ b/monai/transforms/io/array.py @@ -212,7 +212,7 @@ def __call__(self, filename: Union[Sequence[PathLike], PathLike], reader: Option ) img_array, meta_data = reader.get_data(img) - img_array = img_array.astype(self.dtype) + img_array = img_array.astype(self.dtype, copy=False) if self.image_only: return img_array diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 1be621477e..8d1558c230 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -1302,7 +1302,7 @@ def __init__( self.device = device def randomize(self, grid_size: Sequence[int]) -> None: - self.random_offset = self.R.normal(size=([len(grid_size)] + list(grid_size))).astype(np.float32) + self.random_offset = self.R.normal(size=([len(grid_size)] + list(grid_size))).astype(np.float32, copy=False) self.rand_mag = self.R.uniform(self.magnitude[0], self.magnitude[1]) def __call__(self, spatial_size: Sequence[int]): @@ -1978,7 +1978,7 @@ def randomize(self, grid_size: Sequence[int]) -> None: super().randomize(None) if not self._do_transform: return None - self.rand_offset = self.R.uniform(-1.0, 1.0, [3] + list(grid_size)).astype(np.float32) + self.rand_offset = self.R.uniform(-1.0, 1.0, [3] + list(grid_size)).astype(np.float32, copy=False) self.magnitude = self.R.uniform(self.magnitude_range[0], self.magnitude_range[1]) self.sigma = self.R.uniform(self.sigma_range[0], self.sigma_range[1]) self.rand_affine_grid.randomize() diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index 50738978bb..8be270b250 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -1023,7 +1023,7 @@ def __call__(self, img: NdarrayOrTensor): img_np, *_ = convert_data_type(img, np.ndarray) img_flat = img_np.flatten() try: - out_flat = np.copy(img_flat).astype(self.dtype) + out_flat = np.copy(img_flat).astype(self.dtype, copy=False) except ValueError: # can't copy unchanged labels as the expected dtype is not supported, must map all the label values out_flat = np.zeros(shape=img_flat.shape, dtype=self.dtype) diff --git a/monai/utils/type_conversion.py b/monai/utils/type_conversion.py index c9ac49901f..6d6613b6bf 100644 --- a/monai/utils/type_conversion.py +++ b/monai/utils/type_conversion.py @@ -10,6 +10,7 @@ # limitations under the License. import re +from copy import copy from typing import Any, Optional, Sequence, Tuple, Union import numpy as np @@ -157,7 +158,7 @@ def convert_to_numpy(data, dtype: DtypeLike = None, wrap_sequence: bool = False) if isinstance(data, torch.Tensor): data = data.detach().to(dtype=get_equivalent_dtype(dtype, torch.Tensor), device="cpu").numpy() elif has_cp and isinstance(data, cp_ndarray): - data = cp.asnumpy(data).astype(dtype) + data = cp.asnumpy(data).astype(dtype, copy=False) elif isinstance(data, (np.ndarray, float, int, bool)): data = np.asarray(data, dtype=dtype) elif isinstance(data, list): diff --git a/monai/visualize/img2tensorboard.py b/monai/visualize/img2tensorboard.py index eef1b2e764..78c78bc9b8 100644 --- a/monai/visualize/img2tensorboard.py +++ b/monai/visualize/img2tensorboard.py @@ -9,6 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from copy import copy from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union import numpy as np @@ -45,7 +46,7 @@ def _image3_animated_gif(tag: str, image: Union[np.ndarray, torch.Tensor], write if len(image.shape) != 3: raise AssertionError("3D image tensors expected to be in `HWD` format, len(image.shape) != 3") - ims = [(np.asarray(image[:, :, i]) * scale_factor).astype(np.uint8) for i in range(image.shape[2])] + ims = [(np.asarray(image[:, :, i]) * scale_factor).astype(np.uint8, copy=False) for i in range(image.shape[2])] ims = [GifImage.fromarray(im) for im in ims] img_str = b"" for b_data in PIL.GifImagePlugin.getheader(ims[0])[0]: diff --git a/monai/visualize/utils.py b/monai/visualize/utils.py index c0304e9e68..25abbbd646 100644 --- a/monai/visualize/utils.py +++ b/monai/visualize/utils.py @@ -9,6 +9,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from copy import copy from typing import Optional import numpy as np @@ -108,7 +109,7 @@ def matshow3d( cols = max(min(len(vol), frames_per_row), 1) rows = int(np.ceil(len(vol) / cols)) width = [[0, cols * rows - len(vol)]] + [[margin, margin]] * (len(vol.shape) - 1) - vol = np.pad(vol.astype(dtype), width, mode="constant", constant_values=fill_value) + vol = np.pad(vol.astype(dtype, copy=False), width, mode="constant", constant_values=fill_value) im = np.block([[vol[i * cols + j] for j in range(cols)] for i in range(rows)]) # figure related configurations From f2ba306e2643ee2a7a7b7f5c4bdf10425eb93476 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Wed, 17 Nov 2021 19:17:46 +0800 Subject: [PATCH 3/8] [DLMED] fix flake8 Signed-off-by: Nic Ma --- monai/data/nifti_writer.py | 1 - monai/data/test_time_augmentation.py | 2 +- monai/transforms/intensity/array.py | 1 - monai/utils/type_conversion.py | 1 - monai/visualize/img2tensorboard.py | 1 - monai/visualize/utils.py | 1 - 6 files changed, 1 insertion(+), 6 deletions(-) diff --git a/monai/data/nifti_writer.py b/monai/data/nifti_writer.py index 130830dd53..655080824f 100644 --- a/monai/data/nifti_writer.py +++ b/monai/data/nifti_writer.py @@ -9,7 +9,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from enum import Flag from typing import Optional, Sequence, Union import numpy as np diff --git a/monai/data/test_time_augmentation.py b/monai/data/test_time_augmentation.py index a87bf9b7cf..1d81d91a16 100644 --- a/monai/data/test_time_augmentation.py +++ b/monai/data/test_time_augmentation.py @@ -217,7 +217,7 @@ def __call__( # calculate metrics output_t, *_ = convert_data_type(output, output_type=torch.Tensor, dtype=np.int64) - mode = np.array(torch.mode(output_t, dim=0).values) + mode: np.ndarray = np.asarray(torch.mode(output_t, dim=0).values) # type: ignore mean: np.ndarray = np.mean(output, axis=0) # type: ignore std: np.ndarray = np.std(output, axis=0) # type: ignore vvc: float = (np.std(output) / np.mean(output)).item() diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index 131bc19f29..dca5db7a35 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -15,7 +15,6 @@ from abc import abstractmethod from collections.abc import Iterable -from copy import copy from functools import partial from typing import Any, Callable, List, Optional, Sequence, Tuple, Union from warnings import warn diff --git a/monai/utils/type_conversion.py b/monai/utils/type_conversion.py index 6d6613b6bf..2defbe30c1 100644 --- a/monai/utils/type_conversion.py +++ b/monai/utils/type_conversion.py @@ -10,7 +10,6 @@ # limitations under the License. import re -from copy import copy from typing import Any, Optional, Sequence, Tuple, Union import numpy as np diff --git a/monai/visualize/img2tensorboard.py b/monai/visualize/img2tensorboard.py index 78c78bc9b8..309e7f4b18 100644 --- a/monai/visualize/img2tensorboard.py +++ b/monai/visualize/img2tensorboard.py @@ -9,7 +9,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from copy import copy from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union import numpy as np diff --git a/monai/visualize/utils.py b/monai/visualize/utils.py index 25abbbd646..29def9d0ef 100644 --- a/monai/visualize/utils.py +++ b/monai/visualize/utils.py @@ -9,7 +9,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from copy import copy from typing import Optional import numpy as np From 268a3f8edde8ad46682a13b77a43bbb8f08bfc47 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Thu, 18 Nov 2021 16:26:35 +0800 Subject: [PATCH 4/8] [DLMED] update according to comments Signed-off-by: Nic Ma --- monai/data/nifti_writer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/monai/data/nifti_writer.py b/monai/data/nifti_writer.py index 655080824f..078ea2e605 100644 --- a/monai/data/nifti_writer.py +++ b/monai/data/nifti_writer.py @@ -147,8 +147,8 @@ def write_nifti( data_np: np.ndarray = data.reshape(list(spatial_shape) + [-1]) # type: ignore data_np = np.moveaxis(data_np, -1, 0) # channel first for pytorch data_torch = affine_xform( - torch.as_tensor(np.ascontiguousarray(data_np).astype(dtype, copy=False)).unsqueeze(0), - torch.as_tensor(np.ascontiguousarray(transform).astype(dtype, copy=False)), + torch.as_tensor(np.ascontiguousarray(data_np, dtype=dtype)).unsqueeze(0), + torch.as_tensor(np.ascontiguousarray(transform, dtype=dtype)), spatial_size=output_spatial_shape_[:3], ) data_np = data_torch.squeeze(0).detach().cpu().numpy() @@ -158,8 +158,8 @@ def write_nifti( while len(output_spatial_shape_) < len(data.shape): output_spatial_shape_ = output_spatial_shape_ + [1] data_torch = affine_xform( - torch.as_tensor(np.ascontiguousarray(data).astype(dtype, copy=False)[None, None]), - torch.as_tensor(np.ascontiguousarray(transform).astype(dtype, copy=False)), + torch.as_tensor(np.ascontiguousarray(data, dtype=dtype)[None, None]), + torch.as_tensor(np.ascontiguousarray(transform, dtype=dtype)), spatial_size=output_spatial_shape_[: len(data.shape)], ) data_np = data_torch.squeeze(0).squeeze(0).detach().cpu().numpy() From cc6e168f2546d4dc338ad7f03752bec718ee796f Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Thu, 18 Nov 2021 19:34:49 +0800 Subject: [PATCH 5/8] [DLMED] update according to comments Signed-off-by: Nic Ma --- monai/transforms/intensity/array.py | 16 +++++++++++---- monai/transforms/intensity/dictionary.py | 26 ++++++++++++++++-------- tests/test_rand_gaussian_noised.py | 2 +- tests/test_rand_rician_noised.py | 2 +- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index dca5db7a35..a0d7a02670 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -23,7 +23,7 @@ import torch from monai.config import DtypeLike -from monai.config.type_definitions import NdarrayOrTensor, NdarrayTensor +from monai.config.type_definitions import NdarrayOrTensor from monai.data.utils import get_random_patch, get_valid_patch_size from monai.networks.layers import GaussianFilter, HilbertTransform, SavitzkyGolayFilter from monai.transforms.transform import RandomizableTransform, Transform @@ -86,15 +86,17 @@ class RandGaussianNoise(RandomizableTransform): prob: Probability to add Gaussian noise. mean: Mean or “centre” of the distribution. std: Standard deviation (spread) of distribution. + dtype: output data type, if None, same as input image. defaults to float32. """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] - def __init__(self, prob: float = 0.1, mean: float = 0.0, std: float = 0.1) -> None: + def __init__(self, prob: float = 0.1, mean: float = 0.0, std: float = 0.1, dtype: DtypeLike = np.float32) -> None: RandomizableTransform.__init__(self, prob) self.mean = mean self.std = std + self.dtype = dtype self.noise: Optional[np.ndarray] = None def randomize(self, img: NdarrayOrTensor, mean: Optional[float] = None) -> None: @@ -116,6 +118,7 @@ def __call__(self, img: NdarrayOrTensor, mean: Optional[float] = None, randomize if self.noise is None: raise RuntimeError("please call the `randomize()` function first.") + img, *_ = convert_data_type(img, dtype=self.dtype) noise, *_ = convert_to_dst_type(self.noise, img) return img + noise @@ -141,6 +144,8 @@ class RandRicianNoise(RandomizableTransform): histogram. sample_std: If True, sample the spread of the Gaussian distributions uniformly from 0 to std. + dtype: output data type, if None, same as input image. defaults to float32. + """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] @@ -153,6 +158,7 @@ def __init__( channel_wise: bool = False, relative: bool = False, sample_std: bool = True, + dtype: DtypeLike = np.float32, ) -> None: RandomizableTransform.__init__(self, prob) self.prob = prob @@ -161,10 +167,11 @@ def __init__( self.channel_wise = channel_wise self.relative = relative self.sample_std = sample_std + self.dtype = dtype self._noise1: NdarrayOrTensor self._noise2: NdarrayOrTensor - def _add_noise(self, img: NdarrayTensor, mean: float, std: float): + def _add_noise(self, img: NdarrayOrTensor, mean: float, std: float): dtype_np = get_equivalent_dtype(img.dtype, np.ndarray) im_shape = img.shape _std = self.R.uniform(0, std) if self.sample_std else std @@ -177,7 +184,7 @@ def _add_noise(self, img: NdarrayTensor, mean: float, std: float): return np.sqrt((img + self._noise1) ** 2 + self._noise2 ** 2) - def __call__(self, img: NdarrayTensor, randomize: bool = True) -> NdarrayTensor: + def __call__(self, img: NdarrayOrTensor, randomize: bool = True) -> NdarrayOrTensor: """ Apply the transform to `img`. """ @@ -187,6 +194,7 @@ def __call__(self, img: NdarrayTensor, randomize: bool = True) -> NdarrayTensor: if not self._do_transform: return img + img, *_ = convert_data_type(img, dtype=self.dtype) if self.channel_wise: _mean = ensure_tuple_rep(self.mean, len(img)) _std = ensure_tuple_rep(self.std, len(img)) diff --git a/monai/transforms/intensity/dictionary.py b/monai/transforms/intensity/dictionary.py index 71809d7ea2..fa2de4c7b8 100644 --- a/monai/transforms/intensity/dictionary.py +++ b/monai/transforms/intensity/dictionary.py @@ -19,7 +19,7 @@ import numpy as np -from monai.config import DtypeLike, KeysCollection, NdarrayTensor +from monai.config import DtypeLike, KeysCollection from monai.config.type_definitions import NdarrayOrTensor from monai.transforms.intensity.array import ( AdjustContrast, @@ -156,6 +156,7 @@ class RandGaussianNoised(RandomizableTransform, MapTransform): prob: Probability to add Gaussian noise. mean: Mean or “centre” of the distribution. std: Standard deviation (spread) of distribution. + dtype: output data type, if None, same as input image. defaults to float32. allow_missing_keys: don't raise exception if key is missing. """ @@ -167,11 +168,12 @@ def __init__( prob: float = 0.1, mean: float = 0.0, std: float = 0.1, + dtype: DtypeLike = np.float32, allow_missing_keys: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) RandomizableTransform.__init__(self, prob) - self.rand_gaussian_noise = RandGaussianNoise(mean=mean, std=std, prob=1.0) + self.rand_gaussian_noise = RandGaussianNoise(mean=mean, std=std, prob=1.0, dtype=dtype) def set_random_state( self, seed: Optional[int] = None, state: Optional[np.random.RandomState] = None @@ -206,9 +208,7 @@ class RandRicianNoised(RandomizableTransform, MapTransform): Args: keys: Keys of the corresponding items to be transformed. See also: :py:class:`monai.transforms.compose.MapTransform` - global_prob: Probability to add Rician noise to the dictionary. - prob: Probability to add Rician noise to each item in the dictionary, - once asserted that noise will be added to the dictionary at all. + prob: Probability to add Rician noise to the dictionary. mean: Mean or "centre" of the Gaussian distributions sampled to make up the Rician noise. std: Standard deviation (spread) of the Gaussian distributions sampled @@ -219,27 +219,35 @@ class RandRicianNoised(RandomizableTransform, MapTransform): histogram. sample_std: If True, sample the spread of the Gaussian distributions uniformly from 0 to std. + dtype: output data type, if None, same as input image. defaults to float32. allow_missing_keys: Don't raise exception if key is missing. """ backend = RandRicianNoise.backend + @deprecated_arg("global_prob", since="0.7") def __init__( self, keys: KeysCollection, - global_prob: float = 0.1, prob: float = 0.1, mean: Union[Sequence[float], float] = 0.0, std: Union[Sequence[float], float] = 1.0, channel_wise: bool = False, relative: bool = False, sample_std: bool = True, + dtype: DtypeLike = np.float32, allow_missing_keys: bool = False, ) -> None: MapTransform.__init__(self, keys, allow_missing_keys) - RandomizableTransform.__init__(self, global_prob) + RandomizableTransform.__init__(self, prob) self.rand_rician_noise = RandRicianNoise( - prob=1.0, mean=mean, std=std, channel_wise=channel_wise, relative=relative, sample_std=sample_std + prob=1.0, + mean=mean, + std=std, + channel_wise=channel_wise, + relative=relative, + sample_std=sample_std, + dtype=dtype, ) def set_random_state( @@ -249,7 +257,7 @@ def set_random_state( self.rand_rician_noise.set_random_state(seed, state) return self - def __call__(self, data: Mapping[Hashable, NdarrayTensor]) -> Dict[Hashable, NdarrayTensor]: + def __call__(self, data: Mapping[Hashable, NdarrayOrTensor]) -> Dict[Hashable, NdarrayOrTensor]: d = dict(data) self.randomize(None) if not self._do_transform: diff --git a/tests/test_rand_gaussian_noised.py b/tests/test_rand_gaussian_noised.py index d9fd5d023d..3e578075ae 100644 --- a/tests/test_rand_gaussian_noised.py +++ b/tests/test_rand_gaussian_noised.py @@ -29,7 +29,7 @@ class TestRandGaussianNoised(NumpyImageTestCase2D): @parameterized.expand(TESTS) def test_correct_results(self, _, im_type, keys, mean, std): - gaussian_fn = RandGaussianNoised(keys=keys, prob=1.0, mean=mean, std=std) + gaussian_fn = RandGaussianNoised(keys=keys, prob=1.0, mean=mean, std=std, dtype=np.float64) gaussian_fn.set_random_state(seed) im = im_type(self.imt) noised = gaussian_fn({k: im for k in keys}) diff --git a/tests/test_rand_rician_noised.py b/tests/test_rand_rician_noised.py index e8cb84dc99..bb5ebbe8c8 100644 --- a/tests/test_rand_rician_noised.py +++ b/tests/test_rand_rician_noised.py @@ -29,7 +29,7 @@ class TestRandRicianNoisedNumpy(NumpyImageTestCase2D): @parameterized.expand(TESTS) def test_correct_results(self, _, in_type, keys, mean, std): - rician_fn = RandRicianNoised(keys=keys, global_prob=1.0, prob=1.0, mean=mean, std=std) + rician_fn = RandRicianNoised(keys=keys, prob=1.0, mean=mean, std=std, dtype=np.float64) rician_fn.set_random_state(seed) noised = rician_fn({k: in_type(self.imt) for k in keys}) np.random.seed(seed) From be4dd9d93baf86dcde5c780a71934746a0e24dcc Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Thu, 18 Nov 2021 20:09:54 +0800 Subject: [PATCH 6/8] [DLMED] update according to comments Signed-off-by: Nic Ma --- monai/transforms/intensity/array.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index a0d7a02670..3420b1a36a 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -104,7 +104,9 @@ def randomize(self, img: NdarrayOrTensor, mean: Optional[float] = None) -> None: if not self._do_transform: return None rand_std = self.R.uniform(0, self.std) - self.noise = self.R.normal(self.mean if mean is None else mean, rand_std, size=img.shape) + noise = self.R.normal(self.mean if mean is None else mean, rand_std, size=img.shape) + # noise is float64 array, convert to the output dtype to save memory + self.noise, *_ = convert_data_type(noise, dtype=self.dtype) # type: ignore def __call__(self, img: NdarrayOrTensor, mean: Optional[float] = None, randomize: bool = True) -> NdarrayOrTensor: """ From 7ed2a2877aa01790943e0ff82b9c5eaed712fa9c Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Thu, 18 Nov 2021 21:42:20 +0800 Subject: [PATCH 7/8] [DLMED] update according to comments Signed-off-by: Nic Ma --- monai/transforms/utility/array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index 8be270b250..e7a14ce9ea 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -1023,7 +1023,7 @@ def __call__(self, img: NdarrayOrTensor): img_np, *_ = convert_data_type(img, np.ndarray) img_flat = img_np.flatten() try: - out_flat = np.copy(img_flat).astype(self.dtype, copy=False) + out_flat = np.array(img_flat, dtype=self.dtype) except ValueError: # can't copy unchanged labels as the expected dtype is not supported, must map all the label values out_flat = np.zeros(shape=img_flat.shape, dtype=self.dtype) From ec9e835f0005170781e0a3e8d5913dd2ab812bd6 Mon Sep 17 00:00:00 2001 From: Nic Ma Date: Thu, 18 Nov 2021 22:11:10 +0800 Subject: [PATCH 8/8] [DLMED] fix flake8 Signed-off-by: Nic Ma --- monai/transforms/utility/array.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index e7a14ce9ea..be0800474a 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -1033,8 +1033,8 @@ def __call__(self, img: NdarrayOrTensor): continue np.place(out_flat, img_flat == o, t) - out = out_flat.reshape(img_np.shape) - out, *_ = convert_to_dst_type(src=out, dst=img, dtype=self.dtype) + reshaped = out_flat.reshape(img_np.shape) + out, *_ = convert_to_dst_type(src=reshaped, dst=img, dtype=self.dtype) return out