class WorklistClient: """ Worklist client used to test the server""" def __init__(self, serveraddress): self._ae = AE() self._ae.add_requested_context(ModalityWorklistInformationFind) self._ae.acse_timeout = 3600 self._serveraddress = serveraddress def get_worklist(self, query_dataset): """ Fetch worklist from server, and return as PyDicom dataset """ assoc = self._ae.associate(self._serveraddress.address, self._serveraddress.port) if not assoc.is_established: raise Exception('Association rejected, aborted or never connected') worklist = [] for (status, item) in assoc.send_c_find(query_dataset, ModalityWorklistInformationFind): if not status: print('Connection timed out, was aborted or received invalid response') elif status.Status == 0xFF00: # Matches are continuing worklist.append(item) elif status.Status != 0x0000: # Anything except Success raise Exception("Got unexpected status from worklist server") assoc.release() return worklist
def test_scp_callback_info(self): """Test on_c_echo info parameter.""" self.scp = DummyVerificationSCP() self.scp.start() ae = AE() ae.add_requested_context(VerificationSOPClass) ae.acse_timeout = 5 ae.dimse_timeout = 5 assoc = ae.associate('localhost', 11112) assert assoc.is_established rsp = assoc.send_c_echo() assert rsp.Status == 0x0000 assoc.release() assert assoc.is_released assert 'address' in self.scp.info['requestor'] assert self.scp.info['requestor']['ae_title'] == b'PYNETDICOM ' #assert self.scp.info['requestor']['called_aet'] == b'ANY-SCP ' assert isinstance(self.scp.info['requestor']['port'], int) assert self.scp.info['acceptor']['port'] == 11112 assert 'address' in self.scp.info['acceptor'] assert self.scp.info['acceptor']['ae_title'] == b'PYNETDICOM ' self.scp.stop()
def test_unknown_pdu_aborts(self): commands = [ ("recv", None), # recv a-associate-rq ("send", a_associate_ac), ("send", b"\x53\x00\x00\x00\x00\x02"), ("recv", None), ("exit", None), ] self.scp = scp = start_server(commands) def handle(event): scp.step() scp.step() hh = [(evt.EVT_REQUESTED, handle)] ae = AE() ae.acse_timeout = 5 ae.dimse_timeout = 5 ae.network_timeout = 5 ae.add_requested_context("1.2.840.10008.1.1") assoc = ae.associate("localhost", 11112, evt_handlers=hh) assert assoc.is_established scp.step() # send bad PDU time.sleep(0.1) scp.step() # receive abort scp.step() scp.shutdown() assert assoc.is_aborted
def c_get_Option(): print('Executing the C_GET approach i.e. SCU c_get') handlers = [(evt.EVT_C_STORE, handle_store)] CompositeInstanceRetrieveWithoutBulkDataGet = '1.2.840.10008.5.1.4.1.2.5.3' SecondaryCaptureImageStorage = '1.2.840.10008.5.1.4.1.1.7' ae = AE(ae_title=AE_TITLE) ae.add_requested_context(CompositeInstanceRetrieveWithoutBulkDataGet) ae.add_requested_context(SecondaryCaptureImageStorage) role = build_role(SecondaryCaptureImageStorage, scp_role=True) assoc = ae.associate(SERVER_ADDRESS, PORT, ext_neg=[role], evt_handlers=handlers) if assoc.is_established: responses = assoc.send_c_get( create_queryDS('IMAGE'), CompositeInstanceRetrieveWithoutBulkDataGet) for (status, identifier) in responses: if status: print('C-GET query status: 0x{0:04x}'.format(status.Status)) else: print( 'Connection timed out, was aborted or received invalid response' ) assoc.release() else: print('Association rejected, aborted or never connected')
def test_exception_in_handler(self): """Test an exception raised by the handler doesn't shut the server.""" class DummyAE(object): network_timeout = 5 _servers = [] dummy = DummyAE() server = ThreadedAssociationServer(dummy, ('', 11112)) dummy._servers.append(server) thread = threading.Thread(target=server.serve_forever) thread.daemon = True thread.start() ae = AE() ae.add_requested_context('1.2.840.10008.1.1') # assoc.dul is Thread-2 assoc = ae.associate('', 11112) assert server.socket.fileno() != -1 server.shutdown() if sys.version_info[0] == 2: with pytest.raises(socket.error): server.socket.fileno() else: assert server.socket.fileno() == -1
def test_recv_missing_data(self, caplog): """Test missing data when receiving.""" with caplog.at_level(logging.ERROR, logger="pynetdicom"): commands = [ ("recv", None), # recv a-associate-rq ("send", a_associate_ac), ("send", b"\x07\x00\x00\x00\x00\x02\x00"), # Send short PDU ("exit", None), ] self.scp = scp = start_server(commands) def handle(event): scp.step() scp.step() hh = [(evt.EVT_REQUESTED, handle)] ae = AE() ae.acse_timeout = 5 ae.dimse_timeout = 5 # ae.network_timeout = 0.5 ae.add_requested_context("1.2.840.10008.1.1") assoc = ae.associate("localhost", 11112, evt_handlers=hh) assert assoc.is_established scp.step() # send short pdu scp.step() scp.shutdown() assert assoc.is_aborted assert ( "The received PDU is shorter than expected (7 of 8 bytes received)" ) in caplog.text
def search_patients(self, search_query: str, additional_tags: List[str] = None, wildcard: bool = None) -> List[Dataset]: ae = AE(ae_title=self.client_ae) ae.add_requested_context(StudyRootQueryRetrieveInformationModelFind) if wildcard: search_query = f'*{search_query}*' patient_id_to_datasets = defaultdict(Dataset) with association(ae, self.pacs_url, self.pacs_port, self.remote_ae) as assoc: id_responses = _find_patients(assoc, 'PatientID', search_query, additional_tags) for study in checked_responses(id_responses): if hasattr(study, 'PatientID'): result = patient_id_to_datasets[study.PatientID] self.update_patient_result(result, study, additional_tags) # consecutive find must be in separate associations with association(ae, self.pacs_url, self.pacs_port, self.remote_ae) as assoc: name_responses = _find_patients(assoc, 'PatientName', search_query, additional_tags) for study in checked_responses(name_responses): if hasattr(study, 'PatientID'): result = patient_id_to_datasets[study.PatientID] self.update_patient_result(result, study, additional_tags) return list(patient_id_to_datasets.values())
def verify(self) -> bool: ae = AE(ae_title=self.client_ae) ae.add_requested_context(Verification) # setting timeout here doesn't appear to have any effect ae.network_timeout = self.timeout with association(ae, self.pacs_url, self.pacs_port, self.remote_ae) as assoc: logger.debug('Association accepted by the peer') # Send a DIMSE C-ECHO request to the peer # status is a pydicom Dataset object with (at a minimum) a # (0000, 0900) Status element status = assoc.send_c_echo() # Output the response from the peer if status.Status in status_success_or_pending: logger.debug('C-ECHO Response: 0x{0:04x}'.format( status.Status)) return True else: logger.warning('C-ECHO Failure Response: 0x{0:04x}'.format( status.Status)) return False return False
def fetch_slice_thumbnail(self, study_id: str, series_id: str, instance_id: str) -> Optional[str]: ae = AE(ae_title=self.client_ae) ae.add_requested_context(StudyRootQueryRetrieveInformationModelFind) ae.add_requested_context(StudyRootQueryRetrieveInformationModelMove) with association(ae, self.pacs_url, self.pacs_port, self.remote_ae) as assoc: with storage_scp(self.client_ae, self.dicom_dir) as scp: move_dataset = Dataset() move_dataset.StudyInstanceUID = study_id move_dataset.SeriesInstanceUID = series_id move_dataset.SOPInstanceUID = instance_id move_dataset.QueryRetrieveLevel = 'IMAGE' if scp.is_alive(): move_responses = assoc.send_c_move( move_dataset, scp.ae_title, query_model=C_MOVE_QUERY_MODEL) else: raise Exception( f'Storage SCP failed to start for series {series_id}') check_responses(move_responses) dcm_path = os.path.join(self.dicom_dir, f'{instance_id}.dcm') png_path = process_and_write_png_from_file(dcm_path) return png_path
def fetch_image_as_dicom_file(self, study_id: str, series_id: str, sop_instance_id: str) -> Optional[str]: series_path = os.path.join(self.dicom_dir, series_id) with storage_scp(self.client_ae, series_path) as scp: ae = AE(ae_title=self.client_ae) ae.add_requested_context( StudyRootQueryRetrieveInformationModelMove) with association(ae, self.pacs_url, self.pacs_port, self.remote_ae) as assoc: dataset = Dataset() dataset.SeriesInstanceUID = series_id dataset.StudyInstanceUID = study_id dataset.SOPInstanceUID = sop_instance_id dataset.QueryRetrieveLevel = 'IMAGE' if scp.is_alive(): responses = assoc.send_c_move( dataset, scp.ae_title, query_model=C_MOVE_QUERY_MODEL) else: raise Exception( f'Storage SCP failed to start for series {series_id}') check_responses(responses) filepath = scp.path_for_dataset_instance(dataset) return filepath if os.path.exists(filepath) else None return None
def images_for_series(self, study_id, series_id, additional_tags=None, max_count=None) -> List[Dataset]: ae = AE(ae_title=self.client_ae) ae.add_requested_context(StudyRootQueryRetrieveInformationModelFind) image_datasets = [] with association(ae, self.pacs_url, self.pacs_port, self.remote_ae) as series_assoc: series_dataset = Dataset() series_dataset.StudyInstanceUID = study_id series_dataset.SeriesInstanceUID = series_id series_dataset.QueryRetrieveLevel = 'IMAGE' series_dataset.SOPInstanceUID = '' set_undefined_tags_to_blank(series_dataset, additional_tags) series_responses = series_assoc.send_c_find( series_dataset, query_model=C_FIND_QUERY_MODEL) for instance in checked_responses(series_responses): if hasattr(instance, 'SOPInstanceUID'): ds = Dataset() ds.SeriesInstanceUID = instance.SeriesInstanceUID ds.SOPInstanceUID = instance.SOPInstanceUID copy_dicom_attributes(ds, instance, additional_tags) image_datasets.append(ds) if max_count and len(image_datasets) >= max_count: break return image_datasets