🧬 연구 허브

Skill

pdf-vision-extract 코드 참조

페이지를 통째로 넣지 않고 항목 단위로 잘라 확대해 Vision LLM 에 넣는다.

언제 쓰나

표·카드가 빽빽한 PDF를 구조화 데이터로 바꿔야 하는데 텍스트 추출만으로는 배치가 무너질 때. 병리 아웃컴북, 논문 표, 공고책자 모두 해당한다.

정본 코드

~/Documents/혜연/허초프로젝트/scripts/
  crop_regbook_cards.py     100줄  요법 카드 단위로 자르기
  crop_geupyeo_criteria.py  107줄  표의 '행' 단위로 자르기
  merge_regbook_vision.py          Vision 결과 정규화
  apply_crop_review.py             사람 검토 반영
extraction/regbook_vision/crops/   잘린 이미지
extraction/geupyeo_vision/crops/

핵심 — 페이지를 통째로 넣지 마라

페이지 전체를 한 번에 읽으면 조밀한 작은 숫자(200↔1000)를 오독할 수 있어, 카드별로 잘라 확대하면 용량 판독 정확도가 올라간다.

의료 문서에서 200mg과 1000mg을 헷갈리는 건 치명적이다. 항목 단위로 자르고 2배 확대해서 넣는다. 호출 수는 늘지만 정확도가 다른 문제가 된다.

경계를 어떻게 찾나 — 문서마다 다르다

두 스크립트가 서로 다른 단서를 쓴다. 그 문서의 시각적 규칙을 먼저 관찰하고 거기 맞춘다.

1) 색상으로 제목 찾기 (regimen book)

카드 제목·코드가 청록색(teal) 글씨다.

sat = im.max(axis=2) - im.min(axis=2)       # 채도
colored = (sat > 25) & (im.max(axis=2) < 230)  # 유채색이면서 너무 밝지 않은 픽셀

무채색 본문 사이에서 유채색 행만 골라내면 제목 위치가 나온다. 행 간격 6px 이상 벌어지면 다른 제목으로 끊는다.

2) 괘선으로 카드 끝 찾기

가로 괘선(폭 넓은 어두운 1~2px 줄) 감지 → 괘선 아래가 '여백'이면 카드 끝(cut), '텍스트'면 제목 밑줄(무시)

괘선이 다 경계는 아니다. 아래에 뭐가 있는지를 봐야 구분된다.

3) 좌표로 표의 행 찾기 (공고책자)

pdftotext -bbox 로 단어별 좌표를 받아 특정 컬럼(x >= 페이지폭 * 0.40)의 글자를 순서대로 이어붙이고, 찾는 텍스트가 어디 있는지로 y범위를 구한다. 그 y범위만 잘라낸다.

이미지와 좌표를 함께 쓴다. 이미지만 보면 어느 행이 어느 레코드인지 모르고, 좌표만 보면 배치가 안 보인다.

좌표는 캐시한다

_bc = {}
def words(pg):
    if pg not in _bc:
        o = subprocess.run(["pdftotext","-bbox","-f",str(pg),"-l",str(pg),PDF,"-"], ...)

pdftotext 를 페이지마다 새로 부르면 느리다. 크롭을 여러 번 다시 돌리게 되므로 페이지 단위 캐시가 필수다.

오탐을 규칙으로 걸러라

용량줄을 카드 경계로 오인하는 문제를 정규식으로 막는다.

_DOSE = re.compile(r"\d[\d,\.]*\s*(?:mg|mcg|µg|ug|g\b|iu|units?|million)\b|mg/m|mg/kg|\bAUC\s*\d|on\s*D\d", re.I)
IV/PO/every N 같은 단독 토큰은 제목(IV-TBC 등)에도 있어 제외.

단서가 제목에도 나타나는지 반드시 확인한다. 안 하면 제목을 용량줄로 오인해 카드가 잘못 쪼개진다.

Vision 결과는 반드시 정규화한다

LLM이 뱉는 JSON은 같은 필드가 회차마다 형태가 다르다. merge_regbook_vision.py 가 하는 일:

문제처리
references 가 문자열/딕셔너리 혼재{citation, pmid} 로 통일
dose 가 숫자/문자 혼재문자열로 통일
요법 단위 cycle_noteschedules[].cycle 로 이동
출처·검토 필요 여부source_type · needs_review 부여

needs_review 를 처음부터 붙인다. 나중에 붙이려면 어떤 게 자동이고 어떤 게 사람이 본 건지 구분이 안 된다.

사람 검토를 데이터로 되먹인다

크롭 결과를 사람이 보고 crop_review_decisions.json 에 적으면 apply_crop_review.py 가 반영한다. 크롭 규칙은 반드시 몇 %가 틀리므로 재크롭해도 사람 판단이 살아남는 경로를 만든다 — regimen-extract 의 교정 반영 패턴과 같다.

함정

  • DPI를 상수로 두고 좌표 변환에 쓴다(R_DPI=170, PT=72.0/R_DPI / DPI=200, SC=DPI/72.0). PDF 좌표는 72dpi 기준이라 렌더 DPI와 섞이면 전부 어긋난다.
  • 한 암종·한 페이지 범위로 먼저 스키마를 확정하고 넓힌다.
  • 정본 스크립트는 BASE 경로가 박혀 있고 os.chdir 한다. 옮겨 쓸 때 가장 먼저 고칠 곳이다.

연관 skill

regimen-extract(이 크롭을 쓰는 파이프라인), excel-case-validator