Skip to content

Commit

Permalink
Apply pre-commit fix
Browse files Browse the repository at this point in the history
From the artifact of the previous workflow run
  • Loading branch information
geo-ghci-int[bot] committed Jan 7, 2025
1 parent ecd3177 commit 12a3940
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 39 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2019-2024, Stéphane Brunner
Copyright (c) 2019-2025, Stéphane Brunner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
94 changes: 64 additions & 30 deletions deskew/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
if TYPE_CHECKING:
from typing import TypeAlias

ImageType: TypeAlias = npt.NDArray[np.integer[Any] | np.floating[Any]]
ImageTypeUint64: TypeAlias = npt.NDArray[np.uint8]
ImageTypeFloat64: TypeAlias = npt.NDArray[np.float64]
type ImageType = npt.NDArray[np.integer[Any] | np.floating[Any]]
type ImageTypeUint64 = npt.NDArray[np.uint8]
type ImageTypeFloat64 = npt.NDArray[np.float64]
else:
ImageType = np.ndarray
ImageTypeUint64 = np.ndarray
Expand All @@ -27,25 +27,28 @@ def determine_skew_dev(
image: ImageType,
sigma: float = 3.0,
num_peaks: int = 20,
min_angle: Optional[float] = None, # -np.pi / 2,
max_angle: Optional[float] = None, # np.pi / 2,
min_angle: float | None = None, # -np.pi / 2,
max_angle: float | None = None, # np.pi / 2,
min_deviation: float = np.pi / 180,
angle_pm_90: bool = False,
) -> tuple[
Optional[np.float64],
np.float64 | None,
tuple[
tuple[ImageTypeUint64, list[list[np.float64]], ImageTypeFloat64],
tuple[list[Any], list[np.float64], list[np.float64]],
tuple[dict[np.float64, int], dict[np.float64, int]],
],
]:
"""Calculate skew angle."""

num_angles = round(np.pi / min_deviation)
imagergb = rgba2rgb(image) if len(image.shape) == 3 and image.shape[2] == 4 else image
imagergb = (
rgba2rgb(image) if len(image.shape) == 3 and image.shape[2] == 4 else image
)
img = rgb2gray(imagergb) if len(imagergb.shape) == 3 else imagergb
edges = canny(img, sigma=sigma)
out, angles, distances = hough_line(edges, np.linspace(-np.pi / 2, np.pi / 2, num_angles, endpoint=False))
out, angles, distances = hough_line(
edges, np.linspace(-np.pi / 2, np.pi / 2, num_angles, endpoint=False)
)
hough_line_out = (out, angles, distances)

hspace, angles_peaks, dists = hough_line_peaks(
Expand All @@ -62,7 +65,9 @@ def determine_skew_dev(
freqs_original[peak] += 1

angles_peaks_corrected = [
(a % np.pi - np.pi / 2) if angle_pm_90 else ((a + np.pi / 4) % (np.pi / 2) - np.pi / 4)
(a % np.pi - np.pi / 2)
if angle_pm_90
else ((a + np.pi / 4) % (np.pi / 2) - np.pi / 4)
for a in angles_peaks
]
angles_peaks_filtred = (
Expand All @@ -71,7 +76,9 @@ def determine_skew_dev(
else angles_peaks_corrected
)
angles_peaks_filtred = (
[a for a in angles_peaks_filtred if a <= max_angle] if max_angle is not None else angles_peaks_filtred
[a for a in angles_peaks_filtred if a <= max_angle]
if max_angle is not None
else angles_peaks_filtred
)
if not angles_peaks_filtred:
return None, (hough_line_out, hough_line_peaks_out, ({}, {}))
Expand Down Expand Up @@ -106,10 +113,10 @@ def determine_skew_debug_images(
sigma: float = 3.0,
num_peaks: int = 20,
angle_pm_90: bool = False,
min_angle: Optional[float] = None,
max_angle: Optional[float] = None,
min_angle: float | None = None,
max_angle: float | None = None,
min_deviation: float = 1.0,
) -> tuple[Optional[np.float64], list[tuple[str, ImageType]]]:
) -> tuple[np.float64 | None, list[tuple[str, ImageType]]]:
"""Calculate skew angle, and return images useful for debugging."""
import cv2 # pylint: disable=import-outside-toplevel
import matplotlib.pyplot as plt # pylint: disable=import-outside-toplevel
Expand Down Expand Up @@ -144,8 +151,12 @@ def determine_skew_debug_images(
if min_angle_norm < max_angle_norm:
min_angle_norm += np.pi / (1 if angle_pm_90 else 2)
for add in [0.0] if angle_pm_90 else [0.0, np.pi / 2]:
min_angle_limit: float = (min_angle_norm + add + np.pi / 2) % np.pi - np.pi / 2
max_angle_limit: float = (max_angle_norm + add + np.pi / 2) % np.pi - np.pi / 2
min_angle_limit: float = (
min_angle_norm + add + np.pi / 2
) % np.pi - np.pi / 2
max_angle_limit: float = (
max_angle_norm + add + np.pi / 2
) % np.pi - np.pi / 2
min_angle_limit2: float = min_angle_limit % np.pi - np.pi / 2
max_angle_limit2: float = max_angle_limit % np.pi - np.pi / 2
if min_angle_limit < max_angle_limit:
Expand Down Expand Up @@ -208,7 +219,9 @@ def determine_skew_debug_images(
with tempfile.NamedTemporaryFile(suffix=".png") as file:
plt.savefig(file.name)
try:
subprocess.run(["gm", "convert", "-flatten", file.name, file.name], check=True) # nosec
subprocess.run(
["gm", "convert", "-flatten", file.name, file.name], check=True
) # nosec
except FileNotFoundError:
print("Install graphicsmagick to don't have transparent background")

Expand All @@ -222,7 +235,7 @@ def determine_skew_debug_images(
axe.set_axis_off()
axe.set_title("Detected lines")

for _, line_angle, dist in zip(*hough_line_peaks_data):
for _, line_angle, dist in zip(*hough_line_peaks_data, strict=False):
(coord0x, coord0y) = dist * np.array([np.cos(line_angle), np.sin(line_angle)])
angle2 = (
(line_angle % np.pi - np.pi / 2)
Expand All @@ -232,10 +245,15 @@ def determine_skew_debug_images(
diff = float(abs(angle2 - skew_angle)) if skew_angle is not None else 999.0
if diff < 0.001:
axe.axline(
(coord0x, coord0y), slope=np.tan(line_angle + np.pi / 2), linewidth=1, color="lightgreen"
(coord0x, coord0y),
slope=np.tan(line_angle + np.pi / 2),
linewidth=1,
color="lightgreen",
)
else:
axe.axline((coord0x, coord0y), slope=np.tan(line_angle + np.pi / 2), linewidth=1)
axe.axline(
(coord0x, coord0y), slope=np.tan(line_angle + np.pi / 2), linewidth=1
)
axe.text(
coord0x,
coord0y,
Expand All @@ -249,7 +267,9 @@ def determine_skew_debug_images(
with tempfile.NamedTemporaryFile(suffix=".png") as file:
plt.savefig(file.name)
try:
subprocess.run(["gm", "convert", "-flatten", file.name, file.name], check=True) # nosec
subprocess.run(
["gm", "convert", "-flatten", file.name, file.name], check=True
) # nosec
except FileNotFoundError:
print("Install graphicsmagick to don't have transparent background")
image = cv2.imread(file.name)
Expand Down Expand Up @@ -281,19 +301,31 @@ def fill_polar(
axe.axvline(angle, color="lightgreen")

for limit_min, limit_max in limits:
if limit_min != -np.pi / 2 and (not half or -np.pi / 4 < limit_min < np.pi / 4):
if limit_min != -np.pi / 2 and (
not half or -np.pi / 4 < limit_min < np.pi / 4
):
axe.axvline(limit_min)
if limit_max != np.pi / 2 and (not half or -np.pi / 4 < limit_max < np.pi / 4):
if limit_max != np.pi / 2 and (
not half or -np.pi / 4 < limit_max < np.pi / 4
):
axe.axvline(limit_max)

fill_polar(axe0, freqs0, skew_angles0, limits2)
fill_polar(axe1, freqs, [] if skew_angle is None else [float(skew_angle)], limits, not angle_pm_90)
fill_polar(
axe1,
freqs,
[] if skew_angle is None else [float(skew_angle)],
limits,
not angle_pm_90,
)

plt.tight_layout()
with tempfile.NamedTemporaryFile(suffix=".png") as file:
plt.savefig(file.name)
try:
subprocess.run(["gm", "convert", "-flatten", file.name, file.name], check=True) # nosec
subprocess.run(
["gm", "convert", "-flatten", file.name, file.name], check=True
) # nosec
except FileNotFoundError:
print("Install graphicsmagick to don't have transparent background")
image = cv2.imread(file.name)
Expand All @@ -306,12 +338,12 @@ def determine_skew(
image: ImageType,
sigma: float = 3.0,
num_peaks: int = 20,
num_angles: Optional[int] = None,
num_angles: int | None = None,
angle_pm_90: bool = False,
min_angle: Optional[float] = None,
max_angle: Optional[float] = None,
min_angle: float | None = None,
max_angle: float | None = None,
min_deviation: float = 1.0,
) -> Optional[np.float64]:
) -> np.float64 | None:
"""
Calculate skew angle.
Expand Down Expand Up @@ -341,7 +373,9 @@ def determine_skew(
"""
if num_angles is not None:
min_deviation = 180 / num_angles
warnings.warn("num_angles is deprecated, please use min_deviation", DeprecationWarning)
warnings.warn(
"num_angles is deprecated, please use min_deviation", DeprecationWarning
)

angle, _ = determine_skew_dev(
image,
Expand Down
14 changes: 11 additions & 3 deletions deskew/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ def main() -> None:

parser.add_argument("-o", "--output", default=None, help="Output file name")
parser.add_argument("--sigma", default=3.0, type=float, help="The use sigma")
parser.add_argument("--num-peaks", default=20, type=int, help="The used number of peaks")
parser.add_argument(
"--num-angles", default=180, type=int, help="The used number of angle (determine the precision)"
"--num-peaks", default=20, type=int, help="The used number of peaks"
)
parser.add_argument(
"--num-angles",
default=180,
type=int,
help="The used number of angle (determine the precision)",
)
parser.add_argument("--background", help="The used background color")
parser.add_argument(default=None, dest="input", help="Input file name")
Expand All @@ -26,7 +31,10 @@ def main() -> None:
image = io.imread(options.input)
grayscale = image if len(image.shape) == 2 else rgb2gray(image)
angle = determine_skew(
grayscale, sigma=options.sigma, num_peaks=options.num_peaks, num_angles=options.num_angles
grayscale,
sigma=options.sigma,
num_peaks=options.num_peaks,
num_angles=options.num_angles,
)
if options.output is None:
print(f"Estimated angle: {angle}")
Expand Down
22 changes: 17 additions & 5 deletions tests/test_deskew.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,19 @@ def check_image(root_folder, image, name, level=1.0):
if score < level:
cv2.imwrite(result_name, image)
cv2.imwrite(diff_name, diff)
assert score >= level, f"{result_name} != {expected_name} => {diff_name} ({score} < {level})"
assert (
score >= level
), f"{result_name} != {expected_name} => {diff_name} ({score} < {level})"


def image_diff(image1: NpNdarrayInt, image2: NpNdarrayInt) -> tuple[float, NpNdarrayInt]:
def image_diff(
image1: NpNdarrayInt, image2: NpNdarrayInt
) -> tuple[float, NpNdarrayInt]:
"""Do a diff between images."""

score, diff = structural_similarity(image1, image2, multichannel=True, full=True, channel_axis=2)
score, diff = structural_similarity(
image1, image2, multichannel=True, full=True, channel_axis=2
)
diff = (255 - diff * 255).astype("uint8")
return score, diff

Expand Down Expand Up @@ -88,10 +94,16 @@ def test_deskew(image, expected_angle):
(None, None, True, 200, -3, "-90-many-peaks", 0.93),
],
)
def test_determine_skew_debug_images(min_angle, max_angle, angle_pm_90, num_peaks, expected, postfix, level):
def test_determine_skew_debug_images(
min_angle, max_angle, angle_pm_90, num_peaks, expected, postfix, level
):
image = io.imread(os.path.join(os.path.dirname(__file__), "deskew-6.png"))
angle, debug_images = determine_skew_debug_images(
image, min_angle=min_angle, max_angle=max_angle, angle_pm_90=angle_pm_90, num_peaks=num_peaks
image,
min_angle=min_angle,
max_angle=max_angle,
angle_pm_90=angle_pm_90,
num_peaks=num_peaks,
)
for name, debug_image in debug_images:
print(name)
Expand Down

0 comments on commit 12a3940

Please sign in to comment.