waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
/ data-pipeline diagnose_columns.py
| 1 | # -*- coding: utf-8 -*- |
| 2 | """Diagnose: exakte x-Ausdehnung von Antworttext und Ankreuzkästchen.""" |
| 3 | import io |
| 4 | import sys |
| 5 | from collections import Counter |
| 6 | |
| 7 | import fitz |
| 8 | |
| 9 | sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") |
| 10 | PDF = r"C:\Antigravity\Waffensachkundeprüfung\Fragenkatalog_sachkunde_mitAntworten.pdf" |
| 11 | doc = fitz.open(PDF) |
| 12 | |
| 13 | box_x0 = Counter() |
| 14 | box_x1 = Counter() |
| 15 | box_w = Counter() |
| 16 | total_boxes = 0 |
| 17 | for pno in range(5, doc.page_count): |
| 18 | for d in doc[pno].get_drawings(): |
| 19 | for item in d["items"]: |
| 20 | if item[0] == "re": |
| 21 | r = item[1] |
| 22 | if 6 < r.width < 20 and abs(r.width - r.height) < 4 and r.x0 > 400: |
| 23 | total_boxes += 1 |
| 24 | box_x0[round(r.x0)] += 1 |
| 25 | box_x1[round(r.x1)] += 1 |
| 26 | box_w[round(r.width, 1)] += 1 |
| 27 | print(f"Kästchen gesamt (x0>400): {total_boxes}") |
| 28 | print(f" x0-Verteilung: {sorted(box_x0.items())}") |
| 29 | print(f" x1-Verteilung: {sorted(box_x1.items())}") |
| 30 | print(f" Breiten: {sorted(box_w.items())}") |
| 31 | |
| 32 | # Maximale Textausdehnung in der Antwortspalte, getrennt nach Seiten mit/ohne Kästchen |
| 33 | max_x1_with = Counter() |
| 34 | max_x1_without = Counter() |
| 35 | for pno in range(5, doc.page_count): |
| 36 | page = doc[pno] |
| 37 | boxes = [] |
| 38 | for d in page.get_drawings(): |
| 39 | for item in d["items"]: |
| 40 | if item[0] == "re": |
| 41 | r = item[1] |
| 42 | if 6 < r.width < 20 and abs(r.width - r.height) < 4 and r.x0 > 400: |
| 43 | boxes.append(r) |
| 44 | for x0, y0, x1, y1, text, *_ in page.get_text("words"): |
| 45 | if y0 < 92 or x0 < 292 or not text.strip(): |
| 46 | continue |
| 47 | near_box = any(abs(b.y0 - y0) < 12 for b in boxes) |
| 48 | (max_x1_with if near_box else max_x1_without)[round(x1 / 5) * 5] += 1 |
| 49 | |
| 50 | print(f"\nTextende x1 (gerundet auf 5) in Antwortspalte, Zeilen MIT Kästchen:") |
| 51 | print(f" {sorted(max_x1_with.items())[-8:]}") |
| 52 | print(f"Textende x1, Zeilen OHNE Kästchen (Freitext):") |
| 53 | print(f" {sorted(max_x1_without.items())[-8:]}") |
| 54 | |
| 55 | # Kollidiert Text jemals mit einem Kästchen? |
| 56 | collisions = 0 |
| 57 | examples = [] |
| 58 | for pno in range(5, doc.page_count): |
| 59 | page = doc[pno] |
| 60 | boxes = [] |
| 61 | for d in page.get_drawings(): |
| 62 | for item in d["items"]: |
| 63 | if item[0] == "re": |
| 64 | r = item[1] |
| 65 | if 6 < r.width < 20 and abs(r.width - r.height) < 4 and r.x0 > 400: |
| 66 | boxes.append(r) |
| 67 | if not boxes: |
| 68 | continue |
| 69 | for x0, y0, x1, y1, text, *_ in page.get_text("words"): |
| 70 | if y0 < 92 or not text.strip(): |
| 71 | continue |
| 72 | for b in boxes: |
| 73 | if x0 < b.x1 and x1 > b.x0 and y0 < b.y1 and y1 > b.y0: |
| 74 | collisions += 1 |
| 75 | if len(examples) < 5: |
| 76 | examples.append((pno + 1, text, round(x0), round(b.x0))) |
| 77 | print(f"\nText-Kästchen-Überlappungen: {collisions} Beispiele: {examples}") |
| 78 | |
| 79 | # Wie viele Fragen hätten mit Schwelle 495 statt 505 ein Kästchen? |
| 80 | for threshold in (495, 500, 505): |
| 81 | n = sum(1 for pno in range(5, doc.page_count) |
| 82 | for d in doc[pno].get_drawings() |
| 83 | for item in d["items"] |
| 84 | if item[0] == "re" and 6 < item[1].width < 20 |
| 85 | and abs(item[1].width - item[1].height) < 4 and item[1].x0 > threshold) |
| 86 | print(f"Kästchen bei Schwelle x0>{threshold}: {n}") |