Oleg Pianykh, Digital Imaging and Communications in Medicine (DICOM) (Springer, 2008) 정리 6편
참고 챕터: Ch 9
이전 편에서 우리는 얼버무리고 넘어온 것이 있다.
"두 AE가 통신하려면 먼저 Association 을 맺어야 한다"
이번 6편은 그 Association의 정체를 다룬다.
PACS 연동에서 가장 자주 마주치는 에러들
Association rejected (no reason given)Abstract syntax not supportedTransfer syntaxes not supportedImplementation version name too longCalling AE not in whitelist
이 모든 게 Association 협상 단계에서 일어난다.
저자가 말한 그대로
"DICOM association failures account for the vast majority of all DICOM networking problems."
이 글이 끝나면 Orthanc/pynetdicom 로그를 보고 직접 해석할 수 있다.
1. Association이란?
1.1 DICOM Upper Layer (DICOM UL)
DICOM Association은 TCP/IP 위에서 도는 DICOM 전용 통신 계층.
┌─────────────────────────────────────┐
│ DIMSE (Service messages) │ ← 4-5편
├─────────────────────────────────────┤
│ DICOM Upper Layer (UL) │ ← 6편 (이번 글)
│ = Association 협상 + PDU 전송 │
├─────────────────────────────────────┤
│ TCP / IP │
└─────────────────────────────────────┘
TCP는 그냥 "바이트 스트림 옮기는 통로"일 뿐.
DICOM UL이 그 위에
- "우리가 통신할 수 있는 사이인지" 협상하고
- "어떤 형식으로 데이터를 주고받을지" 합의하고
- "실제 데이터를 PDU 단위로 흘려보낸다.
1.2 Association = "DICOM 핸드셰이크"
전화 비유:
- TCP 연결 = 전화선이 깔림
- Association 협상 = "여보세요, ○○입니다. △△ 하려고요" + "네, 가능합니다"
- DIMSE 메시지 = 실제 대화
- Association 종료 = "끊을게요" + "네 끊으세요"
Association 협상 실패하면 그 뒤 통신은 절대 안 됨. TCP는 살아있어도 의미 없음.
2. Association의 3막 (직관적 이해) ⭐
저자가 책에서 만든 비유. 이거 하나만 알고 있어도 50% 끝.
Act 1 — Association Establishment (협상)
MR scanner: "Hi, 나 MR scanner고 DICOM 할 줄 알아. 너도?"
ARCHIVE: "응 나도 DICOM 함"
MR: "너 MR Image Storage SCP 야?"
ARCHIVE: "맞아"
MR: "MR 영상 100장 있어. 압축 안 한 채로 보낼 수도 있고,
JPEG2000으로 압축해서 보낼 수도 있고, JPEG-LS도 가능."
ARCHIVE: "OK, MR 받을게. 압축 안 한 거로 받을게."
MR: "좋아, 보낼게."
ARCHIVE: "받을 준비 완료."
→ 이게 A-Associate-RQ ↔ A-Associate-AC 의 본질.
Act 2 — Data Transfer (전송)
MR: "1번 영상." "2번." "3번..."
ARCHIVE: "1번 OK." "2번 OK..."
→ P-Data-TF PDU로 실제 영상 흐름.
Act 3 — Association Termination (종료)
MR: "100장 다 보냈음. 0장 실패. 성공. 끝낼게."
ARCHIVE: "다 받았음. 끝!"
(연결 종료)
→ A-Release-RQ ↔ A-Release-RP.
3. Abstract Syntax — "어떤 서비스?"
협상 단계에서 가장 먼저 합의하는 것: 무슨 SOP에 대해 통신할 건지.
3.1 Abstract Syntax = SOP Class UID
"Abstract Syntax UID = SOP Class UID" 는 사실상 같은 말. 둘 다 같은 UID를 가리킨다.
Abstract Syntax 1.2.840.10008.5.1.4.1.1.4 = MR Image Storage SOP
↑
이 SOP를 위해 협상하자
3.2 주요 Abstract Syntax UID
| 분류 | Abstract Syntax 이름 | UID |
|---|---|---|
| Verification | Verification | 1.2.840.10008.1.1 |
| Storage | CR Image | 1.2.840.10008.5.1.4.1.1.1 |
| Storage | CT Image | 1.2.840.10008.5.1.4.1.1.2 |
| Storage | MR Image ⭐ | 1.2.840.10008.5.1.4.1.1.4 |
| Storage | US Image | 1.2.840.10008.5.1.4.1.1.6.1 |
| Storage | Secondary Capture ⭐ | 1.2.840.10008.5.1.4.1.1.7 |
| Storage | Enhanced SR | 1.2.840.10008.5.1.4.1.1.88.22 |
| Q/R Find | Study Root | 1.2.840.10008.5.1.4.1.2.2.1 |
| Q/R Get | Study Root | 1.2.840.10008.5.1.4.1.2.2.3 |
| Q/R Move | Study Root | 1.2.840.10008.5.1.4.1.2.2.2 |
| Q/R Find | Patient Root | 1.2.840.10008.5.1.4.1.2.1.1 |
| MWL | MWL Find | 1.2.840.10008.5.1.4.31 |
3.3 인코딩
A-Associate-RQ 메시지 안에서 Abstract Syntax는 이런 형식:
┌──────┬──────────┬─────────┬─────────────────────┐
│ 0x30 │ reserved │ Length │ UID string │
│ 1byte│ 1 byte=0 │ 2 bytes │ L bytes │
└──────┴──────────┴─────────┴─────────────────────┘
예: 1.2.840.10008.5.1.4.1.1.4 (MR Image Storage, 25자 = 0x19)
30 00 00 19 31 2E 32 2E 38 34 30 2E 31 30 30 30 38 2E 35 2E 31 2E 34 2E 31 2E 31 2E 34
3.4 거부 시나리오
ARCHIVE가 MR Image Storage만 지원하고 CT Image Storage는 미지원이라면
CT 장비 → ARCHIVE: "1.2.840.10008.5.1.4.1.1.2 (CT Storage) SCP 야?"
ARCHIVE → CT 장비: "❌ 아니야. Abstract Syntax not supported."
→ 또는 그 Presentation Context만 거부
💡 Abstract Syntax는 협상 불가. 이건 장비 기능 자체. 합의가 아닌 매칭.
4. Transfer Syntax — "어떻게 인코딩?"
이게 DICOM 호환성의 마법. 1980년대 CT 스캐너가 2026년 Windows 노트북과 통신할 수 있는 이유.
4.1 Transfer Syntax가 다루는 것
- Endian (Big/Little) — 1편 6번 참조
- VR encoding (Implicit/Explicit) — 2편 4번
- 압축 알고리즘 (JPEG, JPEG-LS, JPEG2000, RLE)
4.2 주요 Transfer Syntax UID
| Transfer Syntax | UID | 의미 |
|---|---|---|
| Implicit VR Little Endian ⭐ | 1.2.840.10008.1.2 |
DICOM 기본값. 무조건 지원 필수 |
| Explicit VR Little Endian | 1.2.840.10008.1.2.1 |
Explicit 안전성 |
| Explicit VR Big Endian | 1.2.840.10008.1.2.2 |
구 Mac 등 (거의 사용 안 함) |
| JPEG Baseline 8-bit Lossy | 1.2.840.10008.1.2.4.50 |
일반 JPEG 손실 |
| JPEG Baseline 12-bit Lossy | 1.2.840.10008.1.2.4.51 |
12bit 의료영상용 |
| JPEG Lossless | 1.2.840.10008.1.2.4.57 |
JPEG 무손실 |
| JPEG-LS Lossless ⭐ | 1.2.840.10008.1.2.4.80 |
의료영상에서 흔함 |
| JPEG-LS Near-Lossless | 1.2.840.10008.1.2.4.81 |
거의 무손실 |
| JPEG 2000 Lossless ⭐ | 1.2.840.10008.1.2.4.90 |
최신, 권장 |
| JPEG 2000 Lossy | 1.2.840.10008.1.2.4.91 |
손실 압축 |
| RLE Lossless | 1.2.840.10008.1.2.5 |
Run-Length, 단순 |
4.3 핵심 규칙
✅ 모든 DICOM AE는
1.2.840.10008.1.2(Implicit VR LE) 를 무조건 지원해야 함.
이게 안전망. 다른 압축 협상 다 실패해도 이걸로 fallback 하면 됨.
4.4 인코딩 (Abstract와 거의 동일)
┌──────┬──────────┬─────────┬─────────────────────┐
│ 0x40 │ reserved │ Length │ UID string │
│ 1byte│ 1 byte=0 │ 2 bytes │ L bytes │
└──────┴──────────┴─────────┴─────────────────────┘
첫 바이트가 0x40 (Transfer Syntax)인 것만 다름.
4.5 ⚠️ 압축 함정 — 저자 사례
한 병원 US 장비가 영상을 압축 형식으로 저장 (디스크 용량 절약).
Archive 서버는 압축 미지원.
→ US가 영상 보내려 했을 때 → Transfer Syntax 불일치로 거부.
문제 진단:
- US 기사가 자기도 모르게 압축 켬 → Transfer Syntax 바뀜
- US 장비 펌웨어가 "압축 거부되면 압축 안 한 거로 fallback" 안 함 → 표준 위반
해결:
- 모든 AE는 거부당하면 Implicit VR LE로 fallback 해야 함
- 또는 Archive에서 그 압축 지원 추가
4.6 압축 옵션 함정
Transfer Syntax는 알고리즘 이름만 협상. 품질/비율 같은 파라미터는 협상 안 됨.
→ 받는 쪽은 "JPEG2000으로 압축됐다"만 알지 "품질 80%" 같은 건 모름.
→ 그래서 의료영상은 무손실 압축 (Lossless) 선호.
5. Presentation Context = Abstract + Transfer 묶음 ⭐⭐
여기가 핵심. 위 두 개념이 합쳐진다.
5.1 정의
Presentation Context = 1개의 Abstract Syntax + 여러 개의 Transfer Syntax 후보
┌─────────────────────────────────────────────────────────┐
│ Presentation Context (PrC) │
│ ├── Presentation Context ID (1, 3, 5, ... 홀수) │
│ ├── Abstract Syntax: MR Image Storage │
│ └── Transfer Syntax 후보들: │
│ ├── 1.2.840.10008.1.2 (Implicit VR LE) │
│ ├── 1.2.840.10008.1.2.4.80 (JPEG-LS Lossless) │
│ └── 1.2.840.10008.1.2.4.91 (JPEG 2000 Lossy) │
└─────────────────────────────────────────────────────────┘
💡 비유: "이 일(Abstract)을 다음 언어들(Transfer) 중 어떤 걸로든 할 수 있어요" 라는 명함.
5.2 협상 과정
Calling AE → Called AE (요청)
여러 Presentation Context를 묶어서 보냄:
A-Associate-RQ
├── PrC ID=1: MR Storage / [Implicit LE, JPEG-LS, JPEG2000]
├── PrC ID=3: CT Storage / [Implicit LE]
├── PrC ID=5: Verification / [Implicit LE]
└── ...
Called AE → Calling AE (응답)
각 PrC에 대해 수락/거부 + 선택한 Transfer Syntax 1개
A-Associate-AC
├── PrC ID=1: ✅ Accept, Transfer Syntax = Implicit LE
├── PrC ID=3: ❌ Reject (reason 3: Abstract syntax not supported)
├── PrC ID=5: ✅ Accept, Transfer Syntax = Implicit LE
└── ...
→ PrC ID로 어떤 컨텍스트의 응답인지 매칭.
5.3 Acceptance Reason 코드
A-Associate-AC의 각 PrC 응답에 들어가는 "왜 수락/거부했나"
| 코드 | 의미 |
|---|---|
0 |
✅ Acceptance (성공) |
1 |
❌ User-rejection |
2 |
❌ No-reason (provider) |
3 |
❌ Abstract syntax not supported |
4 |
❌ Transfer syntaxes not supported |
🛠 PACS 통신 디버깅의 진짜 핵심:
로그에서 PrC가 거부됐다면 reason 코드 먼저 확인.
- 3이면 → SOP Class 자체를 안 받는 거 (장비 호환성 문제)
- 4면 → SOP는 OK지만 압축 등 인코딩 형식이 안 맞는 거 (해결 가능)
5.4 실제 인코딩 — 두 가지 형식
A-Associate-RQ의 PrC: 첫 바이트 0x20. Abstract 1개 + Transfer 여러 개.
A-Associate-AC의 PrC: 첫 바이트 0x21. Abstract 없음. Transfer 1개만. PrC ID로 매칭.
→ AC에 Abstract Syntax 안 들어있어서 PrC ID 매칭이 유일한 방법. ID 잘못 맞추면 망함.
6. Application Context (대부분 무시)
A-Associate에 들어가지만 거의 안 봄.
- 표준 default:
1.2.840.10008.3.1.1.1 - 거의 모든 구현이 이 default 사용
- 이론상 "어떤 회사 소프트웨어인지" 식별 가능 — 사설 협상 트리거용
- 실무에서는 그냥 default 보내고 받는 쪽도 무시
⚠️ 가끔 악용: 일부 PACS가 "경쟁사 Application Context면 거부" 한다는 루머. 대부분 그냥 무시.
7. User Information — 작지만 까다로운 함정
A-Associate-RQ/AC에 들어가는 잡다한 추가 정보.
7.1 주요 subitem
| Subitem | 의미 | 기본값 |
|---|---|---|
| Maximum PDU Length | PDU 최대 크기 (byte) | 16384, 64KB 등 |
| Implementation Class UID | 구현 식별자 | (벤더별) |
| Implementation Version Name | 버전 문자열 (≤ 16자) | (벤더별) |
| Async Operations Window | 비동기 큐 사이즈 | 1 (= 동기) |
| SCP/SCU Role Selection | 역할 협상 | RQ측=SCU 가정 |
| Extended Negotiation | 추가 협상 (relational query 등) | 없음 |
7.2 ⚠️ 저자의 함정 사례 — 두 글자 때문에 하루 날림
CR ↔ Archive 연결이 안 됨. 하루 종일 디버깅.
원인: Archive의 Implementation Version Name이 18자 ("ArchiveVersion.123").
DICOM 표준상 최대 16자. CR이 그걸 엄격히 검사해서 통째로 거부.
교훈:
- User Information의 모든 길이 제한 엄수
- 그래야 까다로운 벤더와도 호환
7.3 Maximum PDU Length
PDU(Protocol Data Unit) 한 번에 보낼 수 있는 최대 크기. 큰 영상은 여러 PDU로 쪼갬.
- 너무 작으면: 영상이 잘게 쪼개져 오버헤드↑
- 너무 크면: 메모리 부담, 네트워크 단편화
흔한 값: 16384 (16KB), 65536 (64KB), 131072 (128KB)
🛠 Orthanc/pynetdicom 기본값은 보통 16KB. 큰 영상 자주 주고받으면 늘려도 됨.
7.4 SCP/SCU Role Negotiation
기본 가정: RQ를 보낸 쪽 = SCU, 받는 쪽 = SCP.
예외: C-Get/C-Move 처럼 SCP가 SCU에게 거꾸로 C-Store 하는 경우. 이때 Role 명시 필요.
A-Associate-RQ
├── PrC: MR Storage
├── User Info:
│ └── SCP/SCU Role Selection: "나는 SCU 이자 SCP 역할 둘 다 함"
→ Calling AE가 "내가 보내준 영상을 받을 수도 있어요" 라고 알림.
8. PDU — Protocol Data Unit
Association 위에서 흐르는 7가지 메시지 단위.
| PDU | 1st Byte | 역할 |
|---|---|---|
| A-Associate-RQ | 0x01 |
연결 요청 |
| A-Associate-AC | 0x02 |
연결 수락 |
| A-Associate-RJ | 0x03 |
연결 거부 |
| P-Data-TF | 0x04 |
실제 데이터 전송 |
| A-Release-RQ | 0x05 |
정상 종료 요청 |
| A-Release-RP | 0x06 |
정상 종료 응답 |
| A-Abort | 0x07 |
비정상 종료 |
8.1 전체 흐름도
Calling AE Called AE
│ │
│ A-Associate-RQ (0x01) │
─────────────────────────────→ │
│ │
│ ┌────────────────────┐ │
│ │ 협상 결과 │ │
│ └────────────────────┘ │
│ │
│ A-Associate-AC (0x02) │
←───────────────────────────── │ ← 성공 시
│ │
│ 또는 │
│ │
│ A-Associate-RJ (0x03) │
←───────────────────────────── │ ← 거부 시
│ │
│ ─── 협상 성공한 경우 ─── │
│ │
│ P-Data-TF (0x04) ↔ ↔ ↔ │
─────────────────────────────→ │ ← DIMSE 메시지들 흐름
←───────────────────────────── │
│ ... │
│ │
│ A-Release-RQ (0x05) │
─────────────────────────────→ │
│ │
│ A-Release-RP (0x06) │
←───────────────────────────── │
│ │
│ ─── 또는 비정상 종료 ─── │
│ │
│ A-Abort (0x07) │
─────────────────────────────→ │ (양방향 가능)
8.2 A-Associate-RQ 구조
┌────────────────────────────────────────────────────┐
│ PDU type = 0x01 │
│ Reserved (0x00) │
│ Length (4 bytes) │
│ Protocol version (2 bytes, = 0x0001) │
│ Reserved (2 bytes) │
│ Called AE Title (16 bytes, blank padded) │
│ Calling AE Title (16 bytes, blank padded) │
│ Reserved (32 bytes) │
│ │
│ ─── Variable items ───────────────────────── │
│ Application Context Item │
│ Presentation Context Item(s) (여러 개 가능) │
│ User Information Item │
└────────────────────────────────────────────────────┘
8.3 ⚠️ AE Title 16바이트 패딩
AE Title은 정확히 16바이트. 짧으면 공백(0x20) 으로 패딩.
"ORTHANC" → "ORTHANC " (앞 7자 + 공백 9개)
🛠 양쪽이 padding 처리 다르면 매칭 실패. 일부 PACS가 trailing space를 strip 안 하고 비교해서 거부하는 사례 있음. AE Title은 짧고 명확하게 (8자 이하 권장).
8.4 화이트리스트 검사
A-Associate-RQ 받자마자 Called AE가 하는 일:
# 의사코드
def on_associate_rq(rq):
if rq.calling_ae_title not in self.whitelist:
return A_Associate_RJ(reason="no reason given") # 흔한 거부 사유
...
⚠️ 이게 4편 §2.4에서 본 "양방향 등록 함정" 의 정체. PACS의 whitelist에 AI 추론 앱 AE 등록 안 되면 무조건 거부.
9. A-Associate-RJ (거부)
A-Associate-RQ의 거부 응답. 단순 구조.
9.1 거부 메시지 필드
| 필드 | 값 | 의미 |
|---|---|---|
| Result | 1 |
Rejected-Permanent (영구) |
2 |
Rejected-Transient (일시) | |
| Source | 1 |
DICOM UL service-user |
2 |
DICOM UL service-provider (ACSE) | |
3 |
DICOM UL service-provider (Presentation) | |
| Reason | 1 |
No reason given ← 가장 자주 봄 |
2 |
Application context name not supported | |
3 |
Calling AE Title not recognized | |
4-6 |
(reserved) | |
7 |
Called AE Title not recognized |
9.2 "No reason given" 의 진실
DICOM 디버깅의 최대 적. 벤더가 정보 안 줘서 그런 거지 실제로는 명확한 이유가 있음. 가장 흔한 실제 원인
- ⭐ Calling AE Title이 whitelist에 없음
- Abstract Syntax 미지원 (장비가 해당 SOP 모름)
- Maximum PDU Length 협상 실패
- Implementation Version Name 형식 위반
- 방화벽이 association 막음
🛠 저자 권고:
"No reason given" 받으면 자체 디버깅하지 말고 양쪽 벤더를 한자리에 모아라. 단독으로 X 벤더에게 물으면 Y 탓 하고, Y에게 물으면 X 탓 함.
10. P-Data-TF — 실제 데이터 전송
Association이 성공한 후 흐르는 데이터 PDU.
10.1 구조
┌──────────────────────────────────────────────────┐
│ PDU type = 0x04 │
│ Reserved (0x00) │
│ PDU Length │
│ │
│ ─── PDV (Protocol Data Value) items ─── │
│ PDV Length (4 bytes) │
│ Presentation Context ID (1 byte) │
│ MCH (Message Control Header, 1 byte) ↓ │
│ PDV Data (DIMSE message fragment) │
│ ... │
└──────────────────────────────────────────────────┘
10.2 MCH (Message Control Header) — 1바이트의 비밀
Bit 7 6 5 4 3 2 1 0
│ │ │ │
│ │ │ └─ Command(1) or Data(0)?
│ │ └─── Last fragment(1) or not(0)?
└─── reserved
- bit 0: 1이면 Command 객체 (group 0000), 0이면 Data 객체
- bit 1: 1이면 이게 마지막 fragment, 0이면 더 옴
→ DIMSE 메시지를 PDU 크기에 맞게 쪼개고, 받는 쪽에서 다시 조립.
10.3 흐름 예시: C-Store 한 번
C-Store-Rq (Command Object, 작음)
→ 1개 PDV (MCH=0x03 = command + last)
영상 데이터 (큰 IOD, 10MB)
→ 여러 PDV로 쪼개짐:
PDV 1: MCH=0x00 (data + 계속)
PDV 2: MCH=0x00 (data + 계속)
...
PDV N: MCH=0x02 (data + last)
→ C-Store 완료 → C-Store-Rsp 응답
10.4 PrC ID 매칭
각 PDV에는 Presentation Context ID 가 붙어 있음.
→ 받는 쪽이 "이 PDV는 Implicit LE로 해석해야 하나, JPEG 압축으로 해석해야 하나" 를 PrC ID로 결정.
11. Association 종료
11.1 정상 종료 — A-Release
SCU SCP
│ │
│ A-Release-RQ (0x05) │ ← 작업 끝, 끊자
────────────────────────────→ │
│ │
│ A-Release-RP (0x06) │ ← OK
←──────────────────────────── │
│ │
─── TCP 연결 종료 ───
11.2 비정상 종료 — A-Abort
오류 상황에서 즉시 끊기:
- 잘못된 PDU 받음
- 타임아웃
- 사용자가 강제 종료
A-Abort (0x07) → 한쪽이 일방적으로 보냄 → TCP close
11.3 Timeout 종료
상대가 응답 없으면 일정 시간 후 자동 종료. 보통 30-60초 설정.
🛠 너무 짧으면 느린 네트워크에서 끊김. 너무 길면 죽은 연결 감지 늦음.
11.4 ⚠️ 동시 Association 수 라이선스
일부 PACS 벤더는 동시 Association 수 라이선스로 판매. 10개, 50개 등.
→ 모든 Association을 반드시 A-Release로 정상 종료 해야 슬롯 회수됨.
→ 안 그러면 슬롯이 누적되어 새 Association 받을 수 없게 됨.
12. Association 디버깅 — 실전 가이드 ⭐⭐⭐
저자의 노하우 + 실무 베스트 프랙티스.
12.1 디버깅 순서
1. C-Echo (ping)
├── 성공 → AE Title, IP, Port, whitelist 모두 OK → 다음 단계로
└── 실패 → 12.2 로
2. C-Store SCU (실제 영상 전송)
├── 성공 → 완료
└── 실패 → Abstract/Transfer Syntax 협상 문제 → 12.3 로
3. C-Find / C-Move (검색/가져오기)
└── ...
12.2 C-Echo 실패 트러블슈팅
| 증상 | 확인 |
|---|---|
| TCP timeout | 1) 네트워크 케이블/방화벽 2) IP/Port 오타 3) SCP 데몬이 안 돔 |
Association rejected (no reason) |
1) Calling AE Title이 PACS whitelist에 있나? ⭐ 2) Called AE Title 오타 3) Implementation Version Name 16자 초과 |
Abstract syntax not supported |
Verification SOP SCP 미지원 (Conformance 확인) |
| 즉시 disconnect | 일부 PACS는 whitelist 미등록 시 그냥 끊음 |
12.3 C-Store 실패 트러블슈팅
| 증상 | 확인 |
|---|---|
Abstract syntax not supported |
SOP Class UID 확인. CT 영상 보내는데 PACS가 MR Storage만 받는 등. |
Transfer syntaxes not supported |
압축 형식 미지원. Implicit LE로 fallback 시도. |
Out of resources |
PACS 디스크/메모리 부족. 관리자에게 |
Cannot understand |
IOD 형식 위반. Type 1 필드 누락 등 |
| 일부 영상만 실패 | Patient ID 빈 값, 손상된 픽셀 등 개별 데이터 문제 |
12.4 로그 분석 핵심 키워드
Orthanc 로그
W [Orthanc Plugin] Connection failed (rc = 0xc0001000, ...)
I [Orthanc Plugin] ASSOCIATE-RQ from CT_SCANNER (10.0.0.5:11112)
I [Orthanc Plugin] ASSOCIATE-AC sent
I [Orthanc Plugin] P-DATA-TF: 1 PDV, ...
W [Orthanc Plugin] Refusing presentation context (abstract syntax not supported)
pynetdicom 로그
import logging
logging.basicConfig(level=logging.DEBUG)
# 또는
from pynetdicom import debug_logger
debug_logger()
→ 모든 PDU/PDV 바이트 단위로 출력. RQ/AC 협상 내용 다 보임.
12.5 패킷 캡처 (Wireshark)
진짜 안 풀리는 경우 마지막 수단:
tshark -i any -f "port 4242" -w dicom.pcap
Wireshark는 DICOM dissector 내장. 모든 PDU/PDV/Abstract/Transfer Syntax를 트리로 보여줌. AE Title의 trailing space, PDU length 오류 같은 것도 한 번에 드러남.
12.6 ⚠️ 책의 황금률
- DICOM 협상 실패는 거의 항상 양쪽 벤더 책임. 한쪽만 잡지 말고 둘을 마주 앉혀라.
- 명확한 로그/에러 표시 없는 PACS는 사지 말 것. 디버깅 불가.
- Conformance Statement를 신뢰하되 검증하라. "지원한다"고 적힌 게 실제로는 미구현인 경우 흔함.
13. Point-to-Point의 한계
DICOM의 근본적 약점 하나 더.
13.1 정적 등록 강제
모든 AE는 통신할 상대의 (AET, IP, Port)를 미리 등록 해야 함.
- 변경되면 모든 곳을 다시 등록해야 함.
- 모바일/원격 접근 어려움.
13.2 저자의 허리케인 카트리나 사례
카트리나 후 정전 → 라우터 재부팅 → IP 주소가 공장 default로 리셋
→ PACS 서버 IP 바뀜 → 모든 AE가 PACS를 못 찾음 → 전체 마비
→ IP 복원하니 즉시 정상화
→ TCP/IP는 살아있는데 DICOM의 정적 등록 때문에 전체 시스템이 죽는 패러독스.
13.3 C-Move vs C-Get 와 연결
5편 §4.2에서 본 방화벽 함정과 같은 뿌리:
- C-Move는 정적 등록 필수 (별도 association 열어야 하니까)
- C-Get은 동적 가능 (같은 association으로 응답)
13.4 워크어라운드
| 방식 | 설명 |
|---|---|
| VPN | 가상 사설망으로 정적 IP처럼 보이게 |
| DICOMweb (WADO/STOW/QIDO) | HTTP REST 기반, 정적 등록 불필요 |
| Orthanc/dcm4chee gateway | 외부에서 들어온 요청을 내부 PACS로 중계 |
💡 현대 PACS는 DICOMweb 지원이 점점 표준화 중. DICOMweb은 책에 없지만 별도 학습할 가치 있음 (PS3.18).
14. AI 추론 앱 관점 — Orthanc 사용 시 ⭐
저수준 PDU는 Orthanc가 다 처리해줌. 하지만 설정과 디버깅에서 위 개념들이 그대로 등장.
14.1 Orthanc 설정 예시
// orthanc.json
{
"DicomAet": "ORTHANC",
"DicomPort": 4242,
"DicomCheckCalledAet": false,
"DicomCheckModalityHost": false,
"DicomModalities": {
"HOSPITAL_PACS": {
"AET": "HOSPITAL_PACS",
"Host": "10.0.0.50",
"Port": 11112,
"Manufacturer": "Generic",
"AllowEcho": true,
"AllowFind": true,
"AllowGet": false,
"AllowMove": true,
"AllowStore": true
}
},
// ⭐ Transfer Syntax 협상 제어
"AcceptedTransferSyntaxes": [
"1.2.840.10008.1.*"
],
// ⭐ Abstract Syntax 협상 제어
"DefaultEncoding": "Latin1",
"MaximumPduLength": 16384,
// ⭐ Whitelist
"DicomAlwaysAllowEcho": true,
"DicomAlwaysAllowStore": true
}
→ 위 설정의 거의 모든 필드가 이번 글에서 다룬 개념과 직접 대응
DicomAet= 우리 AI 추론 앱의 Calling AE TitleAET/Host/Port= 원격 PACS의 (AE Title, IP, Port) 사전 등록AcceptedTransferSyntaxes= 협상 시 받아들일 Transfer Syntax UID 목록MaximumPduLength= User Information의 Max PDU LengthDicomAlwaysAllowStore= whitelist 검사 우회 (개발 환경에서)
14.2 디버깅 명령
# C-Echo 테스트
curl -X POST http://orthanc:8042/modalities/HOSPITAL_PACS/echo
# 협상 로그 보기 (Orthanc verbose 모드)
Orthanc.exe --verbose orthanc.json
# 또는
Orthanc.exe --trace-dicom orthanc.json # ← Association/PDU 레벨 로그
14.3 흔한 시나리오와 매핑
| 증상 | 원인 (이번 글의 개념) | 해결 |
|---|---|---|
| Orthanc에서 PACS로 C-Echo 실패 | Whitelist 미등록 | PACS에 ORTHANC AE 등록 요청 |
C-Store 시 RejectedPresentationContext |
Transfer Syntax 미협상 | AcceptedTransferSyntaxes에 압축 추가 |
| C-Move 했는데 영상 안 옴 | Move Destination 등록 안 됨 | PACS에 우리 AE를 destination으로 등록 |
| Implementation 16자 오류 | User Info 길이 위반 | Orthanc 버전 업데이트 또는 벤더 문의 |
| 큰 영상 전송 중단 | Max PDU Length | MaximumPduLength 늘림 (e.g., 65536) |
15. 핵심 정리
| 개념 | 한 줄 |
|---|---|
| Association | DICOM Upper Layer 통신 단위. TCP 위 핸드셰이크 |
| Act 1/2/3 | Establishment / Data Transfer / Termination |
| Abstract Syntax | "어떤 SOP" — UID 형식. 협상 불가, 매칭만 |
| Transfer Syntax | "어떤 인코딩" — Endian/Implicit/Explicit/압축. 여러 후보 협상 |
| 기본 Transfer Syntax | 1.2.840.10008.1.2 (Implicit VR LE) — 무조건 지원 필수 |
| Presentation Context | Abstract 1개 + Transfer 후보 N개. PrC ID로 추적 |
| A-Associate-RQ/AC | 협상 요청/수락 PDU |
| A-Associate-RJ | 거부. "no reason given"이 흔함 → 실제는 whitelist 문제 다수 |
| A-Abort | 비정상 즉시 종료 |
| A-Release-RQ/RP | 정상 종료 (라이선스 슬롯 회수) |
| P-Data-TF | 실제 데이터 PDU. PDV로 쪼갬. MCH 1바이트로 command/data + last 표시 |
| User Information | Max PDU, Version Name(16자!), Role Selection 등 |
| PrC Reject Reason 3 | Abstract Syntax 미지원 (SOP 자체 문제) |
| PrC Reject Reason 4 | Transfer Syntax 미지원 (인코딩 문제, fallback 가능) |
| AE Title | 16바이트, 공백 패딩. 짧고 명확하게 |
| 디버깅 순서 | C-Echo → C-Store → 로그 → Wireshark |
| 점-대-점 한계 | 정적 등록 강제. DICOMweb이 대안 |
16. 다음 글에서 다룰 것
DICOM 통신의 모든 기본기가 완성됐다.
마지막 7편은 운영 환경에서의 실전
7편: DICOM 파일/보안 + Orthanc 실전
- DICOM File 포맷 —
DICM매직 헤더, Preamble, Meta Information - DICOMDIR — DVD/CD/USB 미디어 인덱스
- 익명화 (Anonymization) — HIPAA, 환자 데이터 보호 ⭐
- 암호화 — Secure DICOM, 디지털 서명
- Orthanc 실전 통합 시나리오 — AI 추론 앱 전체 파이프라인
- DICOMweb 맛보기 — WADO-RS, STOW-RS, QIDO-RS
참고
- Pianykh, O.S. Digital Imaging and Communications in Medicine (DICOM). Springer, 2008.
- 공식 표준:
- PS3.7 (Message Exchange) — DIMSE
- PS3.8 (Network Communication Support) — 이번 글의 핵심: PDU, Association protocol
- 도구:
- Wireshark — DICOM dissector 내장. 패킷 단위 분석.
- pynetdicom — Python 구현 + DEBUG 로깅
- Orthanc —
--trace-dicom옵션으로 PDU 레벨 로그 - DCMTK
dcmnet—storescu,findscu,echoscu등 CLI 도구
'Dev > DICOM' 카테고리의 다른 글
| [DICOM] 파일 포맷, 보안/익명화, DICOMweb, Orthanc 실전 (0) | 2026.06.10 |
|---|---|
| [DICOM] 영상을 가져오는 방법 — C-Move, C-Get, Modality Worklist (0) | 2026.06.10 |
| [DICOM] DICOM 통신의 시작 — C-Echo, C-Store, C-Find (0) | 2026.06.10 |
| [DICOM] Patient/Study/Series/Image 4계층 + IOD — DICOM 정보 모델의 완성 (0) | 2026.06.02 |
| [DICOM] Tag, Data Dictionary, Object Encoding — DICOM 데이터를 바이트로 푸는 법 (0) | 2026.05.27 |
