Advertisement
Guest User

Bakkin Auto Straighten GPU 2023-12-28

a guest
Dec 29th, 2023
83
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 21.97 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # Usage: DEBUG=1 python rotate_angles.py *.png
  3. import os
  4. import sys
  5. import multiprocessing
  6. import subprocess
  7.  
  8. import numpy as np
  9. import matplotlib.pyplot as plt
  10. import skimage
  11. import cv2
  12. import cv2.cuda
  13. import cv2.cuda as cuda
  14. from scipy import ndimage, signal
  15. from scipy.optimize import linprog
  16. from skimage import transform
  17. from skimage.exposure import rescale_intensity
  18. from skimage.io import imsave
  19. from skimage.transform import radon, rotate
  20. from skimage.color import rgb2gray, rgba2rgb, gray2rgb
  21. import time
  22.  
  23. MAX_ANGLE = 1.5
  24. ANGLE_STEP = 0.001  # Don't go under .01 if not using GPU
  25. PROMINENCE = 250
  26. MAX_BORDER = 200
  27. IGNORE_BORDER = 150
  28.  
  29. SIDE_MARGIN = int(os.getenv("SIDE_MARGIN", 270))
  30. TOP_MARGIN = int(os.getenv("TOP_MARGIN", 290))
  31.  
  32. DEBUG = os.getenv("DEBUG") is not None
  33. ANALYZE_ONLY = os.getenv("ANALYZE_ONLY") is not None
  34. YON_KOMA = os.getenv("YON_KOMA") is not None
  35. NOCROP = os.getenv("NOCROP") is not None
  36. NOROTATE = os.getenv("NOROTATE") is not None
  37. GIMP_PATH = os.getenv("GIMP_PATH", "C:/Program Files/GIMP 2/bin/gimp-console-2.10.exe")
  38. USE_GPU = True
  39.  
  40.  
  41. def smart_remove_margin(radon_slice, img_height, img_width):
  42.     """ Try to find the page margins, and remove them from calculations
  43.        Margins must be >70% img width apart, >10% img height prominence,
  44.        and at least 90% the max white value on the radon transform slice.
  45.        FOR THIS TO WORK, SCANNER LID MUST BE BLACK!."""
  46.     margin_peaks, _ = signal.find_peaks(
  47.         radon_slice, distance=0.7 * img_width,
  48.         prominence=0.1 * img_height, height=0.9 * max(radon_slice))
  49.  
  50.     if margin_peaks.size == 0:
  51.         return radon_slice
  52.  
  53.     elif margin_peaks.size == 1:
  54.         if margin_peaks[0] < (radon_slice.size / 2):
  55.             return radon_slice[margin_peaks[0]:]
  56.         else:
  57.             return radon_slice[: margin_peaks[0]]
  58.  
  59.     else:
  60.         return radon_slice[margin_peaks[0]: margin_peaks[-1]]
  61.  
  62.  
  63. def rotate_img_cuda(img: cuda.GpuMat, angle):
  64.     angle = np.deg2rad(angle)
  65.     (w, h) = img.size()
  66.     center = w // 2
  67.     cos_a, sin_a = np.cos(angle), np.sin(angle)
  68.     R = np.array([[cos_a, sin_a, -center * (cos_a + sin_a - 1)],
  69.                   [-sin_a, cos_a, -center * (cos_a - sin_a - 1)],
  70.                   [0, 0, 1]])
  71.  
  72.     return cv2.cuda.warpPerspective(src=img,
  73.                                     dsize=(w, h),
  74.                                     M=R,
  75.                                     flags=cv2.INTER_LINEAR)
  76.  
  77.  
  78. def _find_variances(img, angles):
  79.     """
  80.    Find variance of second derivative of radon transform at specified angles
  81.  
  82.    Parameters
  83.    ----------
  84.    img :
  85.        a single channel float32 numpy array
  86.    angles :
  87.        list of angles to try
  88.  
  89.    Returns
  90.    -------
  91.    variances_gpu :
  92.        Variance of second derivative of radon transform at specified angles.
  93.    """
  94.  
  95.     # This bit is copied from skimage.transform.radon
  96.     # Basically rotate an image then finding its projection onto one dimension (aka summing up the columns)
  97.     diagonal = int(np.ceil(np.sqrt(2) * max(img.shape)))
  98.     pad = [diagonal - s for s in img.shape]
  99.     new_center = [(s + p) // 2 for s, p in zip(img.shape, pad)]
  100.     old_center = [s // 2 for s in img.shape]
  101.     pad_before = [nc - oc for oc, nc in zip(old_center, new_center)]
  102.     pad_width = [(pb, p - pb) for pb, p in zip(pad_before, pad)]
  103.  
  104.     # Upload image to GPU
  105.     img_gpu = cv2.cuda.GpuMat()
  106.     img_gpu.upload(img)
  107.  
  108.     # Pad image so no pixels are lost when rotating
  109.     img_gpu = cv2.cuda.copyMakeBorder(src=img_gpu,
  110.                                       top=pad_width[0][0],
  111.                                       bottom=pad_width[0][1],
  112.                                       left=pad_width[1][0],
  113.                                       right=pad_width[1][1],
  114.                                       borderType=cv2.BORDER_CONSTANT,
  115.                                       value=[0])
  116.  
  117.     # Output variable
  118.     variances_gpu = np.zeros(len(angles), dtype=img.dtype)
  119.  
  120.     # rotate images and reduce
  121.     for i, angle in enumerate(angles):
  122.         img_gpu_rot = rotate_img_cuda(img_gpu, -angle)
  123.  
  124.         # Sum up columns into 1D vector
  125.         img_gpu_rot_sum = cv2.cuda.reduce(img_gpu_rot, dim=0, reduceOp=cv2.REDUCE_SUM)
  126.  
  127.         # Find laplacian sum vector
  128.         laplace_filter = cv2.cuda.createLaplacianFilter(srcType=img_gpu_rot_sum.type(),
  129.                                                         dstType=img_gpu_rot_sum.type())
  130.         img_gpu_rot_sum_laplaced = laplace_filter.apply(img_gpu_rot_sum)
  131.  
  132.         # Find mean and std deviation of laplaced vector
  133.         img_gpu_rot_sum_laplaced_stddev = cv2.cuda.meanStdDev(img_gpu_rot_sum_laplaced)
  134.  
  135.         # Variance = std dev ^ 2
  136.         variances_gpu[i] = img_gpu_rot_sum_laplaced_stddev.download()[0][1] ** 2
  137.  
  138.     return variances_gpu
  139.  
  140.  
  141. def _find_best_angle_in_range(img, angles, filename=None):
  142.     img = skimage.util.invert(img)
  143.     img = img[IGNORE_BORDER:-IGNORE_BORDER, IGNORE_BORDER:-IGNORE_BORDER]
  144.  
  145.     if USE_GPU:
  146.         variances = _find_variances(img, angles)
  147.     else:
  148.         # Take the radon transformation of the image against those angles.
  149.         # Note circle=False: we don't want any image cropping
  150.         radons = radon(img, angles, circle=False, preserve_range=False)
  151.  
  152.         # Take the second derivative of the radon transform for each angle
  153.         laplacians = [ndimage.laplace(radons[:, i])
  154.                       for i in range(angles.size)]
  155.  
  156.         # Turns out chopping off the edges works too. Commenting "smart" way out.
  157.         # h, w = img.shape
  158.         # laplacians = [ndimage.laplace(smart_remove_margin(radons[:, i], h, w))
  159.         #              for i in range(angles.size)]
  160.  
  161.         # Find the variance of the second derivative of each angle. This is
  162.         # a single number showing how "straight" some edges in the image are.
  163.         variances = np.array([np.var(laplacian) for laplacian in laplacians])
  164.  
  165.     # Find the peaks in those variance values among the angles. Ideally
  166.     # there should be one peak; but things are not ideal. There are generally
  167.     # two more peaks: one alinging the page border (which might not be the
  168.     # panel borders, and one at 0 because the image is sharpest not rotated.
  169.     # For tankoubon pages, there is normally only one sharp page border, and
  170.     # this peak can generally be filtered with prominence.
  171.     peaks = np.array([])
  172.     prominence = PROMINENCE
  173.     while peaks.size == 0 and prominence >= 10:
  174.         peaks, _ = signal.find_peaks(variances, distance=3,
  175.                                      prominence=prominence)
  176.         prominence /= 2
  177.  
  178.     # The pixel grid peak, if present along with other peaks, is removed.
  179.     peak_at_zero = [i for i, idx in enumerate(peaks)
  180.                     if idx == int((angles.size - 1) / 2)]
  181.     if peak_at_zero and peaks.size > 1:
  182.         peaks = np.delete(peaks, peak_at_zero[0])
  183.  
  184.     # Debugging plots
  185.     if DEBUG and filename:
  186.         plt.clf()
  187.         plt.plot(angles, variances)
  188.         plt.plot(angles[peaks], variances[peaks], "xm")
  189.         plt.grid(visible=True, which="both")
  190.         [plt.annotate(f"{angle:.3f}\u00b0", (angle, value),
  191.                       textcoords="offset points", xytext=(0, 5), ha="center")
  192.          for angle, value in zip(angles[peaks], variances[peaks])]
  193.         plt.savefig(filename + "-straightness.png")
  194.  
  195.     # The final angle to rotate is then returned.
  196.     if peaks.size > 0:
  197.         best_peak = max(peaks, key=lambda i: variances[i])
  198.         best_angle = angles[best_peak]
  199.         alternatives = angles[[pk for pk in peaks if pk != best_peak]].tolist()
  200.     else:
  201.         best_angle = 0
  202.         alternatives = []
  203.  
  204.     if DEBUG:
  205.         print(f"> {filename}: {angles[peaks]} - "
  206.               f"{variances[peaks].round(decimals=2)}",
  207.               file=sys.stderr)
  208.     return best_angle, alternatives
  209.  
  210.  
  211. def find_best_angle(img, filename=None):
  212.     if NOROTATE:
  213.         return 0, [], 0, 0
  214.  
  215.     # List all the possible rotation angles to be tried
  216.     angles = np.arange(-MAX_ANGLE, MAX_ANGLE + ANGLE_STEP, ANGLE_STEP)
  217.     angles = angles.round(decimals=3)
  218.     rotate_angle, alts = _find_best_angle_in_range(img, angles, filename)
  219.  
  220.     # Do that again, but for angles around 90 degrees
  221.     angles = np.arange(90 - MAX_ANGLE, 90 + MAX_ANGLE + ANGLE_STEP, ANGLE_STEP)
  222.     angles = angles.round(decimals=3)
  223.     skew_angle, _ = _find_best_angle_in_range(img, angles, f"{filename}-skew")
  224.     shear_angle = skew_angle - 90 - rotate_angle
  225.  
  226.     # Find how many pixels to shear transform. GIMP doesn't take an angle.
  227.     img = rotate(img, -rotate_angle, resize=True, order=3)
  228.     shear_pixels = img.shape[1] * np.tan(shear_angle * np.pi / 180)
  229.  
  230.     if DEBUG:
  231.         print(f"> {filename} shear_pixels: {shear_pixels}")
  232.     return rotate_angle, alts, shear_angle, shear_pixels
  233.  
  234.  
  235. def find_constraint_matrices_old(convex_polygon, R=0.7):
  236.     X = convex_polygon[:, 0]
  237.     Y = convex_polygon[:, 1]
  238.     A0 = np.column_stack((np.roll(Y, -1) - Y, X - np.roll(X, -1)))
  239.     B0 = np.sum(np.multiply(A0, convex_polygon), axis=1).reshape(A0.shape[0], 1)
  240.  
  241.     A1 = (A0 * np.matrix([[0, 0, R, R], [0, 1, 0, 1]]))
  242.     A1 = np.reshape(A1, (A1.size, 1))
  243.  
  244.     A0 = np.column_stack((np.repeat(A0, 4, axis=0), A1))
  245.     B0 = np.repeat(B0, 4, axis=0)
  246.     return A0, B0
  247.  
  248.  
  249. def find_constraint_matrices(convex_polygon, R=0.7):
  250.     """
  251.    Gets constraint matrices for a linear programming problem that finds the
  252.    largest rectangle with aspect ratio width:height = R:1 that fits inside a convex
  253.    polygon.
  254.  
  255.    Parameters
  256.    ----------
  257.    convex_polygon :
  258.        a (N x 2) numpy array which contains points describing a convex polygon.
  259.        The Directio must be counter-clockwise
  260.    R :
  261.        Ratio of width to height of rectangle to find
  262.  
  263.    Returns
  264.    -------
  265.    A :
  266.        Constraint matrix A_ub
  267.    b :
  268.        Constraint vector b_ub
  269.    """
  270.     NUM_VARIABLES = 3  # x, y, and h
  271.     CPS = 4  # Constraints Per Segment, one for each point of the rectangle
  272.  
  273.     num_points = len(convex_polygon)  # number of points in convex hull polygon
  274.     X = convex_polygon[:, 0]  # list of x coordinates
  275.     Y = convex_polygon[:, 1]  # list of y coordinates
  276.  
  277.     y_delta = np.roll(Y, -1) - Y  # y_{n+1} - y_{n}
  278.     x_delta = np.roll(X, -1) - X  # x_{n+1} - x_{n}
  279.  
  280.     # Create vector b
  281.     b = y_delta * X - x_delta * Y
  282.     b = np.repeat(b, CPS)  # replicate 3 times each value in b (4 total copies)
  283.     b = b.reshape((-1, 1))  # Convert B into column vector
  284.  
  285.     # Create array A
  286.     A = np.zeros((num_points * CPS, NUM_VARIABLES))
  287.     A[:, 0] = np.repeat(y_delta, CPS)  # The x coefficient is the same for all 4 points
  288.     A[:, 1] = np.repeat(-1 * x_delta, CPS)  # The y coefficient is the same for all 4 points
  289.     A[1::CPS, 2] = -1 * x_delta  # stride index starting at 1, constraints for top left point
  290.     A[2::CPS, 2] = R * y_delta  # constraints for bottom right point
  291.     A[3::CPS, 2] = R * y_delta - x_delta  # constraints for top right point
  292.     return A, b
  293.  
  294.  
  295. def _find_crop_using_page_borders(img, filename=None):
  296.     img_shape_orig = img.shape
  297.     img_area = img.shape[0] * img.shape[1]
  298.     img = skimage.img_as_ubyte(img)
  299.     if DEBUG and filename:
  300.         img_orig = img.copy()
  301.  
  302.     # Roughly level the page
  303.     black_pt, white_pt = np.percentile(img, (20, 50))
  304.     img = rescale_intensity(img, in_range=(black_pt, white_pt))
  305.     if DEBUG:
  306.         print(f"> levels: ({black_pt}, {white_pt})")
  307.  
  308.     # Surface-blur to smooth out noises
  309.     img = cv2.bilateralFilter(img, 9, 150, 150)
  310.  
  311.     # Convert to binary image
  312.     # img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
  313.     #                            cv2.THRESH_BINARY, 115, 4)
  314.     _, img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
  315.     img = cv2.medianBlur(img, 11)
  316.  
  317.     # Extract contours from edges. The largest of contours will be our page
  318.     # THIS DOESN'T WORK FOR MANY PAGES WITH ARTWORK TO THE EDGES
  319.     contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL,
  320.                                    cv2.CHAIN_APPROX_SIMPLE)
  321.  
  322.     # Approximate a polygon (most of the time a quadrilateral) from the contour
  323.     # max epsilon (error distance) is 0.01% of image width (basically w/e)
  324.     polys = [cv2.approxPolyDP(contour, 0.0001 * img.shape[1], closed=True)
  325.              for contour in contours]
  326.     page_poly = max(polys, key=lambda p: cv2.contourArea(cv2.convexHull(p)))
  327.     convex_poly = cv2.convexHull(page_poly)
  328.     poly_area = cv2.contourArea(convex_poly)
  329.  
  330.     if DEBUG and filename:
  331.         img_c = gray2rgb(img_orig)
  332.         img_c = cv2.drawContours(img_c, contours, -1, (255, 0, 255), 1)
  333.         img_c = cv2.drawContours(img_c, [page_poly], -1, (0, 0, 255), 5)
  334.         img_c = cv2.drawContours(img_c, [convex_poly], -1, (255, 0, 0), 6)
  335.         imsave(f"{filename}-crop.jpg", img_c)
  336.  
  337.     if DEBUG:
  338.         print(f"> poly_area %: {100 * poly_area / img_area}%")
  339.  
  340.     # Only accept if the page area found is > 80% of image size
  341.     if poly_area / img_area < 0.8:
  342.         print(f"NO CROP FOUND FOR {filename}, EDIT MANUALLY.")
  343.         return (0, 0), (img_shape_orig[1], img_shape_orig[0]), False
  344.  
  345.     # Run the polygon through a linear optimizer to find the max rectangle
  346.     # with fixed 7:10 ratio that fits inside it
  347.     poly_matrix = convex_poly.reshape(convex_poly.shape[0],
  348.                                       convex_poly.shape[2])
  349.  
  350.     R = 0.7
  351.     A, b = find_constraint_matrices(poly_matrix, R)
  352.  
  353.     bounds = [(0, img.shape[1]), (0, img.shape[0]), (0, img.shape[0])]
  354.     solution = linprog(np.matrix('0; 0; -1'), A, b, bounds=bounds)
  355.     x, y, h = np.rint(solution.x).astype(int)
  356.     w = int(np.rint(h * R))
  357.  
  358.     if DEBUG and filename:
  359.         img_c = cv2.rectangle(img_c, (x, y), (x + w, y + h), (0, 255, 0), 10)
  360.         imsave(f"{filename}-crop.jpg", img_c)
  361.  
  362.     return (x, y), (w, h), True
  363.  
  364.  
  365. def _find_crop_4koma(img, filename=None):
  366.     h_grad = np.gradient(np.sum(img, axis=0))
  367.     v_grad = np.gradient(np.sum(img, axis=1))
  368.     find_peakz = lambda x, height: \
  369.         signal.find_peaks(x, height=height, distance=3)[0]
  370.  
  371.     try:
  372.         panel_left = [x for x in find_peakz(-h_grad, 200) if x > MAX_BORDER][0]
  373.         panel_right = [x for x in find_peakz(h_grad, 200)
  374.                        if x < img.shape[1] - MAX_BORDER][-1]
  375.         panel_top = [x for x in find_peakz(-v_grad, 200) if x > MAX_BORDER][0]
  376.         panel_bottom = [x for x in find_peakz(v_grad, 200)
  377.                         if x < img.shape[0] - MAX_BORDER][-1]
  378.     except:
  379.         print(f"{filename} crop: failed to find panel borders")
  380.         return (0, 0), (img.shape[1], img.shape[0]), False
  381.  
  382.     if DEBUG:
  383.         print(f"> {filename} panels: ({panel_top}, {panel_left}, "
  384.               f"{panel_bottom}, {panel_right})")
  385.  
  386.     # if panel_left > MAX_BORDER or panel_top > MAX_BORDER or\
  387.     #    panel_bottom < img.shape[1] - MAX_BORDER or\
  388.     #    panel_right < img.shape[0] - MAX_BORDER:
  389.     #    print(f"{filename} crop: panel borders don't look right...")
  390.     #    return (0, 0), (img.shape[1], img.shape[0])
  391.  
  392.     crop_left = panel_left - SIDE_MARGIN
  393.     crop_right = panel_right + SIDE_MARGIN
  394.     # v_center = (panel_bottom + panel_top) / 2
  395.     crop_width = crop_right - crop_left
  396.     crop_height = crop_width * 10 / 7
  397.     # crop_top = v_center - (crop_height / 2)
  398.     crop_top = panel_top - TOP_MARGIN
  399.  
  400.     if DEBUG and filename:
  401.         img_c = gray2rgb(skimage.img_as_ubyte(img))
  402.         panels = np.array([[panel_left, panel_top],
  403.                            [panel_left, panel_bottom],
  404.                            [panel_right, panel_bottom],
  405.                            [panel_right, panel_top]])
  406.         img_c = cv2.drawContours(img_c, [panels], -1, (255, 0, 0), 6)
  407.         imsave(f"{filename}-crop.jpg", img_c)
  408.  
  409.     return (crop_left, int(round(crop_top))), \
  410.            (crop_width, int(round(crop_height))), True
  411.  
  412.  
  413. def find_crop(img, rotate_angle, shear_angle, filename=None):
  414.     if abs(rotate_angle) > 0:
  415.         img = rotate(img, -rotate_angle, resize=True, order=3)
  416.         tan_shear_angle = np.tan(-shear_angle * np.pi / 180)
  417.         transform_matrix = np.array(
  418.             [[1, 0, 0],
  419.              [tan_shear_angle, 1, -img.shape[1] * tan_shear_angle / 2],
  420.              [0, 0, 1]])
  421.         img = transform.warp(img, inverse_map=transform_matrix, order=3)
  422.     if NOCROP:
  423.         return (0, 0), (img.shape[1], img.shape[0]), False
  424.  
  425.     if DEBUG and filename:
  426.         img_c = gray2rgb(skimage.img_as_ubyte(img))
  427.         imsave(f"{filename}-crop.jpg", img_c)
  428.  
  429.     if YON_KOMA:
  430.         return _find_crop_4koma(img, filename)
  431.     return _find_crop_using_page_borders(img, filename)
  432.  
  433.  
  434. GIMP_BATCH = """
  435. (let* ((input-file "{infile}")
  436.       (output-file "{outfile}")
  437.       (rotates '({rotates}))
  438.       (shear-pixels {shear})
  439.       (image (car (gimp-file-load RUN-NONINTERACTIVE input-file input-file)))
  440.       (orig-layer (car (gimp-image-get-active-layer image)))
  441.       (rotate-layer (lambda (theta)
  442.            (let ((layer-name (string-append (number->string theta) " deg"))
  443.                  (new-layer (car (gimp-layer-copy orig-layer FALSE)))
  444.                  (theta-rads (* theta 0.0174533)))
  445.                (gimp-item-set-name new-layer layer-name)
  446.                (gimp-image-insert-layer image new-layer 0 -1)
  447.                (gimp-context-set-interpolation INTERPOLATION-NOHALO)
  448.                (gimp-item-transform-rotate new-layer theta-rads TRUE 0 0))))
  449.       (rotated-layers (reverse (map rotate-layer rotates)))
  450.       (top-layer (if (null? rotated-layers) orig-layer (caar rotated-layers)))
  451.       (width (car (gimp-drawable-width top-layer)))
  452.       (height (car (gimp-drawable-height top-layer)))
  453.       (offset-x (car (gimp-drawable-offsets top-layer)))
  454.       (offset-y (cadr (gimp-drawable-offsets top-layer))))
  455.    (if (not (null? rotated-layers))
  456.        (gimp-image-remove-layer image orig-layer)
  457.        '())
  458.    (if (= shear-pixels 0)
  459.        top-layer
  460.        (car (gimp-item-transform-shear
  461.                top-layer ORIENTATION-VERTICAL shear-pixels)))
  462.    (gimp-image-resize image width height (- offset-x) (- offset-y))
  463.    (gimp-image-resize image {width} {height} (- {offset_x}) (- {offset_y}))
  464.    (file-psd-save RUN-NONINTERACTIVE image top-layer
  465.                   output-file output-file 1 0)
  466.    (gimp-image-delete image))
  467. """
  468.  
  469.  
  470. def rotate_and_crop_with_gimp(filename, rotates, shear, crop_offset, crop_size):
  471.     outfile = f"{os.path.splitext(filename)[0]}.psd"
  472.     str_rotates = " ".join([str(a) for a in rotates if abs(a) > 0.0001])
  473.     gimp_batch_cmd = GIMP_BATCH.format(
  474.         infile=filename, outfile=outfile, rotates=str_rotates, shear=shear,
  475.         offset_x=crop_offset[0], offset_y=crop_offset[1],
  476.         width=crop_size[0], height=crop_size[1])
  477.     gimp_cmd = [GIMP_PATH, "-i", "-d", "-f", "-n",
  478.                 "-b", gimp_batch_cmd, "-b", "(gimp-quit 0)"]
  479.     subprocess.run(gimp_cmd)
  480.  
  481.  
  482. def process_file(filename):
  483.     start = time.time()
  484.     # Read image file and convert to grayscale
  485.     img = plt.imread(filename)
  486.     if img.ndim == 3 and img.shape[2] == 4:
  487.         img = rgba2rgb(img)
  488.     if img.ndim == 3 and img.shape[2] == 3:
  489.         img = rgb2gray(img)
  490.  
  491.     rotate_angle, alts, shear_angle, shear_pixels = \
  492.         find_best_angle(img, filename)
  493.  
  494.     crop_offset, crop_size, crop_found = find_crop(img, rotate_angle, shear_angle, filename)
  495.  
  496.     print(f"{filename}: rotate {rotate_angle:.3f}\u00b0 clockwise." +
  497.           (f" Alternatives: {alts}." if alts else "") +
  498.           f" Shear: {shear_angle:.3f}\u00b0" +
  499.           f" Crop: {crop_offset}, {crop_size}.")
  500.  
  501.     if not ANALYZE_ONLY:
  502.         rotate_and_crop_with_gimp(
  503.             filename, alts + [rotate_angle], shear_pixels,
  504.             crop_offset, crop_size)
  505.  
  506.     runtime = time.time() - start
  507.     print(f"Runtime for {filename}: {runtime}s")
  508.     return rotate_angle, alts, shear_angle, crop_offset, crop_size, crop_found
  509.  
  510.  
  511. if __name__ == "__main__":
  512.     files = sys.argv[1:]
  513.  
  514.     # IMO it's easier to just edit this list instead of passing arguments.
  515.     files = ["work/3.png",
  516.              "work/4.png",
  517.              "work/5.png",
  518.              "work/6.png",
  519.              "work/7.png",
  520.              "work/8.png",
  521.              "work/9.png",
  522.              "work/10.png",
  523.              "work/11.png",
  524.              "work/12.png",
  525.              "work/13.png",
  526.              "work/14.png",
  527.              "work/15.png",
  528.              "work/16.png",
  529.              "work/17.png",
  530.              "work/18.png"]
  531.  
  532.     print(f"> files: {files}, ANALYZE_ONLY={ANALYZE_ONLY}, "
  533.           f"YON_KOMA={YON_KOMA}, NOROTATE={NOROTATE}, "
  534.           f"SIDE_MARGIN={SIDE_MARGIN}, TOP_MARGIN={TOP_MARGIN}, "
  535.           f"NOCROP={NOCROP}, GIMP_PATH={GIMP_PATH}")
  536.  
  537.     prog_start = time.time()
  538.  
  539.     if USE_GPU:
  540.         # I think GPU is getting thrashed when using multicore so just do it sequentially.
  541.         results = []
  542.         for file in files:
  543.             results.append(process_file(file))
  544.         results = list(zip(files, results))
  545.     else:
  546.         with multiprocessing.Pool(multiprocessing.cpu_count()) as pool:
  547.             results = list(zip(files, pool.map(process_file, files)))
  548.  
  549.     total_runtime = time.time() - prog_start
  550.     print(f"{total_runtime=}s")
  551.  
  552.     [print(f"{filename}: rotate {rotate:.3f}\u00b0 clockwise." +
  553.            (f" Alternatives: {alts}." if alts else "") +
  554.            f" Shear: {shear:.3f}\u00b0" +
  555.            f" Crop: {crop_offset}, {crop_size}." +
  556.            f" Crop Found: {crop_found}.")
  557.      for filename, (rotate, alts, shear, crop_offset, crop_size, crop_found)
  558.      in results]
  559.  
  560.     print(f"Manual crop required:")
  561.     for result in results:
  562.         if result[1][5] == False:
  563.             print(f"{result[0]}")
  564.  
  565.     print("done")
  566.  
  567.     # if not ANALYZE_ONLY:
  568.     #    args = [(filename, alts + [rotate], shear, crop_offset, crop_size)
  569.     #            for filename, (rotate, alts, shear, crop_offset, crop_size)
  570.     #            in results]
  571.     #    pool.starmap(rotate_and_crop_with_gimp, args)
  572.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement