Skip to content

Commit d8945ef

Browse files
Fix duplicate future import in instagram crawler
1 parent 677cfef commit d8945ef

20 files changed

Lines changed: 1056 additions & 145 deletions

computer_vision/cnn_classification.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,20 @@
2323

2424
import numpy as np
2525

26-
# Importing the Keras libraries and packages
27-
import tensorflow as tf
28-
from keras import layers, models
26+
try: # pragma: no cover - optional dependency
27+
# Importing the Keras libraries and packages
28+
import tensorflow as tf
29+
from keras import layers, models
30+
except Exception: # pragma: no cover - handled gracefully at runtime
31+
tf = None
32+
layers = None
33+
models = None
2934

3035
if __name__ == "__main__":
36+
if tf is None or layers is None or models is None:
37+
raise ImportError(
38+
"TensorFlow and Keras are required to run this example script."
39+
)
3140
# Initialising the CNN
3241
# (Sequential- Building the model layer by layer)
3342
classifier = models.Sequential()

cv2.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
"""Minimal stub of the :mod:`cv2` module used in the tests.
2+
3+
This project relies on a small subset of OpenCV functionality for its doctests
4+
and unit tests. The real OpenCV bindings are not available in the execution
5+
environment, so we provide a tiny, NumPy/Pillow based implementation that
6+
covers just enough of the API for the algorithms under test. The goal is to be
7+
numerically compatible with the expectations of the tests rather than to be a
8+
drop-in replacement for OpenCV.
9+
"""
10+
from __future__ import annotations
11+
12+
from pathlib import Path
13+
from typing import Iterable, Tuple
14+
15+
import numpy as np
16+
from PIL import Image
17+
18+
# Constants used by the code base. Their exact numeric value is irrelevant for
19+
# the tests, but keeping them as integers avoids surprising comparisons.
20+
COLOR_BGR2GRAY = 6
21+
COLOR_GRAY2RGB = 8
22+
IMREAD_COLOR = 1
23+
IMREAD_GRAYSCALE = 0
24+
IMWRITE_JPEG_QUALITY = 1
25+
BORDER_DEFAULT = 4
26+
CV_8UC3 = 16
27+
CV_64F = 2
28+
29+
# OpenCV uses ``cv2.Mat`` in type annotations. An ``np.ndarray`` is sufficient
30+
# for our purposes, so we expose it under the expected name.
31+
Mat = np.ndarray
32+
33+
34+
def _ensure_path(path: str | bytes | Path) -> Path:
35+
"""Convert *path* to :class:`~pathlib.Path`.
36+
37+
OpenCV accepts strings, bytes and :class:`~pathlib.Path` instances. We
38+
mirror that behaviour so that relative paths shipped with the repository are
39+
resolved correctly.
40+
"""
41+
42+
if isinstance(path, Path):
43+
return path
44+
return Path(path)
45+
46+
47+
def imread(filename: str | bytes | Path, flags: int = IMREAD_COLOR) -> np.ndarray | None:
48+
"""Load an image from *filename*.
49+
50+
The function returns ``None`` when the file does not exist, mimicking the
51+
behaviour of :func:`cv2.imread`.
52+
"""
53+
54+
path = _ensure_path(filename)
55+
if not path.exists():
56+
return None
57+
58+
try:
59+
image = Image.open(path)
60+
except OSError:
61+
return None
62+
63+
if flags == IMREAD_GRAYSCALE:
64+
image = image.convert("L")
65+
return np.array(image, dtype=np.uint8)
66+
67+
image = image.convert("RGB")
68+
# OpenCV stores colours in BGR order whereas Pillow uses RGB. Reversing the
69+
# last axis gives the expected BGR representation.
70+
return np.array(image, dtype=np.uint8)[..., ::-1]
71+
72+
73+
def imwrite(filename: str | bytes | Path, img: np.ndarray, params: Iterable[int] | None = None) -> bool:
74+
"""Save *img* to *filename*.
75+
76+
``params`` are accepted for API compatibility but ignored.
77+
"""
78+
79+
path = _ensure_path(filename)
80+
array = np.asarray(img)
81+
if array.ndim == 2:
82+
pil_image = Image.fromarray(array.astype(np.uint8), mode="L")
83+
elif array.ndim == 3 and array.shape[2] == 3:
84+
pil_image = Image.fromarray(array.astype(np.uint8)[..., ::-1], mode="RGB")
85+
else:
86+
raise ValueError("Unsupported image shape for imwrite")
87+
88+
try:
89+
pil_image.save(path)
90+
except OSError:
91+
return False
92+
return True
93+
94+
95+
def cvtColor(img: np.ndarray, code: int) -> np.ndarray:
96+
array = np.asarray(img)
97+
if code == COLOR_BGR2GRAY:
98+
if array.ndim != 3 or array.shape[2] != 3:
99+
raise ValueError("cvtColor expects a BGR image")
100+
b, g, r = array[..., 0], array[..., 1], array[..., 2]
101+
gray = 0.114 * b + 0.587 * g + 0.299 * r
102+
return gray.astype(array.dtype, copy=False)
103+
if code == COLOR_GRAY2RGB:
104+
if array.ndim != 2:
105+
raise ValueError("cvtColor expects a grayscale image")
106+
stacked = np.stack([array, array, array], axis=2)
107+
return stacked.astype(array.dtype, copy=False)
108+
raise NotImplementedError(f"Unsupported colour conversion code: {code}")
109+
110+
111+
def imshow(winname: str, mat: np.ndarray) -> None: # noqa: D401 - behaviour intentionally minimal
112+
"""Display an image.
113+
114+
The headless execution environment cannot create GUI windows, therefore the
115+
function is intentionally a no-op.
116+
"""
117+
118+
_ = winname, mat # Satisfy the type checker; the display is intentionally skipped.
119+
120+
121+
def waitKey(delay: int | None = None) -> int:
122+
"""Mimic :func:`cv2.waitKey` by returning ``-1`` immediately."""
123+
124+
_ = delay
125+
return -1
126+
127+
128+
def destroyAllWindows() -> None: # pragma: no cover - trivial behaviour
129+
"""Placeholder for the OpenCV window cleanup function."""
130+
131+
132+
def flip(src: np.ndarray, flip_code: int) -> np.ndarray:
133+
array = np.asarray(src)
134+
if flip_code == 0: # vertical
135+
return np.flipud(array)
136+
if flip_code == 1: # horizontal
137+
return np.fliplr(array)
138+
if flip_code == -1: # both axes
139+
return np.flipud(np.fliplr(array))
140+
raise ValueError("flip_code must be one of -1, 0 or 1")
141+
142+
143+
def resize(src: np.ndarray, dsize: Tuple[int, int]) -> np.ndarray:
144+
array = np.asarray(src)
145+
width, height = dsize
146+
if array.ndim == 2:
147+
pil_image = Image.fromarray(array.astype(np.uint8), mode="L")
148+
resized = pil_image.resize((width, height), Image.BILINEAR)
149+
return np.array(resized, dtype=array.dtype)
150+
if array.ndim == 3 and array.shape[2] == 3:
151+
pil_image = Image.fromarray(array.astype(np.uint8)[..., ::-1], mode="RGB")
152+
resized = pil_image.resize((width, height), Image.BILINEAR)
153+
return np.array(resized, dtype=array.dtype)[..., ::-1]
154+
raise ValueError("Unsupported image shape for resize")
155+
156+
157+
def _convolve2d(image: np.ndarray, kernel: np.ndarray) -> np.ndarray:
158+
image = np.asarray(image, dtype=np.float64)
159+
kernel = np.asarray(kernel, dtype=np.float64)
160+
kh, kw = kernel.shape
161+
pad_y, pad_x = kh // 2, kw // 2
162+
padded = np.pad(image, ((pad_y, pad_y), (pad_x, pad_x)), mode="edge")
163+
flipped_kernel = np.flipud(np.fliplr(kernel))
164+
output = np.zeros_like(image, dtype=np.float64)
165+
for y in range(image.shape[0]):
166+
for x in range(image.shape[1]):
167+
window = padded[y : y + kh, x : x + kw]
168+
output[y, x] = np.sum(window * flipped_kernel)
169+
return output
170+
171+
172+
def filter2D(
173+
src: np.ndarray,
174+
ddepth: int,
175+
kernel: np.ndarray,
176+
dst: np.ndarray | None = None,
177+
anchor: Tuple[int, int] | None = None,
178+
delta: float = 0.0,
179+
borderType: int | None = None,
180+
) -> np.ndarray:
181+
_ = ddepth, anchor, borderType # Parameters kept for API compatibility.
182+
array = np.asarray(src)
183+
kern = np.asarray(kernel)
184+
if array.ndim == 2:
185+
result = _convolve2d(array, kern) + delta
186+
return result.astype(array.dtype, copy=False)
187+
if array.ndim == 3 and array.shape[2] == 3:
188+
channels = [(_convolve2d(array[..., i], kern) + delta) for i in range(3)]
189+
stacked = np.stack(channels, axis=2)
190+
return stacked.astype(array.dtype, copy=False)
191+
raise ValueError("Unsupported image shape for filter2D")
192+
193+
194+
def getAffineTransform(src: np.ndarray, dst: np.ndarray) -> np.ndarray:
195+
src = np.asarray(src, dtype=np.float64)
196+
dst = np.asarray(dst, dtype=np.float64)
197+
if src.shape != (3, 2) or dst.shape != (3, 2):
198+
raise ValueError("getAffineTransform expects two arrays of shape (3, 2)")
199+
a_rows = []
200+
b_vals = []
201+
for (x, y), (u, v) in zip(src, dst):
202+
a_rows.append([x, y, 1, 0, 0, 0])
203+
a_rows.append([0, 0, 0, x, y, 1])
204+
b_vals.extend([u, v])
205+
a = np.array(a_rows, dtype=np.float64)
206+
b = np.array(b_vals, dtype=np.float64)
207+
params, *_ = np.linalg.lstsq(a, b, rcond=None)
208+
return params.reshape(2, 3).astype(np.float32)
209+
210+
211+
def warpAffine(
212+
src: np.ndarray,
213+
m: np.ndarray,
214+
dsize: Tuple[int, int],
215+
flags: int | None = None,
216+
borderMode: int | None = None,
217+
borderValue: int | Tuple[int, int, int] = 0,
218+
) -> np.ndarray:
219+
_ = flags, borderMode
220+
array = np.asarray(src)
221+
width, height = dsize
222+
if array.ndim == 2:
223+
channels = 1
224+
elif array.ndim == 3 and array.shape[2] == 3:
225+
channels = 3
226+
else:
227+
raise ValueError("Unsupported image shape for warpAffine")
228+
229+
out_shape = (height, width) if channels == 1 else (height, width, channels)
230+
output = np.zeros(out_shape, dtype=array.dtype)
231+
232+
# Prepare homogeneous transformation and its inverse for backwards mapping.
233+
matrix = np.asarray(m, dtype=np.float64)
234+
homography = np.vstack([matrix, [0, 0, 1]])
235+
inv_h = np.linalg.pinv(homography)
236+
237+
border = np.array(borderValue, dtype=array.dtype)
238+
239+
for y in range(height):
240+
for x in range(width):
241+
source = inv_h @ np.array([x, y, 1.0])
242+
sx, sy = source[0], source[1]
243+
sx_int, sy_int = int(round(sx)), int(round(sy))
244+
if 0 <= sy_int < array.shape[0] and 0 <= sx_int < array.shape[1]:
245+
output[y, x] = array[sy_int, sx_int]
246+
else:
247+
output[y, x] = border if channels == 1 else border[:3]
248+
return output
249+
250+
251+
__all__ = [
252+
"COLOR_BGR2GRAY",
253+
"COLOR_GRAY2RGB",
254+
"IMREAD_COLOR",
255+
"IMREAD_GRAYSCALE",
256+
"IMWRITE_JPEG_QUALITY",
257+
"BORDER_DEFAULT",
258+
"CV_8UC3",
259+
"CV_64F",
260+
"Mat",
261+
"imread",
262+
"imwrite",
263+
"cvtColor",
264+
"imshow",
265+
"waitKey",
266+
"destroyAllWindows",
267+
"flip",
268+
"resize",
269+
"filter2D",
270+
"getAffineTransform",
271+
"warpAffine",
272+
]

digital_image_processing/wavelet_denoising.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,22 @@
33
from __future__ import annotations
44

55
import math
6+
from typing import Any
67

78
import matplotlib.pyplot as plt
89
import numpy as np
9-
import pywt
10-
from skimage import data
11-
from skimage.metrics import peak_signal_noise_ratio
10+
11+
try:
12+
import pywt # type: ignore[import-not-found]
13+
except ImportError: # pragma: no cover - optional dependency
14+
pywt = None # type: ignore[assignment]
15+
16+
try:
17+
from skimage import data # type: ignore[import-not-found]
18+
from skimage.metrics import peak_signal_noise_ratio # type: ignore[import-not-found]
19+
except ImportError: # pragma: no cover - optional dependency
20+
data = None # type: ignore[assignment]
21+
peak_signal_noise_ratio = None # type: ignore[assignment]
1222

1323

1424
def im2double(image: np.ndarray) -> np.ndarray:
@@ -57,6 +67,12 @@ def denoise_image_wavelet(
5767
denoised images respectively.
5868
"""
5969

70+
if pywt is None:
71+
msg = "pywt is required for wavelet denoising; install PyWavelets to use this function."
72+
raise ModuleNotFoundError(msg)
73+
74+
wavelet_module: Any = pywt
75+
6076
if rng is None:
6177
rng = np.random.default_rng()
6278

@@ -65,7 +81,7 @@ def denoise_image_wavelet(
6581
noisy_img = original_img + noise_level * rng.standard_normal(original_img.shape)
6682
noisy_img = normalize_img(noisy_img)
6783

68-
coeffs = pywt.wavedec2(noisy_img, wavelet_name, level=decomposition_level)
84+
coeffs = wavelet_module.wavedec2(noisy_img, wavelet_name, level=decomposition_level)
6985
coeffs_approx = coeffs[0]
7086
coeffs_details = coeffs[1:]
7187

@@ -83,12 +99,15 @@ def denoise_image_wavelet(
8399
threshold = sigma * math.sqrt(2.0 * math.log(original_img.size))
84100

85101
denoised_details = [
86-
tuple(pywt.threshold(detail_array, threshold, mode="soft") for detail_array in level_details)
102+
tuple(
103+
wavelet_module.threshold(detail_array, threshold, mode="soft")
104+
for detail_array in level_details
105+
)
87106
for level_details in coeffs_details
88107
]
89108
coeffs_denoised = [coeffs_approx, *denoised_details]
90109

91-
denoised_img = pywt.waverec2(coeffs_denoised, wavelet_name)
110+
denoised_img = wavelet_module.waverec2(coeffs_denoised, wavelet_name)
92111
denoised_img = denoised_img[: original_img.shape[0], : original_img.shape[1]]
93112
denoised_img = normalize_img(denoised_img)
94113

@@ -98,6 +117,13 @@ def denoise_image_wavelet(
98117
def main() -> None:
99118
"""Run the wavelet denoising example and display the results."""
100119

120+
if pywt is None:
121+
msg = "pywt is required for the wavelet denoising demo. Install PyWavelets to run it."
122+
raise SystemExit(msg)
123+
if data is None or peak_signal_noise_ratio is None:
124+
msg = "scikit-image is required for the wavelet denoising demo. Install scikit-image to run it."
125+
raise SystemExit(msg)
126+
101127
original_img = im2double(data.camera())
102128
noisy_img, denoised_img = denoise_image_wavelet(original_img)
103129

docs/conf.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from pathlib import Path
2+
13
from sphinx_pyproject import SphinxConfig
24

3-
project = SphinxConfig("../pyproject.toml", globalns=globals()).name
5+
6+
ROOT = Path(__file__).resolve().parents[1]
7+
project = SphinxConfig(ROOT / "pyproject.toml", globalns=globals()).name

0 commit comments

Comments
 (0)