60 ScansProcessing: PDF náhled, fuzzy RČ, Medicus podle počítače
- Nová knihovna Knihovny/najdi_medicus.py — připojení k Medicus podle hostname (LEKAR=localhost/M:, SESTRA+LENOVO=192.168.1.10/M:, ostatní=localhost/C:) - Fuzzy matching RČ rozšířen o vkládání nuly (oprava sekvencí 00→0) - PDF náhled jako samostatný subprocess (preview_viewer.py, pymupdf) otevře se před OCR, zůstane do přejmenování, pracuje s temp kopií - Prompt: poznámka čerpá přednostně ze sekce Závěr: zprávy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -226,5 +226,65 @@
|
|||||||
{
|
{
|
||||||
"original": "470629074 2026-04-21 Šebesta, Jaroslav [oznámení ZP správní řízení] [zahájení správního řízení, LRPéče indikace II/3 hypertenzní choroba II-III.st].pdf",
|
"original": "470629074 2026-04-21 Šebesta, Jaroslav [oznámení ZP správní řízení] [zahájení správního řízení, LRPéče indikace II/3 hypertenzní choroba II-III.st].pdf",
|
||||||
"corrected": "470629074 2026-04-21 Šebesta, Jaroslav [oznámení ZP správní řízení] [zahájení správního řízení, návrh lázně, indikace II3 hypertenzní choroba II-III.st].pdf"
|
"corrected": "470629074 2026-04-21 Šebesta, Jaroslav [oznámení ZP správní řízení] [zahájení správního řízení, návrh lázně, indikace II3 hypertenzní choroba II-III.st].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "5503040026 2026-02-17 Koubek, Jiří [LZ kardiologie] [ECHO: EF 65%, konc.hypertrofie, diastol.dysfunkce I.st, Bevimlar 20mg].pdf",
|
||||||
|
"corrected": "5503040026 2026-02-17 Koubek, Jiří [LZ kardiologie] [ECHO EF 65%, konc.hypertrofie, diastol.dysfunkce I.st, Bevimlar 20mg].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "480529219 2026-04-17 Nytra, Vlastimil [Laboratoř] [osteomarkery, Ca, P, ALP, vit.D 67,1 snížen, PTH, Beta-CrossLaps].pdf",
|
||||||
|
"corrected": "480529219 2026-04-17 Nytra, Vlastimil [Laboratoř] [osteomarkery, Ca, P, ALP, vit.D 67.1 snížen, PTH, Beta-CrossLaps].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "435520110 2026-04-20 Nechodomová, Marie [sonografie břicha] [hypersekr.žaludku, lipomatoza pankreatu, steatoza jat., cholecystolithiaza, splenomegalie].pdf",
|
||||||
|
"corrected": "435520110 2026-04-20 Nechodomová, Marie [sonografie břicha] [zesílení stěny žaludku - dovyšetřit, hypersekr.žaludku, lipomatoza pankreatu, steatoza jat., cholecystolithiaza, splenomegalie].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "6903020080 2026-04-20 Novotný, Martin [Laboratoř] [cholesterol 5.54, LDL 3.25, TG 2.06, glukoza 6.1, HbA1c 38].pdf",
|
||||||
|
"corrected": "6903020080 2026-04-20 Novotný, Martin [Laboratoř] [smíšená hyperlipidémie, prediabetes, cholesterol 5.54, LDL 3.25, TG 2.06, glukoza 6.1, HbA1c 38].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "480529219 2026-04-17 Nytra, Vlastimil [Laboratoř] [ELFO bílkovin, bílkovina 69.0, albumin 0.581, gama-globuliny 0.125].pdf",
|
||||||
|
"corrected": "480529219 2026-04-17 Nytra, Vlastimil [Laboratoř] [ELFO bílkovin OK, bílkovina 69.0, albumin 0.581, gama-globuliny 0.125].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "5556046672 2026-04-07 Simionová, Lýdia [Laboratoř] [močový konkrement, whewellit 100%, 6x3mm, hnědý, bradavičnatý].pdf",
|
||||||
|
"corrected": "5556046672 2026-04-07 Simionová, Lýdia [Laboratoř] [močový konkrement analýza, whewellit 100%, 6x3mm, hnědý, bradavičnatý].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "510802325 2026-04-20 Simion, Vladimír [LZ chirurgie] [chronický vřed kůže, TMT amputace IV.+V.prstu PDK, defekt LDK 5x3.5cm].pdf",
|
||||||
|
"corrected": "510802325 2026-04-20 Simion, Vladimír [LZ chirurgie] [chronický vřed kůže, TMT amputace IV.+V.prstu PDK, defekt LDK 5x3.5cm, DP 3xt].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "436114002 2026-03-17 Petrovská, Eliška [LZ kardiologie] [fibrilace síní paroxysmální, sinus, st.p.kardioverzi, rivaroxaban].pdf",
|
||||||
|
"corrected": "436114002 2026-03-17 Petrovská, Eliška [LZ kardiologie] [fibrilace síní paroxysmální, sinus, st.p.kardioverzi, rivaroxaban, ad Holter EKG, bisoprolol vysadí].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "436114002 2026-03-14 Petrovská, Eliška [LZ interna] [fibrilace síní paroxysmální, kardioverze, sinusový rytmus, rivaroxaban].pdf",
|
||||||
|
"corrected": "436114002 2026-03-14 Petrovská, Eliška [LZ interna urgent] [fibrilace síní paroxysmální, kardioverze, sinusový rytmus, rivaroxaban].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "6008091738 2026-04-20 Nikitin, Petro [Laboratoř] [urea 9.47 zvýš, CKD-EPI G2, glukoza 6.6, osmolalita 296, MCV 81.5].pdf",
|
||||||
|
"corrected": "6008091738 2026-04-20 Nikitin, Petro [Laboratoř] [Z000 prediabetes, mikrocyty, urea 9.47 zvýš, CKD-EPI G2, glukoza 6.6, osmolalita 296, MCV 81.5].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "440802018 2026-04-20 Havelka, Miroslav [Laboratoř] [CKD-EPI G2, NT-proBNP 6128 zvýš, CRP 6.6, MCV 81.8, MCHC 314].pdf",
|
||||||
|
"corrected": "440802018 2026-04-20 Havelka, Miroslav [Laboratoř] [srdeční selhání, mikrocyty, CKD-EPI G2, NT-proBNP 6128 zvýš, CRP 6.6, MCV 81.8, MCHC 314].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "7857260422 2023-02-28 Jindrová, Kateřina [LZ ORL] [st.p. incizi inflam aterom P tváře - zhojeno, extirpace atheromu P tváře].pdf",
|
||||||
|
"corrected": "7857260422 2023-02-28 Jindrová, Kateřina [LZ ORL] [st.p. incizi inflam aterom P tváře - zhojeno, extirpace atheromu P tváře domluveno].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "7857260422 2021-05-06 Jindrová, Kateřina [LZ angiologie] [CVI II. st. dle CEAP C4, ortostáza, flebitida/flebotrombóza bilat. neprokázána].pdf",
|
||||||
|
"corrected": "7857260422 2021-05-06 Jindrová, Kateřina [LZ angiologie] [CVI II. st. dle CEAP C4, ortostáza, flebitidaflebotrombóza bilat. neprokázána].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "7857260422 2021-05-20 Jindrová, Kateřina [LZ neurologie] [VAS C-pá, porucha statodynamiky C úseku, tinitus auric. bilat., ad rehab].pdf",
|
||||||
|
"corrected": "7857260422 2021-05-20 Jindrová, Kateřina [LZ neurologie] [VAS Cp, porucha statodynamiky C úseku, tinitus auric. bilat., ad RHB].pdf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"original": "7857260422 2024-02-12 Jindrová, Kateřina [EKG] [sinusový rytmus 70/min, semivertik poloha, osa 55st, fyziol záznam].pdf",
|
||||||
|
"corrected": "7857260422 2024-02-12 Jindrová, Kateřina [EKG] [sinusový rytmus 70min, semivertik poloha, osa 55st, fyziol záznam].pdf"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -13,6 +13,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -121,18 +122,23 @@ def _lookup_by_rc(cur, rc_digits: str) -> dict | None:
|
|||||||
|
|
||||||
def _rc_candidates(rc: str) -> list[str]:
|
def _rc_candidates(rc: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Generuje kandidáty RČ pro fuzzy matching (edit distance 1):
|
Generuje kandidáty RČ pro fuzzy matching:
|
||||||
- vynechání každé cifry (oprava extra znaku z OCR)
|
- vynechání každé cifry (OCR přečetlo znak navíc)
|
||||||
|
- vložení nuly na každou pozici (OCR přehlédlo nulu v sekvenci 00)
|
||||||
- záměna podobně vypadajících číslic na každé pozici
|
- záměna podobně vypadajících číslic na každé pozici
|
||||||
Vrátí unikátní seznam kandidátů bez původního RČ.
|
Vrátí unikátní seznam kandidátů bez původního RČ.
|
||||||
"""
|
"""
|
||||||
similar = {"0": "8", "8": "0", "1": "7", "7": "1", "5": "6", "6": "5", "3": "8"}
|
similar = {"0": "8", "8": "0", "1": "7", "7": "1", "5": "6", "6": "5", "3": "8"}
|
||||||
candidates = set()
|
candidates = set()
|
||||||
|
|
||||||
# Vynechání jedné cifry (nejčastější OCR chyba — přebývající nula)
|
# Vynechání jedné cifry (OCR přečetlo znak navíc)
|
||||||
for i in range(len(rc)):
|
for i in range(len(rc)):
|
||||||
candidates.add(rc[:i] + rc[i+1:])
|
candidates.add(rc[:i] + rc[i+1:])
|
||||||
|
|
||||||
|
# Vložení nuly na každou pozici (nejčastější chyba: sekvence 00 přečtena jako 0)
|
||||||
|
for i in range(len(rc) + 1):
|
||||||
|
candidates.add(rc[:i] + "0" + rc[i:])
|
||||||
|
|
||||||
# Záměna podobné cifry na každé pozici
|
# Záměna podobné cifry na každé pozici
|
||||||
for i, ch in enumerate(rc):
|
for i, ch in enumerate(rc):
|
||||||
if ch in similar:
|
if ch in similar:
|
||||||
@@ -235,7 +241,10 @@ def extract_patient_info(pdf_path: str) -> dict:
|
|||||||
"\"PZ {oddělení}\" = propouštěcí zpráva z hospitalizace (např. \"PZ interna\", \"PZ neurologie\"). "
|
"\"PZ {oddělení}\" = propouštěcí zpráva z hospitalizace (např. \"PZ interna\", \"PZ neurologie\"). "
|
||||||
"Jiné typy: \"Laboratoř\", \"CT břicha\", \"MRI páteře\", \"kolonoskopie\", "
|
"Jiné typy: \"Laboratoř\", \"CT břicha\", \"MRI páteře\", \"kolonoskopie\", "
|
||||||
"\"operační protokol oční\", \"poukaz FT\", \"diagnostická mamografie\" atd.\n"
|
"\"operační protokol oční\", \"poukaz FT\", \"diagnostická mamografie\" atd.\n"
|
||||||
"- \"poznamka\": krátká klinická poznámka česky, max 80 znaků\n"
|
"- \"poznamka\": krátká klinická poznámka česky, max 80 znaků. "
|
||||||
|
"DŮLEŽITÉ: pokud zpráva obsahuje sekci \"Závěr:\" nebo \"Závěr vyšetření:\", "
|
||||||
|
"použij VÝHRADNĚ obsah této sekce — je nejdůležitější. "
|
||||||
|
"Teprve pokud závěr chybí, shrň obsah z celé zprávy.\n"
|
||||||
"- \"nazev_souboru\": název souboru ve formátu "
|
"- \"nazev_souboru\": název souboru ve formátu "
|
||||||
"\"{rodne_cislo} {datum_zpravy} {Příjmení}, {Jméno} [{typ_dokumentu}] [{poznamka}].pdf\" "
|
"\"{rodne_cislo} {datum_zpravy} {Příjmení}, {Jméno} [{typ_dokumentu}] [{poznamka}].pdf\" "
|
||||||
"(jméno bez titulu, RČ bez lomítka)\n"
|
"(jméno bez titulu, RČ bez lomítka)\n"
|
||||||
@@ -281,6 +290,95 @@ def sanitize_filename(name: str) -> str:
|
|||||||
return re.sub(r'[<>:"/\\|?*]', '', name)
|
return re.sub(r'[<>:"/\\|?*]', '', name)
|
||||||
|
|
||||||
|
|
||||||
|
def _open_preview(root, pdf_path: Path):
|
||||||
|
"""Otevře náhledové okno PDF/obrázku jako Toplevel. Pracuje s temp kopií — žádné zamykání originálu."""
|
||||||
|
import tkinter as tk
|
||||||
|
import tempfile
|
||||||
|
import shutil as _shutil
|
||||||
|
try:
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
import fitz
|
||||||
|
except ImportError:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Temp kopie — prohlížeč nikdy nesahá na originál
|
||||||
|
tmp = Path(tempfile.mktemp(suffix=pdf_path.suffix))
|
||||||
|
_shutil.copy2(pdf_path, tmp)
|
||||||
|
|
||||||
|
suffix = pdf_path.suffix.lower()
|
||||||
|
if suffix in (".jpg", ".jpeg", ".png"):
|
||||||
|
pil_pages = [Image.open(tmp)]
|
||||||
|
doc = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
doc = fitz.open(str(tmp))
|
||||||
|
except Exception:
|
||||||
|
tmp.unlink(missing_ok=True)
|
||||||
|
return
|
||||||
|
pil_pages = []
|
||||||
|
|
||||||
|
def render(n) -> Image.Image:
|
||||||
|
if doc is not None:
|
||||||
|
page = doc[n]
|
||||||
|
zoom = min(700 / page.rect.width, (sh - 150) / page.rect.height)
|
||||||
|
pix = page.get_pixmap(matrix=fitz.Matrix(zoom, zoom))
|
||||||
|
return Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
|
||||||
|
else:
|
||||||
|
img = pil_pages[0].copy()
|
||||||
|
img.thumbnail((700, sh - 150), Image.LANCZOS)
|
||||||
|
return img
|
||||||
|
|
||||||
|
def on_close():
|
||||||
|
try:
|
||||||
|
if doc:
|
||||||
|
doc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
tmp.unlink(missing_ok=True)
|
||||||
|
win.destroy()
|
||||||
|
|
||||||
|
page_count = len(doc) if doc else 1
|
||||||
|
sh = root.winfo_screenheight()
|
||||||
|
current = [0]
|
||||||
|
photo_ref = [None]
|
||||||
|
|
||||||
|
win = tk.Toplevel(root)
|
||||||
|
win.title(pdf_path.name)
|
||||||
|
win.attributes("-topmost", True)
|
||||||
|
win.resizable(False, False)
|
||||||
|
win.protocol("WM_DELETE_WINDOW", on_close)
|
||||||
|
|
||||||
|
lbl_img = tk.Label(win)
|
||||||
|
lbl_img.pack()
|
||||||
|
|
||||||
|
frame_nav = tk.Frame(win)
|
||||||
|
frame_nav.pack(pady=4)
|
||||||
|
|
||||||
|
lbl_page = tk.Label(frame_nav, font=("Segoe UI", 9))
|
||||||
|
lbl_page.pack(side="left", padx=10)
|
||||||
|
|
||||||
|
def show(n):
|
||||||
|
current[0] = n
|
||||||
|
img = render(n)
|
||||||
|
photo_ref[0] = ImageTk.PhotoImage(img)
|
||||||
|
lbl_img.config(image=photo_ref[0])
|
||||||
|
lbl_page.config(text=f"Strana {n + 1} / {page_count}")
|
||||||
|
btn_prev.config(state="normal" if n > 0 else "disabled")
|
||||||
|
btn_next.config(state="normal" if n < page_count - 1 else "disabled")
|
||||||
|
|
||||||
|
btn_prev = tk.Button(frame_nav, text="◄ Předchozí",
|
||||||
|
command=lambda: show(current[0] - 1))
|
||||||
|
btn_prev.pack(side="left")
|
||||||
|
btn_next = tk.Button(frame_nav, text="Další ►",
|
||||||
|
command=lambda: show(current[0] + 1))
|
||||||
|
btn_next.pack(side="left")
|
||||||
|
|
||||||
|
show(0)
|
||||||
|
|
||||||
|
win.update_idletasks()
|
||||||
|
win.geometry(f"+0+0")
|
||||||
|
|
||||||
|
|
||||||
def _rename_dialog(nazev: str, info_lines: list[str]) -> str | None:
|
def _rename_dialog(nazev: str, info_lines: list[str]) -> str | None:
|
||||||
"""
|
"""
|
||||||
Tkinter dialog pro schválení / opravu názvu souboru.
|
Tkinter dialog pro schválení / opravu názvu souboru.
|
||||||
@@ -291,15 +389,18 @@ def _rename_dialog(nazev: str, info_lines: list[str]) -> str | None:
|
|||||||
result = {"value": None}
|
result = {"value": None}
|
||||||
|
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
root.tk.call("encoding", "system", "utf-8")
|
root.tk.call("encoding", "system", "utf-8")
|
||||||
root.title("Schválení názvu souboru")
|
|
||||||
root.resizable(True, False)
|
dlg = tk.Toplevel(root)
|
||||||
root.attributes("-topmost", True)
|
dlg.title("Schválení názvu souboru")
|
||||||
|
dlg.resizable(True, False)
|
||||||
|
dlg.attributes("-topmost", True)
|
||||||
|
|
||||||
pad = {"padx": 12, "pady": 6}
|
pad = {"padx": 12, "pady": 6}
|
||||||
|
|
||||||
# Informační sekce
|
# Informační sekce
|
||||||
frame_info = tk.Frame(root, bg="#f0f0f0", bd=1, relief="sunken")
|
frame_info = tk.Frame(dlg, bg="#f0f0f0", bd=1, relief="sunken")
|
||||||
frame_info.pack(fill="x", **pad)
|
frame_info.pack(fill="x", **pad)
|
||||||
for line in info_lines:
|
for line in info_lines:
|
||||||
color = "#b00000" if line.startswith("⚠") else "#004080" if line.startswith("✓") else "#333"
|
color = "#b00000" if line.startswith("⚠") else "#004080" if line.startswith("✓") else "#333"
|
||||||
@@ -307,18 +408,18 @@ def _rename_dialog(nazev: str, info_lines: list[str]) -> str | None:
|
|||||||
fg=color, font=("Segoe UI", 10)).pack(fill="x", padx=8, pady=1)
|
fg=color, font=("Segoe UI", 10)).pack(fill="x", padx=8, pady=1)
|
||||||
|
|
||||||
# Pole pro název (bez .pdf)
|
# Pole pro název (bez .pdf)
|
||||||
tk.Label(root, text="Název souboru (bez .pdf):", anchor="w",
|
tk.Label(dlg, text="Název souboru (bez .pdf):", anchor="w",
|
||||||
font=("Segoe UI", 9, "bold")).pack(fill="x", padx=12, pady=(10, 2))
|
font=("Segoe UI", 9, "bold")).pack(fill="x", padx=12, pady=(10, 2))
|
||||||
|
|
||||||
nazev_bez = nazev[:-4] if nazev and nazev.endswith(".pdf") else (nazev or "")
|
nazev_bez = nazev[:-4] if nazev and nazev.endswith(".pdf") else (nazev or "")
|
||||||
var = tk.StringVar(value=nazev_bez)
|
var = tk.StringVar(value=nazev_bez)
|
||||||
entry = tk.Entry(root, textvariable=var, font=("Segoe UI", 10), width=90)
|
entry = tk.Entry(dlg, textvariable=var, font=("Segoe UI", 10), width=90)
|
||||||
entry.pack(fill="x", padx=12, pady=(0, 10))
|
entry.pack(fill="x", padx=12, pady=(0, 10))
|
||||||
entry.icursor(tk.END)
|
entry.icursor(tk.END)
|
||||||
entry.focus_set()
|
entry.focus_set()
|
||||||
|
|
||||||
# Tlačítka
|
# Tlačítka
|
||||||
frame_btn = tk.Frame(root)
|
frame_btn = tk.Frame(dlg)
|
||||||
frame_btn.pack(pady=(0, 12))
|
frame_btn.pack(pady=(0, 12))
|
||||||
|
|
||||||
def schvalit(event=None):
|
def schvalit(event=None):
|
||||||
@@ -336,14 +437,18 @@ def _rename_dialog(nazev: str, info_lines: list[str]) -> str | None:
|
|||||||
bg="#7a2a2a", fg="white", font=("Segoe UI", 10),
|
bg="#7a2a2a", fg="white", font=("Segoe UI", 10),
|
||||||
padx=16, pady=6).pack(side="left", padx=8)
|
padx=16, pady=6).pack(side="left", padx=8)
|
||||||
|
|
||||||
root.bind("<Return>", schvalit)
|
dlg.bind("<Return>", schvalit)
|
||||||
root.bind("<Escape>", preskocit)
|
dlg.bind("<Escape>", preskocit)
|
||||||
|
|
||||||
# Vystředit okno na obrazovce
|
# Umísti dialog vpravo od náhledu (nebo vystředit pokud náhled není)
|
||||||
root.update_idletasks()
|
dlg.update_idletasks()
|
||||||
w, h = root.winfo_width(), root.winfo_height()
|
sw = dlg.winfo_screenwidth()
|
||||||
sw, sh = root.winfo_screenwidth(), root.winfo_screenheight()
|
sh = dlg.winfo_screenheight()
|
||||||
root.geometry(f"+{(sw - w) // 2}+{(sh - h) // 2}")
|
w = dlg.winfo_width()
|
||||||
|
h = dlg.winfo_height()
|
||||||
|
x = min(720, sw - w - 20)
|
||||||
|
y = (sh - h) // 2
|
||||||
|
dlg.geometry(f"+{x}+{y}")
|
||||||
|
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
return result["value"]
|
return result["value"]
|
||||||
@@ -408,43 +513,8 @@ def interactive_rename(pdf_path: Path, info: dict, verif: dict) -> bool:
|
|||||||
print(f" Navržený název: {nazev}")
|
print(f" Navržený název: {nazev}")
|
||||||
print(" Otevírám dialog...")
|
print(" Otevírám dialog...")
|
||||||
|
|
||||||
# Otevři PDF v prohlížeči (konkrétní proces, abychom zavřeli jen toto okno)
|
|
||||||
viewer_proc = None
|
|
||||||
try:
|
|
||||||
import subprocess
|
|
||||||
import winreg
|
|
||||||
import shlex
|
|
||||||
|
|
||||||
def _get_pdf_exe() -> list[str] | None:
|
|
||||||
try:
|
|
||||||
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, r".pdf") as k:
|
|
||||||
prog_id = winreg.QueryValue(k, "")
|
|
||||||
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, rf"{prog_id}\shell\open\command") as k:
|
|
||||||
cmd = winreg.QueryValue(k, "")
|
|
||||||
parts = shlex.split(cmd.replace("\\", "/"))
|
|
||||||
exe = parts[0].replace("/", "\\")
|
|
||||||
return [exe, str(pdf_path)]
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
cmd = _get_pdf_exe()
|
|
||||||
if cmd:
|
|
||||||
viewer_proc = subprocess.Popen(cmd)
|
|
||||||
else:
|
|
||||||
subprocess.Popen(["start", "", str(pdf_path)], shell=True)
|
|
||||||
except Exception as e:
|
|
||||||
print(f" [Prohlížeč] Nepodařilo se otevřít: {e}")
|
|
||||||
|
|
||||||
odpoved = _rename_dialog(nazev or "", info_lines)
|
odpoved = _rename_dialog(nazev or "", info_lines)
|
||||||
|
|
||||||
# Zavři jen tento konkrétní proces prohlížeče a počkej na uvolnění zámku
|
|
||||||
if viewer_proc is not None:
|
|
||||||
try:
|
|
||||||
viewer_proc.terminate()
|
|
||||||
viewer_proc.wait(timeout=10)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if odpoved is None:
|
if odpoved is None:
|
||||||
print(" Přeskočeno.")
|
print(" Přeskočeno.")
|
||||||
return False
|
return False
|
||||||
@@ -465,19 +535,11 @@ def interactive_rename(pdf_path: Path, info: dict, verif: dict) -> bool:
|
|||||||
print(f" VAROVÁNÍ: '{final_name}' již existuje v Processed, přeskakuji.")
|
print(f" VAROVÁNÍ: '{final_name}' již existuje v Processed, přeskakuji.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for attempt in range(5):
|
|
||||||
try:
|
|
||||||
if pdf_path.suffix.lower() in (".jpg", ".jpeg", ".png"):
|
if pdf_path.suffix.lower() in (".jpg", ".jpeg", ".png"):
|
||||||
from jpg_to_pdf import image_to_pdf
|
from jpg_to_pdf import image_to_pdf
|
||||||
image_to_pdf(pdf_path, dest, rotate_ccw=info.get("rotace") or 0)
|
image_to_pdf(pdf_path, dest, rotate_ccw=info.get("rotace") or 0)
|
||||||
else:
|
else:
|
||||||
shutil.copy2(pdf_path, dest)
|
shutil.copy2(pdf_path, dest)
|
||||||
break
|
|
||||||
except PermissionError:
|
|
||||||
if attempt < 4:
|
|
||||||
time.sleep(0.5)
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
pdf_path.unlink()
|
pdf_path.unlink()
|
||||||
print(f" ✓ Uloženo: Processed/{final_name}")
|
print(f" ✓ Uloženo: Processed/{final_name}")
|
||||||
@@ -486,15 +548,49 @@ def interactive_rename(pdf_path: Path, info: dict, verif: dict) -> bool:
|
|||||||
|
|
||||||
# ─── Hlavní logika ────────────────────────────────────────────────────────────
|
# ─── Hlavní logika ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def process_file(pdf_path: Path):
|
def _start_preview_process(pdf_path: Path):
|
||||||
info = extract_patient_info(str(pdf_path))
|
"""
|
||||||
|
Otevře náhled PDF jako samostatný subprocess (žádné tkinter threading problémy).
|
||||||
|
Pracuje s temp kopií — originál zůstane volný.
|
||||||
|
Vrátí funkci close() pro ukončení procesu.
|
||||||
|
"""
|
||||||
|
import tempfile
|
||||||
|
import shutil as _shutil
|
||||||
|
|
||||||
|
tmp = Path(tempfile.mktemp(suffix=pdf_path.suffix))
|
||||||
|
_shutil.copy2(pdf_path, tmp)
|
||||||
|
|
||||||
|
viewer = Path(__file__).parent / "preview_viewer.py"
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[sys.executable, str(viewer), str(tmp), "--delete-on-close"],
|
||||||
|
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
def close():
|
||||||
|
try:
|
||||||
|
proc.terminate()
|
||||||
|
proc.wait(timeout=3)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
tmp.unlink(missing_ok=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return close
|
||||||
|
|
||||||
|
|
||||||
|
def process_file(pdf_path: Path):
|
||||||
|
close_preview = _start_preview_process(pdf_path)
|
||||||
|
try:
|
||||||
|
info = extract_patient_info(str(pdf_path))
|
||||||
rc_from_scan = re.sub(r"\D", "", info.get("rodne_cislo") or "")
|
rc_from_scan = re.sub(r"\D", "", info.get("rodne_cislo") or "")
|
||||||
print(f" Ověřuji v Medicus (RČ: {rc_from_scan})...")
|
print(f" Ověřuji v Medicus (RČ: {rc_from_scan})...")
|
||||||
verif = verify_patient(rc_from_scan)
|
verif = verify_patient(rc_from_scan)
|
||||||
print_verification(verif, rc_from_scan)
|
print_verification(verif, rc_from_scan)
|
||||||
|
|
||||||
interactive_rename(pdf_path, info, verif)
|
interactive_rename(pdf_path, info, verif)
|
||||||
|
finally:
|
||||||
|
close_preview()
|
||||||
|
|
||||||
def process_folder(folder: Path):
|
def process_folder(folder: Path):
|
||||||
pdf_files = sorted(f for f in folder.iterdir()
|
pdf_files = sorted(f for f in folder.iterdir()
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
"""
|
||||||
|
Standalone PDF/obrázek náhled — spouští se jako subprocess z extract_patient_info.py.
|
||||||
|
Argumenty: preview_viewer.py <soubor> [--delete-on-close]
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
pdf_path = Path(sys.argv[1])
|
||||||
|
delete_on_close = "--delete-on-close" in sys.argv
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
import fitz
|
||||||
|
except ImportError:
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
suffix = pdf_path.suffix.lower()
|
||||||
|
if suffix in (".jpg", ".jpeg", ".png"):
|
||||||
|
pil_img = Image.open(pdf_path)
|
||||||
|
doc = None
|
||||||
|
else:
|
||||||
|
doc = fitz.open(str(pdf_path))
|
||||||
|
pil_img = None
|
||||||
|
|
||||||
|
root = tk.Tk()
|
||||||
|
root.tk.call("encoding", "system", "utf-8")
|
||||||
|
|
||||||
|
sh = root.winfo_screenheight()
|
||||||
|
page_count = len(doc) if doc else 1
|
||||||
|
current = [0]
|
||||||
|
photo_ref = [None]
|
||||||
|
|
||||||
|
def render(n) -> Image.Image:
|
||||||
|
if doc is not None:
|
||||||
|
page = doc[n]
|
||||||
|
zoom = min(700 / page.rect.width, (sh - 150) / page.rect.height)
|
||||||
|
pix = page.get_pixmap(matrix=fitz.Matrix(zoom, zoom))
|
||||||
|
return Image.frombytes("RGB", (pix.width, pix.height), pix.samples)
|
||||||
|
else:
|
||||||
|
img = pil_img.copy()
|
||||||
|
img.thumbnail((700, sh - 150), Image.LANCZOS)
|
||||||
|
return img
|
||||||
|
|
||||||
|
def on_close():
|
||||||
|
if doc:
|
||||||
|
try:
|
||||||
|
doc.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if delete_on_close:
|
||||||
|
try:
|
||||||
|
pdf_path.unlink(missing_ok=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
root.title(pdf_path.stem)
|
||||||
|
root.attributes("-topmost", True)
|
||||||
|
root.resizable(False, False)
|
||||||
|
root.protocol("WM_DELETE_WINDOW", on_close)
|
||||||
|
|
||||||
|
lbl_img = tk.Label(root)
|
||||||
|
lbl_img.pack()
|
||||||
|
|
||||||
|
frame_nav = tk.Frame(root)
|
||||||
|
frame_nav.pack(pady=4)
|
||||||
|
|
||||||
|
lbl_page = tk.Label(frame_nav, font=("Segoe UI", 9))
|
||||||
|
lbl_page.pack(side="left", padx=10)
|
||||||
|
|
||||||
|
def show(n):
|
||||||
|
current[0] = n
|
||||||
|
img = render(n)
|
||||||
|
photo_ref[0] = ImageTk.PhotoImage(img)
|
||||||
|
lbl_img.config(image=photo_ref[0])
|
||||||
|
lbl_page.config(text=f"Strana {n + 1} / {page_count}")
|
||||||
|
btn_prev.config(state="normal" if n > 0 else "disabled")
|
||||||
|
btn_next.config(state="normal" if n < page_count - 1 else "disabled")
|
||||||
|
|
||||||
|
btn_prev = tk.Button(frame_nav, text="◄ Předchozí", command=lambda: show(current[0] - 1))
|
||||||
|
btn_prev.pack(side="left")
|
||||||
|
btn_next = tk.Button(frame_nav, text="Další ►", command=lambda: show(current[0] + 1))
|
||||||
|
btn_next.pack(side="left")
|
||||||
|
|
||||||
|
show(0)
|
||||||
|
root.update_idletasks()
|
||||||
|
root.geometry("+0+0")
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user