z230
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user