From bd33eb84b89ccab1977cc0e3d88f9507bc8b4e6d Mon Sep 17 00:00:00 2001 From: Ruben Young On <r.d.youngon@student.tudelft.nl> Date: Sat, 25 May 2019 18:33:20 +0200 Subject: [PATCH] Revert "Merge branch 'develop' of https://gitlab.kwant-project.org/works-on-my-machine/zesje into feature/pre-grading" This reverts commit f3484bc973a0a779bef1a6a3e49d380398155106, reversing changes made to 4ea46142f97f4ab774a6e385e266a45828a308ed. --- client/views/Exam.jsx | 12 +- client/views/ExamEditor.jsx | 85 +++-------- tests/data/cornermarkers/a4-3-markers.png | Bin 15274 -> 0 bytes .../cornermarkers/a4-rotated-3-markers.png | Bin 15803 -> 0 bytes tests/data/cornermarkers/a4-rotated.png | Bin 16448 -> 0 bytes tests/test_three_corners.py | 57 -------- zesje/api/exams.py | 3 +- zesje/api/mult_choice.py | 4 +- zesje/images.py | 59 -------- zesje/pdf_generation.py | 16 +-- zesje/pregrader.py | 135 ------------------ zesje/scans.py | 27 +--- 12 files changed, 36 insertions(+), 362 deletions(-) delete mode 100644 tests/data/cornermarkers/a4-3-markers.png delete mode 100644 tests/data/cornermarkers/a4-rotated-3-markers.png delete mode 100644 tests/data/cornermarkers/a4-rotated.png delete mode 100644 tests/test_three_corners.py delete mode 100644 zesje/pregrader.py diff --git a/client/views/Exam.jsx b/client/views/Exam.jsx index 3ccbc008..85737fb3 100644 --- a/client/views/Exam.jsx +++ b/client/views/Exam.jsx @@ -42,11 +42,7 @@ class Exams extends React.Component { page: problem.page, name: problem.name, graded: problem.graded, - mc_options: problem.mc_options.map((option) => { - option.widget.x -= 7 - option.widget.y -= 21 - return option - }), + mc_options: problem.mc_options, isMCQ: problem.mc_options && problem.mc_options.length !== 0 // is the problem a mc question - used to display PanelMCQ } } @@ -365,8 +361,8 @@ class Exams extends React.Component { 'feedback_id': null, 'widget': { 'name': 'mc_option_' + labels[index], - 'x': xPos + 7, - 'y': yPos + 21, + 'x': xPos, + 'y': yPos, 'type': 'mcq_widget' } } @@ -379,8 +375,6 @@ class Exams extends React.Component { formData.append('label', data.label) api.put('mult-choice/', formData).then(result => { data.id = result.mult_choice_id - data.widget.x -= 7 - data.widget.y -= 21 this.createNewMCOWidget(problemWidget, data) this.generateAnswerBoxes(problemWidget, labels, index + 1, xPos + 24, yPos) }).catch(err => { diff --git a/client/views/ExamEditor.jsx b/client/views/ExamEditor.jsx index 311cadaf..15781e4c 100644 --- a/client/views/ExamEditor.jsx +++ b/client/views/ExamEditor.jsx @@ -187,27 +187,6 @@ class ExamEditor extends React.Component { this.props.updateExam() }) } - - updateState = (widget, data) => { - this.props.updateMCWidgetPosition(widget, { - x: Math.round(data.x), - y: Math.round(data.y) - }) - } - - updateMCOPosition = (widget, data) => { - this.updateState(widget, data) - - widget.problem.mc_options.forEach( - (option, i) => { - let newData = { - x: Math.round(data.x) + i * 24 + 7, - y: Math.round(data.y) + 21 - } - this.updateWidgetPositionDB(option, newData) - }) - } - /** * This function renders a group of options into one draggable widget * @returns {*} @@ -217,9 +196,6 @@ class ExamEditor extends React.Component { let height = 38 let enableResizing = false const isSelected = widget.id === this.props.selectedWidgetId - let xPos = widget.problem.mc_options[0].widget.x - let yPos = widget.problem.mc_options[0].widget.y - return ( <ResizeAndDrag key={'widget_mc_' + widget.id} @@ -237,8 +213,8 @@ class ExamEditor extends React.Component { topRight: enableResizing }} position={{ - x: xPos, - y: yPos + x: widget.problem.mc_options[0].widget.x, + y: widget.problem.mc_options[0].widget.y }} size={{ width: width, @@ -248,7 +224,19 @@ class ExamEditor extends React.Component { this.props.selectWidget(widget.id) }} onDragStop={(e, data) => { - this.updateMCOPosition(widget, data) + this.props.updateMCWidgetPosition(widget, { + x: Math.round(data.x), + y: Math.round(data.y) + }) + + widget.problem.mc_options.forEach( + (option, i) => { + let newData = { + x: Math.round(data.x), + y: Math.round(data.y) + i * 24 + } + this.updateWidgetPositionDB(option, newData) + }) }} > <div className={isSelected ? 'mcq-widget widget selected' : 'mcq-widget widget '}> @@ -330,28 +318,6 @@ class ExamEditor extends React.Component { onDragStart={() => { this.props.selectWidget(widget.id) }} - onDrag={(e, data) => { - if (widget.problem.mc_options.length > 0) { - let xPos = widget.problem.mc_options[0].widget.x - let yPos = widget.problem.mc_options[0].widget.y - let width = 24 * widget.problem.mc_options.length - let height = 38 - - if (xPos < data.x) { - xPos = data.x - } else if (xPos + width > data.x + widget.width) { - xPos = data.x + widget.width - width - } - - if (yPos < data.y) { - yPos = data.y - } else if (yPos + height > data.y + widget.height) { - yPos = data.y + widget.height - height - } - - this.updateState(widget, { x: xPos, y: yPos }) - } - }} onDragStop={(e, data) => { this.props.updateWidget(widget.id, { x: { $set: Math.round(data.x) }, @@ -361,26 +327,7 @@ class ExamEditor extends React.Component { x: Math.round(data.x), y: Math.round(data.y) }).then(() => { - if (widget.problem.mc_options.length > 0) { - let xPos = widget.problem.mc_options[0].widget.x - let yPos = widget.problem.mc_options[0].widget.y - let width = 24 * widget.problem.mc_options.length - let height = 38 - - if (xPos < data.x) { - xPos = data.x - } else if (xPos + width > data.x + widget.width) { - xPos = data.x + widget.width - width - } - - if (yPos < data.y) { - yPos = data.y - } else if (yPos + height > data.y + widget.height) { - yPos = data.y + widget.height - height - } - - this.updateMCOPosition(widget, { x: xPos, y: yPos }) - } + // ok }).catch(err => { console.log(err) // update to try and get a consistent state diff --git a/tests/data/cornermarkers/a4-3-markers.png b/tests/data/cornermarkers/a4-3-markers.png deleted file mode 100644 index 32bdd11a2fbf87fed48625c3a12f0e609e2088a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15274 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 diff --git a/tests/data/cornermarkers/a4-rotated-3-markers.png b/tests/data/cornermarkers/a4-rotated-3-markers.png deleted file mode 100644 index d32cbacac4d4a2d8a327736ce8967623f8aca22d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15803 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 diff --git a/tests/data/cornermarkers/a4-rotated.png b/tests/data/cornermarkers/a4-rotated.png deleted file mode 100644 index 8dbf8630a73b83a71dcc36eb687933632657f5aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16448 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 diff --git a/tests/test_three_corners.py b/tests/test_three_corners.py deleted file mode 100644 index 1169be12..00000000 --- a/tests/test_three_corners.py +++ /dev/null @@ -1,57 +0,0 @@ -import cv2 -import os -import numpy as np - -from zesje.images import fix_corner_markers -from zesje.scans import find_corner_marker_keypoints - - -def test_three_straight_corners_1(): - shape = (240, 200, 3) - corner_markers = [(50, 50), (120, 50), (50, 200)] - - top_left, corner_markers = fix_corner_markers(corner_markers, shape) - - assert (120, 200) in corner_markers - assert top_left == (50, 50) - - -def test_three_straight_corners_2(): - shape = (240, 200, 3) - corner_markers = [(120, 50), (50, 200), (120, 200)] - - top_left, corner_markers = fix_corner_markers(corner_markers, shape) - - assert (50, 50) in corner_markers - assert top_left == (50, 50) - - -def test_pdf(datadir): - # Max deviation of inferred corner marker and actual location - epsilon = 2 - - # Scan rotated image with 4 corner markers - image_filename1 = 'a4-rotated.png' - image_path = os.path.join(datadir, 'cornermarkers', image_filename1) - page_img = cv2.imread(image_path) - - corners1 = find_corner_marker_keypoints(page_img) - - # Scan the same image with 3 corner markers - image_filename2 = 'a4-rotated-3-markers.png' - image_path = os.path.join(datadir, 'cornermarkers', image_filename2) - page_img = cv2.imread(image_path) - - corners2 = find_corner_marker_keypoints(page_img) - - # Get marker that was removed - diff = [corner for corner in corners1 if corner not in corners2] - diff_marker = min(diff) - - _, fixed_corners2 = fix_corner_markers(corners2, page_img.shape) - added_marker = [corner for corner in fixed_corners2 if corner not in corners2][0] - - # Check if 'inferred' corner marker is not too far away - dist = np.linalg.norm(np.subtract(added_marker, diff_marker)) - - assert dist < epsilon diff --git a/zesje/api/exams.py b/zesje/api/exams.py index a1c449a0..c88db564 100644 --- a/zesje/api/exams.py +++ b/zesje/api/exams.py @@ -47,7 +47,8 @@ def get_cb_data_for_exam(exam): cb_data = [] for problem in exam.problems: page = problem.widget.page - cb_data += [(cb.x, cb.y, page, cb.label) for cb in problem.mc_options] + if page: + cb_data += [(cb.x, cb.y, page, cb.label) for cb in problem.mc_options] return cb_data diff --git a/zesje/api/mult_choice.py b/zesje/api/mult_choice.py index 29eaa61d..8a1b2c9f 100644 --- a/zesje/api/mult_choice.py +++ b/zesje/api/mult_choice.py @@ -64,8 +64,8 @@ class MultipleChoice(Resource): mc_type = 'mcq_widget' if not id: - # Insert new empty feedback option that links to the same problem, with the label as name - new_feedback_option = FeedbackOption(problem_id=problem_id, text=label) + # Insert new empty feedback option that links to the same problem + new_feedback_option = FeedbackOption(problem_id=problem_id, text='') db.session.add(new_feedback_option) db.session.commit() diff --git a/zesje/images.py b/zesje/images.py index 331a7a08..0e2167f2 100644 --- a/zesje/images.py +++ b/zesje/images.py @@ -2,8 +2,6 @@ import numpy as np -from operator import sub, add - def guess_dpi(image_array): h, *_ = image_array.shape @@ -38,63 +36,6 @@ def get_box(image_array, box, padding=0.3): return image_array[top:bottom, left:right] -def fix_corner_markers(corner_keypoints, shape): - """ - Corrects the list of corner markers if only three corner markers are found. - This function raises if less than three corner markers are detected. - - Parameters - ---------- - corner_keypoints : - List of corner marker locations as tuples - shape : - Shape of the image in (x, y, dim) - - Returns - ------- - corner_keypoints : - A list of four corner markers. - top_left : tuple - Coordinates of the top left corner marker - """ - - if len(corner_keypoints) == 4 or len(corner_keypoints) < 3: - raise RuntimeError("Fewer then 3 corner markers found") - - x_sep = shape[1] / 2 - y_sep = shape[0] / 2 - - top_left = [(x, y) for x, y in corner_keypoints if x < x_sep and y < y_sep] - bottom_left = [(x, y) for x, y in corner_keypoints if x < x_sep and y > y_sep] - top_right = [(x, y) for x, y in corner_keypoints if x > x_sep and y < y_sep] - bottom_right = [(x, y) for x, y in corner_keypoints if x > x_sep and y > y_sep] - - missing_point = () - - if not top_left: - # Top left point is missing - (dx, dy) = tuple(map(sub, top_right[0], bottom_right[0])) - missing_point = tuple(map(add, bottom_left[0], (dx, dy))) - top_left = [missing_point] - - elif not bottom_left: - # Bottom left point is missing - (dx, dy) = tuple(map(sub, top_right[0], bottom_right[0])) - missing_point = tuple(map(sub, top_left[0], (dx, dy))) - - elif not top_right: - # Top right point is missing - (dx, dy) = tuple(map(sub, top_left[0], bottom_left[0])) - missing_point = tuple(map(add, bottom_right[0], (dx, dy))) - - elif not bottom_right: - # bottom right - (dx, dy) = tuple(map(sub, top_left[0], bottom_left[0])) - missing_point = tuple(map(sub, top_right[0], (dx, dy))) - - return top_left[0], corner_keypoints + [missing_point] - - def box_is_filled(image_array, box_coords, padding=0.3, threshold=150, pixels=False): """ Determines if a box is filled diff --git a/zesje/pdf_generation.py b/zesje/pdf_generation.py index 09bf9641..0c0b8c5a 100644 --- a/zesje/pdf_generation.py +++ b/zesje/pdf_generation.py @@ -162,27 +162,25 @@ def generate_checkbox(canvas, x, y, label): canvas : reportlab canvas object x : int - the x coordinate of the top left corner of the box in points (pt) + the x coordinate of the top left corner of the box in pixels y : int - the y coordinate of the top left corner of the box in points (pt) + the y coordinate of the top left corner of the box in pixels label: str A string representing the label that is drawn on top of the box, will only take the first character """ fontsize = 11 # Size of font margin = 5 # Margin between elements and sides - markboxsize = fontsize - 2 # Size of checkboxes boxes - x_label = x + 1 # location of the label - y_label = y + margin # remove fontsize from the y label since we draw from the bottom left up - box_y = y - markboxsize # remove the markboxsize because the y is the coord of the top - # and reportlab prints from the bottom + markboxsize = fontsize - 2 # Size of student number boxes + x_label = x + 1 + y_label = y + margin + fontsize # check that there is a label to print if (label and not (len(label) == 0)): canvas.setFont('Helvetica', fontsize) canvas.drawString(x_label, y_label, label[0]) - canvas.rect(x, box_y, markboxsize, markboxsize) + canvas.rect(x, y, markboxsize, markboxsize) def generate_datamatrix(exam_id, page_num, copy_num): @@ -275,8 +273,6 @@ def _generate_overlay(canv, pagesize, exam_id, copy_num, num_pages, id_grid_x, index = 0 max_index = len(cb_data) cb_data = sorted(cb_data, key=lambda tup: tup[2]) - # invert the y axis - cb_data = [(cb[0], pagesize[1] - cb[1], cb[2], cb[3]) for cb in cb_data] else: index = 0 max_index = 0 diff --git a/zesje/pregrader.py b/zesje/pregrader.py deleted file mode 100644 index 4954bc8a..00000000 --- a/zesje/pregrader.py +++ /dev/null @@ -1,135 +0,0 @@ -import cv2 -import numpy as np - -from zesje.database import db, Solution -from zesje.images import guess_dpi, get_box, fix_corner_markers - - -def add_feedback_to_solution(sub, exam, page, page_img, corner_keypoints): - """ - Adds the multiple choice options that are identified as marked as a feedback option to a solution - - Parameters - ------ - sub : Submission - the current submission - exam : Exam - the current exam - page_img : Image - image of the page - corner_keypoints : array - locations of the corner keypoints as (x, y) tuples - """ - problems_on_page = [problem for problem in exam.problems if problem.widget.page == page] - - top_left_point, fixed_corner_keypoints = fix_corner_markers(corner_keypoints, page_img.shape) - - for problem in problems_on_page: - sol = Solution.query.filter(Solution.problem_id == problem.id, Solution.submission_id == sub.id).one_or_none() - - for mc_option in problem.mc_options: - box = (mc_option.x, mc_option.y) - - if box_is_filled(box, page_img, top_left_point): - feedback = mc_option.feedback - sol.feedback.append(feedback) - db.session.commit() - - -def box_is_filled(box, page_img, corner_keypoints, marker_margin=72/2.54, threshold=225, cut_padding=0.1, box_size=11): - """ - A function that finds the checkbox in a general area and then checks if it is filled in. - - Params - ------ - box: (int, int) - The coordinates of the top left (x,y) of the checkbox in points. - page_img: np.array - A numpy array of the image scan - corner_keypoints: (float,float) - The x coordinate of the left markers and the y coordinate of the top markers, - used as point of reference since scans can deviate from the original. - (x,y) are both in pixels. - marker_margin: float - The margin between the corner markers and the edge of a page when generated. - threshold: int - the threshold needed for a checkbox to be considered marked range is between 0 (fully black) - and 255 (absolutely white). - cut_padding: float - The extra padding when retrieving an area where the checkbox is in inches. - box_size: int - the size of the checkbox in points. - - Output - ------ - True if the box is marked, else False. - """ - - # shouldn't be needed, but some images are drawn a bit weirdly - y_shift = 5 - # create an array with y top, y bottom, x left and x right. use the marker margin to allign to the page. - coords = np.asarray([box[1] - marker_margin + y_shift, box[1] + box_size - marker_margin + y_shift, - box[0] - marker_margin, box[0] + box_size - marker_margin])/72 - - # add the actually margin from the scan to corner markers to the coords in inches - dpi = guess_dpi(page_img) - coords[0] = coords[0] + corner_keypoints[1]/dpi - coords[1] = coords[1] + corner_keypoints[1]/dpi - coords[2] = coords[2] + corner_keypoints[0]/dpi - coords[3] = coords[3] + corner_keypoints[0]/dpi - - # get the box where we think the box is - cut_im = get_box(page_img, coords, padding=cut_padding) - - # convert to grayscale - gray_im = cv2.cvtColor(cut_im, cv2.COLOR_BGR2GRAY) - # apply threshold to only have black or white - _, bin_im = cv2.threshold(gray_im, 150, 255, cv2.THRESH_BINARY) - - h_bin, w_bin, *_ = bin_im.shape - # create a mask that gets applied when floodfill the white - mask = np.zeros((h_bin+2, w_bin+2), np.uint8) - flood_im = bin_im.copy() - # fill the image from the top left - cv2.floodFill(flood_im, mask, (0, 0), 0) - # fill it from the bottom right just in case the top left doesn't cover all the white - cv2.floodFill(flood_im, mask, (h_bin-2, w_bin-2), 0) - - # find white parts - coords = cv2.findNonZero(flood_im) - # Find a bounding box of the white parts - x, y, w, h = cv2.boundingRect(coords) - # cut the image to this box - res_rect = bin_im[y:y+h, x:x+w] - - # the size in pixels we expect the drawn box to - box_size_px = box_size*dpi / 72 - - # if the rectangle is bigger (higher) than expected, cut the image up a bit - if h > 1.5 * box_size_px: - print("in h resize") - y_partition = 0.333 - # try getting another bounding box on bottom 2/3 of the screen - coords2 = cv2.findNonZero(flood_im[y + int(y_partition * h): y + h, x: x+w]) - x2, y2, w2, h2 = cv2.boundingRect(coords2) - # add these coords to create a new bounding box we are looking at - new_y = y+y2 + int(y_partition * h) - new_x = x + x2 - res_rect = bin_im[new_y:new_y + h2, new_x:new_x + w2] - - else: - new_x, new_y, w2, h2 = x, y, w, h - - # do the same for width - if w2 > 1.5 * box_size_px: - # usually the checkbox is somewhere in the bottom left of the bounding box - coords3 = cv2.findNonZero(flood_im[new_y: new_y + h2, new_x: new_x + int(0.66 * w2)]) - x3, y3, w3, h3 = cv2.boundingRect(coords3) - res_rect = bin_im[new_y + y3: new_y + y3 + h3, new_x + x3: new_x + x3 + w3] - - # if the found box is smaller than a certain threshold - # it means that we only found a little bit of white and the box is filled - res_x, res_y, *_ = res_rect.shape - if res_x < 0.333 * box_size_px or res_y < 0.333 * box_size_px: - return True - return np.average(res_rect) < threshold diff --git a/zesje/scans.py b/zesje/scans.py index 4d3e1c5b..bcbde9f6 100644 --- a/zesje/scans.py +++ b/zesje/scans.py @@ -5,7 +5,6 @@ import os from collections import namedtuple, Counter from io import BytesIO import signal -import traceback import cv2 import numpy as np @@ -18,7 +17,7 @@ from .database import db, Scan, Exam, Page, Student, Submission, Solution, ExamW from .datamatrix import decode_raw_datamatrix from .images import guess_dpi, get_box from .factory import make_celery -from .pregrader import add_feedback_to_solution + ExtractedBarcode = namedtuple('ExtractedBarcode', ['token', 'copy', 'page']) @@ -55,7 +54,7 @@ def process_pdf(scan_id): # TODO: When #182 is implemented, properly separate user-facing # messages (written to DB) from developer-facing messages, # which should be written into the log. - write_pdf_status(scan_id, 'error', f"Unexpected error: {error}\n Traceback:\n" + traceback.format_exc()) + write_pdf_status(scan_id, 'error', "Unexpected error: " + str(error)) def _process_pdf(scan_id, app_config): @@ -92,8 +91,8 @@ def _process_pdf(scan_id, app_config): print(description) failures.append(page) except Exception as e: - report_error(f'Error processing page {e}.\nTraceback:\n{traceback.format_exc()}') - raise + report_error(f'Error processing page {page}: {e}') + return except Exception as e: report_error(f"Failed to read pdf: {e}") raise @@ -338,13 +337,7 @@ def process_page(image_data, exam_config, output_dir=None, strict=False): else: return True, "Testing, image not saved and database not updated." - sub, exam = update_database(image_path, barcode) - - try: - add_feedback_to_solution(sub, exam, barcode.page, image_array, corner_keypoints) - except RuntimeError as e: - if strict: - return False, str(e) + update_database(image_path, barcode) if barcode.page == 0: description = guess_student( @@ -392,12 +385,8 @@ def update_database(image_path, barcode): Returns ------- - sub, exam where - - sub : Submission - the current submission - exam : Exam - the current exam + signature_validated : bool + If the corresponding submission has a validated signature. """ exam = Exam.query.filter(Exam.token == barcode.token).first() if exam is None: @@ -417,8 +406,6 @@ def update_database(image_path, barcode): db.session.commit() - return sub, exam - def decode_barcode(image, exam_config): """Extract a barcode from a PIL Image.""" -- GitLab