dictk

dictk: Digital Image Correlation Toolkit.

CLI vs. API: the dictk command-line entry points (dictk rosta, dictk checkerboard, dictk astronaut, ...) write image files to disk — that's their whole job. The corresponding Python API functions (dictk.rosta, dictk.checkerboard, dictk.astronaut, ...) do not perform any file I/O; they return NumPy arrays only. This keeps the API composable in a functional style — arrays can be piped through further functions (e.g. dictk.imaging.combine_images) before anything touches disk — and callers who do want a file call dictk.imaging.write_image explicitly as a separate, deliberate step.

 1"""dictk: Digital Image Correlation Toolkit.
 2
 3CLI vs. API: the `dictk` command-line entry points (`dictk rosta`,
 4`dictk checkerboard`, `dictk astronaut`, ...) write image files to disk —
 5that's their whole job. The corresponding Python API functions
 6(`dictk.rosta`, `dictk.checkerboard`, `dictk.astronaut`, ...) do not
 7perform any file I/O; they return NumPy arrays only. This keeps the API
 8composable in a functional style — arrays
 9can be piped through further functions (e.g. `dictk.imaging.combine_images`)
10before anything touches disk — and callers who do want a file call
11`dictk.imaging.write_image` explicitly as a separate, deliberate step.
12"""
13
14from importlib.metadata import PackageNotFoundError, version
15
16from dictk.core import zero_normalized_cross_correlation
17from dictk.imaging import astronaut, checkerboard
18from dictk.rosta import rosta
19
20try:
21    __version__ = version("dictk")
22except PackageNotFoundError:
23    __version__ = "0.0.0+unknown"
24
25__all__ = [
26    "astronaut",
27    "checkerboard",
28    "rosta",
29    "zero_normalized_cross_correlation",
30    "__version__",
31]
def astronaut(width: int = 512, height: int = 512) -> numpy.ndarray:
50def astronaut(width: int = 512, height: int = 512) -> np.ndarray:
51    """Load a bundled real-world grayscale reference image.
52
53    Same parameters and pixel values as the `dictk astronaut` CLI
54    command, minus the file write — see the `dictk` package docstring for
55    why the API layer stops at the array.
56
57    The source is a NASA portrait of astronaut Eileen Collins, from the
58    NASA Great Images database ("No known copyright restrictions, released
59    into the public domain."). It's bundled as a color image and converted
60    with `rgba_to_gray`; unlike `rosta` and `checkerboard`, it isn't
61    procedurally generated, so `width`/`height` other than the native
62    512x512 resize the source image (via `scipy.ndimage.zoom`) rather than
63    computing a fresh pattern at that size.
64
65    Args:
66        width: Image width in pixels. Must be >= 1.
67        height: Image height in pixels. Must be >= 1.
68
69    Returns:
70        A 2D uint8 array of shape (height, width).
71
72    Raises:
73        ValueError: If width or height is less than 1.
74    """
75    if width < 1:
76        raise ValueError(f"width {width} must be >= 1")
77    if height < 1:
78        raise ValueError(f"height {height} must be >= 1")
79
80    asset_path = importlib.resources.files("dictk") / "data" / "astronaut.png"
81    with importlib.resources.as_file(asset_path) as path:
82        color = read_image(path)
83    gray = rgba_to_gray(color)
84
85    native_height, native_width = gray.shape
86    if (width, height) == (native_width, native_height):
87        return gray
88
89    zoom_factors = (height / native_height, width / native_width)
90    resized = zoom(gray.astype(np.float64), zoom_factors, order=3)
91    return np.clip(resized, 0, 255).astype(np.uint8)

Load a bundled real-world grayscale reference image.

Same parameters and pixel values as the dictk astronaut CLI command, minus the file write — see the dictk package docstring for why the API layer stops at the array.

The source is a NASA portrait of astronaut Eileen Collins, from the NASA Great Images database ("No known copyright restrictions, released into the public domain."). It's bundled as a color image and converted with rgba_to_gray; unlike rosta and checkerboard, it isn't procedurally generated, so width/height other than the native 512x512 resize the source image (via scipy.ndimage.zoom) rather than computing a fresh pattern at that size.

Args: width: Image width in pixels. Must be >= 1. height: Image height in pixels. Must be >= 1.

Returns: A 2D uint8 array of shape (height, width).

Raises: ValueError: If width or height is less than 1.

def checkerboard( width: int, height: int, count_x: int = 8, count_y: int = 8) -> numpy.ndarray:
15def checkerboard(
16    width: int, height: int, count_x: int = 8, count_y: int = 8
17) -> np.ndarray:
18    """Generate a black-and-white checkerboard test image.
19
20    Same parameters and pixel values as the `dictk checkerboard` CLI
21    command, minus the file write — see the `dictk` package docstring for
22    why the API layer stops at the array. count_x and count_y are named for
23    the rectangles they divide the image into, not "squares": unequal
24    counts (or a width/height ratio that doesn't match count_x/count_y)
25    produce rectangular cells, not square ones.
26
27    Args:
28        width: Image width in pixels.
29        height: Image height in pixels.
30        count_x: Number of rectangles along the width. Must be >= 1.
31        count_y: Number of rectangles along the height. Must be >= 1.
32
33    Returns:
34        A 2D uint8 array of shape (height, width) with values 0 or 255.
35
36    Raises:
37        ValueError: If count_x or count_y is less than 1.
38    """
39    if count_x < 1:
40        raise ValueError(f"count_x {count_x} must be >= 1")
41    if count_y < 1:
42        raise ValueError(f"count_y {count_y} must be >= 1")
43
44    rows = (np.arange(height) * count_y // height) % 2
45    cols = (np.arange(width) * count_x // width) % 2
46    pattern = np.logical_xor(rows[:, None], cols[None, :])
47    return (pattern * 255).astype(np.uint8)

Generate a black-and-white checkerboard test image.

Same parameters and pixel values as the dictk checkerboard CLI command, minus the file write — see the dictk package docstring for why the API layer stops at the array. count_x and count_y are named for the rectangles they divide the image into, not "squares": unequal counts (or a width/height ratio that doesn't match count_x/count_y) produce rectangular cells, not square ones.

Args: width: Image width in pixels. height: Image height in pixels. count_x: Number of rectangles along the width. Must be >= 1. count_y: Number of rectangles along the height. Must be >= 1.

Returns: A 2D uint8 array of shape (height, width) with values 0 or 255.

Raises: ValueError: If count_x or count_y is less than 1.

def rosta( width: int = 200, height: int = 200, *, dot_size: float = 4.0, density: float = 0.32, smoothness: float = 2.0, random_seed: int = 42) -> numpy.ndarray:
100def rosta(
101    width: int = 200,
102    height: int = 200,
103    *,
104    dot_size: float = 4.0,
105    density: float = 0.32,
106    smoothness: float = 2.0,
107    random_seed: int = 42,
108) -> np.ndarray:
109    """Generate a Rosta speckle pattern as a uint8 grayscale image array.
110
111    Same parameters and pixel values as the `dictk rosta` CLI command, minus
112    the file write — see the `dictk` package docstring for why the API
113    layer stops at the array.
114
115    Args:
116        width: Image width in pixels.
117        height: Image height in pixels.
118        dot_size: Size factor for pattern dots (0.0 to 100.0).
119        density: Density of pattern elements (0.0 to 1.0).
120        smoothness: Smoothness factor for final blur (0.0 to 100.0).
121        random_seed: Seed for reproducible random number generation.
122
123    Returns:
124        A 2D uint8 array of shape (height, width), grayscale in [0, 255].
125
126    Raises:
127        ValueError: If dot_size, density, or smoothness is out of range.
128    """
129    rp = RostaParameters(
130        image_size=ImageSize(width=width, height=height),
131        dot_size=dot_size,
132        density=density,
133        smoothness=smoothness,
134        random_seed=random_seed,
135    )
136    pattern = rosta_pattern(rp)
137    return (pattern * 255).astype(np.uint8)

Generate a Rosta speckle pattern as a uint8 grayscale image array.

Same parameters and pixel values as the dictk rosta CLI command, minus the file write — see the dictk package docstring for why the API layer stops at the array.

Args: width: Image width in pixels. height: Image height in pixels. dot_size: Size factor for pattern dots (0.0 to 100.0). density: Density of pattern elements (0.0 to 1.0). smoothness: Smoothness factor for final blur (0.0 to 100.0). random_seed: Seed for reproducible random number generation.

Returns: A 2D uint8 array of shape (height, width), grayscale in [0, 255].

Raises: ValueError: If dot_size, density, or smoothness is out of range.

def zero_normalized_cross_correlation(a: numpy.ndarray, b: numpy.ndarray) -> float:
 7def zero_normalized_cross_correlation(a: np.ndarray, b: np.ndarray) -> float:
 8    """Compute the zero-normalized cross-correlation (ZNCC) between two arrays.
 9
10    ZNCC is a similarity metric commonly used as the matching criterion in
11    digital image correlation (DIC) template matching: it compares two
12    equal-shaped patches (e.g. image subsets) while being invariant to
13    linear changes in brightness and contrast. A return value of ``1.0``
14    indicates a perfect match, ``-1.0`` a perfect inverse match, and ``0.0``
15    no correlation.
16
17    Args:
18        a: First array (e.g. a reference image subset).
19        b: Second array, same shape as ``a`` (e.g. a deformed image subset).
20
21    Returns:
22        The ZNCC score in the range ``[-1.0, 1.0]``.
23
24    Raises:
25        ValueError: If ``a`` and ``b`` do not have the same shape.
26    """
27    a = np.asarray(a, dtype=np.float64)
28    b = np.asarray(b, dtype=np.float64)
29
30    if a.shape != b.shape:
31        raise ValueError(f"shape mismatch: a.shape={a.shape}, b.shape={b.shape}")
32
33    a_centered = a - a.mean()
34    b_centered = b - b.mean()
35
36    denominator = np.sqrt(np.sum(a_centered**2) * np.sum(b_centered**2))
37    if denominator == 0:
38        return 0.0
39
40    return float(np.sum(a_centered * b_centered) / denominator)

Compute the zero-normalized cross-correlation (ZNCC) between two arrays.

ZNCC is a similarity metric commonly used as the matching criterion in digital image correlation (DIC) template matching: it compares two equal-shaped patches (e.g. image subsets) while being invariant to linear changes in brightness and contrast. A return value of 1.0 indicates a perfect match, -1.0 a perfect inverse match, and 0.0 no correlation.

Args: a: First array (e.g. a reference image subset). b: Second array, same shape as a (e.g. a deformed image subset).

Returns: The ZNCC score in the range [-1.0, 1.0].

Raises: ValueError: If a and b do not have the same shape.

__version__ = '0.1.dev1+g50d15e132'