def test_uid_conformance_false(self): """Test UID conformance with ENFORCE_UID_CONFORMANCE = False""" _config.ENFORCE_UID_CONFORMANCE = False primitive = SOPClassExtendedNegotiation() primitive.sop_class_uid = 'abc' assert primitive.sop_class_uid == 'abc'
def test_uid_conformance_true(self): """Test UID conformance with ENFORCE_UID_CONFORMANCE = True""" _config.ENFORCE_UID_CONFORMANCE = True primitive = SOPClassExtendedNegotiation() with pytest.raises(ValueError): primitive.sop_class_uid = 'abc'
def _check_sop_class_extended(self): """Check the user's response to a SOP Class Extended request. Returns ------- list of pdu_primitives.SOPClassExtendedNegotiation The SOP Class Extended Negotiation items to be sent in response """ # pylint: disable=broad-except try: default = evt.get_default_handler(evt.EVT_SOP_EXTENDED) if self.assoc.get_handlers(evt.EVT_SOP_EXTENDED) != default: user_response = evt.trigger( self.assoc, evt.EVT_SOP_EXTENDED, {'app_info' : self.assoc.requestor.sop_class_extended} ) else: user_response = self.assoc.ae.on_sop_class_extended( self.assoc.requestor.sop_class_extended ) except Exception as exc: user_response = {} LOGGER.error( "Exception raised in user's 'on_sop_class_extended' " "implementation" ) LOGGER.exception(exc) if not isinstance(user_response, (type(None), dict)): LOGGER.error( "Invalid type returned by user's 'on_sop_class_extended' " "implementation" ) user_response = {} if not user_response: return [] items = [] for sop_class, app_info in user_response.items(): try: item = SOPClassExtendedNegotiation() item.sop_class_uid = sop_class item.service_class_application_information = app_info items.append(item) except Exception as exc: LOGGER.error( "Unable to set the SOP Class Extended Negotiation " "response values for the SOP Class UID {}" .format(sop_class) ) LOGGER.exception(exc) return items
def test_conversion(self): """ Check converting to PDU item works correctly """ primitive = SOPClassExtendedNegotiation() primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2' primitive.service_class_application_information = ( b'\x02\x00\x03\x00\x01\x00') item = primitive.from_primitive() assert item.encode() == ( b'\x56\x00\x00\x21\x00\x19\x31\x2e\x32\x2e\x38\x34\x30\x2e\x31\x30' b'\x30\x30\x38\x2e\x35\x2e\x31\x2e\x34\x2e\x31\x2e\x31\x2e\x32' b'\x02\x00\x03\x00\x01\x00')
def add_sop_ext(self, primitive): """Add SOP Class Extended to the A-ASSOCIATE primitive.""" req = { '1.2.3.4': b'\x00\x01', '1.2.840.10008.1.1': b'\x00\x01\x02\x03' * 10 } for uid, data in req.items(): item = SOPClassExtendedNegotiation() item.sop_class_uid = uid item.service_class_application_information = data primitive.user_information.append(item)
def _check_sop_class_extended(self): """Check the user's response to a SOP Class Extended request. Returns ------- list of pdu_primitives.SOPClassExtendedNegotiation The SOP Class Extended Negotiation items to be sent in response """ # pylint: disable=broad-except try: user_response = evt.trigger( self.assoc, evt.EVT_SOP_EXTENDED, {'app_info' : self.requestor.sop_class_extended} ) except Exception as exc: user_response = {} LOGGER.error( "Exception raised in handler bound to 'evt.EVT_SOP_EXTENDED'" ) LOGGER.exception(exc) if not isinstance(user_response, (type(None), dict)): LOGGER.error( "Invalid type returned by handler bount to " "'evt.EVT_SOP_EXTENDED'" ) user_response = {} if not user_response: return [] items = [] for sop_class, app_info in user_response.items(): try: item = SOPClassExtendedNegotiation() item.sop_class_uid = sop_class item.service_class_application_information = app_info items.append(item) except Exception as exc: LOGGER.error( f"Unable to set the SOP Class Extended Negotiation " f"response values for the SOP Class UID {sop_class}" ) LOGGER.exception(exc) return items
def test_scp_handler_sop_extended(self): """Test handler event's assoc attribute with SOP Class Extended""" attrs = {} def handle_sop(event): return event.app_info def handle(event): attrs["context"] = event.context attrs["assoc"] = event.assoc attrs["request"] = event.request attrs["dataset"] = event.dataset return 0x0000 handlers = [(evt.EVT_C_STORE, handle), (evt.EVT_SOP_EXTENDED, handle_sop)] self.ae = ae = AE() ae.add_supported_context(CTImageStorage) ae.add_requested_context(CTImageStorage) scp = ae.start_server(("localhost", 11112), block=False, evt_handlers=handlers) ext_neg = [] item = SOPClassExtendedNegotiation() item.sop_class_uid = "1.2.3" item.service_class_application_information = b"\x00\x01" ext_neg.append(item) item = SOPClassExtendedNegotiation() item.sop_class_uid = "1.2.4" item.service_class_application_information = b"\x00\x02" ext_neg.append(item) assoc = ae.associate("localhost", 11112, ext_neg=ext_neg) assert assoc.is_established status = assoc.send_c_store(DATASET) assert status.Status == 0x0000 assoc.release() assert assoc.is_released ext = attrs["assoc"].acceptor.sop_class_extended assert ext["1.2.3"] == b"\x00\x01" assert ext["1.2.4"] == b"\x00\x02" scp.shutdown()
def test_scp_handler_sop_class_extended(self): """Test that the SOP Class Extended info is available.""" def on_ext(req): return req self.scp = DummyStorageSCP() self.scp.ae.on_sop_class_extended = on_ext self.scp.start() ae = AE() ae.add_requested_context(CTImageStorage) ae.acse_timeout = 5 ae.dimse_timeout = 5 ext_neg = [] item = SOPClassExtendedNegotiation() item.sop_class_uid = '1.2.3' item.service_class_application_information = b'\x00\x01' ext_neg.append(item) item = SOPClassExtendedNegotiation() item.sop_class_uid = '1.2.4' item.service_class_application_information = b'\x00\x02' ext_neg.append(item) assoc = ae.associate('localhost', 11112, ext_neg=ext_neg) assert assoc.is_established status = assoc.send_c_store(DATASET) assert status.Status == 0x0000 assoc.release() assert assoc.is_released info = self.scp.info['sop_class_extended'] assert len(info) == 2 assert info['1.2.3'] == b'\x00\x01' assert info['1.2.4'] == b'\x00\x02' self.scp.stop()
def test_assignment_and_exceptions(self): """ Check incorrect types/values for properties raise exceptions """ primitive = SOPClassExtendedNegotiation() ## Check assignment # SOP Class UID reference_uid = UID('1.2.840.10008.5.1.4.1.1.2') primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2' assert primitive.sop_class_uid == reference_uid primitive.sop_class_uid = '1.2.840.10008.5.1.4.1.1.2' assert primitive.sop_class_uid == reference_uid primitive.sop_class_uid = UID('1.2.840.10008.5.1.4.1.1.2') assert primitive.sop_class_uid == reference_uid # Service Class Application Information primitive.service_class_application_information = ( b'\x02\x00\x03\x00\x01\x00') assert primitive.service_class_application_information == ( b'\x02\x00\x03\x00\x01\x00') ## Check exceptions # SOP Class UID with pytest.raises(TypeError): primitive.sop_class_uid = 10 with pytest.raises(TypeError): primitive.sop_class_uid = 45.2 # Service Class Application Information with pytest.raises(TypeError): primitive.service_class_application_information = 10 with pytest.raises(TypeError): primitive.service_class_application_information = 45.2 # Python 2 compatibility all bytes are str #with pytest.raises(TypeError): # primitive.service_class_application_information = 'abc' # No value set primitive = SOPClassExtendedNegotiation() with pytest.raises(ValueError): item = primitive.from_primitive() primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2' with pytest.raises(ValueError): item = primitive.from_primitive() primitive = SOPClassExtendedNegotiation() primitive.service_class_application_information = ( b'\x02\x00\x03\x00\x01\x00') with pytest.raises(ValueError): item = primitive.from_primitive()
def test_assignment_and_exceptions(self): """Check incorrect types/values for properties raise exceptions""" primitive = SOPClassExtendedNegotiation() ## Check assignment # SOP Class UID reference_uid = UID("1.2.840.10008.5.1.4.1.1.2") primitive.sop_class_uid = None assert primitive.sop_class_uid is None primitive.sop_class_uid = b"1.2.840.10008.5.1.4.1.1.2" assert primitive.sop_class_uid == reference_uid primitive.sop_class_uid = "1.2.840.10008.5.1.4.1.1.2" assert primitive.sop_class_uid == reference_uid primitive.sop_class_uid = UID("1.2.840.10008.5.1.4.1.1.2") assert primitive.sop_class_uid == reference_uid # Service Class Application Information primitive.service_class_application_information = None assert primitive.service_class_application_information is None primitive.service_class_application_information = b"\x02\x00\x03\x00\x01\x00" assert primitive.service_class_application_information == ( b"\x02\x00\x03\x00\x01\x00" ) ## Check exceptions # SOP Class UID with pytest.raises(TypeError): primitive.sop_class_uid = 10 with pytest.raises(TypeError): primitive.sop_class_uid = 45.2 # Service Class Application Information with pytest.raises(TypeError): primitive.service_class_application_information = 10 with pytest.raises(TypeError): primitive.service_class_application_information = 45.2 # No value set primitive = SOPClassExtendedNegotiation() with pytest.raises(ValueError): item = primitive.from_primitive() primitive.sop_class_uid = b"1.2.840.10008.5.1.4.1.1.2" with pytest.raises(ValueError): item = primitive.from_primitive() primitive = SOPClassExtendedNegotiation() primitive.service_class_application_information = b"\x02\x00\x03\x00\x01\x00" with pytest.raises(ValueError): item = primitive.from_primitive()
def main(args=None): """Run the application.""" if args is not None: sys.argv = args args = _setup_argparser() if args.version: print(f"movescu.py v{__version__}") sys.exit() APP_LOGGER = setup_logging(args, "movescu") APP_LOGGER.debug(f"movescu.py v{__version__}") APP_LOGGER.debug("") # Create query (identifier) dataset try: # If you're looking at this to see how QR Move works then `identifer` # is a pydicom Dataset instance with your query keys, e.g.: # identifier = Dataset() # identifier.QueryRetrieveLevel = 'PATIENT' # identifier.PatientName = '*' identifier = create_dataset(args, APP_LOGGER) except Exception as exc: APP_LOGGER.exception(exc) sys.exit(1) # Create application entity ae = AE() # Start the Store SCP (optional) scp = None if args.store: transfer_syntax = ALL_TRANSFER_SYNTAXES[:] store_handlers = [(evt.EVT_C_STORE, handle_store, [args, APP_LOGGER])] ae.ae_title = args.store_aet for cx in AllStoragePresentationContexts: ae.add_supported_context(cx.abstract_syntax, transfer_syntax) scp = ae.start_server(("localhost", args.store_port), block=False, evt_handlers=store_handlers) ae.ae_title = args.calling_aet ae.acse_timeout = args.acse_timeout ae.dimse_timeout = args.dimse_timeout ae.network_timeout = args.network_timeout ae.requested_contexts = QueryRetrievePresentationContexts ae.supported_contexts = [] # Query/Retrieve Information Models if args.study: query_model = StudyRootQueryRetrieveInformationModelMove elif args.psonly: query_model = PatientStudyOnlyQueryRetrieveInformationModelMove else: query_model = PatientRootQueryRetrieveInformationModelMove # Extended Negotiation ext_neg = [] ext_opts = [args.relational_retrieval, args.enhanced_conversion] if any(ext_opts): app_info = b"" for option in ext_opts: app_info += b"\x01" if option else b"\x00" item = SOPClassExtendedNegotiation() item.sop_class_uid = query_model item.service_class_application_information = app_info ext_neg = [item] # Request association with remote AE assoc = ae.associate( args.addr, args.port, ae_title=args.called_aet, max_pdu=args.max_pdu, ext_neg=ext_neg, ) if assoc.is_established: # Send query move_aet = args.move_aet or args.calling_aet responses = assoc.send_c_move(identifier, move_aet, query_model) for (status, rsp_identifier) in responses: # If `status.Status` is one of the 'Pending' statuses then # `rsp_identifier` is the C-MOVE response's Identifier dataset if status and status.Status in [0xFF00, 0xFF01]: # `rsp_identifier` is a pydicom Dataset containing a query # response. You may want to do something interesting here... pass assoc.release() _EXIT_VALUE = 0 else: _EXIT_VALUE = 1 # Shutdown the Storage SCP (if used) if scp: scp.shutdown() sys.exit(_EXIT_VALUE)
def main(args=None): """Run the application.""" if args is not None: sys.argv = args args = _setup_argparser() if args.version: print(f"getscu.py v{__version__}") sys.exit() APP_LOGGER = setup_logging(args, "getscu") APP_LOGGER.debug(f"getscu.py v{__version__}") APP_LOGGER.debug("") # Create query (identifier) dataset try: # If you're looking at this to see how QR Get works then `identifer` # is a pydicom Dataset instance with your query keys, e.g.: # identifier = Dataset() # identifier.QueryRetrieveLevel = 'PATIENT' # identifier.PatientName = '*' identifier = create_dataset(args, APP_LOGGER) except Exception as exc: APP_LOGGER.exception(exc) sys.exit(1) # Exclude these SOP Classes _exclusion = [ EncapsulatedSTLStorage, EncapsulatedOBJStorage, EncapsulatedMTLStorage, ] store_contexts = [ cx for cx in StoragePresentationContexts if cx.abstract_syntax not in _exclusion ] # Create application entity # Binding to port 0 lets the OS pick an available port ae = AE(ae_title=args.calling_aet) ae.acse_timeout = args.acse_timeout ae.dimse_timeout = args.dimse_timeout ae.network_timeout = args.network_timeout # Extended Negotiation - SCP/SCU Role Selection ext_neg = [] ae.add_requested_context(PatientRootQueryRetrieveInformationModelGet) ae.add_requested_context(StudyRootQueryRetrieveInformationModelGet) ae.add_requested_context(PatientStudyOnlyQueryRetrieveInformationModelGet) for cx in store_contexts: ae.add_requested_context(cx.abstract_syntax) # Add SCP/SCU Role Selection Negotiation to the extended negotiation # We want to act as a Storage SCP ext_neg.append(build_role(cx.abstract_syntax, scp_role=True)) if args.study: query_model = StudyRootQueryRetrieveInformationModelGet elif args.psonly: query_model = PatientStudyOnlyQueryRetrieveInformationModelGet else: query_model = PatientRootQueryRetrieveInformationModelGet # Extended Negotiation - SOP Class Extended ext_opts = [args.relational_retrieval, args.enhanced_conversion] if any(ext_opts): app_info = b"" for option in ext_opts: app_info += b"\x01" if option else b"\x00" item = SOPClassExtendedNegotiation() item.sop_class_uid = query_model item.service_class_application_information = app_info ext_neg.append(item) # Request association with remote assoc = ae.associate( args.addr, args.port, ae_title=args.called_aet, ext_neg=ext_neg, evt_handlers=[(evt.EVT_C_STORE, handle_store, [args, APP_LOGGER])], max_pdu=args.max_pdu, ) if assoc.is_established: # Send query responses = assoc.send_c_get(identifier, query_model) for (status, rsp_identifier) in responses: # If `status.Status` is one of the 'Pending' statuses then # `rsp_identifier` is the C-GET response's Identifier dataset if status and status.Status in [0xFF00, 0xFF01]: # `rsp_identifier` is a pydicom Dataset containing a query # response. You may want to do something interesting here... pass assoc.release() else: sys.exit(1)
def main(args=None): """Run the application.""" if args is not None: sys.argv = args args = _setup_argparser() if args.version: print(f"findscu.py v{__version__}") sys.exit() APP_LOGGER = setup_logging(args, "findscu") APP_LOGGER.debug(f"findscu.py v{__version__}") APP_LOGGER.debug("") # Create query (identifier) dataset try: # If you're looking at this to see how QR Find works then `identifer` # is a pydicom Dataset instance with your query keys, e.g.: # identifier = Dataset() # identifier.QueryRetrieveLevel = 'PATIENT' # identifier.PatientName = '' identifier = create_dataset(args, APP_LOGGER) except Exception as exc: APP_LOGGER.exception(exc) sys.exit(1) # Create application entity # Binding to port 0 lets the OS pick an available port ae = AE(ae_title=args.calling_aet) # Set timeouts ae.acse_timeout = args.acse_timeout ae.dimse_timeout = args.dimse_timeout ae.network_timeout = args.network_timeout # Set the Presentation Contexts we are requesting the Find SCP support ae.requested_contexts = (QueryRetrievePresentationContexts + BasicWorklistManagementPresentationContexts) # Query/Retrieve Information Models if args.worklist: query_model = ModalityWorklistInformationFind elif args.study: query_model = StudyRootQueryRetrieveInformationModelFind elif args.psonly: query_model = PatientStudyOnlyQueryRetrieveInformationModelFind else: query_model = PatientRootQueryRetrieveInformationModelFind # Extended Negotiation ext_neg = [] ext_opts = [ args.relational_query, args.dt_matching, args.fuzzy_names, args.timezone_adj, args.enhanced_conversion, ] if not args.worklist and any(ext_opts): app_info = b"" for option in ext_opts: app_info += b"\x01" if option else b"\x00" item = SOPClassExtendedNegotiation() item.sop_class_uid = query_model item.service_class_application_information = app_info ext_neg = [item] elif args.worklist and any([args.fuzzy_names, args.timezone_adj]): app_info = b"\x01\x01" for option in [args.fuzzy_names, args.timezone_adj]: app_info += b"\x01" if option else b"\x00" item = SOPClassExtendedNegotiation() item.sop_class_uid = query_model item.service_class_application_information = app_info ext_neg = [item] # Request association with (QR/BWM) Find SCP assoc = ae.associate( args.addr, args.port, ae_title=args.called_aet, max_pdu=args.max_pdu, ext_neg=ext_neg, ) if assoc.is_established: # Send C-FIND request, `responses` is a generator responses = assoc.send_c_find(identifier, query_model) # Used to generate filenames if args.write used fname = generate_filename() for (status, rsp_identifier) in responses: # If `status.Status` is one of the 'Pending' statuses then # `rsp_identifier` is the C-FIND response's Identifier dataset if status and status.Status in [0xFF00, 0xFF01]: if args.write: rsp_identifier.file_meta = get_file_meta( assoc, query_model) rsp_identifier.save_as(next(fname), write_like_original=False) # Release the association assoc.release() else: sys.exit(1)