This commit is contained in:
mario
2025-07-11 15:16:11 +07:00
6 changed files with 524 additions and 77 deletions

View File

@@ -269,4 +269,82 @@ class DicomFinder:
logger.error(error_msg)
raise DicomQueryError(error_msg)
return first_instance
return first_instance
@dicom_retry(exception_types=(DicomQueryError, ConnectionError))
def find_study_by_uid(self, study_instance_uid):
"""
Find a specific study by its StudyInstanceUID.
Args:
study_instance_uid (str): StudyInstanceUID to find
Returns:
Dataset: Study dataset or None if not found
"""
logger.info(f"Finding study with UID: {study_instance_uid}")
# Create query dataset
ds = Dataset()
ds.QueryRetrieveLevel = 'STUDY'
# Set the StudyInstanceUID filter
ds.StudyInstanceUID = study_instance_uid
# Required fields (minimal set)
ds.StudyDate = ''
# Additional fields we want to retrieve
ds.AccessionNumber = ''
ds.PatientID = '' # MedrecID
ds.StudyDescription = ''
ds.StudyTime = ''
ds.NumberOfStudyRelatedSeries = ''
# Create association
assoc = self.ae.associate(
self.pacs_config['host'],
self.pacs_config['port'],
ae_title=self.pacs_config['aet']
)
study = None
if assoc.is_established:
try:
logger.debug("Association established, sending C-FIND request")
# Send C-FIND request
responses = assoc.send_c_find(
ds,
StudyRootQueryRetrieveInformationModelFind
)
# Process responses - just get the first one
for (status, dataset) in responses:
if status and status.Status == 0xFF00: # Pending
if dataset:
study = dataset
logger.debug(f"Found study: {dataset.StudyInstanceUID}")
break
elif status and status.Status != 0x0000: # Not success
logger.error(f"C-FIND error: {status}")
if study:
logger.info(f"Found study {study_instance_uid}")
else:
logger.warning(f"No study found with UID {study_instance_uid}")
except Exception as e:
logger.error(f"Error during C-FIND: {str(e)}")
raise DicomQueryError(f"C-FIND operation failed: {str(e)}")
finally:
# Release the association
assoc.release()
logger.debug("Association released")
else:
error_msg = f"Association rejected, aborted or never connected to {self.pacs_config['aet']}"
logger.error(error_msg)
raise DicomQueryError(error_msg)
return study

View File

@@ -39,6 +39,7 @@ class DicomRetriever:
# Add storage presentation contexts (needed for receiving the images)
# for context in StoragePresentationContexts:
# self.ae.add_requested_context(context.abstract_syntax)
storage_uids = [
'1.2.840.10008.5.1.4.1.1.1', # CR Storage
'1.2.840.10008.5.1.4.1.1.1.1', # Digital X-Ray Image Storage

View File

@@ -6,11 +6,15 @@ import glob
import pydicom
import shutil
from pydicom.dataset import Dataset
from pynetdicom import AE, StoragePresentationContexts, evt
from pynetdicom import AE, StoragePresentationContexts, evt, build_role
from config import settings
from utils.logger import main_logger as logger
from utils.error_handler import DicomStoreError, dicom_retry
from utils.dicom_utils import create_directory_if_not_exists
from pynetdicom.sop_class import (
StudyRootQueryRetrieveInformationModelGet,
PatientRootQueryRetrieveInformationModelGet
)
class DicomSender:
"""
@@ -27,9 +31,32 @@ class DicomSender:
self.pacs_config = pacs_config or settings.DESTINATION_PACS
self.ae = AE(ae_title=settings.SOURCE_AET)
# Add storage presentation contexts (all standard transfer syntaxes for each SOP class)
for context in StoragePresentationContexts:
self.ae.add_requested_context(context.abstract_syntax)
# Add the Query/Retrieve SOP classes
self.ae.add_requested_context(StudyRootQueryRetrieveInformationModelGet)
self.ae.add_requested_context(PatientRootQueryRetrieveInformationModelGet)
# Add storage presentation contexts (needed for receiving the images)
# for context in StoragePresentationContexts:
# self.ae.add_requested_context(context.abstract_syntax)
storage_uids = [
'1.2.840.10008.5.1.4.1.1.1', # CR Storage
'1.2.840.10008.5.1.4.1.1.1.1', # Digital X-Ray Image Storage
'1.2.840.10008.5.1.4.1.1.2', # CT Image Storage
'1.2.840.10008.5.1.4.1.1.4', # MR Image Storage
'1.2.840.10008.5.1.4.1.1.7', # Secondary Capture Image Storage
'1.2.840.10008.5.1.4.1.1.6.1', # Ultrasound Image Storage
'1.2.840.10008.5.1.4.1.1.128', # PET Image Storage
'1.2.840.10008.5.1.4.1.1.20', # Nuclear Medicine Image Storage
'1.2.840.10008.5.1.4.1.1.9.1.1', # 12-lead ECG Waveform Storage
'1.2.840.10008.5.1.4.1.1.9.1.2', # General ECG Waveform Storage
]
self.ext_neg = []
for uid in storage_uids:
self.ae.add_requested_context(uid)
role = build_role(uid, scp_role=True)
self.ext_neg.append(role)
logger.info(f"DicomSender initialized with destination PACS: {self.pacs_config['aet']}@{self.pacs_config['host']}:{self.pacs_config['port']}")