Thresholding#

Simple intensity-based segmentation: keep the pixels inside a value range, or split an image at Otsu’s threshold.

kornia.filters.in_range(input, lower, upper, return_mask=False)[source]#

Create a mask indicating whether elements of the input torch.Tensor are within the specified range.

_images/in_range.png

The formula applied for single-channel torch.Tensor is:

\[\text{out}(I) = \text{lower}(I) \leq \text{input}(I) \leq \text{upper}(I)\]

The formula applied for multi-channel torch.Tensor is:

\[\text{out}(I) = \bigwedge_{c=0}^{C-1} \left( \text{lower}_c(I) \leq \text{input}_c(I) \leq \text{upper}_c(I) \right)\]

where C is the number of channels. Both comparisons are inclusive.

Convention:
  • A NaN channel fails its pixel. The mask has the input’s dtype, 1 for a pass. For floating input, bounds are cast to the input dtype before comparison, so only an interval still reversed after that conversion selects nothing.

  • return_mask=False returns input * mask: the channels of a failing pixel become 0, except a NaN or infinite one, which becomes NaN.

  • For integer inputs, fractional lower bounds round up and fractional upper bounds round down. Bounds outside the input dtype’s range do not wrap or overflow; a range containing no representable input value produces an empty mask.

Parameters:
  • input (Tensor) – The input torch.Tensor to be filtered in the shape of \((*, *, H, W)\).

  • lower (Union[tuple[Any, ...], Tensor]) – The lower bounds of the filter (inclusive).

  • upper (Union[tuple[Any, ...], Tensor]) – The upper bounds of the filter (inclusive).

  • return_mask (bool, optional) – If is true, the filtered mask is returned, otherwise the filtered input image. Default: False

Return type:

Tensor

Returns:

A binary mask \((*, 1, H, W)\) of input indicating whether elements are within the range or filtered input image \((*, *, H, W)\).

Raises:
  • TypeError – If lower or upper is neither a tuple nor a torch.Tensor, if one is a tuple and the other a torch.Tensor, or if return_mask is not a bool.

  • ValueError – If a tuple bound does not have one element per channel, or a torch.Tensor bound does not fit the input as described in the note.

Note

Clarification of lower and upper:

  • If provided as a tuple, it should have the same number of elements as the channels in the input torch.Tensor. This bound is then applied uniformly across all batches.

  • When provided as a torch.Tensor, it allows for different bounds to be applied to each batch. The torch.Tensor shape should be (B, C, 1, 1), where B is the batch size and C is the number of channels.

  • A 1-D torch.Tensor is read per channel, never per column: its shape is (C,) (or one element, for the same bound in every channel) and the same bound is applied across all batches. A 1-D bound of any other length, such as (W,) when W is neither C nor 1, raises. When W == C, a (W,) bound is the (C,) bound, not a per-column one.

  • Any other torch.Tensor with at most four dimensions is aligned with the input \((B, C, H, W)\) from the last dimension, and every dimension must be 1 or the input’s. For example (1, C, 1, 1) and (C, 1, 1) apply the same per-channel bound to all batches, (B, C, H, W), (C, H, W) and (H, W) give a different bound at each pixel, and a 0-d torch.Tensor applies one bound everywhere.

  • Each torch.Tensor bound is checked on its own, so a mis-shaped one raises whatever the other bound is. The input is read as \((B, C, H, W)\): a 3-D input is \((1, C, H, W)\), a 2-D input is \((1, 1, H, W)\), and for more than four dimensions the leading dimensions are flattened into B.

Examples

>>> rng = torch.manual_seed(1)
>>> input = torch.rand(1, 3, 3, 3)
>>> lower = (0.2, 0.3, 0.4)
>>> upper = (0.8, 0.9, 1.0)
>>> mask = in_range(input, lower, upper, return_mask=True)
>>> mask
tensor([[[[1., 1., 0.],
          [0., 0., 0.],
          [0., 1., 1.]]]])
>>> mask.shape
torch.Size([1, 1, 3, 3])

Apply different bounds (lower and upper) for each batch:

>>> rng = torch.manual_seed(1)
>>> input_tensor = torch.rand((2, 3, 3, 3))
>>> input_shape = input_tensor.shape
>>> lower = torch.tensor([[0.2, 0.2, 0.2], [0.2, 0.2, 0.2]]).reshape(input_shape[0], input_shape[1], 1, 1)
>>> upper = torch.tensor([[0.6, 0.6, 0.6], [0.8, 0.8, 0.8]]).reshape(input_shape[0], input_shape[1], 1, 1)
>>> mask = in_range(input_tensor, lower, upper, return_mask=True)
>>> mask
tensor([[[[0., 0., 1.],
          [0., 0., 0.],
          [1., 0., 0.]]],


        [[[0., 0., 0.],
          [1., 0., 0.],
          [0., 0., 1.]]]])
class kornia.filters.InRange(lower, upper, return_mask=False)[source]#

Create a module for applying lower and upper bounds to input tensors.

Convention:

See the Convention block on in_range().

Parameters:
  • lower (Union[tuple[Any, ...], Tensor]) – The lower bounds of the filter (inclusive).

  • upper (Union[tuple[Any, ...], Tensor]) – The upper bounds of the filter (inclusive).

  • return_mask (bool, optional) – If is true, the filtered mask is returned, otherwise the filtered input image. Default: False

Returns:

A binary mask \((*, 1, H, W)\) of input indicating whether elements are within the range or filtered input image \((*, *, H, W)\).

Note

View complete documentation in kornia.filters.in_range().

Examples

>>> rng = torch.manual_seed(1)
>>> input = torch.rand(1, 3, 3, 3)
>>> lower = (0.2, 0.3, 0.4)
>>> upper = (0.8, 0.9, 1.0)
>>> mask = InRange(lower, upper, return_mask=True)(input)
>>> mask
tensor([[[[1., 1., 0.],
          [0., 0., 0.],
          [0., 1., 1.]]]])
kornia.filters.otsu_threshold(x, nbins=256, slow_and_differentiable=False, return_mask=False)[source]#

Apply automatic image thresholding using Otsu algorithm to the input tensor.

Convention:
  • One threshold is returned per plane over the last two axes: a \((B, C, H, W)\) input gives B * C thresholds, flattened, and a tensor of at most two dimensions is one plane.

  • Each plane is histogrammed on its own range, nbins bins between its minimum and its maximum, not over a fixed \([0, 1]\) or \([0, 255]\). The threshold is the upper edge of the selected histogram bin, in input units and in the input’s dtype. For an integer input it is the largest integer below that value, so x > threshold keeps every pixel on or above it. For floating input, rounding is corrected when necessary to keep the threshold at or above the largest background pixel and below the smallest foreground pixel, so x > threshold matches the histogram split. On MPS, a zero foreground boundary in float32 or bfloat16 uses the smallest normal negative value because comparisons flush subnormal values to zero.

  • Histogram and bin-edge arithmetic uses float64 only for float64 input, and float32 for every other input dtype, including integers and half precision. Large or narrowly spaced integer values can therefore lose distinctions and select a different split; when that precision is required, pass float64 input whose values are representable there, on a device that supports float64 (not MPS).

  • Empty bins do not introduce candidate splits, so the lowest split across a run of empty bins wins on every device. A constant plane uses its constant value as the threshold.

  • The foreground is x > threshold, strictly. The first output is x * (x > threshold), not a binary image, and its gradient is that mask.

  • With slow_and_differentiable=True, every pixel contributes to a Gaussian kernel density estimate and the threshold has a straight-through gradient; see the note below for the estimate and surrogate. The default path’s threshold has no gradient. Both paths support torch.export and full-graph torch.compile.

Parameters:
  • x (Tensor) – Input tensor (image or batch of images) of at most five dimensions.

  • nbins (int) – Number of bins for histogram computation, default is 256. Default: 256

  • slow_and_differentiable (bool) – If True, build the histogram from a Gaussian kernel density estimate and give the threshold a gradient with respect to x; see the note below. Default is False. Default: False

  • return_mask (bool) – If True, return the boolean mask x > threshold in place of the thresholded image, with each pixel compared against the threshold of its own image and channel. If False, return the thresholded image. Default: False

Returns:

Thresholded tensor, or the boolean mask x > threshold when return_mask is True, each with the shape of x, and the computed threshold values. The thresholded tensor cannot tell a kept pixel of value 0 from a dropped one; use the mask for that.

Return type:

Tuple[torch.Tensor, torch.Tensor]

Raises:
  • ValueError – If the input tensor has more than five dimensions.

  • BaseError – If the input dtype is not supported.

Note

You can find more information about the Otsu algorithm here: https://en.wikipedia.org/wiki/Otsu’s_method

Note

With slow_and_differentiable=True, each pixel is spread into a Gaussian with a standard deviation of 0.1 bin, and the histogram holds the mass of these Gaussians in each bin, a pixel’s mass beyond the plane’s range staying in the first or the last bin, so every pixel contributes its full mass. The threshold is the upper edge of the bin selected by the best split of that histogram, as on the default path, and is usually within one bin of the default threshold. A bin counts as non-empty only above 1 % of one pixel’s mass, so of the splits that give the same partition the lowest is taken on every device. Each pixel’s class is the side of the selected edge its bin lies on, and a floating threshold is corrected to separate the two classes as on the default path.

The threshold uses a hard split, and its first-order gradient is a straight-through surrogate: the gradient of a soft-argmax over the between-class variance curve \(\sigma_B^2(k)\) of a wider estimate, with a standard deviation of 0.5 bin, so that a pixel’s gradient varies smoothly with its value instead of concentrating on pixels near a bin edge. Split \(k\) is weighted by \(\operatorname{softmax}_k\left(\sigma_B^2(k) / (0.01 \max_j \sigma_B^2(j))\right)\). The curve’s means are measured in bins and the temperature is relative to the curve, so the weights do not depend on the intensity scale or offset of x. The surrogate is evaluated on the plane shifted by its minimum and divided by its range, or by its largest magnitude when the range overflows, with these factors cancelled from its first-order gradient, so narrow and finite extreme ranges stay safe. Higher-order derivatives through this straight-through normalization do not represent derivatives of the original-unit soft threshold. Finite differences of the hard threshold do not match the surrogate gradient. The wider estimate is computed only when x requires a gradient. With the default slow_and_differentiable=False the threshold has no gradient. On both paths the thresholded image is x * (x > threshold), whose gradient with respect to x is that mask.

Example

>>> import torch
>>> from kornia.filters.otsu_thresholding import otsu_threshold
>>> x = torch.tensor([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
>>> x
tensor([[10, 20, 30],
        [40, 50, 60],
        [70, 80, 90]])
>>> otsu_threshold(x)
(tensor([[ 0,  0,  0],
        [ 0, 50, 60],
        [70, 80, 90]]), tensor([40]))
class kornia.filters.OtsuThreshold[source]#

Otsu thresholding module for PyTorch tensors.

Convention:

See the Convention block on otsu_threshold(). forward has no return_mask and always returns the thresholded tensor with the thresholds.