Compare commits
No commits in common. "81d79a1602cfc8627eb5969fc168324c439e865f" and "b93ef905aede17477a870f59b3f76b9e81f513f2" have entirely different histories.
81d79a1602
...
b93ef905ae
|
@ -1,2 +1 @@
|
|||
env
|
||||
__pycache__
|
|
@ -8,27 +8,9 @@ Two scripts:
|
|||
|
||||
## Analysis Steps
|
||||
|
||||
1. Locate positions of 6 hole punches - normalize.py
|
||||
2. Orient to square position - normalize.py
|
||||
1. Locate positions of 6 hole punches
|
||||
2. Orient to square position
|
||||
3. Find all 4 fiducials
|
||||
4. Calculate their position relative to the hole punches
|
||||
5. Create a template
|
||||
6. Use the template-filling script to recreate calibration page as a proof to be checked on a lightbox
|
||||
|
||||
## Page Hole Punch Order
|
||||
|
||||
```
|
||||
______________
|
||||
| |
|
||||
| 1 3 |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| 3 4 |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| 5 6 |
|
||||
| |
|
||||
---------------
|
||||
```
|
||||
6. Use the template-filling script to recreate calibration page as a proof to be checked on a lightbox
|
|
@ -1,62 +0,0 @@
|
|||
import sys
|
||||
import cv2
|
||||
import numpy as np
|
||||
import math
|
||||
from os.path import exists, basename
|
||||
from common import image_resize, display, normalize_angle
|
||||
|
||||
#clockwise from top left
|
||||
order = [ 1, 3, 4, 6, 5, 2 ]
|
||||
|
||||
def read_text (textPath) :
|
||||
holePunches = {}
|
||||
with open(textPath) as t:
|
||||
for line in t:
|
||||
i = int(line[0])
|
||||
parts = line.split(' : ')
|
||||
vals = parts[1].split(',')
|
||||
holePunches[i] = {
|
||||
'x' : int(vals[0]),
|
||||
'y' : int(vals[1])
|
||||
}
|
||||
return holePunches
|
||||
|
||||
#
|
||||
# CALIBRATE
|
||||
#
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print('Please provide path of normalized scan to calibrate to')
|
||||
exit(1)
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print('Please provide path to output svg template')
|
||||
exit(2)
|
||||
|
||||
normalImage = sys.argv[-2]
|
||||
|
||||
if not exists(normalImage) :
|
||||
print('Normalized scan does not exist, please provide one that does')
|
||||
exit(2)
|
||||
|
||||
normalText = normalImage + '.txt'
|
||||
|
||||
if not exists(normalText) :
|
||||
print('Corresponding normalized scan text does not exist, please generate one')
|
||||
exit(3)
|
||||
|
||||
outputTmpl = sys.argv[-1]
|
||||
|
||||
print(f'Calibrating to scan {basename(normalImage)}')
|
||||
|
||||
holePunches = read_text(normalText)
|
||||
original = cv2.imread(normalImage)
|
||||
img = original.copy()
|
||||
height, width = img.shape[:2]
|
||||
orientation = height > width
|
||||
|
||||
if not orientation :
|
||||
print(f'Scan is not in portrait mode, exiting...')
|
||||
exit(3)
|
||||
|
||||
display(img)
|
|
@ -1,122 +0,0 @@
|
|||
import cv2
|
||||
import math
|
||||
|
||||
def image_resize(image, width = None, height = None, inter = cv2.INTER_AREA):
|
||||
dim = None
|
||||
(h, w) = image.shape[:2]
|
||||
|
||||
if width is None and height is None:
|
||||
return image
|
||||
|
||||
if width is None:
|
||||
r = height / float(h)
|
||||
dim = (int(w * r), height)
|
||||
else:
|
||||
r = width / float(w)
|
||||
dim = (width, int(h * r))
|
||||
resized = cv2.resize(image, dim, interpolation = inter)
|
||||
|
||||
return resized
|
||||
|
||||
def display (image) :
|
||||
resized = image_resize(image, 800, 800)
|
||||
cv2.imshow('img', resized)
|
||||
|
||||
while cv2.getWindowProperty('img', cv2.WND_PROP_VISIBLE) > 0:
|
||||
key = cv2.waitKey(0)
|
||||
if key == 27:
|
||||
cv2.destroyAllWindows()
|
||||
break
|
||||
exit(0)
|
||||
|
||||
# taken from
|
||||
# https://gist.github.com/phn/1111712/35e8883de01916f64f7f97da9434622000ac0390
|
||||
def normalize_angle (num, lower=0.0, upper=360.0, b=False):
|
||||
"""Normalize number to range [lower, upper) or [lower, upper].
|
||||
Parameters
|
||||
----------
|
||||
num : float
|
||||
The number to be normalized.
|
||||
lower : float
|
||||
Lower limit of range. Default is 0.0.
|
||||
upper : float
|
||||
Upper limit of range. Default is 360.0.
|
||||
b : bool
|
||||
Type of normalization. See notes.
|
||||
Returns
|
||||
-------
|
||||
n : float
|
||||
A number in the range [lower, upper) or [lower, upper].
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If lower >= upper.
|
||||
Notes
|
||||
-----
|
||||
If the keyword `b == False`, the default, then the normalization
|
||||
is done in the following way. Consider the numbers to be arranged
|
||||
in a circle, with the lower and upper marks sitting on top of each
|
||||
other. Moving past one limit, takes the number into the beginning
|
||||
of the other end. For example, if range is [0 - 360), then 361
|
||||
becomes 1. Negative numbers move from higher to lower
|
||||
numbers. So, -1 normalized to [0 - 360) becomes 359.
|
||||
If the keyword `b == True` then the given number is considered to
|
||||
"bounce" between the two limits. So, -91 normalized to [-90, 90],
|
||||
becomes -89, instead of 89. In this case the range is [lower,
|
||||
upper]. This code is based on the function `fmt_delta` of `TPM`.
|
||||
Range must be symmetric about 0 or lower == 0.
|
||||
Examples
|
||||
--------
|
||||
>>> normalize(-270,-180,180)
|
||||
90
|
||||
>>> import math
|
||||
>>> math.degrees(normalize(-2*math.pi,-math.pi,math.pi))
|
||||
0.0
|
||||
>>> normalize(181,-180,180)
|
||||
-179
|
||||
>>> normalize(-180,0,360)
|
||||
180
|
||||
>>> normalize(36,0,24)
|
||||
12
|
||||
>>> normalize(368.5,-180,180)
|
||||
8.5
|
||||
>>> normalize(-100, -90, 90, b=True)
|
||||
-80.0
|
||||
>>> normalize(100, -90, 90, b=True)
|
||||
80.0
|
||||
>>> normalize(181, -90, 90, b=True)
|
||||
-1.0
|
||||
>>> normalize(270, -90, 90, b=True)
|
||||
-90.0
|
||||
"""
|
||||
# abs(num + upper) and abs(num - lower) are needed, instead of
|
||||
# abs(num), since the lower and upper limits need not be 0. We need
|
||||
# to add half size of the range, so that the final result is lower +
|
||||
# <value> or upper - <value>, respectively.
|
||||
res = num
|
||||
if not b:
|
||||
if lower >= upper:
|
||||
raise ValueError("Invalid lower and upper limits: (%s, %s)" %
|
||||
(lower, upper))
|
||||
|
||||
res = num
|
||||
if num > upper or num == lower:
|
||||
num = lower + abs(num + upper) % (abs(lower) + abs(upper))
|
||||
if num < lower or num == upper:
|
||||
num = upper - abs(num - lower) % (abs(lower) + abs(upper))
|
||||
|
||||
res = lower if res == upper else num
|
||||
else:
|
||||
total_length = abs(lower) + abs(upper)
|
||||
if num < -total_length:
|
||||
num += math.ceil(num / (-2 * total_length)) * 2 * total_length
|
||||
if num > total_length:
|
||||
num -= math.floor(num / (2 * total_length)) * 2 * total_length
|
||||
if num > upper:
|
||||
num = total_length - num
|
||||
if num < lower:
|
||||
num = -total_length - num
|
||||
|
||||
res = num * 1.0 # Make all numbers float, to be consistent
|
||||
|
||||
return res
|
|
@ -2,12 +2,34 @@ import sys
|
|||
import cv2
|
||||
import numpy as np
|
||||
import math
|
||||
from json import dumps
|
||||
from os.path import exists
|
||||
from common import image_resize, display, normalize_angle
|
||||
|
||||
#clockwise from top left
|
||||
order = [ 1, 3, 4, 6, 5, 2 ]
|
||||
def image_resize(image, width = None, height = None, inter = cv2.INTER_AREA):
|
||||
dim = None
|
||||
(h, w) = image.shape[:2]
|
||||
|
||||
if width is None and height is None:
|
||||
return image
|
||||
|
||||
if width is None:
|
||||
r = height / float(h)
|
||||
dim = (int(w * r), height)
|
||||
else:
|
||||
r = width / float(w)
|
||||
dim = (width, int(h * r))
|
||||
resized = cv2.resize(image, dim, interpolation = inter)
|
||||
|
||||
return resized
|
||||
|
||||
def display (image) :
|
||||
resized = image_resize(image, 800, 800)
|
||||
cv2.imshow('img', resized)
|
||||
|
||||
while cv2.getWindowProperty('img', cv2.WND_PROP_VISIBLE) > 0:
|
||||
key = cv2.waitKey(0)
|
||||
if key == 27:
|
||||
cv2.destroyAllWindows()
|
||||
break
|
||||
exit(0)
|
||||
|
||||
def get_center (contour) :
|
||||
M = cv2.moments(contour)
|
||||
|
@ -39,6 +61,98 @@ def is_close (point, points) :
|
|||
return True
|
||||
return False
|
||||
|
||||
# taken from
|
||||
# https://gist.github.com/phn/1111712/35e8883de01916f64f7f97da9434622000ac0390
|
||||
def normalize_angle (num, lower=0.0, upper=360.0, b=False):
|
||||
"""Normalize number to range [lower, upper) or [lower, upper].
|
||||
Parameters
|
||||
----------
|
||||
num : float
|
||||
The number to be normalized.
|
||||
lower : float
|
||||
Lower limit of range. Default is 0.0.
|
||||
upper : float
|
||||
Upper limit of range. Default is 360.0.
|
||||
b : bool
|
||||
Type of normalization. See notes.
|
||||
Returns
|
||||
-------
|
||||
n : float
|
||||
A number in the range [lower, upper) or [lower, upper].
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If lower >= upper.
|
||||
Notes
|
||||
-----
|
||||
If the keyword `b == False`, the default, then the normalization
|
||||
is done in the following way. Consider the numbers to be arranged
|
||||
in a circle, with the lower and upper marks sitting on top of each
|
||||
other. Moving past one limit, takes the number into the beginning
|
||||
of the other end. For example, if range is [0 - 360), then 361
|
||||
becomes 1. Negative numbers move from higher to lower
|
||||
numbers. So, -1 normalized to [0 - 360) becomes 359.
|
||||
If the keyword `b == True` then the given number is considered to
|
||||
"bounce" between the two limits. So, -91 normalized to [-90, 90],
|
||||
becomes -89, instead of 89. In this case the range is [lower,
|
||||
upper]. This code is based on the function `fmt_delta` of `TPM`.
|
||||
Range must be symmetric about 0 or lower == 0.
|
||||
Examples
|
||||
--------
|
||||
>>> normalize(-270,-180,180)
|
||||
90
|
||||
>>> import math
|
||||
>>> math.degrees(normalize(-2*math.pi,-math.pi,math.pi))
|
||||
0.0
|
||||
>>> normalize(181,-180,180)
|
||||
-179
|
||||
>>> normalize(-180,0,360)
|
||||
180
|
||||
>>> normalize(36,0,24)
|
||||
12
|
||||
>>> normalize(368.5,-180,180)
|
||||
8.5
|
||||
>>> normalize(-100, -90, 90, b=True)
|
||||
-80.0
|
||||
>>> normalize(100, -90, 90, b=True)
|
||||
80.0
|
||||
>>> normalize(181, -90, 90, b=True)
|
||||
-1.0
|
||||
>>> normalize(270, -90, 90, b=True)
|
||||
-90.0
|
||||
"""
|
||||
# abs(num + upper) and abs(num - lower) are needed, instead of
|
||||
# abs(num), since the lower and upper limits need not be 0. We need
|
||||
# to add half size of the range, so that the final result is lower +
|
||||
# <value> or upper - <value>, respectively.
|
||||
res = num
|
||||
if not b:
|
||||
if lower >= upper:
|
||||
raise ValueError("Invalid lower and upper limits: (%s, %s)" %
|
||||
(lower, upper))
|
||||
|
||||
res = num
|
||||
if num > upper or num == lower:
|
||||
num = lower + abs(num + upper) % (abs(lower) + abs(upper))
|
||||
if num < lower or num == upper:
|
||||
num = upper - abs(num - lower) % (abs(lower) + abs(upper))
|
||||
|
||||
res = lower if res == upper else num
|
||||
else:
|
||||
total_length = abs(lower) + abs(upper)
|
||||
if num < -total_length:
|
||||
num += math.ceil(num / (-2 * total_length)) * 2 * total_length
|
||||
if num > total_length:
|
||||
num -= math.floor(num / (2 * total_length)) * 2 * total_length
|
||||
if num > upper:
|
||||
num = total_length - num
|
||||
if num < lower:
|
||||
num = -total_length - num
|
||||
|
||||
res = num * 1.0 # Make all numbers float, to be consistent
|
||||
|
||||
return res
|
||||
|
||||
def mean (lst):
|
||||
return sum(lst) / len(lst)
|
||||
|
||||
|
@ -135,15 +249,6 @@ def find_hole_punches (img) :
|
|||
|
||||
return holePunches
|
||||
|
||||
def simplify_hole_punches (holePunches) :
|
||||
simple = {}
|
||||
for hp in holePunches :
|
||||
simple[hp['order']] = {
|
||||
'x' : hp['x'],
|
||||
'y' : hp['y']
|
||||
}
|
||||
return simple
|
||||
|
||||
def correct_rotation (img, original, holePunches) :
|
||||
horizLines = [
|
||||
(3, 1),
|
||||
|
@ -290,10 +395,6 @@ def normalize_image(blank, rotated, offset, tl) :
|
|||
|
||||
return blank
|
||||
|
||||
#
|
||||
# NORMALIZE
|
||||
#
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print('Please provide path of scan to normalize')
|
||||
exit(1)
|
||||
|
@ -303,11 +404,6 @@ if len(sys.argv) < 3:
|
|||
exit(2)
|
||||
|
||||
scanImage = sys.argv[-2]
|
||||
|
||||
if not exists(scanImage) :
|
||||
print('Scan provided does not exist')
|
||||
exit(5)
|
||||
|
||||
normalImage = sys.argv[-1]
|
||||
pageDim = (11, 8.5)
|
||||
pageRatio = pageDim[1] / pageDim[0]
|
||||
|
@ -359,8 +455,9 @@ print(f'Writing normalized image to {normalImage}')
|
|||
cv2.imwrite(normalImage, normal)
|
||||
|
||||
evaluation = find_hole_punches(normal)
|
||||
jsonOut = simplify_hole_punches(evaluation)
|
||||
|
||||
with open(f'{normalImage}.json', 'w') as output:
|
||||
output.write(dumps(jsonOut, sort_keys = True, indent = 4))
|
||||
print(f'Wrote hole punch definition file to {normalImage}.json')
|
||||
with open(f'{normalImage}.txt', 'w') as evalFile :
|
||||
for hp in evaluation:
|
||||
evalFile.write(f'{hp["order"] + 1} : {hp["x"]},{hp["y"]}\n')
|
||||
|
||||
#display(normal)
|
Loading…
Reference in New Issue