cds2.image
Grayscale image processing: convolution, edges, morphology.
Grayscale image processing: convolution, filtering and morphology.
EdgeResult
dataclass
Gradient magnitude and orientation returned by :func:sobel_edges.
Source code in src/cds2/image.py
| @dataclass(frozen=True)
class EdgeResult:
"""Gradient magnitude and orientation returned by :func:`sobel_edges`."""
magnitude: FloatArray
direction: FloatArray
|
convolve2d(image, kernel, mode='same')
True 2-D convolution (kernel flipped) with zero padding in same mode.
Source code in src/cds2/image.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 | def convolve2d(image: ImageLike, kernel: ImageLike, mode: str = "same") -> FloatArray:
"""True 2-D convolution (kernel flipped) with zero padding in ``same`` mode."""
img = _as_image(image)
ker = _as_square_kernel(kernel)
if mode not in ("same", "valid"):
msg = "mode must be same or valid"
raise ValueError(msg)
side = ker.shape[0]
pad = side // 2
flipped = np.ascontiguousarray(ker[::-1, ::-1])
if mode == "same":
padded = np.pad(img, pad, mode="constant", constant_values=0.0)
else:
padded = img
return _sliding_dot(padded, flipped)
|
gaussian_kernel(size=5, sigma=1.0)
Separable Gaussian kernel normalized to unit sum.
Source code in src/cds2/image.py
68
69
70
71
72
73
74
75
76
77
78
79 | def gaussian_kernel(size: int = 5, sigma: float = 1.0) -> FloatArray:
"""Separable Gaussian kernel normalized to unit sum."""
if size < 3 or size % 2 == 0:
msg = "kernel size must be odd and at least 3"
raise ValueError(msg)
if sigma <= 0:
msg = "sigma must be positive"
raise ValueError(msg)
offsets = np.arange(size, dtype=float) - (size - 1) / 2
profile = np.exp(-(offsets**2) / (2.0 * sigma**2))
profile /= profile.sum()
return np.asarray(np.outer(profile, profile), dtype=float)
|
gaussian_blur(image, sigma=1.0, size=5)
Smooth image by convolving with a size x size Gaussian kernel.
Source code in src/cds2/image.py
| def gaussian_blur(image: ImageLike, sigma: float = 1.0, size: int = 5) -> FloatArray:
"""Smooth ``image`` by convolving with a ``size`` x ``size`` Gaussian kernel."""
return convolve2d(image, gaussian_kernel(size=size, sigma=sigma), mode="same")
|
sobel_edges(image)
Sobel gradient magnitude and direction via correlation (no flip).
Source code in src/cds2/image.py
102
103
104
105
106
107
108
109
110
111
112
113
114 | def sobel_edges(image: ImageLike) -> EdgeResult:
"""Sobel gradient magnitude and direction via correlation (no flip)."""
img = _as_image(image)
sobel_x = np.array([[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]])
sobel_y = sobel_x.T.copy()
gx = _correlate2d(img, sobel_x)
gy = _correlate2d(img, sobel_y)
magnitude = np.hypot(gx, gy)
direction = np.arctan2(gy, gx)
return EdgeResult(
magnitude=np.asarray(magnitude, dtype=float),
direction=np.asarray(direction, dtype=float),
)
|
downsample(image, factor=2, method='mean')
Reduce resolution by non-overlapping factor blocks after cropping.
Source code in src/cds2/image.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 | def downsample(image: ImageLike, factor: int = 2, method: str = "mean") -> FloatArray:
"""Reduce resolution by non-overlapping ``factor`` blocks after cropping."""
img = _as_image(image)
if method not in ("mean", "max", "min"):
msg = "method must be mean, max or min"
raise ValueError(msg)
if factor < 2:
msg = "factor must be at least 2"
raise ValueError(msg)
height = (img.shape[0] // factor) * factor
width = (img.shape[1] // factor) * factor
blocks = img[:height, :width].reshape(height // factor, factor, width // factor, factor)
if method == "mean":
reduced = blocks.mean(axis=(1, 3))
elif method == "max":
reduced = blocks.max(axis=(1, 3))
else:
reduced = blocks.min(axis=(1, 3))
return np.asarray(reduced, dtype=float)
|
binarize(image, threshold)
Threshold an image into a 0/1 mask of pixels strictly above threshold.
Source code in src/cds2/image.py
| def binarize(image: ImageLike, threshold: float) -> FloatArray:
"""Threshold an image into a 0/1 mask of pixels strictly above ``threshold``."""
img = _as_image(image)
return np.asarray((img > threshold).astype(float), dtype=float)
|
erode(binary, structure_size=3)
Morphological erosion as a min filter over a square window.
Source code in src/cds2/image.py
159
160
161
162
163
164
165
166 | def erode(binary: ImageLike, structure_size: int = 3) -> FloatArray:
"""Morphological erosion as a min filter over a square window."""
arr = _as_binary(binary)
side = _as_structure_size(structure_size)
pad = side // 2
padded = np.pad(arr, pad, mode="constant", constant_values=1.0)
windows = np.lib.stride_tricks.sliding_window_view(padded, (side, side))
return np.asarray(windows.min(axis=(-2, -1)), dtype=float)
|
dilate(binary, structure_size=3)
Morphological dilation as a max filter over a square window.
Source code in src/cds2/image.py
169
170
171
172
173
174
175
176 | def dilate(binary: ImageLike, structure_size: int = 3) -> FloatArray:
"""Morphological dilation as a max filter over a square window."""
arr = _as_binary(binary)
side = _as_structure_size(structure_size)
pad = side // 2
padded = np.pad(arr, pad, mode="constant", constant_values=0.0)
windows = np.lib.stride_tricks.sliding_window_view(padded, (side, side))
return np.asarray(windows.max(axis=(-2, -1)), dtype=float)
|