diff --git a/Medevio/60 ScansProcessing/corrections.json b/Medevio/60 ScansProcessing/corrections.json index daca3b2..f118fd0 100644 --- a/Medevio/60 ScansProcessing/corrections.json +++ b/Medevio/60 ScansProcessing/corrections.json @@ -1226,5 +1226,37 @@ { "original": "6105242044 2024-06-12 Mikisch, Richard [LZ neurochirurgie] [synoviální cysta L3/4 dx, foraminostenóza dx, dop. resekce L3/4 zprava ev. fuse].pdf", "corrected": "6105242044 2024-06-12 Mikisch, Richard [LZ neurochirurgie] [synoviální cysta L34 dx, foraminostenóza dx, dop. resekce L34 zprava ev. fuse].pdf" + }, + { + "original": "0309010405 Diepold, Tomáš split_010.pdf", + "corrected": "0309010405 2026-05-14 Diepold, Tomáš [EKG] [normální křivka].pdf" + }, + { + "original": "5559070209 2026-05-12 Cimplová, Dagmar [LZ ORL] [Mírná hypertrofie sliznice dolních skořep bilat., chrupavčitý výrůstek septa vpravo vpředu].pdf", + "corrected": "5559070209 2026-05-12 Cimplová, Dagmar [LZ ORL] [Mírná hypertrofie sliznice dolních skořep bilat., chrupavčitý výrůstek septa vpravo vpředu, kontrola k endoskopii +3m].pdf" + }, + { + "original": "6012096904 2026-03-03 Suchý, Milan [LZ plicní] [OSA, AHI 61.6, ODI 67.3, hypoxie 42% doby spánku, BPAP ST 19.5/8 mbar, rAHI 40.2].pdf", + "corrected": "6012096904 2026-03-03 Suchý, Milan [LZ plicní] [OSA, AHI 61.6, ODI 67.3, hypoxie 42% doby spánku, BPAP ST 19.58 mbar, rAHI 40.2].pdf" + }, + { + "original": "6012096904 null Suchý, Milan [poukaz spirometrie] [Spirometrie, Body test, Difuze; TK 179/93/156, SAT 96%, FENO 26].pdf", + "corrected": "6012096904 2026-01-12 Suchý, Milan [spirometrie] [Spirometrie, Body test, Difuze; TK 17993156, SAT 96%, FENO 26].pdf" + }, + { + "original": "6012096904 2025-11-27 Suchý, Milan [LZ kardiologie] [Perzist. FiS cryo-PVI, EKV na SR s časnou recidivou, amiodaron od 8/24, AH komp., obezita, susp. spánkové apnoe].pdf", + "corrected": "6012096904 2025-11-27 Suchý, Milan [LZ kardiologie] [Perzist. FiS cryo-PVI, EKV na SR s časnou recidivou, amiodaron od 824, AH komp., obezita, susp. spánkové apnoe, ko 02SEP2026].pdf" + }, + { + "original": "9109091123 2026-02-25 Zívr, Jan [LZ ORL] [sono ŠŽ: cystická přestavba ŠŽ, jinak klidný nález, dop. endokrinolog].pdf", + "corrected": "9109091123 2026-02-25 Zívr, Jan [LZ ORL] [sono ŠŽ cystická přestavba ŠŽ, jinak klidný nález, dop. endokrinolog].pdf" + }, + { + "original": "9109091123 2026-05-14 Zívr, Jan [RTG plic] [Plíce ZP: bránice hladké, srdce nedilatované, parenchym bez ložisk. změn].pdf", + "corrected": "9109091123 2026-05-14 Zívr, Jan [RTG plic] [Plíce ZP bránice hladké, srdce nedilatované, parenchym bez ložisk. změn].pdf" + }, + { + "original": "465525112 2026-05-04 Hoserová, Marie [LZ kardiologie] [Progrese náh. dušnosti, CHSS, FiS perm., trikuspidální regurgitace 3/4, PASP 40-45mmHg, EF LK 55%, DM2, AH, intolerance Jardiance+Forxiga].pdf", + "corrected": "465525112 2026-05-04 Hoserová, Marie [LZ kardiologie] [Progrese náh. dušnosti, CHSS, FiS perm., trikuspidální regurgitace 34, PASP 40-45mmHg, EF LK 55%, DM2, AH, intolerance Jardiance+Forxiga].pdf" } ] \ No newline at end of file diff --git a/Medevio/90 RychléZpracováníObrázků/2518e068-c7fb-44fc-b130-c75f104ea50e.jpeg b/Medevio/90 RychléZpracováníObrázků/2518e068-c7fb-44fc-b130-c75f104ea50e.jpeg new file mode 100644 index 0000000..a61c260 Binary files /dev/null and b/Medevio/90 RychléZpracováníObrázků/2518e068-c7fb-44fc-b130-c75f104ea50e.jpeg differ diff --git a/Medevio/90 RychléZpracováníObrázků/deskew_tool.py b/Medevio/90 RychléZpracováníObrázků/deskew_tool.py new file mode 100644 index 0000000..337849d --- /dev/null +++ b/Medevio/90 RychléZpracováníObrázků/deskew_tool.py @@ -0,0 +1,239 @@ +""" +Deskew Tool v2 — interaktivní oprava sklonu dokumentu. + +Použití: + Klikej na body podél JAKÉKOLI rovné hrany (min. 2 body). + Skript sám rozpozná, jestli je hrana horizontální nebo vertikální. + Výsledný úhel lze doladit sliderem. Náhled před uložením. +""" + +import math +import sys +from pathlib import Path +import tkinter as tk +from tkinter import messagebox +import numpy as np +from PIL import Image, ImageTk + +IMAGE_PATH = Path(__file__).parent / "2518e068-c7fb-44fc-b130-c75f104ea50e.jpeg" +OUTPUT_PDF = Path(__file__).parent / "opraveny_dokument.pdf" + +MAX_DISPLAY = (1300, 820) + + +def fit_scale(img_w, img_h, max_w, max_h): + scale = min(max_w / img_w, max_h / img_h, 1.0) + return scale, int(img_w * scale), int(img_h * scale) + + +def angle_of_points(pts): + """Lineární regrese → úhel přímky procházející body (ve stupních, -90..+90).""" + xs = np.array([p[0] for p in pts], dtype=float) + ys = np.array([p[1] for p in pts], dtype=float) + if len(pts) < 2: + return 0.0 + # fit line + if np.ptp(xs) < 1e-6: # téměř vertikální + return 90.0 + coeffs = np.polyfit(xs, ys, 1) + slope = coeffs[0] # dy/dx + return math.degrees(math.atan(slope)) # -90..+90 + + +def rotation_from_edge(pts): + """ + Z bodů na hraně určí potřebnou rotaci. + Automaticky rozpozná, jestli je hrana blíž horizontální nebo vertikální. + """ + a = angle_of_points(pts) # úhel vůči horizontální ose (y↓ souřadnice) + # Je-li hrana blíže horizontální (|a| < 45°): přímá korekce + if abs(a) <= 45: + return -a + # Je-li blíže vertikální: hrana by měla být ±90° + if a > 0: + return -(a - 90) + else: + return -(a + 90) + + +def rotate_image(img, angle): + """Rotuje PIL Image kolem středu, bílé pozadí, zachová rozměry obsahu.""" + return img.rotate(angle, expand=True, fillcolor="white", resample=Image.BICUBIC) + + +class DeskewApp: + DOT_COLOR = "#e74c3c" + LINE_COLOR = "#e74c3c" + SLIDER_RES = 0.05 # krok slideru ve stupních + + def __init__(self, root, image_path): + self.root = root + self.root.title("Deskew Tool v2") + + self.orig_img = Image.open(image_path) + iw, ih = self.orig_img.size + self.scale, dw, dh = fit_scale(iw, ih, *MAX_DISPLAY) + + self.disp_img = self.orig_img.resize((dw, dh), Image.LANCZOS) + self.tk_img = ImageTk.PhotoImage(self.disp_img) + + self.points = [] # (x_orig, y_orig) + self.rot_var = tk.DoubleVar(value=0.0) + + self._build_ui(dw, dh) + + # ------------------------------------------------------------------ UI -- + def _build_ui(self, dw, dh): + # --- horní panel --- + top = tk.Frame(self.root) + top.pack(fill=tk.X, padx=8, pady=(6, 2)) + + self.lbl = tk.Label( + top, + text="Klikni na body podél rovné hrany (min. 2 body) — Skript sám určí orientaci hrany", + font=("Segoe UI", 10), + fg="#333", + anchor="w", + ) + self.lbl.pack(side=tk.LEFT, fill=tk.X, expand=True) + + tk.Button(top, text="Reset", command=self.reset, width=8).pack(side=tk.RIGHT, padx=2) + tk.Button(top, text="Spočítej úhel", command=self.compute_angle, + width=14, bg="#2980b9", fg="white").pack(side=tk.RIGHT, padx=2) + + # --- canvas --- + self.canvas = tk.Canvas(self.root, width=dw, height=dh, cursor="crosshair") + self.canvas.pack(padx=8, pady=2) + self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_img) + self.canvas.bind("", self.on_click) + + # --- slider panel --- + bot = tk.Frame(self.root) + bot.pack(fill=tk.X, padx=8, pady=(2, 6)) + + tk.Label(bot, text="Rotace (°):", font=("Segoe UI", 10)).pack(side=tk.LEFT) + + self.lbl_angle = tk.Label(bot, text="0.00°", width=8, + font=("Segoe UI", 10, "bold"), fg="#c0392b") + self.lbl_angle.pack(side=tk.LEFT, padx=4) + + self.slider = tk.Scale( + bot, + from_=-20, to=20, + resolution=self.SLIDER_RES, + orient=tk.HORIZONTAL, + variable=self.rot_var, + length=400, + command=self._on_slider, + showvalue=False, + ) + self.slider.pack(side=tk.LEFT, padx=6) + + tk.Button(bot, text="Náhled", command=self.preview, + width=10, bg="#8e44ad", fg="white").pack(side=tk.LEFT, padx=6) + + self.btn_save = tk.Button( + bot, text="Ulož PDF", command=self.save_pdf, + width=12, bg="#27ae60", fg="white", + font=("Segoe UI", 10, "bold"), + ) + self.btn_save.pack(side=tk.LEFT, padx=4) + + self.root.bind("", lambda e: self.save_pdf()) + + # ---------------------------------------------------------- interactions -- + def on_click(self, event): + x_orig = event.x / self.scale + y_orig = event.y / self.scale + self.points.append((x_orig, y_orig)) + + r = 6 + self.canvas.create_oval( + event.x - r, event.y - r, event.x + r, event.y + r, + outline=self.DOT_COLOR, fill=self.DOT_COLOR, + ) + + # průběžná regresní přímka + if len(self.points) >= 2: + self._redraw_line() + + n = len(self.points) + suffix = 'y' if 1 < n < 5 else 'ů' if n >= 5 else '' + self.lbl.config(text=f"{n} bod{suffix} — klikej dál nebo stiskni 'Spočítej úhel'") + + def _redraw_line(self): + """Překreslí regresní přímku přes celou canvas.""" + self.canvas.delete("regline") + if len(self.points) < 2: + return + xs = [p[0] * self.scale for p in self.points] + ys = [p[1] * self.scale for p in self.points] + x0, x1 = 0, self.canvas.winfo_width() + if np.ptp(xs) < 1: + self.canvas.create_line(xs[0], 0, xs[0], self.canvas.winfo_height(), + fill=self.LINE_COLOR, width=2, dash=(8,4), tags="regline") + return + coeffs = np.polyfit(xs, ys, 1) + y0 = coeffs[0] * x0 + coeffs[1] + y1 = coeffs[0] * x1 + coeffs[1] + self.canvas.create_line(x0, y0, x1, y1, + fill=self.LINE_COLOR, width=2, dash=(8, 4), tags="regline") + + def compute_angle(self): + if len(self.points) < 2: + messagebox.showwarning("Málo bodů", "Klikni alespoň na 2 body podél hrany.") + return + rot = rotation_from_edge(self.points) + self.rot_var.set(round(rot, 2)) + self.lbl_angle.config(text=f"{rot:+.2f}°") + self.lbl.config(text=f"Vypočítaná rotace: {rot:+.2f}° — dolaď sliderem nebo klikej na Náhled / Ulož PDF") + + def _on_slider(self, val): + v = float(val) + self.lbl_angle.config(text=f"{v:+.2f}°") + + def reset(self): + self.points.clear() + self.canvas.delete("all") + self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_img) + self.rot_var.set(0.0) + self.lbl_angle.config(text="0.00°") + self.lbl.config(text="Klikni na body podél rovné hrany (min. 2 body) — Skript sám určí orientaci hrany") + + def _corrected_image(self): + return rotate_image(self.orig_img, self.rot_var.get()) + + def preview(self): + rot = self.rot_var.get() + corrected = self._corrected_image() + pw, ph = corrected.size + scale2, dw2, dh2 = fit_scale(pw, ph, *MAX_DISPLAY) + thumb = corrected.resize((dw2, dh2), Image.LANCZOS) + + win = tk.Toplevel(self.root) + win.title(f"Náhled — rotace {rot:+.2f}°") + tk_thumb = ImageTk.PhotoImage(thumb) + lbl = tk.Label(win, image=tk_thumb) + lbl.image = tk_thumb # drž referenci + lbl.pack() + + def save_pdf(self): + corrected = self._corrected_image() + if corrected.mode != "RGB": + corrected = corrected.convert("RGB") + corrected.save(OUTPUT_PDF, "PDF", resolution=200) + rot = self.rot_var.get() + messagebox.showinfo("Uloženo", f"Rotace: {rot:+.2f}°\nPDF:\n{OUTPUT_PDF}") + self.root.destroy() + + +def main(): + if not IMAGE_PATH.exists(): + sys.exit(f"Obrázek nenalezen: {IMAGE_PATH}") + root = tk.Tk() + DeskewApp(root, IMAGE_PATH) + root.mainloop() + + +if __name__ == "__main__": + main()