[DICOM] DICOM 통신의 시작 — C-Echo, C-Store, C-Find

 

Oleg Pianykh, Digital Imaging and Communications in Medicine (DICOM) (Springer, 2008) 정리 4편
참고 챕터: Ch 7.1 – 7.4

 

이번 글부터는 "그 데이터가 어떻게 네트워크를 타고 흘러가는가" 의 세계로 진입이라고 볼 수 있다. 

 

이번 편의 목표는

  • AE Title / IP / Port — 네트워크에서 DICOM 단위를 식별하는 법
  • SCU ↔ SCP 모델, DIMSE 프로토콜
  • 가장 기본 3개 서비스: C-Echo, C-Store, C-Find
  • 진짜 PACS 통신 모듈을 짜기 위한 토대

여기까지 알면 Orthanc 로그에서 일어나는 일을 80% 해석할 수 있다.


1. DICOM Networking의 위치

DICOM은 새로운 네트워크 프로토콜이 아니다. TCP/IP 위에서 도는 응용 계층 프로토콜이다.

┌─────────────────────────────────────┐
│    DIMSE (DICOM 응용 메시지)         │  ← 7-9편에서 다룰 것
├─────────────────────────────────────┤
│    DICOM Upper Layer (UL) Protocol   │  ← Association (6편)
├─────────────────────────────────────┤
│    TCP                                │
├─────────────────────────────────────┤
│    IP                                 │
├─────────────────────────────────────┤
│    Ethernet / WiFi                    │
└─────────────────────────────────────┘
  • HTTP가 80번 포트, SMTP가 25번 처럼
  • DICOM 기본 포트는 104번 (옛날에 NEMA가 받아둠)

흥미로운 역사: DICOM은 인터넷이 보급되기 부터 설계됐다. 옛 PS3.9는 "Point-to-Point 케이블 통신"용이었음. 지금은 폐기되고 TCP/IP만 사용.

 


2. AE / AE Title / IP / Port

2.1 AE (Application Entity)란?

⚠️ 헷갈리기 쉬운 포인트:
AE는 컴퓨터(장비)가 아니라 그 위에서 도는 "DICOM 애플리케이션" 이다.

 

한 PC에 여러 AE가 돌 수 있다.

PC 1대 (IP: 192.168.1.10)
  ├─ AE "ORTHANC_MAIN"     포트 4242
  ├─ AE "AI_INFERENCE"      포트 11112
  └─ AE "BACKUP_SCP"        포트 11113

→ 같은 IP라도 AE Title + Port 조합으로 구분.

 

2.2 AE를 식별하는 3종 세트

PACS 네트워크에서 어떤 AE와 통신하려면 3가지 정보가 필요하다:

항목 설명 예시
AE Title (AET) DICOM 상의 이름 (AE VR, 16자 이내) ORTHANC
IP Address 네트워크 주소 192.168.1.10
Port Number TCP 포트 4242 (또는 104)

 

💡 비유

  • 컴퓨터 이름 = 거리 이름
  • 포트 번호 = 집 번호
  • AE Title = 거기 사는 사람 이름

 

2.3 AE Title 명명 규칙

표준은 느슨하지만 실무 컨벤션:

  • 대문자, 영숫자, _ 만 사용 (PACSSERVER, CT_WORKSTATION1)
  • ❌ 공백, 한글, 특수문자, 소문자 ❌ (벤더마다 대소문자 구분이 다름)
  • 역할/위치를 의미있게 (MR1FLOOR, RADIOLOGY_ARCHIVE)
  • ❌ 의미 없는 이름 (AE1, XYZ) ❌

 

2.4 ⚠️ 양방향 등록 함정

표준상: AE X가 AE Y로 보내려면 X가 Y의 (IP/Port/AET)을 알면 됨.

현실: 많은 PACS가 양방향 등록 을 요구한다. Y도 X를 알아야 함.

❌ 문제 상황 예시
PACS 서버 Y의 화이트리스트에 AI 추론 앱 (X)이 등록 안 됨
→ AI 추론 앱이 보낸 C-Store를 PACS가 거부 ("Unknown AE")

 

🛠 PACS 모듈 개발 시 체크리스트:

  1. AI 추론 앱의 AE 정보를 PACS 벤더에게 알려서 등록 요청
  2. PACS의 AE/IP/Port를 AI 추론 앱 설정에 넣기
  3. C-Echo로 양방향 verify (둘 다 응답해야 함)

 

2.5 포트 번호 베스트 프랙티스

✅ 104 (DICOM 공식 포트)
✅ 11112 (pynetdicom 기본값, Orthanc 기본 DICOM port와 다름)
✅ 4242 (Orthanc DICOM 기본값)
✅ 10000번대 (충돌 적음)

❌ 80 (HTTP가 잡음)
❌ 22 (SSH)
❌ 25 (SMTP)

 

🔍 Orthanc의 흔한 포트 구성 (참고):

  • 4242: DICOM (DIMSE) 포트
  • 8042: HTTP REST API / 웹 UI
  • 둘 다 따로 설정함

 


3. SCU ↔ SCP 모델

DICOM의 모든 통신은 클라이언트–서버 구조다.

역할 의미
SCU (Service Class User) 서비스를 요청하는 쪽 (클라이언트)
SCP (Service Class Provider) 서비스를 제공하는 쪽 (서버)
   "이미지 저장해줘"
SCU ─────────────────→ SCP
              ← ─────  "저장 완료"

 

3.1 같은 AE가 양쪽 다 할 수 있다

한 AE가 여러 서비스에서 SCU, SCP 둘 다 가능. 흔한 패턴:

AE C-Echo C-Store C-Find C-Move
CT 장비 SCP SCU
PACS 서버 SCU/SCP SCU/SCP SCP SCP
판독 워크스테이션 SCU SCU SCU
AI 추론 앱 SCU/SCP SCU/SCP SCU SCU

 

🛠 PACS 모듈 개발 시:
AI 추론 앱은 보통 C-Move SCU(영상 가져오기) + C-Store SCP(가져온 영상 받기) + C-Store SCU(결과 다시 보내기) 의 세 역할을 동시에 함.

 


4. DIMSE — DICOM의 응용 메시지 프로토콜

DIMSE = DICOM Message Service Elements

AE끼리 주고받는 메시지의 표준 형식.

4.1 메시지 구조 — Command + (선택적) Data

┌─────────────────────────────┐
│   Command Object             │  ← 항상 있음 (group 0000)
│   - 어떤 서비스인지            │
│   - Message ID                │
│   - Status                    │
├─────────────────────────────┤
│   Data Object (선택)          │  ← 있을 때도, 없을 때도
│   - 실제 IOD (영상, 환자 정보 등)│
└─────────────────────────────┘

 

핵심 필드: (0000, 0800) Data Set Type

  • 0x0101 = NULL (Data 없음, Command만)
  • 그 외 = Data가 뒤따라옴

 

4.2 DIMSE-C vs DIMSE-N

  다루는 IOD 예시
DIMSE-C (Composite) Composite IOD (영상 등) C-Echo, C-Store, C-Find, C-Move, C-Get
DIMSE-N (Normalized) Normalized IOD N-Set, N-Get, N-Action, ... (Print, MPPS 등)

→ 거의 모든 영상 작업은 DIMSE-C. PACS 통신 시 99%가 이쪽.

 

4.3 Rq / Rsp 패턴

요청(Request)과 응답(Response)이 짝지어 다님:

  • C-Store-Rq (요청) ↔ C-Store-Rsp (응답)
  • Command Field 값으로 구분:
    • 0x0001 = C-Store-Rq
    • 0x8001 = C-Store-Rsp (Rsp는 항상 8___ 패턴)

 

4.4 Message ID — 요청/응답 짝맞추기

바쁜 PACS는 초당 수십 개의 DIMSE를 받음. Message ID 로 짝을 맞춤:

SCU                                SCP
 │                                  │
 │ C-Store-Rq  Msg ID = 42         │
 ───────────────────────────────→
 │                                  │
 │  Msg ID Being Responded To = 42  │
 │  C-Store-Rsp                     │
 ←───────────────────────────────

(0000, 0110) Message ID (요청) → (0000, 0120) Message ID Being Responded To (응답)

 


5. C-Echo — DICOM의 ping

5.1 왜 필요한가?

연결 확인은 일반 ping으로도 되지 않나? 안 된다. 이유는 

  1. ICMP ping은 TCP/IP만 확인 — DICOM 소프트웨어 안 돌고 있어도 성공
  2. AE Title, Port가 잘못돼도 ping은 성공
  3. 방화벽이 DICOM 포트만 막아도 ping은 성공

C-Echo만이 "이 AE가 진짜 DICOM 말 할 줄 안다" 를 증명 한다.

 

5.2 C-Echo 동작

SCU                                 SCP
 │                                   │
 │  C-Echo-Rq                        │
 │  (0000,0002) UID=1.2.840.10008.1.1│
 │  (0000,0100) Command=0x0030       │
 │  (0000,0110) MessageID=42         │
 │  (0000,0800) Data Set Type=0x0101 │
 ───────────────────────────────→
 │                                   │
 │  C-Echo-Rsp                       │
 │  (0000,0100) Command=0x8030       │
 │  (0000,0120) Resp to=42           │
 │  (0000,0900) Status=0x0000        │ ← 0 = 성공
 ←───────────────────────────────

구현해야 할 건 단 하나 — Message ID 생성/매칭. 나머지는 다 고정값.

 

5.3 C-Echo-Rq 바이트 덤프

실제 네트워크에는 68 byte가 흘러간다 (Implicit VR + Little Endian):

Byte # 의미
1–8 00 00 00 00 04 00 00 00 (0000,0000) Group Length 태그 + length=4
9–12 38 00 00 00 Group Length 값 = 56 (이후 바이트 수)
13–20 00 00 02 00 12 00 00 00 (0000,0002) + length=18
21–38 "1.2.840.10008.1.1\0" SOP Class UID (18 byte)
39–48 00 00 00 01 02 00 00 00 30 00 (0000,0100) + length=2 + value=0x0030
49–56 00 00 10 01 02 00 00 00 (0000,0110) + length=2
57–58 (생성한 ID) Message ID
59–66 00 00 00 08 02 00 00 00 (0000,0800) + length=2
67–68 01 01 Data Set Type = 0x0101 (NULL)

→ 정말 단순함. wireshark로 캡처하면 이대로 보임.

 

5.4 C-Echo 실패 시 트러블슈팅

증상 원인
Timeout 1. 네트워크 케이블/방화벽
  2. 잘못된 IP/Port
  3. SCP가 안 도는 중
0x0211 Unrecognized Operation SCP가 Verification SOP 미지원 (벤더 문제)
Association rejected AE Title이 화이트리스트에 없음

 

🛠 PACS 통신 디버깅의 첫 단계는 무조건 C-Echo. Orthanc UI에 "Test Echo" 버튼 있음. 거기서 시작.

 

5.5 ⚠️ Verification SCP 미지원 = 위험 신호

저자가 겪은 실제 사례:
한 CR 장비 벤더가 Verification SCP를 일부러 끄고 "보안" 이라고 주장.
C-Echo는 데이터 전송하지 않음. 보안과 무관. 그냥 게으름.
Verification SCP 안 되는 장비는 사지 말 것 (저자 추천).

 


6. SOP — Service-Object Pair 의 정확한 의미

SOP = DIMSE 서비스 + IOD 를 한 묶음으로 정의한 것.

SOP = "이 IOD에 이 서비스를 적용한다" 의 표준화된 단위

 

예시:

  • Verification SOP = C-Echo + (IOD 없음)
  • CT Image Storage SOP = C-Store + CT Image IOD
  • MR Image Storage SOP = C-Store + MR Image IOD
  • Study Root Query/Retrieve SOP = C-Find/C-Move + Patient-Study 키 IOD

각 SOP는 고유 UID 를 가짐:

  • 1.2.840.10008.1.1 = Verification SOP Class UID
  • 1.2.840.10008.5.1.4.1.1.2 = CT Image Storage SOP Class UID

 

💡 Conformance Statement는 SOP Class UID 목록의 형태로 작성 된다.
"우리 장비는 이런 SOP들을 지원하고, 어떤 건 SCU, 어떤 건 SCP다" 식으로.

 


7. C-Store — 영상 저장

가장 자주 쓰이는 서비스. 영상을 한 AE에서 다른 AE로 보낸다.

7.1 IOD 타입별 SOP Class UID

Modality / Type SOP Class UID
CR Image 1.2.840.10008.5.1.4.1.1.1
CT Image 1.2.840.10008.5.1.4.1.1.2
Enhanced CT 1.2.840.10008.5.1.4.1.1.2.1
MR Image 1.2.840.10008.5.1.4.1.1.4
Enhanced MR 1.2.840.10008.5.1.4.1.1.4.1
US Single Frame 1.2.840.10008.5.1.4.1.1.6.1
US Multiframe (cine) 1.2.840.10008.5.1.4.1.1.3.1
NM (Nuclear Medicine) 1.2.840.10008.5.1.4.1.1.20
PET 1.2.840.10008.5.1.4.1.1.128
Secondary Capture 1.2.840.10008.5.1.4.1.1.7
Basic Text SR 1.2.840.10008.5.1.4.1.1.88.11
Enhanced SR 1.2.840.10008.5.1.4.1.1.88.22
Comprehensive SR 1.2.840.10008.5.1.4.1.1.88.33

 

🛠 AI 추론 앱 입장:

  • 수신: MR Image Storage SOP (...1.4) — MR 받음
  • 송신: Secondary Capture (...1.7) 또는 Enhanced SR — 추론 결과 보냄

 

7.2 C-Store 한 번 = 영상 1개

핵심 규칙: 한 C-Store는 1개의 IOD instance만 보낸다.

CT 시리즈 2000장 → C-Store 2000번 호출

 

예외: Multi-frame IOD (US cine, multi-frame CT 등) 는 한 객체 안에 여러 frame 가능 → 1번 호출.
오버헤드는 좀 있지만 각 영상의 성공/실패를 개별 추적 할 수 있음.

 

7.3 C-Store-Rq 핵심 필드

Tag 내용 비고
(0000,0002) Affected SOP Class UID 위 표의 UID
(0000,0100) Command Field = 0x0001 C-Store-Rq
(0000,0110) Message ID  
(0000,0700) Priority low/med/high (대부분 무시)
(0000,0800) Data Set Type ≠ 0x0101 (데이터 있음)
(0000,1000) Affected SOP Instance UID 이미지의 UID
(0000,1030) Move Originator AET C-Move로 호출됐을 때만
(0000,1031) Move Originator Message ID 위와 같음

→ 그 뒤에 실제 영상 IOD (Data Object) 가 붙어서 전송됨.

 

7.4 C-Store-Rsp Status 값

Status 의미
0x0000 성공
0xFF00 진행 중
0xA700, 0xA900, 0xC000~ 에러 (벤더별 코드, Conformance 참조)

 

7.5 ⚠️ C-Store 실무 함정

저자 사례:

 

한 CR 장비가 Patient ID 빈 영상 1건의 C-Store가 reject되자 그 이후 모든 전송을 중단. 며칠 만에 로컬 디스크 가득 차서 환자 못 받음.

 

🛠 PACS 모듈 개발 원칙:

  1. 개별 실패가 전체 큐를 중단시키면 안 됨
  2. 실패 영상은 별도 큐로 빼고 나머지는 계속 진행
  3. 재시도는 backoff 로 (즉시 재시도 ❌)
  4. 모든 실패를 로그에 남김 (Status 코드, SOP Instance UID 포함)

 


8. C-Find — 영상 검색

PACS에서 "무엇이 있는지" 질의하는 서비스.

 

8.1 Query Root 3종 (사실상 2종)

SOP Class UID 비고
Patient Root Q/R Find 1.2.840.10008.5.1.4.1.2.1.1 Patient부터
Study Root Q/R Find 1.2.840.10008.5.1.4.1.2.2.1 Study부터 (가장 흔함)
Patient-Study Root 1.2.840.10008.5.1.4.1.2.3.1 Retired (사용 금지)

현대 PACS의 99%는 Study Root. 환자 단위가 아니라 검사 단위로 워크플로우가 돌기 때문.

 

8.2 Query Level — 4계층 어디서 검색?

3편의 Patient→Study→Series→Image 4계층. C-Find는 이 중 어느 레벨에서 검색할지 명시:

(0008, 0052) Query/Retrieve Level    CS
값: "PATIENT" | "STUDY" | "SERIES" | "IMAGE"

 

→ 보통 단계별 drill-down:

  1. STUDY 레벨에서 환자/날짜로 검색 → Study UID 목록
  2. 그 Study UID + SERIES 레벨로 검색 → Series UID 목록
  3. 그 Series UID + IMAGE 레벨로 검색 → SOP Instance UID 목록

 

8.3 매칭 방식 4가지

방식 예시 설명
Wildcard Smit* * = 임의 문자열, ? = 한 글자
List Smith\Graham \ = OR
Universal "" (빈값) 모든 값 매칭 (필드를 받아오고 싶을 때)
Range 20060101-20070101 - = 범위 (날짜/시간용)

 

8.4 C-Find IOD — 자주 쓰는 필드

Tag 이름 예시 매칭
(0008,0052) Query Level STUDY 필수
(0010,0010) Patient Name Smit*\Grah* Wildcard + List
(0010,0020) Patient ID 12345 보통 정확 매칭
(0008,0020) Study Date 20260101-20260601 Range
(0008,0030) Study Time 080000-180000 Range
(0008,0050) Accession Number ABC789 정확 매칭 (RIS 연동)
(0020,000D) Study Instance UID   정확 매칭
(0008,0061) Modalities in Study MR\CT List
(0008,1030) Study Description *chest* Wildcard
(0010,0030) Birth Date 19560101-19860101 Range

 

⚠️ Universal Matching의 미묘함:

  • 빈 값으로 보냄 → "이 필드 매칭하고 응답에 값 받고 싶음"
  • 필드 자체를 안 보냄 → "필요 없음"
  • 둘이 다름! 응답에서 받고 싶은 필드는 빈 값이라도 반드시 포함 해야 함.

 

8.5 ⚠️ Modality 함정

대부분 사람들이 "Modality는 Study의 속성"으로 생각하지만:

  • (0008, 0060) Modality실제로는 Series 레벨 속성
  • (0008, 0061) Modalities in Study → Study 레벨 (옵션, 지원 안 하는 PACS 많음)

→ Study Root에서 modality로 필터하고 싶으면:

  1. Modalities in Study 시도 (안 될 수 있음)
  2. 안 되면 Study 받아서 → Series 레벨 추가 쿼리

 

8.6 C-Find Response — Multi-response 패턴

C-Find는 응답이 여러 번 온다.

SCU                                     SCP
 │                                       │
 │  C-Find-Rq                            │
 ────────────────────────────────────→
 │                                       │
 │  C-Find-Rsp  Status=0xFF00 (pending)  │
 │  + IOD #1                             │
 ←────────────────────────────────────
 │  C-Find-Rsp  Status=0xFF00 (pending)  │
 │  + IOD #2                             │
 ←────────────────────────────────────
 │  ...                                  │
 │                                       │
 │  C-Find-Rsp  Status=0x0000 (done)     │
 │  (마지막, IOD 없음)                    │
 ←────────────────────────────────────
  • 매치마다 Status=0xFF00 (pending) + IOD 1개
  • 마지막에 Status=0x0000 (success) + IOD 없음
  • 매치 0건이면 첫 응답이 곧장 success
  • 에러 시 다른 Status 코드

 

8.7 C-Cancel — 검색 중단

너무 광범위한 쿼리(e.g., wildcard도 안 주고 검색)는 영원히 안 끝남.

  • C-Cancel-Find (Command = 0x0FFF) 로 중단
  • (0000, 0120) 에 중단할 메시지 ID 적음

 

🛠 PACS 모듈 개발 시: Find 함수에는 timeout + max_results 가드 필수. 50,000개 결과 받다가 메모리 터지는 사고 흔함.

 

8.8 C-Find 에러 흔한 원인

  1. AE 미등록 — SCU AET가 SCP 화이트리스트에 없음 (제일 흔함)
  2. Hierarchical 규칙 위반 — Series 검색하면서 Study UID 안 줌
  3. Query Root 미스매치 — Study Root만 지원하는데 Patient Root SOP로 요청
  4. C-Find SCP 미지원 — 일부 modality는 C-Store SCU만 함 (Find 안 받음)

 


9. AI 추론 앱 관점 — 실전 시나리오

9.1 시나리오: 추론 큐 처리

1. 의사가 PACS UI에서 "AI 분석" 버튼 클릭
   → PACS가 AI 추론 앱으로 C-Store (MR 시리즈)

2. AI 추론 앱 (= C-Store SCP) 영상 수신
   → 로컬 디스크 저장 → AI 모델 추론 시작

3. 추론 완료 → Secondary Capture IOD 생성
   - Patient ID: 원본 유지
   - Study UID: 원본 유지       ← 같은 검사 하위로 묶이게
   - Series UID: 새로 생성
   - SOP Class UID: 1.2.840.10008.5.1.4.1.1.7 (SC)

4. AI 추론 앱 (= C-Store SCU)
   → PACS로 C-Store
   → PACS UI에서 원본 옆에 "AI 결과" 시리즈로 보임

 

9.2 시나리오: AI 추론 앱이 직접 PACS 쿼리

1. AI 추론 앱 UI에서 환자 검색
   → PACS로 C-Find (Study Root)
   - Query Level: STUDY
   - Patient ID: "12345"
   - Modality: "MR"
   - Study Date: "20260601-"

2. PACS가 C-Find-Rsp 여러 번 응답
   → AI 추론 앱 UI에 Study 목록 표시

3. 사용자가 특정 Study 선택
   → PACS로 C-Move (5편에서)

 

9.3 Orthanc 사용 시 추상화 레벨

Orthanc를 쓰면 위 작업이 HTTP REST 호출로 간략화:

# C-Find (Study Root) → REST
import requests

r = requests.post(
    "http://orthanc:8042/modalities/PACS/query",
    json={"Level": "Study",
          "Query": {"PatientID": "12345",
                    "ModalitiesInStudy": "MR"}}
)
query_id = r.json()["ID"]

# 결과 조회
studies = requests.get(
    f"http://orthanc:8042/queries/{query_id}/answers"
).json()

→ Orthanc가 내부적으로 진짜 DICOM C-Find를 PACS에 보냄.
하지만 디버깅하려면 결국 위 표들을 알아야 한다. Orthanc 로그에 똑같이 나옴.

 


10. 핵심 정리 

개념 한 줄
AE DICOM 애플리케이션 (≠ 컴퓨터). AET + IP + Port 3종 세트로 식별
SCU/SCP 클라이언트/서버. 한 AE가 양쪽 다 가능
DIMSE DICOM 응용 메시지 프로토콜. Command + (옵션) Data
DIMSE-C / DIMSE-N Composite IOD용 / Normalized IOD용. 거의 다 C 씀
Rq / Rsp 요청/응답. Message ID로 짝맞춤. Rsp는 Command Field 0x8___
C-Echo DICOM ping. 연결 확인. 데이터 없음. 디버깅의 시작
SOP DIMSE + IOD 묶음. 각 SOP는 고유 UID
C-Store 영상 저장. IOD 종류마다 다른 SOP Class UID
C-Find 영상 검색. Study Root (...2.2.1) 가 표준
Query Level PATIENT / STUDY / SERIES / IMAGE
매칭 방식 Wildcard, List, Universal, Range
Multi-response C-Find는 매치마다 응답. 마지막은 Status=0x0000

 


11. 다음 글에서 다룰 것

C-Find로 "무엇이 있는지" 알아냈으니, 이제 실제로 가져와야 한다.

5편: C-Move, C-Get, Modality Worklist

  • C-Move — 가장 흔한 영상 가져오기 (C-Store를 trigger)
  • C-Get — 직접 가져오기 (덜 쓰이지만 단순함)
  • C-Move vs C-Get 비교
  • Modality Worklist (MWL) — RIS에서 환자 일정 받기
  • AI 추론 앱 영상 수신 파이프라인 완성

여기까지가 DICOM 통신 서비스의 본진. 6편 Association이 그 통신을 떠받치는 인프라.

 


참고

  • Pianykh, O.S. Digital Imaging and Communications in Medicine (DICOM). Springer, 2008.
  • 공식 표준 매핑:
    • PS3.4 (Service Class Specifications) — Verification, Storage, Q/R 정의
    • PS3.7 (Message Exchange) — DIMSE 메시지 구조, Command Field 코드
  • 라이브러리:
    • Python: pynetdicom (DICOM 네트워크), pydicom (DICOM 파일)
    • 구현 시 거의 표준
  • Orthanc 관련: