This commit is contained in:
2026-05-15 13:09:40 +02:00
parent bf3114f09b
commit a18ad914c3
3 changed files with 271 additions and 0 deletions
@@ -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"
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

@@ -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("<Button-1>", 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("<Return>", 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()