207 lines
5.7 KiB
Python
207 lines
5.7 KiB
Python
# SPDX-License-Identifier: MIT
|
|
# Copyright (c) 2019 Akumatic
|
|
|
|
from . import utils
|
|
|
|
def evaluate (
|
|
centers: list,
|
|
n: int,
|
|
radius: int
|
|
) -> list:
|
|
""" Evaluates the given data
|
|
|
|
Args:
|
|
centers (list):
|
|
contains tuples with coordinates and the black-pixel-ratio
|
|
(x, y, ratio)
|
|
n (int):
|
|
the number of possible answers per question
|
|
radius (int):
|
|
the radius of the circle around a box
|
|
|
|
Returns:
|
|
A list containings lists with evaluated answers
|
|
"""
|
|
start_of_file, _ = utils.find_closest_element(centers, (0,0))
|
|
|
|
# detect distance to next field on the right side
|
|
point_1 = start_of_file
|
|
point_r = find_next(centers, point_1, 2* radius, "r")
|
|
dist_r = utils.distance(point_1[:2], point_r[:2])
|
|
|
|
# detect distance to next field below
|
|
point_d = find_next(centers, point_1, 2 * radius, "d")
|
|
dist_d = utils.distance(point_1[:2], point_d[:2])
|
|
|
|
# detect distance to the next set of answers
|
|
for i in range(n - 2):
|
|
point_r = find_next(centers, point_r, dist_r, "r")
|
|
|
|
point_3 = find_next(centers, point_r, 3 * radius, "r", max_dist=dist_r)
|
|
if point_3 is not None:
|
|
dist_set = utils.distance(start_of_file, point_3)
|
|
else:
|
|
dist_set = None
|
|
|
|
answers = eval_set(start_of_file, centers, n, dist_r, dist_d)
|
|
if dist_set is not None:
|
|
cur = find_next(centers, start_of_file, dist_set, "r", max_dist = radius)
|
|
while cur is not None:
|
|
answers += eval_set(cur, centers, n, dist_r, dist_d)
|
|
cur = find_next(centers, cur, dist_set, "r", max_dist = radius)
|
|
|
|
return answers
|
|
|
|
def eval_set (
|
|
start: tuple,
|
|
centers: list,
|
|
n: int,
|
|
dist_r: int,
|
|
dist_d: int
|
|
) -> list:
|
|
""" Evaluates a set of answers
|
|
|
|
Args:
|
|
start (tuple):
|
|
Containing the coordinates of the top left answer of a set
|
|
centers (list):
|
|
contains tuples with coordinates and the black-pixel-ratio
|
|
(x, y, ratio)
|
|
n (int):
|
|
the number of possible answers per question
|
|
dist_r (int):
|
|
the horizontal distance between two columns of answers
|
|
dist_d (int):
|
|
the vertical distance between two rows of answers
|
|
|
|
Returns:
|
|
A list containing all evaluated data
|
|
"""
|
|
result = []
|
|
result.append(eval_row(start, centers, n, dist_r))
|
|
|
|
cur = start
|
|
while cur != None:
|
|
cur = find_next(centers, cur, dist_d, "d", max_dist=dist_r / 2)
|
|
if cur is not None:
|
|
result.append(eval_row(cur, centers, n, dist_r))
|
|
return result
|
|
|
|
def eval_row (
|
|
start: tuple,
|
|
centers: int,
|
|
n: int,
|
|
dist_r: int
|
|
) -> list:
|
|
""" Evaluates a row of a set of answers with length n
|
|
|
|
Args:
|
|
start (tuple):
|
|
Containing the coordinates of the top left answer of a set
|
|
centers (list):
|
|
contains tuples with coordinates and the black-pixel-ratio
|
|
(x, y, ratio)
|
|
n (int):
|
|
the number of possible answers per question
|
|
dist_r (int):
|
|
the horizontal distance between two columns of answers
|
|
|
|
Returns:
|
|
A list containing all evaluated answer data from a given row
|
|
"""
|
|
result = []
|
|
result.append(rate_black_pixel_ratio(start[2]))
|
|
|
|
cur = start
|
|
for i in range(n - 1):
|
|
cur = find_next(centers, cur, dist_r, "r")
|
|
result.append(rate_black_pixel_ratio(cur[2]))
|
|
|
|
return result
|
|
|
|
def rate_black_pixel_ratio (
|
|
ratio: int,
|
|
thres_1: int = 20,
|
|
thres_2: int = 50
|
|
) -> int:
|
|
""" Evaluates the given black-pixel-ratio and returns the detected marking.
|
|
|
|
Args:
|
|
ratio (int):
|
|
the ratio of black pixels compared to total pixels
|
|
thres_1 (int):
|
|
threshold for checked boxes
|
|
thres_2 (int):
|
|
threshold for corrected boxes
|
|
|
|
Returns:
|
|
-1 if a box is filled
|
|
1 if a box is checked
|
|
0 if a box is empty
|
|
"""
|
|
if ratio >= thres_2:
|
|
return -1 # corrected box
|
|
if ratio >= thres_1:
|
|
return 1 # checked
|
|
else: # empty box
|
|
return 0
|
|
|
|
def find_next (
|
|
centers: list,
|
|
cur: tuple,
|
|
dist: int,
|
|
dir: str,
|
|
max_dist: int = None
|
|
):
|
|
""" Finds the next point in a given direction with a given distance
|
|
|
|
Args:
|
|
centers (list):
|
|
contains tuples with coordinates and the black-pixel-ratio
|
|
(x, y, ratio)
|
|
cur (tuple):
|
|
the current point
|
|
dist (int):
|
|
approximated distance to the next point
|
|
dir (str):
|
|
direction of the distance. can be either "r" or "d"
|
|
max_dist (int):
|
|
max distance to be allowe
|
|
|
|
Returns:
|
|
returns the next found point.
|
|
if max_dist is given, returns None if distance > max_dist
|
|
"""
|
|
if dir == "r":
|
|
e, d = utils.find_closest_element(centers, (cur[0] + dist, cur[1]))
|
|
|
|
elif dir == "d":
|
|
e, d = utils.find_closest_element(centers, (cur[0], cur[1] + dist))
|
|
|
|
if max_dist is None or d <= max_dist:
|
|
return e
|
|
return None
|
|
|
|
def compare(
|
|
data_a: list,
|
|
data_b: list
|
|
):
|
|
""" Compares two sets of evaluated data with the same
|
|
|
|
Args:
|
|
data_a (list):
|
|
data set A
|
|
data_b (list):
|
|
data set B
|
|
|
|
Returns:
|
|
A String "Correct/Total Answers" and the percentage
|
|
"""
|
|
# asserts the number of questions is the same
|
|
assert len(data_a) == len(data_b)
|
|
cnt = 0
|
|
for i in range(len(data_a)):
|
|
if data_a[i] == data_b[i]:
|
|
cnt += 1
|
|
|
|
return (f"{cnt}/{len(data_a)}", cnt / len(data_a) * 100) |