From ccf84aa2820785a0884c7178b2536fdeeb39e45f Mon Sep 17 00:00:00 2001 From: "vladimir.buzalka" Date: Wed, 20 May 2026 08:45:15 +0200 Subject: [PATCH] z230 --- Medevio/60 ScansProcessing/corrections.json | 44 +++++++ Medevio/60 ScansProcessing/naming_rules.md | 8 +- Medevio/70 DěleníSouboruPDF/NOTES.md | 1 + Medevio/70 DěleníSouboruPDF/rozdelit_pdf.py | 123 ++++++++++++-------- 4 files changed, 127 insertions(+), 49 deletions(-) diff --git a/Medevio/60 ScansProcessing/corrections.json b/Medevio/60 ScansProcessing/corrections.json index 0c14197..3da1c9a 100644 --- a/Medevio/60 ScansProcessing/corrections.json +++ b/Medevio/60 ScansProcessing/corrections.json @@ -1378,5 +1378,49 @@ { "original": "split_013.pdf", "corrected": "1006055083 2026-05-18 Šmíd, Jiří [EKG] [bez hodnocení].pdf" + }, + { + "original": "5955100272 2022-06-29 Čulíková, Hana [LZ endokrinologie] [autoimunitní thyreoiditida ve fázi hypotyreózy, autoprotilátky pozit., substituce, arteriální hypertenze, dyslipidémie, zvýšená glykémie nalačno].pdf", + "corrected": "5955100272 2022-06-29 Čulíková, Hana [LZ endokrinologie] [autoimunitní thyreoiditida ve fázi hypotyreózy, autoprotilátky pozit., substituce, arteriální hypertenze, dyslipidémie, zvýšená glykémie nalačno, ko +3m].pdf" + }, + { + "original": "5955100272 2024-04-10 Čulíková, Hana [LZ endokrinologie] [seropozitivní AI thyreoiditida ve fázi hypotyreózy, substituce, arteriální hypertenze, dyslipidémie, zvýšená glykémie nalačno, osteopénie femur/krček/předloktí].pdf", + "corrected": "5955100272 2024-04-10 Čulíková, Hana [LZ endokrinologie] [seropozitivní AI thyreoiditida ve fázi hypotyreózy, substituce, arteriální hypertenze, dyslipidémie, zvýšená glykémie nalačno, osteopénie femurkrčekpředloktí].pdf" + }, + { + "original": "5955100272 Čulíková, Hana split_009.pdf", + "corrected": "5955100272 2026-05-19 Čulíková, Hana [EKG] [bez hodnocení].pdf" + }, + { + "original": "6804160990 2025-10-02 Pokorný, Bohumil [LZ chirurgie] [st.p. kýla klasicky 2008, TAPP 2018, tlaky a bolest L tříslo, recidivu nehmatám, sono + CT třísla, ko urol/ortop/koloskopie].pdf", + "corrected": "6804160990 2025-10-02 Pokorný, Bohumil [LZ chirurgie] [st.p. kýla klasicky 2008, TAPP 2018, tlaky a bolest L tříslo, recidivu nehmatám, sono + CT třísla, ko urolortopkoloskopie].pdf" + }, + { + "original": "7261142658 2025-06-18 Šindelářová, Věra [LZ imunologie] [časté resp. infekce po COVID 11/2021, prick testy neg., spirometrie v normě, FeNO 19 PPB, ANA neg., IgE 19.8].pdf", + "corrected": "7261142658 2025-06-18 Šindelářová, Věra [LZ imunologie] [časté resp. infekce po COVID 112021, prick testy neg., spirometrie v normě, FeNO 19 PPB, ANA neg., IgE 19.8].pdf" + }, + { + "original": "315620122 2025-12-19 Tesaříková, Květuše [LZ endokrinologie] [hypotyreóza, TSH 2.514, fT4 17.84, Euthyrox 50ug, ekzém DKK, DEXA 5/2026].pdf", + "corrected": "315620122 2025-12-19 Tesaříková, Květuše [LZ endokrinologie] [hypotyreóza, TSH 2.514, fT4 17.84, Euthyrox 50ug, ekzém DKK, DEXA 52026].pdf" + }, + { + "original": "315620122 2026-01-14 Tesaříková, Květuše [LZ kardiologie] [EF 65%, lehká porucha diastol. funkce, mitrální regurgitace 3/4, hraniční PK].pdf", + "corrected": "315620122 2026-01-14 Tesaříková, Květuše [LZ kardiologie] [EF 65%, lehká porucha diastol. funkce, mitrální regurgitace 34, hraniční PK, ko JUN2026].pdf" + }, + { + "original": "515803110 2025-12-16 Rygerová, Lenka [Laboratoř] [dg. J069 - CKD-EPI CHRIG5, leukocyty 13.1, MCH 27.4, trombocyty 521, RF 24, ESR 78].pdf", + "corrected": "515803110 2025-12-16 Rygerová, Lenka [Laboratoř] [dg. J069 - CKD-EPI CHRIG2, leukocyty 13.1, MCH 27.4, trombocyty 521, RF 24, ESR 78].pdf" + }, + { + "original": "6860090446 2025-12-15 Diepoldová, Kateřina [Laboratoř] [dg. K29 - CKD-EPI CHRIG3b, MCV 80.0 (↓), MCH 27.8 (↓)].pdf", + "corrected": "6860090446 2025-12-15 Diepoldová, Kateřina [Laboratoř] [dg. K29 - CKD-EPI CHRIG2, MCV 80.0 (↓), MCH 27.8 (↓)].pdf" + }, + { + "original": "7153180551 2025-12-15 Šamšová, Irena [Laboratoř] [dg. J069 - Anti HAV total 25.22 IU/L (↑), pacient s ochrannou hladinou protilátek].pdf", + "corrected": "7153180551 2025-12-15 Šamšová, Irena [Laboratoř] [dg. J069 - Anti HAV total 25.22 IUL (↑), pacient s ochrannou hladinou protilátek].pdf" + }, + { + "original": "9959130423 2025-12-17 Sládková, Aneta [Laboratoř] [dg. N309 - E. coli 10E6 CFU/ml, citlivá na ampicilin, cefuroxim, cotrimoxazol, nitrofurantoin, fosfomycin].pdf", + "corrected": "9959130423 2025-12-17 Sládková, Aneta [Laboratoř] [dg. N309 - E. coli 10E6 CFUml, citlivá na ampicilin, cefuroxim, cotrimoxazol, nitrofurantoin, fosfomycin].pdf" } ] \ No newline at end of file diff --git a/Medevio/60 ScansProcessing/naming_rules.md b/Medevio/60 ScansProcessing/naming_rules.md index 785a8f0..ed780a1 100644 --- a/Medevio/60 ScansProcessing/naming_rules.md +++ b/Medevio/60 ScansProcessing/naming_rules.md @@ -13,7 +13,13 @@ Tato pravidla platí vždy při generování polí `poznamka` a `nazev_souboru`. - Pokud datum hospitalizace nelze určit, druhou závorku napiš bez datumu. 3. Když je dokument typ "Laboratoř", do `poznamka` uváděj POUZE hodnoty mimo normu (patologické nálezy) — hodnoty v normě vynech. **Osmolalitu séra (Osmolalita, Osm, osmolality) NIKDY nezmiňuj — ani když je mimo normu, ani v jakékoli zkratce.** Toto je absolutní výjimka: osmolalita se do názvu souboru ani do poznámky nepíše nikdy za žádných okolností. Chybně: `C_Osmolalita 293 (↑)` — správně: tuto hodnotu zcela vynech. -4. Pokud laboratorní výsledky obsahují glomerulární filtraci — bývá označena jako eGFR, CKD-EPI nebo CK-EPI — do `poznamka` nikdy nepiš číselnou hodnotu eGFR. Místo toho uveď pouze klasifikaci: eGFR ≥ 90 → CHRIG1, 60–89 → CHRIG2, 45–59 → CHRIG3a, 30–44 → CHRIG3b, 15–29 → CHRIG4, < 15 → CHRIG5. Klasifikaci uváděj pouze pokud je CHRIG2 nebo horší (tj. eGFR < 90) — CHRIG1 je v normě, nezmiňuj ho. +4. Pokud laboratorní výsledky obsahují glomerulární filtraci — bývá označena jako eGFR, CKD-EPI nebo CK-EPI — do `poznamka` nikdy nepiš číselnou hodnotu eGFR. Místo toho uveď pouze klasifikaci dle stadií CHRIG1–CHRIG5. + - **Jednotka:** Nejprve zkontroluj jednotku uvedenou v laboratoři: + - Pokud je hodnota v **ml/s** nebo **ml/sec** (typicky malá čísla jako 0.8, 1.14, 1.5…), přenásob ×60 pro převod na ml/min. + - Pokud je hodnota v **ml/min** nebo **ml/min/1.73m²** (typicky velká čísla jako 55, 68, 90…), použij přímo. + - **Klasifikace** (v ml/min/1.73m²): ≥ 90 → CHRIG1, 60–89 → CHRIG2, 45–59 → CHRIG3a, 30–44 → CHRIG3b, 15–29 → CHRIG4, < 15 → CHRIG5. + - Prahové hodnoty pro orientaci při jednotce ml/s: ≥ 1.50 → G1, 1.00–1.49 → G2, 0.75–0.99 → G3a, 0.50–0.74 → G3b, 0.25–0.49 → G4, < 0.25 → G5. + - Klasifikaci uváděj pouze pokud je CHRIG2 nebo horší (tj. eGFR < 90 ml/min nebo < 1.50 ml/s) — CHRIG1 je v normě, nezmiňuj ho. 5. Když je dokument typ "Laboratoř" a zpráva obsahuje diagnózu (dg., dg:, diagnóza), umísti ji do `nazev_souboru` jako první část druhé závorky, tedy: `[Laboratoř] [dg. XY00 - stručná poznamka]`. 6. Zkratky a pojmenování: slovo „sono" (sonografie/ultrazvuk) piš vždy malými písmeny — `sono břicha`, `sono ŠŽ`, nikoli `SONO`. Štítnou žlázu označuj vždy zkratkou `ŠŽ`. Sonografii prsu/prsů (sono mamm., sono mamografie, sono mamma apod.) piš vždy jako `sono prsů`. Denzitometrii (DEXA, DXA, denzitometrie) piš vždy pouze jako `[DXA]` — bez prefixu LZ. Algologii piš vždy jako `[LZ léčba bolesti]`. Dermatovenerologii (dermatologie, dermatovenerologie, kožní) piš vždy jako `[LZ kožní]`. Angiologii piš vždy jako `[LZ cévní]`. 7. V číselných hodnotách VŽDY používej desetinnou tečku, nikoli desetinnou čárku. Toto pravidlo platí absolutně pro všechna čísla v `poznamka` i `nazev_souboru` — např. `TG 4.73`, nikoli `TG 4,73`. diff --git a/Medevio/70 DěleníSouboruPDF/NOTES.md b/Medevio/70 DěleníSouboruPDF/NOTES.md index a425a56..e2f661f 100644 --- a/Medevio/70 DěleníSouboruPDF/NOTES.md +++ b/Medevio/70 DěleníSouboruPDF/NOTES.md @@ -29,6 +29,7 @@ Pro vývoj: `TESTOVANI = True` + `PATH_TO_TESTFILE` na začátku skriptu. | `/` | otočit CCW (counterclockwise) | | `*` | otočit CW (clockwise) | | `Del` / `.` | smaž stránku (vynech z exportu) | +| `0` | znovu spustí OCR na aktuální stránce (smaže cache pro tuto stránku) | | `Enter` | exportuj všechny skupiny | | `Esc` | konec | diff --git a/Medevio/70 DěleníSouboruPDF/rozdelit_pdf.py b/Medevio/70 DěleníSouboruPDF/rozdelit_pdf.py index d523460..f5e9049 100644 --- a/Medevio/70 DěleníSouboruPDF/rozdelit_pdf.py +++ b/Medevio/70 DěleníSouboruPDF/rozdelit_pdf.py @@ -12,6 +12,7 @@ Numerická klávesnice: 5 / Space přepni hranici pacienta před touto stránkou 8 / Up přesuň stránku doleva (swap) 2 / Down přesuň stránku doprava (swap) + 0 znovu spustí OCR na aktuální stránce (smaže cache pro tuto stránku) - výběr pacienta ručně z Medicusu Enter exportuj všechny skupiny do Split/ Esc konec @@ -271,63 +272,76 @@ class OcrWorker: def stop(self): self._stop.set() - def _run(self): + def _ocr_page(self, i: int): + """Spustí OCR pipeline pro stránku i a uloží výsledek do self.results.""" import pytesseract pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH - n = len(self.doc) - for i in range(n): + page = self.doc[i] + mat = fitz.Matrix(2.0, 2.0) # 144 DPI — dostatečné pro OCR + pix = page.get_pixmap(matrix=mat, colorspace=fitz.csRGB) + img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) + + # 1. Tesseract + rc = None + tess_text = None + try: + tess_text = pytesseract.image_to_string(img, lang="ces") + rc = _extract_rc(tess_text) + except Exception as e: + print(f"[OCR str.{i+1}] Tesseract: {e}") + + # 2. Medicus — první pokus + medicus = _verify_medicus(rc) if rc else None + + # 3. Claude Vision — když Tesseract nenašel RČ, nebo našel ale Medicus nezná + claude_raw = None + claude_usage = None + if not rc or (medicus and medicus.get("status") == "not_found"): + try: + rc_claude, claude_raw, claude_usage = self._claude_rc(img) + if rc_claude: + medicus_claude = _verify_medicus(rc_claude) + if medicus_claude.get("status") in ("ok", "fuzzy"): + print(f"[OCR str.{i+1}] Claude opravil RČ: {rc} → {rc_claude}") + rc = rc_claude + medicus = medicus_claude + elif not rc: + rc = rc_claude + medicus = medicus_claude + except Exception as e: + print(f"[OCR str.{i+1}] Claude: {e}") + + result = { + "rc": rc, + "medicus": medicus, + "tesseract_text": tess_text, + "claude_raw": claude_raw, + "claude_usage": claude_usage, + } + with self._lock: + self.results[i] = result + + def _run(self): + for i in range(len(self.doc)): if self._stop.is_set(): break if i in self.results: continue # cache hit - - page = self.doc[i] - mat = fitz.Matrix(2.0, 2.0) # 144 DPI — dostatečné pro OCR - pix = page.get_pixmap(matrix=mat, colorspace=fitz.csRGB) - img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) - - # 1. Tesseract - rc = None - tess_text = None - try: - tess_text = pytesseract.image_to_string(img, lang="ces") - rc = _extract_rc(tess_text) - except Exception as e: - print(f"[OCR str.{i+1}] Tesseract: {e}") - - # 2. Medicus — první pokus - medicus = _verify_medicus(rc) if rc else None - - # 3. Claude Vision — když Tesseract nenašel RČ, nebo našel ale Medicus nezná - claude_raw = None - claude_usage = None - if not rc or (medicus and medicus.get("status") == "not_found"): - try: - rc_claude, claude_raw, claude_usage = self._claude_rc(img) - if rc_claude: - medicus_claude = _verify_medicus(rc_claude) - if medicus_claude.get("status") in ("ok", "fuzzy"): - print(f"[OCR str.{i+1}] Claude opravil RČ: {rc} → {rc_claude}") - rc = rc_claude - medicus = medicus_claude - elif not rc: - rc = rc_claude - medicus = medicus_claude - except Exception as e: - print(f"[OCR str.{i+1}] Claude: {e}") - - result = { - "rc": rc, - "medicus": medicus, - "tesseract_text": tess_text, - "claude_raw": claude_raw, - "claude_usage": claude_usage, - } - self.results[i] = result + self._ocr_page(i) self._save_cache() self.on_page_done(i) + def rerun_page(self, page_idx: int, on_done): + """Znovu spustí OCR pro jednu stránku (ignoruje cache). Volá on_done(page_idx) po dokončení.""" + def _worker(): + with self._lock: + self.results.pop(page_idx, None) + self._ocr_page(page_idx) + self._save_cache() + on_done(page_idx) + threading.Thread(target=_worker, daemon=True).start() + def _claude_rc(self, img: Image.Image) -> tuple[Optional[str], Optional[str], Optional[dict]]: import anthropic, base64 @@ -633,7 +647,7 @@ class SplitterUI: "1/3: přesuň stránku " "/: otočit ↺CCW *: otočit ↻CW " "Del/.: smaž stránku " - "-: vyber pacienta ručně " + "0: znovu OCR -: vyber pacienta ručně " "Enter: exportuj Esc: konec" ) self.bot_label = tk.Label( @@ -738,6 +752,7 @@ class SplitterUI: 103: "num7", 105: "num9", 97: "num1", 99: "num3", 110: "numdot", 111: "numslash", 106: "numstar", 109: "numminus", + 96: "num0", } action = numpad.get(kc) or { "Left": "num4", "Right": "num6", @@ -748,6 +763,7 @@ class SplitterUI: "KP_Divide": "numslash", "KP_Multiply": "numstar", "slash": "numslash", "asterisk": "numstar", "KP_Subtract": "numminus", "minus": "numminus", + "Insert": "num0", "KP_Insert": "num0", }.get(ks) if action == "num4": @@ -772,6 +788,8 @@ class SplitterUI: self._delete_page() elif action == "numminus": self._open_patient_picker() + elif action == "num0": + self._rerun_ocr_current() elif ks in ("Return", "KP_Enter"): self._export() elif ks == "Escape": @@ -843,6 +861,15 @@ class SplitterUI: PatientPickerDialog(self.root, on_select) + def _rerun_ocr_current(self): + page_idx = self.page_order[self.cursor] + self.ocr_results.pop(page_idx, None) + self._redraw() + self.ocr_worker.rerun_page( + page_idx, + on_done=lambda idx: self.root.after(0, self._on_ocr_done, idx), + ) + def _update_boundaries_around(self, pos: int): """Přidá/odstraní hranice kolem pozice pos podle potvrzených pacientů.""" def confirmed_rc(p: int) -> Optional[str]: