def plot_2d_or_3d_image( data: Union[torch.Tensor, np.ndarray], step: int, writer, index: int = 0, max_channels: int = 1, max_frames: int = 64, tag: str = "output", ): """Plot 2D or 3D image on the TensorBoard, 3D image will be converted to GIF image. Note: Plot 3D or 2D image(with more than 3 channels) as separate images. Args: data (Tensor or np.array): target data to be plotted as image on the TensorBoard. The data is expected to have 'NCHW[D]' dimensions, and only plot the first in the batch. step: current step to plot in a chart. writer (SummaryWriter): specify TensorBoard SummaryWriter to plot the image. index: plot which element in the input data batch, default is the first element. max_channels: number of channels to plot. max_frames: number of frames for 2D-t plot. tag: tag of the plotted image on TensorBoard. """ assert isinstance(writer, SummaryWriter) is True, "must provide a TensorBoard SummaryWriter." d = data[index] if torch.is_tensor(d): d = d.detach().cpu().numpy() if d.ndim == 2: d = rescale_array(d, 0, 1) dataformats = "HW" writer.add_image(f"{tag}_{dataformats}", d, step, dataformats=dataformats) return if d.ndim == 3: if d.shape[0] == 3 and max_channels == 3: # RGB dataformats = "CHW" writer.add_image(f"{tag}_{dataformats}", d, step, dataformats=dataformats) return for j, d2 in enumerate(d[:max_channels]): d2 = rescale_array(d2, 0, 1) dataformats = "HW" writer.add_image(f"{tag}_{dataformats}_{j}", d2, step, dataformats=dataformats) return if d.ndim >= 4: spatial = d.shape[-3:] for j, d3 in enumerate(d.reshape([-1] + list(spatial))[:max_channels]): d3 = rescale_array(d3, 0, 255) add_animated_gif(writer, f"{tag}_HWD_{j}", d3[None], max_frames, 1.0, step) return
def create_test_image_3d( height: int, width: int, depth: int, num_objs: int = 12, rad_max: int = 30, noise_max: float = 0.0, num_seg_classes: int = 5, channel_dim: Optional[int] = None, random_state: Optional[np.random.RandomState] = None, ) -> Tuple[np.ndarray, np.ndarray]: """ Return a noisy 3D image and segmentation. Args: height: height of the image. width: width of the image. depth: depth of the image. num_objs: number of circles to generate. Defaults to `12`. rad_max: maximum circle radius. Defaults to `30`. noise_max: if greater than 0 then noise will be added to the image taken from the uniform distribution on range `[0,noise_max)`. Defaults to `0`. num_seg_classes: number of classes for segmentations. Defaults to `5`. channel_dim: if None, create an image without channel dimension, otherwise create an image with channel dimension as first dim or last dim. Defaults to `None`. random_state: the random generator to use. Defaults to `np.random`. See also: :py:meth:`~create_test_image_2d` """ image = np.zeros((width, height, depth)) rs = np.random if random_state is None else random_state for _ in range(num_objs): x = rs.randint(rad_max, width - rad_max) y = rs.randint(rad_max, height - rad_max) z = rs.randint(rad_max, depth - rad_max) rad = rs.randint(5, rad_max) spy, spx, spz = np.ogrid[-x:width - x, -y:height - y, -z:depth - z] circle = (spx * spx + spy * spy + spz * spz) <= rad * rad if num_seg_classes > 1: image[circle] = np.ceil(rs.random() * num_seg_classes) else: image[circle] = rs.random() * 0.5 + 0.5 labels = np.ceil(image).astype(np.int32) norm = rs.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage = rescale_array(np.maximum(image, norm)) if channel_dim is not None: assert isinstance( channel_dim, int) and channel_dim in (-1, 0, 3), "invalid channel dim." noisyimage, labels = ((noisyimage[None], labels[None]) if channel_dim == 0 else (noisyimage[..., None], labels[..., None])) return noisyimage, labels
def create_test_image_3d(height, width, depth, num_objs=12, rad_max=30, noise_max=0.0, num_seg_classes=5): """ Return a noisy 3D image and segmentation. See also: create_test_image_2d """ image = np.zeros((width, height, depth)) for i in range(num_objs): x = np.random.randint(rad_max, width - rad_max) y = np.random.randint(rad_max, height - rad_max) z = np.random.randint(rad_max, depth - rad_max) rad = np.random.randint(5, rad_max) spy, spx, spz = np.ogrid[-x:width - x, -y:height - y, -z:depth - z] circle = (spx * spx + spy * spy + spz * spz) <= rad * rad if num_seg_classes > 1: image[circle] = np.ceil(np.random.random() * num_seg_classes) else: image[circle] = np.random.random() * 0.5 + 0.5 labels = np.ceil(image).astype(np.int32) norm = np.random.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage = rescale_array(np.maximum(image, norm)) return noisyimage, labels
def create_test_image_2d(width, height, num_objs=12, rad_max=30, noise_max=0.0, num_seg_classes=5): """ Return a noisy 2D image with `numObj' circles and a 2D mask image. The maximum radius of the circles is given as `radMax'. The mask will have `numSegClasses' number of classes for segmentations labeled sequentially from 1, plus a background class represented as 0. If `noiseMax' is greater than 0 then noise will be added to the image taken from the uniform distribution on range [0,noiseMax). """ image = np.zeros((width, height)) for i in range(num_objs): x = np.random.randint(rad_max, width - rad_max) y = np.random.randint(rad_max, height - rad_max) rad = np.random.randint(5, rad_max) spy, spx = np.ogrid[-x:width - x, -y:height - y] circle = (spx * spx + spy * spy) <= rad * rad if num_seg_classes > 1: image[circle] = np.ceil(np.random.random() * num_seg_classes) else: image[circle] = np.random.random() * 0.5 + 0.5 labels = np.ceil(image).astype(np.int32) norm = np.random.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage = rescale_array(np.maximum(image, norm)) return noisyimage, labels
def create_test_image_2d( width: int, height: int, num_objs: int = 12, rad_max: int = 30, noise_max: float = 0.0, num_seg_classes: int = 5, channel_dim: Optional[int] = None, random_state: Optional[np.random.RandomState] = None, ) -> Tuple[np.ndarray, np.ndarray]: """ Return a noisy 2D image with `num_objs` circles and a 2D mask image. The maximum radius of the circles is given as `rad_max`. The mask will have `num_seg_classes` number of classes for segmentations labeled sequentially from 1, plus a background class represented as 0. If `noise_max` is greater than 0 then noise will be added to the image taken from the uniform distribution on range `[0,noise_max)`. If `channel_dim` is None, will create an image without channel dimension, otherwise create an image with channel dimension as first dim or last dim. Args: width: width of the image. height: height of the image. num_objs: number of circles to generate. Defaults to `12`. rad_max: maximum circle radius. Defaults to `30`. noise_max: if greater than 0 then noise will be added to the image taken from the uniform distribution on range `[0,noise_max)`. Defaults to `0`. num_seg_classes: number of classes for segmentations. Defaults to `5`. channel_dim: if None, create an image without channel dimension, otherwise create an image with channel dimension as first dim or last dim. Defaults to `None`. random_state: the random generator to use. Defaults to `np.random`. """ image = np.zeros((width, height)) rs = np.random if random_state is None else random_state for _ in range(num_objs): x = rs.randint(rad_max, width - rad_max) y = rs.randint(rad_max, height - rad_max) rad = rs.randint(5, rad_max) spy, spx = np.ogrid[-x : width - x, -y : height - y] circle = (spx * spx + spy * spy) <= rad * rad if num_seg_classes > 1: image[circle] = np.ceil(rs.random() * num_seg_classes) else: image[circle] = rs.random() * 0.5 + 0.5 labels = np.ceil(image).astype(np.int32) norm = rs.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage = rescale_array(np.maximum(image, norm)) if channel_dim is not None: if not (isinstance(channel_dim, int) and channel_dim in (-1, 0, 2)): raise AssertionError("invalid channel dim.") if channel_dim == 0: noisyimage = noisyimage[None] labels = labels[None] else: noisyimage = noisyimage[..., None] labels = labels[..., None] return noisyimage, labels
def __call__(self, img): """ Apply the transform to `img`. """ if self.minv is not None and self.maxv is not None: return rescale_array(img, self.minv, self.maxv, img.dtype) else: return (img * (1 + self.factor)).astype(img.dtype)
def blend_images(image: NdarrayOrTensor, label: NdarrayOrTensor, alpha: float = 0.5, cmap: str = "hsv", rescale_arrays: bool = True): """ Blend an image and a label. Both should have the shape CHW[D]. The image may have C==1 or 3 channels (greyscale or RGB). The label is expected to have C==1. Args: image: the input image to blend with label data. label: the input label to blend with image data. alpha: when blending image and label, `alpha` is the weight for the image region mapping to `label != 0`, and `1 - alpha` is the weight for the label region that `label != 0`, default to `0.5`. cmap: specify colormap in the matplotlib, default to `hsv`, for more details, please refer to: https://matplotlib.org/2.0.2/users/colormaps.html. rescale_arrays: whether to rescale the array to [0, 1] first, default to `True`. """ if label.shape[0] != 1: raise ValueError("Label should have 1 channel") if image.shape[0] not in (1, 3): raise ValueError("Image should have 1 or 3 channels") # rescale arrays to [0, 1] if desired if rescale_arrays: image = rescale_array(image) label = rescale_array(label) # convert image to rgb (if necessary) and then rgba if image.shape[0] == 1: image = repeat(image, 3, axis=0) def get_label_rgb(cmap: str, label: NdarrayOrTensor): _cmap = cm.get_cmap(cmap) label_np, *_ = convert_data_type(label, np.ndarray) label_rgb_np = _cmap(label_np[0]) label_rgb_np = np.moveaxis(label_rgb_np, -1, 0)[:3] label_rgb, *_ = convert_to_dst_type(label_rgb_np, label) return label_rgb label_rgb = get_label_rgb(cmap, label) w_image = where(label == 0, 1.0, alpha) w_label = where(label == 0, 0.0, 1 - alpha) return w_image * image + w_label * label_rgb
def test_max_none(self): for p in TEST_NDARRAYS: scaler = ScaleIntensity(minv=0.0, maxv=None, factor=0.1) result = scaler(p(self.imt)) expected = rescale_array(p(self.imt), minv=0.0, maxv=None) assert_allclose(result, expected, type_test="tensor", rtol=1e-3, atol=1e-3)
def _add_2_or_3_d(self, data, step, tag='output'): # for i, d in enumerate(data): # go through a batch of images d = data[0] # show the first element in a batch if d.ndim == 2: d = rescale_array(d, 0, 1) dataformats = 'HW' self._writer.add_image('{}_{}'.format(tag, dataformats), d, step, dataformats=dataformats) return if d.ndim == 3: if d.shape[0] == 3 and self.max_channels == 3: # RGB dataformats = 'CHW' self._writer.add_image('{}_{}'.format(tag, dataformats), d, step, dataformats=dataformats) return for j, d2 in enumerate(d[:self.max_channels]): d2 = rescale_array(d2, 0, 1) dataformats = 'HW' self._writer.add_image('{}_{}_{}'.format(tag, dataformats, j), d2, step, dataformats=dataformats) return if d.ndim >= 4: spatial = d.shape[-3:] for j, d3 in enumerate( d.reshape([-1] + list(spatial))[:self.max_channels]): d3 = rescale_array(d3, 0, 255) img2tensorboard.add_animated_gif(self._writer, '{}_HWD_{}'.format(tag, j), d3[None], self.max_frames, 1.0, step) return
def __call__(self, img: np.ndarray) -> np.ndarray: """ Apply the transform to `img`. Raises: ValueError: When ``self.minv=None`` or ``self.maxv=None`` and ``self.factor=None``. Incompatible values. """ if self.minv is not None and self.maxv is not None: return rescale_array(img, self.minv, self.maxv, img.dtype) if self.factor is not None: return (img * (1 + self.factor)).astype(img.dtype) raise ValueError("Incompatible values: minv=None or maxv=None and factor=None.")
def create_test_image_3d( height: int, width: int, depth: int, num_objs: int = 12, rad_max: int = 30, noise_max: float = 0.0, num_seg_classes: int = 5, channel_dim: Optional[int] = None, random_state: Optional[np.random.RandomState] = None, ): """ Return a noisy 3D image and segmentation. See also: :py:meth:`~create_test_image_2d` """ image = np.zeros((width, height, depth)) rs = np.random if random_state is None else random_state for _ in range(num_objs): x = rs.randint(rad_max, width - rad_max) y = rs.randint(rad_max, height - rad_max) z = rs.randint(rad_max, depth - rad_max) rad = rs.randint(5, rad_max) spy, spx, spz = np.ogrid[-x:width - x, -y:height - y, -z:depth - z] circle = (spx * spx + spy * spy + spz * spz) <= rad * rad if num_seg_classes > 1: image[circle] = np.ceil(rs.random() * num_seg_classes) else: image[circle] = rs.random() * 0.5 + 0.5 labels = np.ceil(image).astype(np.int32) norm = rs.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage = rescale_array(np.maximum(image, norm)) if channel_dim is not None: assert isinstance( channel_dim, int) and channel_dim in (-1, 0, 3), "invalid channel dim." noisyimage, labels = ((noisyimage[None], labels[None]) if channel_dim == 0 else (noisyimage[..., None], labels[..., None])) return noisyimage, labels
def create_test_image_2d(width, height, num_objs=12, rad_max=30, noise_max=0.0, num_seg_classes=5, channel_dim=None): """ Return a noisy 2D image with `num_obj` circles and a 2D mask image. The maximum radius of the circles is given as `rad_max`. The mask will have `num_seg_classes` number of classes for segmentations labeled sequentially from 1, plus a background class represented as 0. If `noise_max` is greater than 0 then noise will be added to the image taken from the uniform distribution on range `[0,noise_max)`. If `channel_dim` is None, will create an image without channel dimension, otherwise create an image with channel dimension as first dim or last dim. """ image = np.zeros((width, height)) for i in range(num_objs): x = np.random.randint(rad_max, width - rad_max) y = np.random.randint(rad_max, height - rad_max) rad = np.random.randint(5, rad_max) spy, spx = np.ogrid[-x:width - x, -y:height - y] circle = (spx * spx + spy * spy) <= rad * rad if num_seg_classes > 1: image[circle] = np.ceil(np.random.random() * num_seg_classes) else: image[circle] = np.random.random() * 0.5 + 0.5 labels = np.ceil(image).astype(np.int32) norm = np.random.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage = rescale_array(np.maximum(image, norm)) if channel_dim is not None: assert isinstance( channel_dim, int) and channel_dim in (-1, 0, 2), "invalid channel dim." noisyimage, labels = ( noisyimage[None], labels[None] if channel_dim == 0 else (noisyimage[..., None], labels[..., None]), ) return noisyimage, labels
def create_test_image_3d(height, width, depth, num_objs=12, rad_max=30, noise_max=0.0, num_seg_classes=5, channel_dim=None): """ Return a noisy 3D image and segmentation. See also: :py:meth:`~create_test_image_2d` """ image = np.zeros((width, height, depth)) for i in range(num_objs): x = np.random.randint(rad_max, width - rad_max) y = np.random.randint(rad_max, height - rad_max) z = np.random.randint(rad_max, depth - rad_max) rad = np.random.randint(5, rad_max) spy, spx, spz = np.ogrid[-x:width - x, -y:height - y, -z:depth - z] circle = (spx * spx + spy * spy + spz * spz) <= rad * rad if num_seg_classes > 1: image[circle] = np.ceil(np.random.random() * num_seg_classes) else: image[circle] = np.random.random() * 0.5 + 0.5 labels = np.ceil(image).astype(np.int32) norm = np.random.uniform(0, num_seg_classes * noise_max, size=image.shape) noisyimage = rescale_array(np.maximum(image, norm)) if channel_dim is not None: assert isinstance( channel_dim, int) and channel_dim in (-1, 0, 3), 'invalid channel dim.' noisyimage, labels = (noisyimage[None], labels[None]) \ if channel_dim == 0 else (noisyimage[..., None], labels[..., None]) return noisyimage, labels
def __call__(self, img): if self.minv is not None and self.maxv is not None: return rescale_array(img, self.minv, self.maxv, img.dtype) else: return (img * (1 + self.factor)).astype(img.dtype)
def __call__(self, img): return rescale_array(img, self.minv, self.maxv, self.dtype)