[DICOM] 파일 포맷, 보안/익명화, DICOMweb, Orthanc 실전

Oleg Pianykh, Digital Imaging and Communications in Medicine (DICOM) (Springer, 2008) 정리 7편
참고 챕터: Ch 10, Ch 11 + 책에 없는 PS3.18 (DICOMweb)

 

 

시리즈 마무리 글

  • DICOM 파일 포맷DICM 헤더, Group 0002, DICOMDIR
  • 보안 & 익명화 — HIPAA, 환자 데이터 보호의 함정
  • DICOMweb — 책에 없는 현대 PACS의 표준
  • Orthanc 실전 통합 시나리오 — AI 추론 앱 전체 파이프라인
  • 시리즈 정리 + 다음 학습 방향

 


1. DICOM 파일 포맷 구조

1.1 전체 레이아웃

┌──────────────────────────────────────┐
│  Preamble (128 bytes, 보통 모두 0)    │
├──────────────────────────────────────┤
│  Magic: "DICM" (4 bytes)              │
├──────────────────────────────────────┤
│  File Meta Information (group 0002)   │  ← Explicit VR 강제
│  - Transfer Syntax UID                │
│  - SOP Class UID                      │
│  - SOP Instance UID                   │
│  - ...                                │
├──────────────────────────────────────┤
│  Data Object (group 0008 이상)         │  ← Meta에서 명시한 Transfer Syntax로
│  - Patient Name, ID, ...              │
│  - Image Pixel Data                   │
│  - ...                                │
└──────────────────────────────────────┘

 

1.2 Preamble (128 byte)

  • 첫 128 byte는 자유롭게 사용
  • 표준은 내용을 지정 안 함
  • 대부분의 구현이 그냥 0으로 채움
  • TIFF 등 다른 포맷도 같은 관행

 

💡 일부 응용에서 활용 가능: 예를 들어 이 영역에 small thumbnail BMP를 넣어두면 비-DICOM 뷰어에서도 미리보기 가능. 거의 안 쓰임.

 

1.3 "DICM" Magic Header

  • byte 129~132: 정확히 'D' 'I' 'C' 'M' 4글자
  • 이게 DICOM 파일 식별의 유일한 신뢰 가능한 방법
  • 파일 확장자도, UID도, 이름도 다 못 믿음
def is_dicom_file(path):
    with open(path, 'rb') as f:
        f.seek(128)
        return f.read(4) == b'DICM'

 

⚠️ 옛 ACR-NEMA 2.0 파일은 DICM 헤더 없음. 점점 드물어짐.

 

1.4 File Meta Information (Group 0002)

byte 133부터 시작. Explicit VR Little Endian으로 강제 인코딩.

Tag 이름 VR 필수 비고
(0002, 0000) Group Length UL group 0002 전체 길이
(0002, 0001) File Meta Version OB 0x00 0x01
(0002, 0002) Media Storage SOP Class UID UI 이 파일이 어떤 IOD인지
(0002, 0003) Media Storage SOP Instance UID UI 이 인스턴스의 UID
(0002, 0010) Transfer Syntax UID UI 데이터 영역 인코딩 방식
(0002, 0012) Implementation Class UID UI 누가 만들었나
(0002, 0013) Implementation Version Name SH ≤16자! (6편 §7.2 함정)
(0002, 0016) Source AET AE 어느 AE가 마지막 수정
(0002, 0100) Private Info Creator UID UI  
(0002, 0102) Private Information OB  

 

(0002, 0010) Transfer Syntax UID가 가장 중요. 이 값에 따라 그 뒤 Data Object를 Implicit/Explicit/압축 중 어떤 걸로 읽을지 결정.

 

1.5 Data Object (group 0008 이상)

  • 우리가 2편에서 본 그 객체
  • Meta의 Transfer Syntax UID에 따라 인코딩
  • group 0008부터 시작 (group 0002 = meta, group 0004 = directory)

 

⚠️ 함정: 많은 DICOM 구현이 group 0002는 Explicit으로 잘 읽다가 Data 영역에서 Transfer Syntax 전환을 깜빡해서 깨짐.

 

1.6 ⚠️ 파일 이름 함정

DICOM 표준 PS3.10이 정의하는 File ID:

  • 컴포넌트 8개, 각 8자 이내
  • 대문자/숫자/언더스코어만 (A-Z 0-9 _)
  • 컴포넌트는 \ 로 구분 (예: DIR1\SUBDIR\ABC123)

 

문제 1: 백슬래시 \ 는 DICOM 와일드카드!

"DIR1\SUBDIR\ABC123" = ?
  → 파일 경로?
  → 아니면 "DIR1 OR SUBDIR OR ABC123" ?
  → DICOM 소프트웨어가 헷갈림. 흔한 버그.

 

해결: 내부에서는 / 로 통일.

 

문제 2: .dcm 확장자

  • 표준에 명시 안 됨 (어떤 곳은 prohibited, 어떤 곳은 required)
  • 실무에선 다 쓰지만 공식적으로 모호함

 

문제 3: 실무에서는 SOP Instance UID를 파일명으로 쓰는 게 흔함

1.2.840.10008.234.2354.437345.79086.dcm
1.2.840.10008.5.1.4.1.1.4.20260601.103014.0042.dcm
  • 장점: 항상 unique
  • 단점: 너무 김 (Windows 경로 제한 260자 부딪힐 수 있음)

 

🛠 AI 추론 앱 / Orthanc 시 권장:

  1. 파일명에 환자 정보 절대 노출 X (보안)
  2. SOP Instance UID 기반 (전역 유일)
  3. 디렉토리 구조로 정리: {StudyUID}/{SeriesUID}/{SOPInstanceUID}.dcm

 


2. DICOMDIR — 미디어 인덱스 파일

2.1 무엇인가

DVD/CD/USB 같은 이동식 미디어에 DICOM 영상을 담을 때 함께 들어있는 인덱스 파일.

DICOM_CD/
├── DICOMDIR          ← 이 파일이 인덱스
├── IMAGES/
│   ├── STUDY01/
│   │   ├── SERIES01/
│   │   │   ├── IMG001
│   │   │   ├── IMG002
│   │   │   └── ...
│   │   └── SERIES02/
│   └── STUDY02/
└── ...

DICOM CD를 PC에 넣었을 때 뷰어가 "환자 / 검사 / 시리즈 / 영상 목록" 을 트리로 보여주는 게 DICOMDIR을 읽어서 만든 것.

 

2.2 구조

DICOMDIR도 하나의 DICOM 파일. group 0002 + Basic Directory IOD.

DICOMDIR (자체도 DICOM 파일)
├── Preamble + DICM
├── Group 0002 (File Meta)
└── Data Object (Basic Directory IOD)
    ├── (0004, 1130) File Set ID
    ├── (0004, 1200) Offset of first root record
    ├── (0004, 1220) Directory Record Sequence (SQ)
    │   ├── Item 1: PATIENT record
    │   ├── Item 2: STUDY record
    │   ├── Item 3: SERIES record
    │   ├── Item 4: IMAGE record (파일 위치 포함)
    │   ├── Item 5: IMAGE record
    │   └── ...
    └── ...

 

2.3 Directory Record 종류

(0004, 1430) Directory Record Type:

  • PATIENT
  • STUDY
  • SERIES
  • IMAGE ⭐ — 실제 파일 위치 포함
  • OVERLAY, WAVEFORM, SR DOCUMENT, ...

각 IMAGE record에는:

  • (0004, 1500) Referenced File ID — 파일 경로
  • (0004, 1510) Referenced SOP Class UID
  • (0004, 1511) Referenced SOP Instance UID
  • (0004, 1512) Referenced Transfer Syntax UID

→ DICOMDIR만 읽어도 어떤 환자 / 검사가 들어있는지 빠르게 파악 가능.

 

2.4 AI 추론 앱에서의 활용

병원 외부에서 받은 CD/DVD에 담긴 영상을 AI 추론 앱이 처리해야 할 때:

import pydicom

# DICOMDIR 읽기
dcmdir = pydicom.dcmread("D:/DICOMDIR")

# 환자/검사/시리즈/영상 트리 순회
for patient_record in dcmdir.patient_records:
    print(f"Patient: {patient_record.PatientName}")
    for study in patient_record.children:
        print(f"  Study: {study.StudyDate}")
        for series in study.children:
            print(f"    Series: {series.Modality}")
            for image in series.children:
                # 실제 파일 위치
                rel_path = "/".join(image.ReferencedFileID)
                file_path = f"D:/{rel_path}"
                ds = pydicom.dcmread(file_path)
                # AI 추론 입력으로 사용

 

🛠 Orthanc 사용 시: REST API로 DICOMDIR 자동 생성 가능

GET /studies/{id}/media   → DICOMDIR + 영상 zip

 

2.5 한계

  • DICOMDIR 작성에는 모든 영상이 사전에 다 있어야 함 (length 미리 계산)
  • 영상 추가/삭제 시 DICOMDIR 다시 빌드 필요
  • 실무에서는 자동 생성에 맡기는 게 정신건강에 좋음 (Orthanc, dcm4chee 등)

 


3. DICOM 보안 — 평문 그대로 노출

3.1 ⚠️ 충격적 사실: DICOM 파일은 사실상 평문

저자의 hacking 데모. 메모장(WordPad)으로 열어도 환자 정보가 보임

(binary garbage) DICM (binary)
... SMITH^JOHN ... 19560423 ... M ... PATIENT123 ... DR.KIM ...

 

이름, ID, 생년월일이 그냥 ASCII로 박혀 있음.

 

3.2 평문 = 변조 가능

저자가 시연

  1. WordPad에서 DICOM 파일 열기
  2. SMITH^JOE 검색
  3. 같은 길이(9자)로 다른 이름 입력 (BETH^MARY)
  4. 저장 → DICOM 뷰어에서 환자 이름이 바뀌어 보임

→ VR length가 변하지 않게 같은 길이로 바꾸면 DICOM 구조 그대로 유지되면서 데이터 변조됨. 어떤 DICOM 검증도 못 잡음.

 

3.3 ⚠️ 픽셀 추출도 단순함

저자의 또 다른 시연:

  • CT/MR 영상이 보통 256×256 또는 512×512, 1 or 2 byte/pixel
  • 파일 크기 보고 차원 추측
  • 파일 끝에서 N×N×bytes 만큼 잘라내면 그게 픽셀
  • BMP로 변환 가능

→ DICOM 파일은 암호화되지 않으면 평문. PACS 시스템 전체를 보호망으로 둘러싸야 함.

 


4. Workflow 보안 — 환경 단단히

4.1 기본 원칙 (HIPAA 베스트)

  1. 전용 서버에 격리 — 영상 서버에 다른 서비스 X
  2. 물리적 보안 — 잠긴 방, 출입 통제
  3. 백업 매일 — 영상 손실은 회복 불가
  4. 다른 의료기관과 서버 공유 절대 금지
  5. 사용자별 권한 분리 — 각자 자기 데이터만
  6. VPN + 방화벽
  7. 자동 화면 잠금 (15분 idle)

 

4.2 VPN vs 방화벽 — 자주 헷갈림

  방화벽 VPN
보호 범위 단일 컴퓨터 두 컴퓨터 간 통신 전체
통신 자체 암호화
외부 접근 차단

 

방화벽만으로는 부족. 데이터가 공용망을 지나가면 평문 노출. VPN 필수.

 

4.3 ❌ 안티패턴

저자가 본 사고:

  • "그냥 우리 서버에 로그인해서 봐주세요" — 가장 흔하고 가장 위험
  • "옛날 DOS 컴퓨터가 보안이 낫다" — 정반대. 권한 관리 자체가 없음
  • "컴퓨터를 안 쓰면 보안이 좋다" — 잠긴 문 없는 은행

 

4.4 ⭐ HIPAA가 정의한 18가지 PHI (Protected Health Information)

DICOM에서 제거/익명화 대상:

  1. 이름 (Name)
  2. 지리 정보 (주보다 작은 단위, 우편번호 포함)
  3. 날짜 (생년월일, 입원, 퇴원, 검사 등 모든 날짜)
  4. 전화번호
  5. 팩스
  6. 이메일
  7. 사회보장번호
  8. 의료기록번호
  9. 보험 수혜자 번호
  10. 계좌번호
  11. 자격증/면허 번호
  12. 차량 식별번호
  13. 장비 시리얼 번호
  14. URL
  15. IP 주소
  16. 생체 식별자 (지문, 음성)
  17. 얼굴 전체 사진 (CT/MR head로 3D 재구성 가능!)
  18. 기타 식별 가능한 모든 코드

 

⚠️ 17번이 의외의 함정. Head CT/MR을 3D rendering하면 얼굴 복원됨. 진정한 익명화 어려움.

 


5. 익명화 (Anonymization) ⭐⭐⭐

PACS 통신 모듈에서 가장 중요한 보안 작업.

 

5.1 익명화 정의

원본 파일에서 PHI를 제거하거나 변환해 환자 식별 불가능하게 만드는 작업.

  • 비가역적 (irreversible) — 일반적으로 원본 복원 불가
  • 그래서 암호화보다 안전: 해커도 없는 데이터는 못 뽑음
  • 단점: 임상 가치 손상 가능성

 

5.2 단순 wipe의 함정

❌ 잘못된 익명화: Patient ID를 빈 값으로

(0010, 0020) Patient ID = ""

 

결과:

  • DICOM 표준: Patient ID는 Type 1 (필수 + 값 필수)
  • 빈 값 = 와일드카드로 해석될 수도 (3편 §2.2)
  • 결과: 모든 환자가 한 명으로 merge → "Mr. Unknown" 사고

 

5.3 일관성 (Consistency) 함정

❌ 잘못된 익명화: 같은 환자를 다르게 매핑

Day 1:  Patient ID "12345" → "anon_001"
Day 2:  Patient ID "12345" → "anon_487"   ← 다른 값!

결과: 같은 환자의 시간순 검사가 분리 → 추적 불가, 임상 가치 0.

 

✅ 올바른 익명화: 결정론적 매핑 (deterministic)

# 같은 원본 → 항상 같은 가명
import hashlib

def anonymize_patient_id(original_id, salt):
    h = hashlib.sha256((original_id + salt).encode()).hexdigest()
    return f"ANON_{h[:12]}"

anonymize_patient_id("12345", salt="my_secret")
# → 항상 "ANON_a8f3c4d2e9b1"

→ 매번 같은 결과. 같은 환자 = 같은 가명. 다른 환자 = 다른 가명.

 

5.4 충돌 함정

해시 출력 공간이 좁으면 다른 환자 → 같은 가명 가능.

# ❌ 위험: 16비트만 사용 → 65,536명 넘으면 collision
short_id = h[:4]

# ✅ 안전: 충분히 긴 prefix
long_id = h[:16]   # 16^16 가지

 

5.5 픽셀에 박힌 정보 (Burned-in PHI) ⚠️

US, 일부 X-ray는 픽셀 데이터에 환자 이름이 그려져 있음.

┌──────────────────────────────────┐
│  PATIENT: SMITH^JOHN             │  ← 이 부분이 픽셀!
│  ID: 12345                       │  ← Tag로는 못 지움
│  DATE: 2026-06-10                │
│ ┌──────────────────────────────┐ │
│ │                              │ │
│ │      [Ultrasound]            │ │
│ │                              │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────┘

 

태그만 익명화하면 픽셀의 텍스트는 그대로 남음.

 

해결책:

  • 단순: 상단 N픽셀을 검은색으로 (US 헤더 영역) → 임상 정보 손실 위험
  • OCR로 텍스트 영역 인식 → 자동화 어려움
  • 메타데이터 (0028, 0301) Burned In Annotation = "YES" 가 있는지 확인 + 사람 검수

 

🛠 실무 추천: Burned-in 가능 modality (US, 일부 X-ray) 는 반드시 사람 검수 단계 거치기.

 

5.6 임상 가치 균형

저자의 노하우: HIPAA 18가지를 다 지우면 임상 데이터로 못 씀.

Tag HIPAA 임상적 가치 권장
Patient Name 낮음 익명화
Patient ID 낮음 결정론적 매핑
Birth Date 나이 계산에 필요 연도만 남기거나 나이로 변환
Patient Age 중간 보통 남김
Study Date 시계열 추적에 필수 남기는 게 표준 (저자 권장)
Study Description ⚠️ 높음 (예: "Prostate MR Protocol") 남김
Patient Weight 임상 중요 남김
Patient Comments ⚠️ 가변 검토 후 결정
Referring Physician 낮음 제거
Institution Name 낮음 제거 또는 일반화

 

💡 저자: "Study Date를 지우면 진단 데이터로서 가치가 사라진다. read radiologist에게는 남기되, 공개 시에는 월 단위로 jitter (예: ±15일 random shift, 환자별 일관)."

 

5.7 ⚠️ 안 좋은 패턴 — 사설 태그로 숨기기

저자가 발견한 어처구니 없는 익명화 도구:

원본 (0010, 0010) Patient Name = "SMITH^JOHN"
→ 익명화 후:
  (0010, 0010) = "ANON_X1"        ← 가명
  (0099, 1010) = "SMITH^JOHN"     ← 원본을 사설 태그에 숨김 ⚠️

WordPad로 열면 그대로 보임. "숨기기"는 보안이 아니다.

 

5.8 권장 익명화 도구

표준 + 검증된 도구:

  • DICOM PS3.15 Annex E — 공식 익명화 가이드라인
  • gdcmanon (GDCM의 익명화) — 오픈소스
  • dcm4che dcm2dcm + 익명화 프로파일
  • pydicom-anonymizer (간단한 Python 도구)
  • Orthanc 익명화 API

 

Orthanc 익명화 예시

import requests

# POST: Study 단위 익명화
r = requests.post(
    "http://orthanc:8042/studies/{study_id}/anonymize",
    json={
        "Replace": {
            "PatientName": "ANON_001",
            "PatientID": "ANON_001"
        },
        "Keep": [
            "StudyDescription",
            "SeriesDescription",
            "StudyDate"     # 임상 가치 위해 유지
        ],
        "KeepPrivateTags": False,   # 사설 태그 제거
        "Force": True,
        "DicomVersion": "2021b"      # PS3.15 프로파일 버전
    }
)

# 결과: 새 익명 study가 생성됨
new_study_id = r.json()["ID"]

 

Orthanc는 PS3.15 Annex E 표준 프로파일을 자동 적용. 안전한 default.

 


6. 암호화 (Encryption)

6.1 익명화 vs 암호화

  익명화 암호화
가역성 비가역 가역 (키 있으면 복원)
목적 정보 자체 제거 정보 숨김
임상 손실 가능 없음
위험 키 분실 시 영구 손실 키 분실 시 복호화 불가

 

6.2 알고리즘 — DICOM이 채택한 것들

알고리즘 종류 용도
RSA 비대칭 키 교환, 디지털 서명
AES 대칭 대용량 데이터 암호화 (현대 표준)
DES, 3DES 대칭 (구식, 점점 비추)
SHA-1/256/512 해시 무결성 검증

 

6.3 공개키/개인키 — 돼지저금통 비유 ⭐

저자의 비유:

  • 돼지저금통: 동전 넣기는 쉬움, 꺼내기는 키 없으면 어려움
  • 같은 원리: 공개키로 암호화는 쉬움, 개인키 없이 복호화는 사실상 불가능

 

수학적 기반: 큰 소수 인수분해

  • 두 큰 소수 A × B = N (곱셈은 빠름)
  • N에서 A, B 찾기 (인수분해는 매우 느림)
  • A를 알려주면 B = N / A (나눗셈은 빠름)

→ 이게 RSA의 핵심.

 

6.4 SHA 무결성 검증

원본 데이터 → SHA-256 → 짧은 hash 문자열 (32 byte)

데이터 + hash 를 같이 전송
받는 쪽에서 다시 SHA-256 돌려서 일치 확인
→ 1바이트라도 변조되면 hash 완전히 다름

 

5편 §8.1의 Storage Commitment와 결합 가능: "받았는데 무결한가?"

 

6.5 디지털 서명 vs 스캔 서명

디지털 서명 ≠ 스캔된 사인 이미지.

❌ 가짜 디지털 서명:
  - 사인을 사진으로 찍어서 PDF에 붙이기
  - "Signed by: 김의사" 텍스트
  - 단순 체크박스

✅ 진짜 디지털 서명:
  - 인증기관(CA)에서 발급한 인증서
  - 개인키로 서명
  - 공개키로 검증
  - 변조 시 무효화
  - 서명 시점/위치/이유 기록

 

DICOM PS3.15 Annex C에서 정의. SR(Structured Report) 에 서명 첨부 가능.

 

6.6 Secure DICOM File Format (Ch 10.2.2)

PS3.10이 정의하는 보안 파일 포맷:

  • 전체 DICOM 파일을 하나의 암호화된 객체로 래핑
  • 키 없이는 group 0002조차 못 봄
  • 디지털 서명 첨부 가능

 

⚠️ 현실: 거의 모든 PACS가 미지원. 표준이 너무 모호. VPN + 안전한 저장 환경으로 보안하는 게 표준 패턴.

 


7. DICOMweb — 책에 없지만 현대 PACS의 표준 ⭐

Pianykh 책 2008년판에는 없음. 하지만 현대 PACS 통신에서 빠질 수 없는 표준 (PS3.18, 2011~).

 

7.1 등장 배경

DIMSE의 한계 (6편 §13):

  • 점-대-점 정적 등록 (AE Title + IP + Port 사전 등록)
  • 방화벽 친화적이지 않음 (특히 C-Move)
  • 모바일/웹/클라우드와 잘 안 맞음

DICOMweb의 해결:

  • HTTP REST API 기반
  • 정적 등록 불필요
  • 방화벽 통과 (HTTPS 사용)
  • JSON 응답 가능 (XML/DICOM도 가능)

 

7.2 3개 핵심 서비스

DIMSE DICOMweb 역할
C-Store STOW-RS (Store Over the Web) 영상 업로드
C-Find QIDO-RS (Query based on ID for DICOM Objects) 검색
C-Move / C-Get WADO-RS (Web Access to DICOM Objects) 다운로드

 

7.3 QIDO-RS — 검색

GET /studies?PatientID=12345&StudyDate=20260601-
Accept: application/dicom+json

 

응답 (JSON):

[
  {
    "0020000D": {"vr": "UI", "Value": ["1.2.840.xxx"]},
    "00100010": {"vr": "PN", "Value": [{"Alphabetic": "SMITH^JOHN"}]},
    "00100020": {"vr": "LO", "Value": ["12345"]},
    "00080020": {"vr": "DA", "Value": ["20260601"]},
    "00080060": {"vr": "CS", "Value": ["MR"]}
  },
  ...
]

JSON으로 응답하니까 웹 프론트엔드에서 바로 사용 가능.

 

7.4 WADO-RS — 다운로드

Study 통째로

GET /studies/{StudyUID}
Accept: multipart/related; type="application/dicom"

→ Multipart 응답으로 모든 영상 받음.

 

단일 영상 메타데이터만 (JSON)

GET /studies/{StudyUID}/series/{SeriesUID}/instances/{SOPUID}/metadata
Accept: application/dicom+json

 

픽셀만 (rendered PNG/JPEG)

GET /studies/.../instances/.../rendered?window=400,40
Accept: image/jpeg

브라우저에서 바로 표시 가능! 별도 DICOM 디코더 불필요.

 

7.5 STOW-RS — 업로드

POST /studies
Content-Type: multipart/related; type="application/dicom"

(여러 DICOM 파일을 multipart로 첨부)

 

7.6 DICOMweb의 장점

                  DIMSE                    DICOMweb
                ────────                  ──────────
연결            점-대-점 정적 등록         HTTP 호출 즉시
인증            AE Title whitelist         OAuth2, JWT, Bearer Token
방화벽          C-Move 특히 곤란            HTTPS 1포트만
프론트엔드      DICOM 라이브러리 필요       JSON + rendered JPEG
스케일링        제한적                     로드밸런서 / CDN
캐싱            없음                      HTTP 캐시
모니터링        custom                    표준 HTTP 로그

 

7.7 Orthanc의 DICOMweb 플러그인

Orthanc는 공식 DICOMweb 플러그인 제공:

// orthanc.json
{
  "Plugins": [
    "/usr/share/orthanc/plugins/libOrthancDicomWeb.so"
  ],
  "DicomWeb": {
    "Enable": true,
    "Root": "/dicom-web/",
    "EnableMetadata": true,
    "PublicRoot": "https://my-hospital.com/dicom-web/"
  }
}

 

→ 자동으로

  • /dicom-web/studies (QIDO-RS)
  • /dicom-web/studies/{uid}/series/... (WADO-RS)
  • POST /dicom-web/studies (STOW-RS)

엔드포인트 활성화.

 

7.8 ⚠️ DIMSE를 안 버리는 이유

  • 기존 PACS / modality의 90%가 DIMSE만 지원
  • DICOMweb 지원 PACS는 신형/고급
  • 현실의 통합 모듈은 DIMSE + DICOMweb 둘 다 지원해야 함

 

🛠 Orthanc는 양쪽 다 동시 지원. DIMSE로 받고 DICOMweb으로 노출 가능.

 


8. AI 추론 앱 전체 통합 시나리오 (실전)

이제까지 본 모든 개념을 종합한 현실적 배포 아키텍처.

 

8.1 아키텍처 다이어그램

                      ┌─────────────────────────────────┐
                      │       병원 내부 네트워크          │
                      │                                  │
   ┌───────────┐      │   ┌─────────────────────────┐  │
   │           │      │   │                          │  │
   │  CT/MR    │─DIMSE│   │   Hospital PACS          │  │
   │ Scanner   │─────→│   │   (DIMSE + DICOMweb)     │  │
   │           │      │   │                          │  │
   └───────────┘      │   └────────┬────────────────┘  │
                      │            │                    │
                      │            │ DIMSE (C-Store/Move)│
                      │            │ 또는 DICOMweb        │
                      │            ↓                    │
                      │   ┌─────────────────────────┐  │
                      │   │     Orthanc (게이트웨이)   │  │
                      │   │                          │  │
                      │   │  ┌─────────────────────┐ │  │
                      │   │  │  DIMSE SCP (4242)    │ │  │
                      │   │  │  DICOMweb (8042)     │ │  │
                      │   │  └─────────────────────┘ │  │
                      │   │                          │  │
                      │   │  Lua / Python plugin     │  │
                      │   │  └─→ AI 추론 트리거        │ │  │
                      │   └────────┬────────────────┘  │
                      │            │                    │
                      │            ↓                    │
                      │   ┌─────────────────────────┐  │
                      │   │   AI 추론 앱              │  │
                      │   │   (nnU-Net, etc.)        │  │
                      │   │                          │  │
                      │   │  ┌─────────────────────┐ │  │
                      │   │  │ Preprocessing       │ │  │
                      │   │  │ Inference (GPU)     │ │  │
                      │   │  │ Postprocessing      │ │  │
                      │   │  │ DICOM SC/SR 생성     │ │  │
                      │   │  └─────────────────────┘ │  │
                      │   └────────┬────────────────┘  │
                      │            │                    │
                      │            │ 결과 업로드          │
                      │            ↓                    │
                      │   ┌─────────────────────────┐  │
                      │   │     Orthanc             │  │
                      │   │  → DIMSE C-Store        │  │
                      │   │  → Hospital PACS         │  │
                      │   └──────────────────────────┘  │
                      └─────────────────────────────────┘

 

8.2 단계별 흐름

Step 1: 의사 트리거

PACS UI 또는 별도 시스템에서 "이 환자 AI 분석" 클릭.

 

Step 2: PACS → Orthanc로 영상 전달

방법 A (DIMSE):

PACS → DIMSE C-Store → Orthanc:4242
SOP Class UID: 1.2.840.10008.5.1.4.1.1.4 (MR)
Transfer Syntax: 1.2.840.10008.1.2 (Implicit VR LE)

 

방법 B (DICOMweb):

PACS → STOW-RS POST /dicom-web/studies → Orthanc:8042

 

Step 3: Orthanc Lua/Python 훅 → AI 트리거

-- orthanc.json: "LuaScripts": ["./on_store.lua"]
function OnStoredInstance(instanceId, tags, metadata)
    -- 어떤 modality + body part?
    if tags['Modality'] == 'MR' and
       string.find(tags['StudyDescription'] or '', 'PROSTATE') then
        -- AI 추론 앱 호출
        os.execute('curl -X POST http://ai-app:8080/infer?instance=' .. instanceId)
    end
end

 

Step 4: AI 추론

# AI 추론 앱 (Python)
@app.post("/infer")
def infer(instance: str):
    # Orthanc에서 영상 다운로드 (DICOMweb)
    r = requests.get(
        f"http://orthanc:8042/instances/{instance}/file",
        auth=("user", "pass")
    )
    ds = pydicom.dcmread(io.BytesIO(r.content))

    # 추론용 numpy array
    pixel_array = ds.pixel_array

    # 추론
    result = ai_model(pixel_array)
    segmentation_mask = result['mask']

    # Secondary Capture IOD 생성
    sc = create_secondary_capture(
        pixel_array=segmentation_mask,
        patient_id=ds.PatientID,            # 원본 유지
        study_uid=ds.StudyInstanceUID,      # 원본 유지!
        series_uid=generate_uid(),          # 새로 생성
        sop_instance_uid=generate_uid(),    # 새로 생성
        sop_class_uid="1.2.840.10008.5.1.4.1.1.7"  # SC
    )

    # Orthanc에 다시 업로드 → PACS로 자동 전달
    requests.post(
        "http://orthanc:8042/instances",
        data=sc.tobytes(),
        headers={"Content-Type": "application/dicom"}
    )

 

Step 5: Orthanc → PACS로 결과 송신

Orthanc에 자동 라우팅 규칙 설정

-- 결과 SC가 들어오면 자동 PACS 전송
function OnStoredInstance(instanceId, tags, metadata)
    if tags['Modality'] == 'SC' and
       string.find(tags['SeriesDescription'] or '', 'AI_RESULT') then
        SendToModality(instanceId, 'HOSPITAL_PACS')
    end
end

 

Step 6: PACS UI에 결과 표시

PACS UI에서 환자 study 펼치면:

SMITH^JOHN — 2026-06-10 — Prostate MR
├── Series 1: T2W axial
├── Series 2: DWI
├── Series 3: ADC
└── Series 99: AI_RESULT (Secondary Capture)   ← AI 추론 결과

 

8.3 익명화가 필요한 시나리오

연구 데이터로 외부 공유 시

# Orthanc 익명화 API
r = requests.post(
    f"http://orthanc:8042/studies/{study_id}/anonymize",
    json={
        "Replace": {
            "PatientName": deterministic_anonymize(name, salt),
            "PatientID": deterministic_anonymize(pid, salt)
        },
        "Keep": ["StudyDescription", "SeriesDescription"],
        "KeepPrivateTags": False,
        "Force": True
    }
)
# r.json()["Path"] = 익명화된 새 study의 URL

 

8.4 최종 체크리스트

배포 전 점검

☐ Orthanc + AI 앱이 분리된 네트워크 세그먼트
☐ Orthanc ↔ PACS DIMSE 양방향 등록 완료
☐ C-Echo 양방향 verify 통과
☐ Transfer Syntax 협상 OK (Implicit LE + 압축 옵션)
☐ Max PDU Length 조정 (필요시 65536)
☐ Orthanc DICOMweb 활성화
☐ AI 결과 SOP Class UID 올바름 (SC 또는 SR)
☐ 결과의 Patient/Study UID 원본 유지
☐ 결과의 Series/SOP Instance UID 새로 생성
☐ 익명화 (연구 데이터인 경우)
☐ 모든 통신 VPN 또는 HTTPS
☐ Orthanc 인증 활성화 (RegisteredUsers)
☐ 로그 보존 + 외부 SIEM 연동
☐ 백업 정책
☐ 장애 시 fallback (큐, 재시도, 알림)

 


9. 시리즈 마무리 — 전체 그림

큰 그림으로 정리한다.

 

9.1 7편 매핑

주제 핵심 개념
1편 DICOM 입문 + VR 27가지 VR, Endian, 짝수 길이
2편 Tag, Dictionary, Encoding (group,element), Implicit/Explicit, SQ
3편 4계층 + IOD Patient/Study/Series/Image, Module → IE → IOD
4편 C-Echo, C-Store, C-Find AE Title, SCU/SCP, DIMSE
5편 C-Move, C-Get, MWL Q/R, SC/SR (AI 결과 출력 포맷)
6편 Association ⭐ Abstract/Transfer Syntax 협상, PDU, 디버깅 본진
7편 파일, 보안, DICOMweb, Orthanc DICM 헤더, 익명화, REST API

 

9.2 DICOM의 모순

아름다운 점:

  • 1990년대 설계가 2026년에도 작동
  • 객체지향 모델 (IOD/SOP)
  • 모든 modality를 하나의 표준으로

 

불편한 점:

  • 한 표준이 16권 문서로 분산
  • 점-대-점 정적 등록의 한계
  • "No reason given" 거부 메시지
  • Implementation Version Name 16자 같은 사소한 함정
  • 시간대 미지원
  • 익명화 vs 임상 가치의 영원한 트레이드오프

 

9.3 책에서 다 못 다룬 것 (별도 학습 권장)

주제 학습 자료
DICOMweb DICOM PS3.18, dicomweb.org
DICOM SEG (Segmentation 전용 SOP) PS3.3 Annex A.51
DICOM SR Templates David Clunie의 책
IHE Profiles (워크플로우 통합) ihe.net
HL7 FHIR + DICOM FHIRcast
DICOM 표준 매뉴얼 (공식) dicomstandard.org

 

9.4 다음 단계 — 실전으로

이 시리즈가 이론 + 개념까지 줬다면, 다음은:

  1. Orthanc 직접 설치 + 가지고 놀기
    • DIMSE C-Echo, C-Store 보내보기
    • DICOMweb 호출해보기
    • Lua 스크립트로 자동화
  2. pynetdicom 으로 작은 SCU/SCP 직접 짜보기
    • 책의 표를 코드로 옮겨보기
  3. Wireshark로 DIMSE 패킷 보기
    • A-Associate-RQ/AC 까보기
    • 6편의 PDU 구조가 진짜로 그렇게 생겼나 확인
  4. PACS 벤더의 실제 Conformance Statement 읽기
    • 시리즈 전체 개념이 어떻게 명세되어 있는지
  5. DICOM 익명화 도구 비교
    • PS3.15 Annex E 프로파일
    • Orthanc vs gdcm vs custom

 


10. 핵심 정리 (전체)

┌─────────────────────────────────────────────────────────────────┐
│                        DICOM의 전체 구조                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  데이터 계층                                                       │
│  ├── VR (27가지)                          ─── 1편                 │
│  ├── Tag (group, element)                  ─── 2편                 │
│  ├── Encoding (Implicit/Explicit)          ─── 2편                 │
│  ├── Patient/Study/Series/Image            ─── 3편                 │
│  └── IOD (Information Object Definition)   ─── 3편                 │
│                                                                  │
│  서비스 계층                                                       │
│  ├── SOP = DIMSE + IOD                     ─── 4편                 │
│  ├── C-Echo / C-Store / C-Find             ─── 4편                 │
│  ├── C-Move / C-Get / MWL                  ─── 5편                 │
│  └── Storage Commitment / SR / SC          ─── 5편                 │
│                                                                  │
│  통신 인프라                                                        │
│  ├── Association                           ─── 6편                 │
│  ├── Abstract / Transfer Syntax            ─── 6편                 │
│  ├── Presentation Context                  ─── 6편                 │
│  └── PDU                                   ─── 6편                 │
│                                                                  │
│  파일 + 보안                                                        │
│  ├── DICOM File Format                     ─── 7편                 │
│  ├── DICOMDIR                              ─── 7편                 │
│  ├── 익명화 / 암호화                          ─── 7편                 │
│  └── DICOMweb (현대 표준)                    ─── 7편                 │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

DICOM은 이론은 단순하지만 실무가 까다로운 표준이다.

 


참고

  • Pianykh, O.S. Digital Imaging and Communications in Medicine (DICOM). Springer, 2008.
  • 공식 표준:
    • PS3.10 — Media Storage and File Format
    • PS3.11 — Media Storage Application Profiles
    • PS3.12 — Media Formats and Physical Media
    • PS3.15 — Security and System Management Profiles ⭐
    • PS3.18 — Web Services (DICOMweb) ⭐
  • 라이브러리/도구:
    • Orthanc — DICOM 서버 + DICOMweb 게이트웨이
    • pydicom — Python DICOM 파일 처리
    • pynetdicom — Python DIMSE
    • dcm4che — Java DICOM 도구
    • GDCM — C++ DICOM 라이브러리 + 익명화
  • 참고 사이트:
  • 학습 자료:
    • Clunie, D. DICOM Structured Reporting (2000)
    • IHE Profiles (워크플로우 통합)
    • NEMA의 DICOM webinar / Working Group 문서