Advanced usage#
In this notebook we will look at some advanced usage of the imageruler.
Ignore schemes#
By default, the imageruler will ignore certain violations on the edges of large features, as these generally arise from discretization artifacts and are not particularly meaningful. However, we can configure the imageruler to be either more permissive, or more strict.
We will study designs that consist of two circular solid features with a discretization artifact separated by a variable gap. First, define a function to construct these and make a plot.
import matplotlib.pyplot as plt
import numpy as onp
from imageruler import imageruler
def separated_circles(
separation_distance: int, include_extra_pixel: bool
) -> onp.ndarray:
left_circle = imageruler.kernel_for_length_scale(80)
right_circle = imageruler.kernel_for_length_scale(60)
right_circle = onp.pad(right_circle, ((10, 10), (0, 0)))
circles = onp.concatenate(
[left_circle, onp.zeros((80, separation_distance)), right_circle],
axis=1,
)
circles = onp.pad(circles, ((10, 10), (10, 10))).astype(bool)
if include_extra_pixel:
circles[9, 50] = True
return circles
_ = plt.imshow(separated_circles(separation_distance=1, include_extra_pixel=True))
Above we see the two circles (with diameters of 80 and 60 pixels, respectively), along with the extra pixel we’ve added at position (9, 50)
. Now, let’s use the imageruler to measure the minimum width and spacing using the most-strict scheme which does not ignore any violations: IgnoreScheme.NONE
.
minimum_width, minimum_spacing = imageruler.minimum_length_scale(
x=separated_circles(separation_distance=1, include_extra_pixel=True),
ignore_scheme=imageruler.IgnoreScheme.NONE,
)
print(
f"Minimum width and spacing are {minimum_width} and {minimum_spacing}, respectively"
)
Minimum width and spacing are 3 and 1, respectively
As we can see, the measured minimum spacing is a single pixel, as we would hope. However, the minimum width is reported as only three pixels—much smaller the 60 pixel value we might expect. At issue here is the extra pixel; let’s measure the length scale for the two circles without the extra pixel.
minimum_width, minimum_spacing = imageruler.minimum_length_scale(
x=separated_circles(separation_distance=1, include_extra_pixel=False),
ignore_scheme=imageruler.IgnoreScheme.NONE,
)
print(
f"Minimum width and spacing are {minimum_width} and {minimum_spacing}, respectively"
)
Minimum width and spacing are 60 and 1, respectively
These are the expected values. In order to obtain these values from the imageruler even for a design that includes an errant pixel, we need to tell the imageruler to ignore certain violations. Let’s start with the most permissive scheme, IgnoreScheme.EDGES
, which ignores violations on the edges of all features.
minimum_width, minimum_spacing = imageruler.minimum_length_scale(
x=separated_circles(separation_distance=1, include_extra_pixel=True),
ignore_scheme=imageruler.IgnoreScheme.EDGES,
)
print(
f"Minimum width and spacing are {minimum_width} and {minimum_spacing}, respectively"
)
Minimum width and spacing are 60 and 3, respectively
Here, we see that the width is now correctly reported, but we are also over-estimating the spacing. Fortunately, there is a third scheme for ignoring iolations, IgnoreScheme.LARGE_FEATURE_EDGES_STRICT
, which only ignores violations on the edges of large features. This is actually the default choice, so if we simply call imageruler.minimum_length_scale
without specifying an ignore scheme, this is what we will get.
minimum_width, minimum_spacing = imageruler.minimum_length_scale(
x=separated_circles(separation_distance=1, include_extra_pixel=True),
)
print(
f"Minimum width and spacing are {minimum_width} and {minimum_spacing}, respectively"
)
Minimum width and spacing are 60 and 1, respectively
These are the values we hoped for.
Challenging test cases#
The various ignore schemes appear quite similar for most designs. However, some designs are problematic, such as checkerboard patterns. Here we show a checkerboard and other test designs, and the measurements reported with different ignore schemes.
x = onp.asarray([[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]])
x = onp.kron(x, onp.ones((7, 9))).astype(bool)
title_str = ""
for scheme in imageruler.IgnoreScheme:
min_width, min_spacing = imageruler.minimum_length_scale(x, ignore_scheme=scheme)
title_str += f"{scheme.name}: {min_width=}, {min_spacing=}\n"
plt.imshow(x)
_ = plt.title(title_str)
x = onp.asarray([[0, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]])
x = onp.kron(x, onp.ones((7, 9))).astype(bool)
x[13, :] = False
title_str = ""
for scheme in imageruler.IgnoreScheme:
min_width, min_spacing = imageruler.minimum_length_scale(x, ignore_scheme=scheme)
title_str += f"{scheme.name}: {min_width=}, {min_spacing=}\n"
plt.imshow(x)
_ = plt.title(title_str)
x = onp.asarray([[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]])
x = onp.kron(x, onp.ones((10, 10))).astype(bool)
title_str = ""
for scheme in imageruler.IgnoreScheme:
min_width, min_spacing = imageruler.minimum_length_scale(x, ignore_scheme=scheme)
title_str += f"{scheme.name}: {min_width=}, {min_spacing=}\n"
plt.imshow(x)
_ = plt.title(title_str)
x = onp.asarray([[0, 1, 1, 1, 0], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 1, 1, 1, 0]])
x = onp.pad(x, ((3, 3), (3, 3))).astype(bool)
x[2, 5] = True
title_str = ""
for scheme in imageruler.IgnoreScheme:
min_width, min_spacing = imageruler.minimum_length_scale(x, ignore_scheme=scheme)
title_str += f"{scheme.name}: {min_width=}, {min_spacing=}\n"
plt.imshow(x)
_ = plt.title(title_str)