https://pydicom.github.io/pynetdicom/stable/tutorials/installation.html
설치하기
DICOM 통신이므로 다루기위해 우선 pydicom 이 있어야 한다.
pip install -U pynetdicom
python -m pip install -U pynetdicom
pynetdicom이란
DICOM 네트워크를 구축하기 위한 프로토콜 등 도구들을 제공
TCP/IP 용 DICOM 상위 계층 프로토콜
Application Entity(AE)로 DICOM 통신을 처리할 수 있다.
ae = AE()
이렇게 만들어진 ae 인스턴스는 1.서비스 클래스 컨텍스트 2. 이벤트 핸들러를 설정하여 SCP/SCU 로 사용할 수 있다.
SCP/SCU ?
- SCP
- SCU의 요청에 응답하여 서비스를 제공
- 이미지의 저장, 검색, 등 요청을 수신하고 처리
- SCU
- DICOM 서비스에 대한 요청
- 의료 이미지 검색, 저장 등의 SCP 기타 작업을 수행하기 위한 요청 쿼리
간단한 SCP
from pydicom.uid import ExplicitVRLittleEndian
from pynetdicom import AE, debug_logger
from pynetdicom.sop_class import CTImageStorage
debug_logger()
ae = AE()
ae.add_supported_context(CTImageStorage, ExplicitVRLittleEndian)
ae.start_server(("127.0.0.1", 11112), block=True)
- ae.add_supported_context() 메서드는 이미지 저장 서비스 클래스 컨텍스트는 SCP로 지원을 추가한다는 의미
- CTImageStorage의 sop_class 로 불러와서 매개변수에 넣어줌으로써 CT 스캔 이미지를 지원하는 의미가 됨
- ExplicitVRLittleEndian 의 UID 또한 마찬가지로 SCP에 지원해준다는 의미
- ae.start_server() 에는 튜플형태로 주소, 포트를 넣고 block을 true로 설정해주면 SCP 서버가 열리게 된다.
간단한 SCU
from pydicom.uid import ExplicitVRLittleEndian
from pynetdicom import AE, debug_logger
from pynetdicom.sop_class import CTImageStorage
debug_logger()
ae = AE()
ae.add_requested_context(CTImageStorage)
assoc = ae.associate("127.0.0.1", 11112)
if assoc.is_established:
print('Association established with Echo SCP!')
status = assoc.send_c_echo()
assoc.release()
else:
print('Failed to associate')
- ae.add_requested_context() 메서드는 동일하게 sop_class를 매개변수로 넣어줘서 SCU 요청이 가능하게 만드는 메서드이다.
- 즉, SCU로 작동하는 AE가 다른 AE에게 요청하는 DICOM 서비스 클래스를 설정하고 요청하는데 사용되는 것
- ae.associate() 로 연결을 요청한다. 이 메서드를 통해 AE 간 통신이 시작된다.
- assoc.is_established 로 연결의 성공 여부를 boolean 값으로 받아서 분기문으로 처리한다.
- assoc.send_c_echo() 메서드는 DICOM Echo 서비스를 요청하는 메서드로 네트워크 연결 및 DICOM 연합 상태를 확인하기 위해 사용된다. 즉, SCU와 SCP가 통신이 가능한지 확인하는 용도이다. (단순하게 Echo 메세지를 보내는 것)
- assoc.release() 메서드는 연결을 해제하는 것
SCP를 틀어놓고 아래와 같이 명령해주면 dcm 파일을 SCP에 보내주게 된다. 아래와 같은 값을 반환한다. 확인해보면 당연하게도 0xC211 로 Failure 가 떴다.
python -m pynetdicom storescu 127.0.0.1 11112 파일명.dcm -v -cx
- Requesting Association 요청 연결
- Association Accepted 요청 받아들임
- Sending file dcm 파일을 보내줌
- Sending Store Request 저장 요청
- Received Store Response 저장 응답 -----> 실패
- Releasing Association 연결 해제
SCP에서 debug를 확인해보면 위같은 E: 가 보인다. 에러 내용을 담고 있다.
SCU에서 정상적으로 dcm 파일을 SCP에 보내줬으나 SCP에 받아준 dcm 파일을 처리해줄 수 있는 방법이 없다는(저장 응답에서 실패가 떴으니) 것을 알 수 있다.
즉 실패의 이유는 evt.EVT_C_STORE 이벤트 핸들러가 없기 때문에 발생한 것이다.
SCP의 evt를 설정
from pynetdicom import AE, debug_logger, evt
pynetdicom에 evt를 추가한다. evt.EVT_C_STORE 형식으로 접근해준다.
def handle_store(event):
"""events."""
return 0x0000
handlers = [(evt.EVT_C_STORE, handle_store)]
ae = AE()
ae.add_supported_context(CTImageStorage, ExplicitVRLittleEndian)
ae.start_server(("127.0.0.1", 11112), block=True, evt_handlers=handlers)
- ae.start_server 안에 evt_handlers에 배열로 지정해준 handlers를 넣어준다.
- handlers 에 이벤트 핸들러는 Tuple 형태로 (이벤트핸들러종류, 이벤트핸들러함수) 로 지정해준다.
- 이벤트 핸들러 함수로 만들어준 handle_store 함수는 0x0000 을 리턴해주는데 이는 성공했다는 의미이다.
- 현재 위의 상태로는 아직 저장되지 않는다. 주석 위치에서 저장해주는 로직을 추가해줘야 함
- 위의 실패를 임시적으로 무마해준다.
핸들러 완성하기
def handle_store(event):
ds = event.dataset
ds.file_meta = event.file_meta
ds.save_as(ds.SOPInstanceUID, write_like_original=False)
return 0x0000
- event.dataset 은 피디콤 데이터셋으로 SCU에서 수신한 디코딩된 데이터셋
- event.file_meta는 호환되는 파일 메타 정보 요소를 포함하는 데이터셋
- ds.save_as() 메서드에 첫번째 매개변수로는 파일 이름이 되어줄 ds.SOPInstanceUID를, 두번째 매개변수로는 write_like_original을 False 로 지정해준다.
- write_like_original 이 False여야 DICOM 파일 형식으로 작성됨.
위 핸들러를 기반으로 요청을 보내면 SCP 파일 위치에 SOPInstanceUID 의 이름을 가진 dcm 파일이 만들어지는 것을 확인할 수 있다. 정상적으로 저장된 것이다!
이제 원래의 SCP의 add_supported_context() 의 종류를 늘리자. (지금은 오직 CTImageStorage 만 가능하므로)
// ...
from pynetdicom import AE, debug_logger, evt, AllStoragePresentationContexts, ALL_TRANSFER_SYNTAXES
// ...
storage_sop_classes = [cx.abstract_syntax for cx in AllStoragePresentationContexts]
for uid in storage_sop_classes:
ae.add_supported_context(uid, ALL_TRANSFER_SYNTAXES)
- AllStoragePresentationContexts는 미리 빌드된 프레젠테이션 컨텍스트의 목록 - 스토리지 서비스의 모든 SOP 클래스에 대해 하나씩 있음.
- 그러나 기본적으로 이러한 컨텍스트는 비압축 전송 구문만 지원
- 압축 전송 구문과 비압축 전송 구문을 모두 지원하려면 추상 구문을 분리한 다음 ALL_TRANSFER_SYNTAXES를 대신 사용한다.
일단 가장 간단한 SCP와 SCU를 만들어 보았다.
'Ect. > Library' 카테고리의 다른 글
VTK in python - VTK and Cylinder (0) | 2023.12.06 |
---|---|
pynetdicom 의 storescu에 대해 (1) | 2023.10.08 |
react-babylonjs 라이브러리 사용해보기 (1) | 2023.10.03 |
Ag-grid react 사용기 - 정렬(Sorting) (0) | 2023.09.06 |
React Chart 라이브러리 - recharts (0) | 2023.08.31 |