Commit 5e58ce50 authored by Mickaël Desfrênes's avatar Mickaël Desfrênes
Browse files

add skew detection in rpc.methods

parent a307ff57
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -11,3 +11,5 @@ django-ranged-fileresponse
pyvips
huey
pre-commit
numpy
opencv-python-headless
 No newline at end of file
+64 −0
Original line number Diff line number Diff line
@@ -33,6 +33,8 @@ from rpc.const import *
import logging
import pyvips
import tempfile
import cv2
import numpy
from resources.helpers import handle_local_file as _handle_local_file


@@ -49,6 +51,52 @@ class ServiceException(Exception):
        self.message = args[0]


def _deskew(image_path: str, max_skew: int = 10) -> float:
    im = cv2.imread(image_path)
    height, width, _ = im.shape
    im_gs = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    im_gs = cv2.fastNlMeansDenoising(im_gs, h=3)
    im_bw = cv2.threshold(im_gs, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
    lines = cv2.HoughLinesP(
        im_bw, 1, numpy.pi / 180, 200, minLineLength=width / 12, maxLineGap=width / 150
    )
    # Collect the angles of these lines (in radians)
    angles = []
    for line in lines:
        x1, y1, x2, y2 = line[0]
        angles.append(numpy.arctan2(y2 - y1, x2 - x1))

    # If the majority of our lines are vertical, this is probably a landscape image
    landscape = (
        numpy.sum([abs(angle) > numpy.pi / 4 for angle in angles]) > len(angles) / 2
    )

    # Filter the angles to remove outliers based on max_skew
    if landscape:
        angles = [
            angle
            for angle in angles
            if numpy.deg2rad(90 - max_skew) < abs(angle) < numpy.deg2rad(90 + max_skew)
        ]
    else:
        angles = [angle for angle in angles if abs(angle) < numpy.deg2rad(max_skew)]

    if len(angles) < 5:
        # Insufficient data to deskew
        return 0

    # Average the angles to a degree offset
    angle_deg = numpy.rad2deg(numpy.median(angles))

    # If this is landscape image, rotate the entire canvas appropriately
    if landscape:
        if angle_deg < 0:
            angle_deg += 90
        elif angle_deg > 0:
            angle_deg -= 90
    return angle_deg * -1


def _validate_limits(limit_from: int, limit_to: int) -> Tuple[int, int]:
    try:
        limit_from = int(limit_from)
@@ -3060,3 +3108,19 @@ def remove_meta_value_from_selection(
            user, collection_instance.pk, meta_value_id, True
        )
    return True


@_log_call
@_rpc_groups(["Resources"])
def auto_find_rotate_angle(user: User, resource_id: int) -> float:
    """
    Tries to determine skew angle of image with text.
    """
    resource_instance = Resource.objects.filter(pk=resource_id).first()
    if not resource_instance:
        raise ServiceException(NO_SUCH_RESOURCE)
    if not resource_instance.file:
        raise ServiceException(NOT_A_FILE)
    if not resource_instance.file.should_have_iiif():
        raise ServiceException(NOT_AN_IMAGE)
    return _deskew(resource_instance.file.local_path())