def add_scp_scu_role(self, primitive): """Add SCP/SCU Role Selection to the A-ASSOCIATE primitive.""" contexts = [ build_context('1.2.840.10008.1.1'), build_context('1.2.840.10008.1.2'), build_context('1.2.840.10008.1.3'), build_context('1.2.840.10008.1.4'), ] for ii, cx in enumerate(contexts): cx.context_id = ii * 2 + 1 primitive.presentation_context_definition_list = contexts item = SCP_SCU_RoleSelectionNegotiation() item.sop_class_uid = '1.2.840.10008.1.2' item.scu_role = True item.scp_role = False primitive.user_information.append(item) item = SCP_SCU_RoleSelectionNegotiation() item.sop_class_uid = '1.2.840.10008.1.3' item.scu_role = False item.scp_role = True primitive.user_information.append(item) item = SCP_SCU_RoleSelectionNegotiation() item.sop_class_uid = '1.2.840.10008.1.4' item.scu_role = True item.scp_role = True primitive.user_information.append(item)
def build_role(uid, scu_role=False, scp_role=False): """Return a SCP/SCU Role Selection Negotiation item. Parameters ---------- uid : str or UID or sop_class.SOPClass The UID or SOPClass instance to use as the *SOP Class UID* parameter value. scu_role : bool, optional True to propose the SCU role for the *Requestor*, False otherwise (default). scp_role : bool, optional True to propose the SCP role for the *Requestor*, False otherwise (default). Returns ------- pdu_primitives.SCP_SCU_RoleSelectionNegotiation The role selection item. """ from pynetdicom.pdu_primitives import SCP_SCU_RoleSelectionNegotiation role = SCP_SCU_RoleSelectionNegotiation() role.sop_class_uid = uid role.scu_role = scu_role role.scp_role = scp_role return role
def build_role(uid: Union[str, UID], scu_role: bool = False, scp_role: bool = False) -> "SCP_SCU_RoleSelectionNegotiation": """Return a SCP/SCU Role Selection Negotiation item. .. versionadded:: 1.2 Parameters ---------- uid : str or UID or sop_class.SOPClass The :class:`~pydicom.uid.UID` or subclass of :class:`~pynetdicom.sop_class.SOPClass` instance to use as the *SOP Class UID* parameter value. scu_role : bool, optional ``True`` to propose the SCU role for the *Requestor*, ``False`` otherwise (default). scp_role : bool, optional ``True`` to propose the SCP role for the *Requestor*, ``False`` otherwise (default). Returns ------- pdu_primitives.SCP_SCU_RoleSelectionNegotiation The role selection item. """ from pynetdicom.pdu_primitives import SCP_SCU_RoleSelectionNegotiation role = SCP_SCU_RoleSelectionNegotiation() role.sop_class_uid = UID(uid) role.scu_role = scu_role role.scp_role = scp_role return role
def test_conversion(self): """ Check converting to PDU item works correctly """ primitive = SCP_SCU_RoleSelectionNegotiation() primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2' primitive.scp_role = True primitive.scu_role = False item = primitive.from_primitive() assert item.encode() == ( b'\x54\x00\x00\x1d\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'\x00\x01') primitive = SCP_SCU_RoleSelectionNegotiation() primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2' primitive.scp_role = False primitive.scu_role = False with pytest.raises(ValueError): primitive.from_primitive()
def test_assignment_and_exceptions(self): """ Check incorrect types/values for properties raise exceptions """ primitive = SCP_SCU_RoleSelectionNegotiation() ## 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 # SCP Role primitive.scp_role = False assert primitive.scp_role is False # SCU Role primitive.scu_role = True assert primitive.scu_role is True ## Check exceptions with pytest.raises(TypeError): primitive.sop_class_uid = 10 with pytest.raises(TypeError): primitive.sop_class_uid = 45.2 with pytest.raises(TypeError): primitive.scp_role = 1 with pytest.raises(TypeError): primitive.scp_role = 'abc' with pytest.raises(TypeError): primitive.scu_role = 1 with pytest.raises(TypeError): primitive.scu_role = 'abc' # No value set primitive = SCP_SCU_RoleSelectionNegotiation() 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.scp_role = False with pytest.raises(ValueError): item = primitive.from_primitive() primitive = SCP_SCU_RoleSelectionNegotiation() primitive.sop_class_uid = b'1.2.840.10008.5.1.4.1.1.2' primitive.scu_role = True item = primitive.from_primitive() assert item.scu_role assert not item.scp_role primitive = SCP_SCU_RoleSelectionNegotiation() primitive.scp_role = True primitive.scu_role = True with pytest.raises(ValueError): item = primitive.from_primitive()
def negotiate_as_acceptor(rq_contexts, ac_contexts, roles=None): """Process the Presentation Contexts as an Association acceptor. Parameters ---------- rq_contexts : list of PresentationContext The Presentation Contexts proposed by the peer. Each item has values for Context ID, Abstract Syntax and Transfer Syntax. ac_contexts : list of PresentationContext The Presentation Contexts supported by the local AE when acting as an Association acceptor. Each item has values for Context ID Abstract Syntax and Transfer Syntax. roles : dict or None If the requestor has included one or more SCP/SCU Role Selection Negotiation items then this will be a dict of {SOP Class UID : (SCU role, SCP role)}, otherwise None (default) Returns ------- list of PresentationContext The accepted presentation context items, each with a Result value a Context ID, an Abstract Syntax and one Transfer Syntax item. Items are sorted in increasing Context ID value. list of SCP_SCU_RoleSelectionNegotiation If `roles` is not None then this is a list of SCP/SCU Role Selection Negotiation items that can be sent back to the requestor. """ from pynetdicom.pdu_primitives import SCP_SCU_RoleSelectionNegotiation roles = roles or {} result_contexts = [] reply_roles = {} # No requestor presentation contexts if not rq_contexts: return result_contexts, [] # Acceptor doesn't support any presentation contexts if not ac_contexts: for rq_context in rq_contexts: context = PresentationContext() context.context_id = rq_context.context_id context.abstract_syntax = rq_context.abstract_syntax context.transfer_syntax = [rq_context.transfer_syntax[0]] context.result = 0x03 result_contexts.append(context) return result_contexts, [] # Optimisation notes (for iterating through contexts only, not # including actual context negotiation) # - Create dict, use set intersection/difference of dict keys: ~600 us # - Create dict, iterate over dict keys: ~400 us # - Iterate over lists: ~52000 us # Requestor may use the same Abstract Syntax in multiple Presentation # Contexts so we need a more specific key than UID requestor_contexts = {(cx.context_id, cx.abstract_syntax): cx for cx in rq_contexts} # Acceptor supported SOP Classes must be unique so we can use UID as # the key acceptor_contexts = {cx.abstract_syntax: cx for cx in ac_contexts} for (cntx_id, ab_syntax) in requestor_contexts: # Convenience variable rq_context = requestor_contexts[(cntx_id, ab_syntax)] # Create a new PresentationContext item that will store the # results of the negotiation context = PresentationContext() context.context_id = cntx_id context.abstract_syntax = ab_syntax context._as_scu = False context._as_scp = False # Check if the acceptor supports the Abstract Syntax if ab_syntax in acceptor_contexts: # Convenience variables ac_context = acceptor_contexts[ab_syntax] ac_roles = (ac_context.scu_role, ac_context.scp_role) try: rq_roles = roles[ab_syntax] has_role = True except KeyError: rq_roles = (None, None) has_role = False # Abstract syntax supported so check Transfer Syntax for tr_syntax in rq_context.transfer_syntax: # If transfer syntax supported then (provisionally) accept if tr_syntax in ac_context.transfer_syntax: context.transfer_syntax = [tr_syntax] context.result = 0x00 result_contexts.append(context) break ## SCP/SCU Role Selection Negotiation # Only for (provisionally) accepted contexts if context.result == 0x00: if None in ac_roles: # Default roles context._as_scu = False context._as_scp = True # If either aq.scu_role or ac.scp_role is None then # don't send an SCP/SCU negotiation reply has_role = False else: # Use a LUT to make changes to outcomes easier # also its much simpler than coding if/then branches outcome = SCP_SCU_ROLES[rq_roles][ac_roles] context._as_scu = outcome[2] context._as_scp = outcome[3] # If can't act as either SCU nor SCP then reject the context if context.as_scu is False and context.as_scp is False: # User rejection context.result = 0x01 # Need to check against None as 0x00 is a possible value if context.result is None: # Reject context - transfer syntax not supported context.result = 0x04 context.transfer_syntax = [rq_context.transfer_syntax[0]] result_contexts.append(context) elif context.result == 0x00 and has_role: # Create new SCP/SCU Role Selection Negotiation item role = SCP_SCU_RoleSelectionNegotiation() role.sop_class_uid = context.abstract_syntax # Can't return 0x01 if proposed 0x00 if rq_roles[0] is False: role.scu_role = False else: role.scu_role = ac_context.scu_role if rq_roles[1] is False: role.scp_role = False else: role.scp_role = ac_context.scp_role reply_roles[context.abstract_syntax] = role else: # Reject context - abstract syntax not supported context.result = 0x03 context.transfer_syntax = [rq_context.transfer_syntax[0]] result_contexts.append(context) # Sort by presentation context ID # This isn't required by the DICOM Standard but its a nice thing to do result_contexts = sorted(result_contexts, key=lambda x: x.context_id) # Sort role selection by abstract syntax, also not required but nice reply_roles = sorted(reply_roles.values(), key=lambda x: x.sop_class_uid) return result_contexts, reply_roles
CTImageStorage ) # Initialise the Application Entity ae = AE() # Add the requested presentation contexts (QR SCU) ae.add_requested_context(PatientRootQueryRetrieveInformationModelGet) # Add the requested presentation context (Storage SCP) ae.add_requested_context(CTImageStorage) # Add an SCP/SCU Role Selection Negotiation item for CT Image Storage role = SCP_SCU_RoleSelectionNegotiation() role.sop_class_uid = CTImageStorage # We will be acting as an SCP for CT Image Storage role.scu_role = False role.scp_role = True # Extended negotiation items ext_neg = [role] # Create our Identifier (query) dataset # We need to supply a Unique Key Attribute for each level above the # Query/Retrieve level ds = Dataset() ds.QueryRetrieveLevel = 'PATIENT' # Unique key for PATIENT level ds.PatientID = '1CT1' # Unique key for STUDY level ds.StudyInstanceUID = '1.2.3' # Unique key for SERIES level
def negotiate_unrestricted(rq_contexts: ListCXType, ac_contexts: ListCXType, roles: RoleType = None) -> CXNegotiationReturn: """Process the Presentation Contexts as an Association *Acceptor* with an unrestricted storage service. ..versionadded:: 2.0 Parameters ---------- rq_contexts : list of PresentationContext The Presentation Contexts proposed by the peer. Each item has values for Context ID, Abstract Syntax and Transfer Syntax. ac_contexts : list of PresentationContext The Presentation Contexts supported by the local AE when acting as an Association *Acceptor*. Each item has values for Context ID, Abstract Syntax and Transfer Syntax. roles : dict or None If the *Requestor* has included one or more SCP/SCU Role Selection Negotiation items then this will be a :class:`dict` of ``{'SOP Class UID' : (SCU role, SCP role)}``, otherwise ``None`` (default) Returns ------- list of PresentationContext The accepted presentation context items, each with a Result value a Context ID, an Abstract Syntax and one Transfer Syntax item. Items are sorted in increasing Context ID value. list of SCP_SCU_RoleSelectionNegotiation If `roles` is not ``None`` then this is a :class:`list` of SCP/SCU Role Selection Negotiation items that can be sent back to the *Requestor*. """ from pynetdicom.pdu_primitives import SCP_SCU_RoleSelectionNegotiation roles = roles or {} storage_contexts: List[PresentationContext] = [] non_storage_contexts: List[PresentationContext] = [] reply_roles: Dict[UID, SCP_SCU_RoleSelectionNegotiation] = {} storage_uids = _STORAGE_CLASSES.values() # Split out private/unknown/storage cx's from everything else for cx in rq_contexts: ab_syntax = cast(UID, cx.abstract_syntax) if (ab_syntax.is_private or ab_syntax in storage_uids or not hasattr(pynetdicom.sop_class, ab_syntax.keyword)): storage_contexts.append(cx) else: non_storage_contexts.append(cx) # Negotiate non-storage contexts as normal result_cx, result_roles = negotiate_as_acceptor(non_storage_contexts, ac_contexts, roles) # Accept all storage-like contexts for rcx in storage_contexts: cx = PresentationContext() cx.context_id = rcx.context_id cx.abstract_syntax = rcx.abstract_syntax cx.transfer_syntax = [rcx.transfer_syntax[0]] cx.result = 0x00 cx._as_scu = True cx._as_scp = True # Role selection if rcx.abstract_syntax in roles: role = SCP_SCU_RoleSelectionNegotiation() role.sop_class_uid = rcx.abstract_syntax rq_roles = roles[rcx.abstract_syntax] outcome = SCP_SCU_ROLES[rq_roles][(True, True)] cx._as_scu = outcome[2] cx._as_scp = outcome[3] # Can't return 0x01 if proposed 0x00 role.scu_role = False if not rq_roles[0] else True role.scp_role = False if not rq_roles[1] else True reply_roles[cast(UID, cx.abstract_syntax)] = role result_cx.append(cx) # Not required but a nice thing to do result_cx = sorted(result_cx, key=lambda x: cast(int, x.context_id)) result_roles = sorted(reply_roles.values(), key=lambda x: cast(UID, x.sop_class_uid)) return result_cx, result_roles
def download_series(self, seriesInstanceUID, recieved_callback=None, query_model="P"): self.recieved_callback = recieved_callback ae = AE() # Specify which SOP Classes are supported as an SCU for context in QueryRetrievePresentationContexts: ae.add_requested_context(context.abstract_syntax, ImplicitVRLittleEndian) for context in StoragePresentationContexts[:115]: ae.add_requested_context(context.abstract_syntax, ImplicitVRLittleEndian) # Add SCP/SCU Role Selection Negotiation to the extended negotiation # We want to act as a Storage SCP ext_neg = [] for context in StoragePresentationContexts: role = SCP_SCU_RoleSelectionNegotiation() role.sop_class_uid = context.abstract_syntax role.scp_role = True role.scu_role = False ext_neg.append(role) handlers = [(evt.EVT_C_STORE, self.on_c_store)] if len(self.ae_title) > 0: assoc = ae.associate( self.host, self.port, ae_title=self.ae_title, ext_neg=ext_neg, evt_handlers=handlers, ) else: assoc = ae.associate(self.host, self.port, ext_neg=ext_neg, evt_handlers=handlers) if assoc.is_established: logger.info("Association accepted by the peer") dataset = Dataset() dataset.SeriesInstanceUID = seriesInstanceUID dataset.QueryRetrieveLevel = "SERIES" responses = assoc.send_c_get(dataset, query_model=query_model) for (a, b) in responses: pass # Release the association assoc.release() logger.info("Finished") return self.current_dir