Thresholding#

in_range() keeps the pixels whose channels lie between bounds you give. otsu_threshold() picks one threshold per image plane from its histogram and keeps the pixels above it.

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

Keep the pixels whose channels all lie in an inclusive range.

out = in_range(input, lower, upper) multiplies input by a mask that is 1 where every channel lies between its lower and upper bounds, and 0 elsewhere. With return_mask=True it returns the mask instead.

_images/in_range.png

For an input with C channels the mask at pixel \(I\) is:

\[\text{mask}(I) = \bigwedge_{c=0}^{C-1} \left( \text{lower}_c(I) \leq \text{input}_c(I) \leq \text{upper}_c(I) \right)\]
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) – image with shape (*, C, H, W).

  • lower (Union[tuple[Any, ...], Tensor]) – inclusive lower bounds, a tuple with one entry per channel or a tensor; see the note.

  • upper (Union[tuple[Any, ...], Tensor]) – inclusive upper bounds, in the same form as lower.

  • return_mask (bool, optional) – if True, return the mask; otherwise return the masked image. Default: False

Return type:

Tensor

Returns:

the mask with shape (*, 1, H, W) if return_mask is True, otherwise the masked image with the shape of input.

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

The forms of lower and upper:

  • A tuple has one entry per channel and applies to every sample.

  • A tensor of shape (B, C, 1, 1) gives each sample its own bounds.

  • A 1-D tensor is read per channel, never per column: its shape is (C,), or one element for the same bound in every channel, and it applies to every sample. Any other 1-D 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 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 samples, (B, C, H, W), (C, H, W) and (H, W) give a different bound at each pixel, and a 0-d tensor applies one bound everywhere.

  • Each 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 gives a 2-D output, and for more than four dimensions the leading dimensions are flattened into B.

See also

  • InRange: the module form.

  • otsu_threshold(): picks the threshold from the image histogram instead of taking bounds.

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]#

Keep the pixels whose channels all lie in stored inclusive bounds (module form).

keep = InRange(lower, upper, return_mask) stores its arguments, and out = keep(input) is in_range() on an image with shape (*, C, H, W).

Convention:

See the Convention block on in_range().

Parameters:
Shape:
  • Input: (*, C, H, W)

  • Output: (*, 1, H, W) with return_mask=True, otherwise the shape of the input.

See also

  • in_range(): the one-call form, with the forms the bounds can take.

  • OtsuThreshold: picks the threshold from the image histogram.

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]#

Threshold an image at the level Otsu’s method picks, one threshold per image plane.

thresholded, threshold = otsu_threshold(x) finds the threshold that best splits the histogram of each image plane of x into two classes and keeps the pixels above it. With return_mask=True the first output is the boolean mask x > threshold instead.

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.

  • The threshold of the default path has no gradient. With slow_and_differentiable=True it has a straight-through gradient with respect to x; see the note. Both paths support torch.export and full-graph torch.compile.

Parameters:
  • x (Tensor) – image or batch of images with at most five dimensions, such as (B, C, H, W).

  • nbins (int, optional) – number of histogram bins. Default: 256

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

  • return_mask (bool, optional) – if True, the first output is the mask, with each pixel compared against the threshold of its own image and channel. Default: False

Return type:

Tuple[Tensor, Tensor]

Returns:

the first output, with the shape of x, and the thresholds. The thresholded tensor cannot tell a kept pixel of value 0 from a dropped one; use the mask for that.

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, the threshold is the upper edge of the bin selected by the best split of the smoothed histogram, as on the default path, and is a hard split. Its first-order gradient is a straight-through surrogate, the gradient of a soft-argmax over the between-class variance of a wider estimate. It does not match finite differences of the hard threshold, and its higher-order derivatives are not those of a soft threshold.

See also

  • OtsuThreshold: the module form, without return_mask.

  • in_range(): thresholds with bounds you give instead of a histogram.

  • threshold(): thresholds at a level you give, with OpenCV’s modes.

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]#

Threshold an image at the level Otsu’s method picks (module form).

otsu = OtsuThreshold() stores no settings, and thresholded, threshold = otsu(x, nbins, slow_and_differentiable) is otsu_threshold() without return_mask.

Convention:

See the Convention block on otsu_threshold().

See also