def __init__( self, spatial_dims: int = 2, sigma: Union[Sequence[float], float, Sequence[torch.Tensor], torch.Tensor] = 0.0, prob_threshold: float = 0.5, box_size: Union[int, Sequence[int]] = 48, ) -> None: self.sigma = sigma self.spatial_dims = spatial_dims if self.sigma != 0: self.filter = GaussianFilter(spatial_dims=spatial_dims, sigma=sigma) if prob_threshold < 0: raise ValueError("prob_threshold should be no less than 0.0.") self.prob_threshold = prob_threshold if isinstance(box_size, int): self.box_size = np.asarray([box_size] * spatial_dims) elif len(box_size) != spatial_dims: raise ValueError( "the sequence length of box_size should be the same as spatial_dims." ) else: self.box_size = np.asarray(box_size) if self.box_size.min() <= 0: raise ValueError("box_size should be larger than 0.") self.box_lower_bd = self.box_size // 2 self.box_upper_bd = self.box_size - self.box_lower_bd
def __call__(self, img: np.ndarray) -> np.ndarray: gaussian_filter1 = GaussianFilter(img.ndim - 1, self.sigma1, approx=self.approx) gaussian_filter2 = GaussianFilter(img.ndim - 1, self.sigma2, approx=self.approx) input_data = torch.as_tensor(np.ascontiguousarray(img), dtype=torch.float).unsqueeze(0) blurred_f = gaussian_filter1(input_data) filter_blurred_f = gaussian_filter2(blurred_f) return (blurred_f + self.alpha * (blurred_f - filter_blurred_f)).squeeze(0).detach().numpy()
def test_train(self, input_args): input_dims = input_args.get("dims", (2, 3, 8)) device = (torch.device("cuda") if (input_args.get("device") == "cuda" and torch.cuda.is_available()) else torch.device("cpu:0")) base = torch.ones(*input_dims).to(device) gt = torch.tensor(input_args["gt"], requires_grad=False).to(device) g_type = input_args["type"] lr = input_args.get("lr", 0.1) init_sigma = input_args.get("init", 1.0) # static filter to generate a target spatial_dims = len(base.shape) - 2 filtering = GaussianFilter(spatial_dims=spatial_dims, sigma=gt, approx=g_type, requires_grad=False) filtering.to(device) target = filtering(base) self.assertFalse(filtering.sigma[0].requires_grad) # build trainable init_sigma = torch.tensor(init_sigma).to(device) trainable = GaussianFilter(spatial_dims=spatial_dims, sigma=init_sigma, approx=g_type, requires_grad=True) trainable.to(device) self.assertTrue(trainable.sigma[0].requires_grad) # train optimizer = torch.optim.Adam(trainable.parameters(), lr=lr) for s in range(1000): optimizer.zero_grad() pred = trainable(base) loss = torch.square(pred - target).mean() loss.backward() if (s + 1) % 50 == 0: var = list(trainable.parameters())[0] print(f"step {s} loss {loss}") print(var, var.grad) if loss.item() < 1e-7: break optimizer.step() # check the result print(s, gt) for idx, s in enumerate(trainable.sigma): np.testing.assert_allclose( s.cpu().item(), gt.cpu() if len(gt.shape) == 0 else gt[idx].cpu().item(), rtol=1e-2)
def test_2d(self): a = torch.ones(1, 1, 3, 3) g = GaussianFilter(2, 3, 3).to(torch.device("cpu:0")) expected = np.array([[[ [0.13239081, 0.13932934, 0.13239081], [0.13932936, 0.14663152, 0.13932936], [0.13239081, 0.13932934, 0.13239081], ]]]) np.testing.assert_allclose(g(a).cpu().numpy(), expected, rtol=1e-5) if torch.cuda.is_available(): g = GaussianFilter(2, 3, 3).to(torch.device("cuda:0")) np.testing.assert_allclose(g(a.cuda()).cpu().numpy(), expected, rtol=1e-2)
def _get_signal(self, image, guidance): dimensions = 3 if len(image.shape) > 3 else 2 guidance = guidance.tolist() if isinstance(guidance, np.ndarray) else guidance if dimensions == 3: signal = np.zeros((len(guidance), image.shape[-3], image.shape[-2], image.shape[-1]), dtype=np.float32) else: signal = np.zeros((len(guidance), image.shape[-2], image.shape[-1]), dtype=np.float32) sshape = signal.shape for i in range(len(guidance)): for point in guidance[i]: if np.any(np.asarray(point) < 0): continue if dimensions == 3: p1 = max(0, min(int(point[-3]), sshape[-3] - 1)) p2 = max(0, min(int(point[-2]), sshape[-2] - 1)) p3 = max(0, min(int(point[-1]), sshape[-1] - 1)) signal[i, p1, p2, p3] = 1.0 else: p1 = max(0, min(int(point[-2]), sshape[-2] - 1)) p2 = max(0, min(int(point[-1]), sshape[-1] - 1)) signal[i, p1, p2] = 1.0 if np.max(signal[i]) > 0: signal_tensor = torch.tensor(signal[i]) pt_gaussian = GaussianFilter(len(signal_tensor.shape), sigma=self.sigma) signal_tensor = pt_gaussian(signal_tensor.unsqueeze(0).unsqueeze(0)) signal_tensor = signal_tensor.squeeze(0).squeeze(0) signal[i] = signal_tensor.detach().cpu().numpy() signal[i] = (signal[i] - np.min(signal[i])) / (np.max(signal[i]) - np.min(signal[i])) return signal
def test_3d(self): a = torch.ones(1, 1, 4, 3, 4) g = GaussianFilter(3, 3, 3).to(torch.device("cpu:0")) expected = np.array([[[ [ [0.07294822, 0.08033235, 0.08033235, 0.07294822], [0.07680509, 0.08457965, 0.08457965, 0.07680509], [0.07294822, 0.08033235, 0.08033235, 0.07294822], ], [ [0.08033235, 0.08846395, 0.08846395, 0.08033235], [0.08457965, 0.09314119, 0.09314119, 0.08457966], [0.08033235, 0.08846396, 0.08846396, 0.08033236], ], [ [0.08033235, 0.08846395, 0.08846395, 0.08033235], [0.08457965, 0.09314119, 0.09314119, 0.08457966], [0.08033235, 0.08846396, 0.08846396, 0.08033236], ], [ [0.07294822, 0.08033235, 0.08033235, 0.07294822], [0.07680509, 0.08457965, 0.08457965, 0.07680509], [0.07294822, 0.08033235, 0.08033235, 0.07294822], ], ]]]) np.testing.assert_allclose(g(a).cpu().numpy(), expected, rtol=1e-5)
def __call__(self, img: np.ndarray) -> np.ndarray: gaussian_filter = GaussianFilter(img.ndim - 1, self.sigma, approx=self.approx) input_data = torch.as_tensor(np.ascontiguousarray(img), dtype=torch.float).unsqueeze(0) return gaussian_filter(input_data).squeeze(0).detach().numpy()
def test_3d(self): a = torch.ones(1, 1, 4, 3, 4) g = GaussianFilter(3, 3, 3).to(torch.device("cpu:0")) expected = np.array([[[ [ [0.07189433, 0.07911152, 0.07911152, 0.07189433], [0.07566228, 0.08325771, 0.08325771, 0.07566228], [0.07189433, 0.07911152, 0.07911152, 0.07189433], ], [ [0.07911152, 0.08705322, 0.08705322, 0.07911152], [0.08325771, 0.09161563, 0.09161563, 0.08325771], [0.07911152, 0.08705322, 0.08705322, 0.07911152], ], [ [0.07911152, 0.08705322, 0.08705322, 0.07911152], [0.08325771, 0.09161563, 0.09161563, 0.08325771], [0.07911152, 0.08705322, 0.08705322, 0.07911152], ], [ [0.07189433, 0.07911152, 0.07911152, 0.07189433], [0.07566228, 0.08325771, 0.08325771, 0.07566228], [0.07189433, 0.07911152, 0.07911152, 0.07189433], ], ]]]) np.testing.assert_allclose(g(a).cpu().numpy(), expected, rtol=1e-5)
def test_2d(self): a = torch.ones(1, 1, 3, 3) g = GaussianFilter(2, 3, 3, torch.device('cpu:0')) expected = np.array([[[[0.13380532, 0.14087981, 0.13380532], [0.14087981, 0.14832835, 0.14087981], [0.13380532, 0.14087981, 0.13380532]]]]) np.testing.assert_allclose(g(a).cpu().numpy(), expected)
def test_2d(self): a = torch.ones(1, 1, 3, 3) g = GaussianFilter(2, 3, 3).to(torch.device("cpu:0")) expected = np.array( [ [ [ [0.13380532, 0.14087981, 0.13380532], [0.14087981, 0.14832835, 0.14087981], [0.13380532, 0.14087981, 0.13380532], ] ] ] ) np.testing.assert_allclose(g(a).cpu().numpy(), expected, rtol=1e-5) if torch.cuda.is_available(): g = GaussianFilter(2, 3, 3).to(torch.device("cuda:0")) np.testing.assert_allclose(g(a).cpu().numpy(), expected, rtol=1e-2)
def test_3d_sigmas(self): a = torch.ones(1, 1, 4, 3, 2) g = GaussianFilter(3, [3, 2, 1], 3).to(torch.device("cpu:0")) expected = np.array([[[ [[0.1422854, 0.1422854], [0.15806103, 0.15806103], [0.1422854, 0.1422854]], [[0.15668818, 0.15668817], [0.17406069, 0.17406069], [0.15668818, 0.15668817]], [[0.15668818, 0.15668817], [0.17406069, 0.17406069], [0.15668818, 0.15668817]], [[0.1422854, 0.1422854], [0.15806103, 0.15806103], [0.1422854, 0.1422854]], ]]]) np.testing.assert_allclose(g(a).cpu().numpy(), expected, rtol=1e-5) if torch.cuda.is_available(): g = GaussianFilter(3, [3, 2, 1], 3).to(torch.device("cuda:0")) np.testing.assert_allclose(g(a.cuda()).cpu().numpy(), expected, rtol=1e-2)
def _get_signal(self, image, guidance): dimensions = 3 if len(image.shape) > 3 else 2 guidance = guidance.tolist() if isinstance(guidance, np.ndarray) else guidance guidance = json.loads(guidance) if isinstance(guidance, str) else guidance # In inference the user may not provide clicks for some channels/labels if len(guidance): if dimensions == 3: # Assume channel is first and depth is last CHWD signal = np.zeros( (1, image.shape[-3], image.shape[-2], image.shape[-1]), dtype=np.float32) else: signal = np.zeros((1, image.shape[-2], image.shape[-1]), dtype=np.float32) sshape = signal.shape for point in guidance: # TO DO: make the guidance a list only - it is currently a list of list if np.any(np.asarray(point) < 0): continue if dimensions == 3: # Making sure points fall inside the image dimension p1 = max(0, min(int(point[-3]), sshape[-3] - 1)) p2 = max(0, min(int(point[-2]), sshape[-2] - 1)) p3 = max(0, min(int(point[-1]), sshape[-1] - 1)) signal[:, p1, p2, p3] = 1.0 else: p1 = max(0, min(int(point[-2]), sshape[-2] - 1)) p2 = max(0, min(int(point[-1]), sshape[-1] - 1)) signal[:, p1, p2] = 1.0 # Apply a Gaussian filter to the signal if np.max(signal[0]) > 0: signal_tensor = torch.tensor(signal[0]) pt_gaussian = GaussianFilter(len(signal_tensor.shape), sigma=self.sigma) signal_tensor = pt_gaussian( signal_tensor.unsqueeze(0).unsqueeze(0)) signal_tensor = signal_tensor.squeeze(0).squeeze(0) signal[0] = signal_tensor.detach().cpu().numpy() signal[0] = (signal[0] - np.min(signal[0])) / ( np.max(signal[0]) - np.min(signal[0])) return signal else: if dimensions == 3: signal = np.zeros( (1, image.shape[-3], image.shape[-2], image.shape[-1]), dtype=np.float32) else: signal = np.zeros((1, image.shape[-2], image.shape[-1]), dtype=np.float32) return signal
def test_1d(self): a = torch.ones(1, 8, 10) g = GaussianFilter(1, 3, 3, torch.device('cpu:0')) expected = np.array([[ [ 0.56658804, 0.69108766, 0.79392236, 0.86594427, 0.90267116, 0.9026711, 0.8659443, 0.7939224, 0.6910876, 0.56658804 ], ]]) expected = np.tile(expected, (1, 8, 1)) np.testing.assert_allclose(g(a).cpu().numpy(), expected)
def test_3d_sigmas(self): a = torch.ones(1, 1, 4, 3, 2) g = GaussianFilter(3, [3, 2, 1], 3).to(torch.device("cpu:0")) expected = np.array([[[ [[0.13690521, 0.13690521], [0.15181276, 0.15181276], [0.13690521, 0.13690521]], [[0.1506486, 0.15064861], [0.16705267, 0.16705267], [0.1506486, 0.15064861]], [[0.1506486, 0.15064861], [0.16705267, 0.16705267], [0.1506486, 0.15064861]], [[0.13690521, 0.13690521], [0.15181276, 0.15181276], [0.13690521, 0.13690521]], ]]]) np.testing.assert_allclose(g(a).cpu().numpy(), expected, rtol=1e-5) if torch.cuda.is_available(): g = GaussianFilter(3, [3, 2, 1], 3).to(torch.device("cuda:0")) np.testing.assert_allclose(g(a.cuda()).cpu().numpy(), expected, rtol=1e-2)
def test_1d(self): a = torch.ones(1, 8, 10) g = GaussianFilter(1, 3, 3).to(torch.device("cpu:0")) expected = np.array([[[ 0.5654129, 0.68915915, 0.79146194, 0.8631974, 0.8998163, 0.8998163, 0.8631973, 0.79146194, 0.6891592, 0.5654129, ]]]) expected = np.tile(expected, (1, 8, 1)) np.testing.assert_allclose(g(a).cpu().numpy(), expected, rtol=1e-5)
def extreme_points_to_image( points: List[Tuple[int, ...]], label: np.ndarray, sigma: Union[Sequence[float], float, Sequence[torch.Tensor], torch.Tensor] = 0.0, rescale_min: float = -1.0, rescale_max: float = 1.0, ): """ Please refer to :py:class:`monai.transforms.AddExtremePointsChannel` for the usage. Applies a gaussian filter to the extreme points image. Then the pixel values in points image are rescaled to range [rescale_min, rescale_max]. Args: points: Extreme points of the object/organ. label: label image to get extreme points from. Shape must be (1, spatial_dim1, [, spatial_dim2, ...]). Doesn't support one-hot labels. sigma: if a list of values, must match the count of spatial dimensions of input data, and apply every value in the list to 1 spatial dimension. if only 1 value provided, use it for all spatial dimensions. rescale_min: minimum value of output data. rescale_max: maximum value of output data. """ # points to image points_image = torch.zeros(label.shape[1:], dtype=torch.float) for p in points: points_image[p] = 1.0 # add channel and add batch points_image = points_image.unsqueeze(0).unsqueeze(0) gaussian_filter = GaussianFilter(label.ndim - 1, sigma=sigma) points_image = gaussian_filter(points_image).squeeze(0).detach().numpy() # rescale the points image to [rescale_min, rescale_max] min_intensity = np.min(points_image) max_intensity = np.max(points_image) points_image = (points_image - min_intensity) / (max_intensity - min_intensity) points_image = points_image * (rescale_max - rescale_min) + rescale_min return points_image
class ProbNMS(Transform): """ Performs probability based non-maximum suppression (NMS) on the probabilities map via iteratively selecting the coordinate with highest probability and then move it as well as its surrounding values. The remove range is determined by the parameter `box_size`. If multiple coordinates have the same highest probability, only one of them will be selected. Args: spatial_dims: number of spatial dimensions of the input probabilities map. Defaults to 2. sigma: the standard deviation for gaussian filter. It could be a single value, or `spatial_dims` number of values. Defaults to 0.0. prob_threshold: the probability threshold, the function will stop searching if the highest probability is no larger than the threshold. The value should be no less than 0.0. Defaults to 0.5. box_size: the box size (in pixel) to be removed around the the pixel with the maximum probability. It can be an integer that defines the size of a square or cube, or a list containing different values for each dimensions. Defaults to 48. Return: a list of selected lists, where inner lists contain probability and coordinates. For example, for 3D input, the inner lists are in the form of [probability, x, y, z]. Raises: ValueError: When ``prob_threshold`` is less than 0.0. ValueError: When ``box_size`` is a list or tuple, and its length is not equal to `spatial_dims`. ValueError: When ``box_size`` has a less than 1 value. """ backend = [TransformBackends.TORCH, TransformBackends.NUMPY] def __init__( self, spatial_dims: int = 2, sigma: Union[Sequence[float], float, Sequence[torch.Tensor], torch.Tensor] = 0.0, prob_threshold: float = 0.5, box_size: Union[int, Sequence[int]] = 48, ) -> None: self.sigma = sigma self.spatial_dims = spatial_dims if self.sigma != 0: self.filter = GaussianFilter(spatial_dims=spatial_dims, sigma=sigma) if prob_threshold < 0: raise ValueError("prob_threshold should be no less than 0.0.") self.prob_threshold = prob_threshold if isinstance(box_size, int): self.box_size = np.asarray([box_size] * spatial_dims) elif len(box_size) != spatial_dims: raise ValueError( "the sequence length of box_size should be the same as spatial_dims." ) else: self.box_size = np.asarray(box_size) if self.box_size.min() <= 0: raise ValueError("box_size should be larger than 0.") self.box_lower_bd = self.box_size // 2 self.box_upper_bd = self.box_size - self.box_lower_bd def __call__(self, prob_map: NdarrayOrTensor): """ prob_map: the input probabilities map, it must have shape (H[, W, ...]). """ if self.sigma != 0: if not isinstance(prob_map, torch.Tensor): prob_map = torch.as_tensor(prob_map, dtype=torch.float) self.filter.to(prob_map.device) prob_map = self.filter(prob_map) prob_map_shape = prob_map.shape outputs = [] while prob_map.max() > self.prob_threshold: max_idx = unravel_index(prob_map.argmax(), prob_map_shape) prob_max = prob_map[tuple(max_idx)] max_idx = max_idx.cpu().numpy() if isinstance( max_idx, torch.Tensor) else max_idx prob_max = prob_max.item() if isinstance( prob_max, torch.Tensor) else prob_max outputs.append([prob_max] + list(max_idx)) idx_min_range = (max_idx - self.box_lower_bd).clip(0, None) idx_max_range = (max_idx + self.box_upper_bd).clip( None, prob_map_shape) # for each dimension, set values during index ranges to 0 slices = tuple( slice(idx_min_range[i], idx_max_range[i]) for i in range(self.spatial_dims)) prob_map[slices] = 0 return outputs
def test_wrong_args(self): with self.assertRaisesRegex(ValueError, ""): GaussianFilter(3, [3, 2], 3).to(torch.device("cpu:0")) GaussianFilter(3, [3, 2, 1], 3).to(torch.device("cpu:0")) # test init