def test_assignment(self): """ Check assignment works correctly """ primitive = N_EVENT_REPORT() # AffectedSOPClassUID primitive.AffectedSOPClassUID = '1.1.1' assert primitive.AffectedSOPClassUID == UID('1.1.1') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPClassUID = UID('1.1.2') assert primitive.AffectedSOPClassUID == UID('1.1.2') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPClassUID = b'1.1.3' assert primitive.AffectedSOPClassUID == UID('1.1.3') assert isinstance(primitive.AffectedSOPClassUID, UID) # AffectedSOPInstanceUID primitive.AffectedSOPInstanceUID = b'1.2.1' assert primitive.AffectedSOPInstanceUID == UID('1.2.1') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPInstanceUID = UID('1.2.2') assert primitive.AffectedSOPInstanceUID == UID('1.2.2') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPInstanceUID = '1.2.3' assert primitive.AffectedSOPInstanceUID == UID('1.2.3') assert isinstance(primitive.AffectedSOPClassUID, UID) # Event Information ds = Dataset() ds.PatientID = '1234567' primitive.EventInformation = BytesIO(encode(ds, True, True)) ds = decode(primitive.EventInformation, True, True) assert ds.PatientID == '1234567' # Event Reply ds = Dataset() ds.PatientID = '123456' primitive.EventReply = BytesIO(encode(ds, True, True)) ds = decode(primitive.EventReply, True, True) assert ds.PatientID == '123456' # Event Type ID primitive.EventTypeID = 0x0000 assert primitive.EventTypeID == 0x0000 # MessageID primitive.MessageID = 11 assert 11 == primitive.MessageID # MessageIDBeingRespondedTo primitive.MessageIDBeingRespondedTo = 13 assert 13 == primitive.MessageIDBeingRespondedTo # Status primitive.Status = 0x0000 assert primitive.Status == 0x0000
def test_message_to_primitive_c_store(self): """Test converting C_STORE_RQ and _RSP to C_STORE primitive.""" msg = C_STORE_RQ() for data in [c_store_rq_cmd, c_store_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, C_STORE) assert isinstance(primitive.DataSet, BytesIO) assert primitive.AffectedSOPClassUID == UID("1.1.1") assert primitive.AffectedSOPInstanceUID == UID("1.2.1") assert primitive.Priority == 2 assert primitive.MoveOriginatorApplicationEntityTitle == "UNITTEST" assert primitive.MoveOriginatorMessageID == 3 ds = decode(primitive.DataSet, True, True) assert ds.PatientName == "Tube^HeNe" assert ds.PatientID == "Test1101" msg = C_STORE_RSP() p_data = P_DATA() p_data.presentation_data_value_list.append([0, c_store_rsp_cmd]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, C_STORE) assert primitive.DataSet is None for elem in msg.command_set: if hasattr(primitive, elem.keyword): item = getattr(primitive, elem.keyword) assert item == elem.value
def test_message_to_primitive_c_move_with_duplicate_command_fields(self): msg = C_MOVE_RSP() for data in [c_move_rsp_cmd_with_dup, c_move_rsp_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, C_MOVE) assert isinstance(primitive.Identifier, BytesIO) assert primitive.AffectedSOPClassUID == UID( "1.2.840.10008.5.1.4.1.1.2") assert primitive.Status == 65280 assert primitive.MessageIDBeingRespondedTo == 5 # Fields with VM > 1 that can only appear once assert primitive.NumberOfRemainingSuboperations == 3 assert primitive.NumberOfCompletedSuboperations == 1 assert primitive.NumberOfFailedSuboperations == 2 assert primitive.NumberOfWarningSuboperations == 4 # Fields with VM > 1 that are allowed to appear multiple times assert primitive.OffendingElement == [(0x0, 0x1), (0x0, 0x2)] ds = decode(primitive.Identifier, True, True) assert ds.QueryRetrieveLevel == "PATIENT" assert ds.PatientID == "*"
def test_implicit_little(self): """Test decoding using implicit VR little endian.""" bytestring = BytesIO() bytestring.write(b'\x10\x00\x10\x00\x0e\x00\x00\x00\x43\x49' \ b'\x54\x49\x5a\x45\x4e\x5e\x53\x6e\x69\x70' \ b'\x73\x20') ds = decode(bytestring, True, True) assert ds.PatientName == 'CITIZEN^Snips'
def test_explicit_big(self): """Test decoding using explicit VR big endian.""" bytestring = BytesIO() bytestring.write(b'\x00\x10\x00\x10\x50\x4e\x00\x0e\x43\x49' \ b'\x54\x49\x5a\x45\x4e\x5e\x53\x6e\x69\x70' \ b'\x73\x20') ds = decode(bytestring, False, False) assert ds.PatientName == 'CITIZEN^Snips'
def _get_dataset(self, attr: str, exc_msg: str) -> Dataset: """Return DIMSE dataset-like parameter as a *pydicom* Dataset. Parameters ---------- attr : str The name of the DIMSE primitive's dataset-like parameter, one of 'DataSet', 'Identifier', 'AttributeList', 'ModificationList', 'EventInformation', 'ActionInformation'. exc_msg : str The exception message to use if the request primitive has no dataset-like parameter. Returns ------- pydicom.dataset.Dataset The decoded dataset-like parameter. Raises ------ AttributeError If the corresponding event is not due to one of the DIMSE requests with a dataset-like parameter. """ try: bytestream = getattr(self.request, attr) # If no change in encoded data then return stored decode if self._hash == hash(bytestream): return cast(Dataset, self._decoded) # Some dataset-like parameters are optional if bytestream and bytestream.getvalue() != b"": # Dataset-like parameter has been used t_syntax = self.context.transfer_syntax ds = decode( bytestream, t_syntax.is_implicit_VR, t_syntax.is_little_endian, t_syntax.is_deflated, ) ds.is_little_endian = t_syntax.is_little_endian ds.is_implicit_VR = t_syntax.is_implicit_VR # Store the decoded dataset in case its accessed again self._decoded = ds else: # Dataset-like parameter hasn't been used self._decoded = Dataset() self._hash = hash(bytestream) return self._decoded except AttributeError as exc: pass raise AttributeError(exc_msg)
def test_deflated(self): """Test decoding a deflated explicit VR little endian dataset.""" with open(DEFL_DATASET, 'rb') as f: # Only the main dataset uses deflated encoding f.seek(334) b = BytesIO(f.read()) ds = decode(b, False, True, True) assert ds.PatientName == "^^^^"
def test_explicit_little(self): """Test decoding using explicit VR little endian.""" bytestring = BytesIO() bytestring.write( b"\x10\x00\x10\x00\x50\x4e\x0e\x00\x43\x49" b"\x54\x49\x5a\x45\x4e\x5e\x53\x6e\x69\x70" b"\x73\x20" ) ds = decode(bytestring, False, True) assert ds.PatientName == "CITIZEN^Snips"
def test_message_to_primitive_c_move(self): """Test converting C_MOVE_RQ to C_MOVE primitive.""" msg = C_MOVE_RQ() for data in [c_move_rq_cmd, c_move_rq_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, C_MOVE) assert isinstance(primitive.Identifier, BytesIO) assert primitive.AffectedSOPClassUID == UID( "1.2.840.10008.5.1.4.1.1.2") assert primitive.Priority == 2 assert primitive.MoveDestination == "MOVE_SCP" assert primitive.MessageID == 7 ds = decode(primitive.Identifier, True, True) assert ds.QueryRetrieveLevel == "PATIENT" assert ds.PatientID == "*" msg = C_MOVE_RSP() for data in [c_move_rsp_cmd, c_move_rsp_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, C_MOVE) assert isinstance(primitive.Identifier, BytesIO) assert primitive.AffectedSOPClassUID == UID( "1.2.840.10008.5.1.4.1.1.2") assert primitive.Status == 65280 assert primitive.MessageIDBeingRespondedTo == 5 assert primitive.NumberOfRemainingSuboperations == 3 assert primitive.NumberOfCompletedSuboperations == 1 assert primitive.NumberOfFailedSuboperations == 2 assert primitive.NumberOfWarningSuboperations == 4 ds = decode(primitive.Identifier, True, True) assert ds.QueryRetrieveLevel == "PATIENT" assert ds.PatientID == "*"
def test_message_to_primitive_n_action(self): """Test converting N_ACTION_RQ and _RSP to primitive.""" # N-ACTION-RQ msg = N_ACTION_RQ() for data in [n_action_rq_cmd, n_action_rq_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_ACTION) assert primitive.RequestedSOPClassUID == UID( "1.2.840.10008.5.1.4.1.1.2") assert primitive.RequestedSOPInstanceUID == UID( "1.2.392.200036.9116.2.6.1.48") assert primitive.MessageID == 7 assert primitive.ActionTypeID == 1 ds = decode(primitive.ActionInformation, True, True) assert ds.PatientName == "Tube HeNe" assert ds.PatientID == "Test1101" # N-ACTION-RSP msg = N_ACTION_RSP() for data in [n_action_rsp_cmd, n_action_rsp_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_ACTION) assert primitive.AffectedSOPClassUID == UID("1.2.4.10") assert primitive.AffectedSOPInstanceUID == UID("1.2.4.5.7.8") assert primitive.MessageIDBeingRespondedTo == 5 assert primitive.ActionTypeID == 1 assert primitive.Status == 0x0000 ds = decode(primitive.ActionReply, True, True) assert ds.PatientName == "Tube HeNe" assert ds.PatientID == "Test1101"
def test_message_to_primitive_n_set(self): """Test converting N_SET_RQ and _RSP to primitive.""" # N-SET-RQ msg = N_SET_RQ() for data in [n_set_rq_cmd, n_set_rq_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_SET) assert primitive.RequestedSOPClassUID == UID( "1.2.840.10008.5.1.4.1.1.2") assert primitive.RequestedSOPInstanceUID == UID( "1.2.392.200036.9116.2.6.1.48") assert primitive.MessageID == 7 ds = decode(primitive.ModificationList, True, True) assert ds.PatientName == "Tube HeNe" assert ds.PatientID == "Test1101" # N-SET-RSP msg = N_SET_RSP() for data in [n_set_rsp_cmd, n_set_rsp_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_SET) assert isinstance(primitive.AttributeList, BytesIO) assert primitive.AffectedSOPClassUID == UID("1.2.4.10") assert primitive.AffectedSOPInstanceUID == UID("1.2.4.5.7.8") assert primitive.MessageIDBeingRespondedTo == 5 assert primitive.Status == 0x0000 ds = decode(primitive.AttributeList, True, True) assert ds.PatientName == "Tube HeNe" assert ds.PatientID == "Test1101"
def test_message_to_primitive_n_event_report(self): """Test converting N_EVENT_REPORT_RQ and _RSP to primitive.""" # N-EVENT-REPORT-RQ msg = N_EVENT_REPORT_RQ() for data in [n_er_rq_cmd, n_er_rq_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_EVENT_REPORT) assert primitive.AffectedSOPClassUID == UID( '1.2.840.10008.5.1.4.1.1.2') assert primitive.AffectedSOPInstanceUID == UID( '1.2.392.200036.9116.2.6.1.48') assert primitive.MessageID == 7 assert primitive.EventTypeID == 2 ds = decode(primitive.EventInformation, True, True) assert ds.PatientName == 'Tube HeNe' assert ds.PatientID == 'Test1101' # N-EVENT-REPORT-RSP msg = N_EVENT_REPORT_RSP() for data in [n_er_rsp_cmd, n_er_rsp_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_EVENT_REPORT) assert primitive.AffectedSOPClassUID == UID('1.2.4.10') assert primitive.AffectedSOPInstanceUID == UID('1.2.4.5.7.8') assert primitive.MessageIDBeingRespondedTo == 5 assert primitive.EventTypeID == 2 assert primitive.Status == 0x0000 ds = decode(primitive.EventReply, True, True) assert ds.PatientName == 'Tube HeNe' assert ds.PatientID == 'Test1101'
def test_message_to_primitive_c_find(self): """Test converting C_FIND_RQ to C_FIND primitive.""" msg = C_FIND_RQ() for data in [c_find_rq_cmd, c_find_rq_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, C_FIND) assert isinstance(primitive.Identifier, BytesIO) assert primitive.AffectedSOPClassUID == UID( "1.2.840.10008.5.1.4.1.1.2") assert primitive.Priority == 2 assert primitive.MessageID == 7 ds = decode(primitive.Identifier, True, True) assert ds.QueryRetrieveLevel == "PATIENT" assert ds.PatientID == "*" msg = C_GET_RSP() for data in [c_find_rsp_cmd, c_find_rsp_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, C_FIND) assert isinstance(primitive.Identifier, BytesIO) assert primitive.AffectedSOPClassUID == UID( "1.2.840.10008.5.1.4.1.1.2") assert primitive.Status == 65280 ds = decode(primitive.Identifier, True, True) assert ds.QueryRetrieveLevel == "PATIENT" assert ds.RetrieveAETitle == "FINDSCP" assert ds.PatientName == "ANON^A^B^C^D"
def test_message_to_primitive_n_create(self): """Test converting N_CREATE_RQ and _RSP to primitive.""" # N-CREATE-RQ msg = N_CREATE_RQ() for data in [n_create_rq_cmd, n_create_rq_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_CREATE) assert primitive.AffectedSOPClassUID == UID( '1.2.840.10008.5.1.4.1.1.2') assert primitive.AffectedSOPInstanceUID == UID( '1.2.392.200036.9116.2.6.1.48') assert primitive.MessageID == 7 ds = decode(primitive.AttributeList, True, True) assert ds.PatientName == 'Tube HeNe' assert ds.PatientID == 'Test1101' # N-ACTION-RSP msg = N_CREATE_RSP() for data in [n_create_rsp_cmd, n_create_rsp_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_CREATE) assert primitive.AffectedSOPClassUID == UID('1.2.4.10') assert primitive.AffectedSOPInstanceUID == UID('1.2.4.5.7.8') assert primitive.MessageIDBeingRespondedTo == 5 assert primitive.Status == 0x0000 ds = decode(primitive.AttributeList, True, True) assert ds.PatientName == 'Tube HeNe' assert ds.PatientID == 'Test1101'
def test_assignment(self): """ Check assignment works correctly """ primitive = N_CREATE() # AffectedSOPClassUID primitive.AffectedSOPClassUID = '1.1.1' assert primitive.AffectedSOPClassUID == UID('1.1.1') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPClassUID = UID('1.1.2') assert primitive.AffectedSOPClassUID == UID('1.1.2') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPClassUID = b'1.1.3' assert primitive.AffectedSOPClassUID == UID('1.1.3') assert isinstance(primitive.AffectedSOPClassUID, UID) # AffectedSOPInstanceUID primitive.AffectedSOPInstanceUID = b'1.2.1' assert primitive.AffectedSOPInstanceUID == UID('1.2.1') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPInstanceUID = UID('1.2.2') assert primitive.AffectedSOPInstanceUID == UID('1.2.2') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPInstanceUID = '1.2.3' assert primitive.AffectedSOPInstanceUID == UID('1.2.3') assert isinstance(primitive.AffectedSOPClassUID, UID) # AttributeList ref_ds = Dataset() ref_ds.PatientID = '1234567' primitive.AttributeList = BytesIO(encode(ref_ds, True, True)) ds = decode(primitive.AttributeList, True, True) assert ds.PatientID == '1234567' # MessageID primitive.MessageID = 11 assert 11 == primitive.MessageID # MessageIDBeingRespondedTo primitive.MessageIDBeingRespondedTo = 13 assert 13 == primitive.MessageIDBeingRespondedTo # Status primitive.Status = 0x0000 assert primitive.Status == 0x0000
def identifier(self): """Return a C-FIND, C-GET or C-MOVE request's `Identifier` as a *pydicom* Dataset. Because *pydicom* defers data parsing during decoding until an element is actually required the returned ``Dataset`` may raise an exception when any element is first accessed. It's therefore important that proper error handling be part of any handler that uses the returned ``Dataset``. Returns ------- pydicom.dataset.Dataset The decoded *Identifier* dataset. Raises ------ AttributeError If the corresponding event is not a C-FIND, C-GET or C-MOVE request. """ try: # If no change in encoded data then return stored decode if self._hash == hash(self.request.Identifier): return self._decoded t_syntax = self.context.transfer_syntax ds = decode(self.request.Identifier, t_syntax.is_implicit_VR, t_syntax.is_little_endian) # Store the decoded dataset in case its accessed again self._hash = hash(self.request.Identifier) self._decoded = ds return ds except AttributeError: pass raise AttributeError( "The corresponding event is not a C-FIND, C-GET or C-MOVE request " "and has no 'Identifier' parameter" )
def test_message_to_primitive_n_get(self): """Test converting N_GET_RQ and _RSP to primitive.""" # N-GET-RQ msg = N_GET_RQ() for data in [n_get_rq_cmd]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_GET) assert primitive.RequestedSOPClassUID == UID( '1.2.840.10008.5.1.4.1.1.2') assert primitive.RequestedSOPInstanceUID == UID( '1.2.392.200036.9116.2.6.1.48') assert primitive.MessageID == 7 primitive.AttributeIdentifierList = [(0x7fe0, 0x0010), (0x0000, 0x0000), (0xFFFF, 0xFFFF)] msg = N_GET_RSP() for data in [n_get_rsp_cmd, n_get_rsp_ds]: p_data = P_DATA() p_data.presentation_data_value_list.append([0, data]) msg.decode_msg(p_data) msg.decode_msg(p_data) primitive = msg.message_to_primitive() assert isinstance(primitive, N_GET) assert primitive.AttributeIdentifierList is None assert primitive.AffectedSOPClassUID == UID('1.2.4.10') assert primitive.AffectedSOPInstanceUID == UID('1.2.4.5.7.8') assert primitive.MessageIDBeingRespondedTo == 5 assert primitive.Status == 0x0000 ds = decode(primitive.AttributeList, True, True) assert ds.PatientName == 'Tube HeNe' assert ds.PatientID == 'Test1101'
def decode_msg(self, primitive): """Converts P-DATA primitives into a DIMSEMessage sub-class. Decodes the data from the P-DATA service primitive (which may contain the results of one or more P-DATA-TF PDUs) into the `command_set` and `data_set` attributes. Also sets the `ID` and `encoded_command_set` attributes of the DIMSEMessage sub-class object. Parameters ---------- primitive : pdu_primitives.P_DATA The P-DATA service primitive to be decoded into a DIMSE message. Returns ------- bool True when the DIMSE message is completely decoded, False otherwise. References ---------- * DICOM Standard, Part 8, Annex E """ # Make sure this is a P-DATA primitive if primitive.__class__ != P_DATA or primitive is None: return False for (context_id, data) in primitive.presentation_data_value_list: # The first byte of the P-DATA is the Message Control Header # See Part 8, Annex E.2 # The standard says that only the significant bits (ie the last # two) should be checked # xxxxxx00 - Message Dataset information, not the last fragment # xxxxxx01 - Command information, not the last fragment # xxxxxx10 - Message Dataset information, the last fragment # xxxxxx11 - Command information, the last fragment ## Compatibility # Python 2 # - data[0] returns length 1 str # - data[:1] returns length 1 str # Python 3 # - data[0] returns int # - data[:1] returns length 1 bytes # So grab str/bytes and convert to int rather than grab # str/int and convert the str to int. The reason for this is # that a type check is twice as expensive as just converting # str/bytes to int control_header_byte = ord(data[:1]) # LOGGER.debug('Control header byte %s', control_header_byte) #print('Control header byte {}'.format(control_header_byte)) # COMMAND SET # P-DATA fragment contains Command Set information # (control_header_byte is xxxxxx01 or xxxxxx11) if control_header_byte & 1: # The command set may be spread out over a number # of fragments and P-DATA primitives and we need to remember # the elements from previous fragments, hence the # encoded_command_set class attribute # This adds all the command set data to the class object self.encoded_command_set.write(data[1:]) # The final command set fragment (xxxxxx11) has been added # so decode the command set if control_header_byte & 2: # Presentation Context ID # Set this now as must only be one final command set # fragment and command set must always be present self.context_id = context_id # Command Set is always encoded Implicit VR Little Endian # decode(dataset, is_implicit_VR, is_little_endian) # pylint: disable=attribute-defined-outside-init self.command_set = decode(self.encoded_command_set, True, True) # Determine which DIMSE Message class to use self.__class__ = _MESSAGE_CLASS_TYPES[ self.command_set.CommandField] # Determine if a Data Set is present by checking for # (0000, 0800) CommandDataSetType US 1. If the value is # 0x0101 no dataset present, otherwise one is. if self.command_set.CommandDataSetType == 0x0101: # By returning True we're indicating that the message # has been completely decoded return True # DATA SET # P-DATA fragment contains Data Set information # (control_header_byte is xxxxxx00 or xxxxxx10) else: # As with the command set, the data set may be spread over # a number of fragments in each P-DATA primitive and a # number of P-DATA primitives. self.data_set.write(data[1:]) # The final data set fragment (xxxxxx10) has been added if control_header_byte & 2 != 0: # By returning True we're indicating that the message # has been completely decoded return True # We return False to indicate that the message isn't yet fully decoded return False
def Decode(self, pdata): """ Converts a series of P-DATA primitives into data for the DIMSE Message Decodes the data from the P-DATA service primitive (which may contain the results of one or more P-DATA-TF PDUs) into the `command_set` and `data_set` attributes. Also sets the `ID` and `encoded_command_set` attributes PS3.9 Section 9.3.1: The encoding of the DICOM UL PDUs is big endian byte ordering, while the encoding of the PDV message fragments is defined by the negotiated Transfer Syntax at association establishment. A fragment is also known as an Application Protocol Data Unit (APDU) using the OSI nomenclature (PS3.7 8.1). Parameters ---------- pdata : pynetdicom.DULparameters.P_DATA_ServiceParameters The P-DATA service primitive to be decoded into a DIMSE message Returns ------- bool True when complete, False otherwise. """ # Make sure this is a P-DATA primitive if pdata.__class__ != P_DATA or pdata is None: return False for pdv_item in pdata.presentation_data_value_list: # Presentation Context ID self.ID = pdv_item[0] # The first byte of the P-DATA is the Message Control Header # See PS3.8 Annex E.2 # The standard says that only the significant bits (ie the last # two) should be checked # xxxxxx00 - Message Dataset information, not the last fragment # xxxxxx01 - Command information, not the last fragment # xxxxxx10 - Message Dataset information, the last fragment # xxxxxx11 - Command information, the last fragment control_header_byte = pdv_item[1][0] ## COMMAND SET # P-DATA fragment contains Command information # (control_header_byte is xxxxxx01 or xxxxxx11) if control_header_byte & 1: # The command set may be spread out over a number # of fragments and we need to remember the elements # from previous fragments, hence the encoded_command_set # class attribute self.encoded_command_set.write(pdv_item[1][1:]) # The P-DATA fragment is the last one (xxxxxx11) if control_header_byte & 2: # Command Set is always encoded Implicit VR Little Endian # decode(dataset, is_implicit_VR, is_little_endian) self.command_set = decode(self.encoded_command_set, True, True) # Determine which DIMSE Message class to use self.__class__ = MessageType[self.command_set.CommandField] # (0000, 0800) CommandDataSetType US 1 # if value is 0101H no dataset present # otherwise a dataset is included in the Message if self.command_set.CommandDataSetType == 0x0101: return True ## DATA SET # P-DATA fragment contains Message Dataset information # (control_header_byte is xxxxxx00 or xxxxxx10) else: self.data_set.write(pdv_item[1][1:]) # The P-DATA fragment is the last one (xxxxxx10) if control_header_byte & 2 != 0: return True return False
def test_failure(self): bytestream = BytesIO(b'\x08\x00\x01\x00\x04\x00\x00\x00\x00\x08\x00\x49') with pytest.raises(NotImplementedError): ds = decode(bytestream, False, True) print(ds)
def decode_msg(self, primitive, assoc=None): """Converts P-DATA primitives into a ``DIMSEMessage`` sub-class. Decodes the data from the P-DATA service primitive (which may contain the results of one or more P-DATA-TF PDUs) into the :attr:`~DIMSEMessage.command_set` and :attr:`~DIMSEMessage.data_set` attributes. Also sets the :attr:`~DIMSEMessage.context_id` and :attr:`~DIMSEMessage.encoded_command_set` attributes of the ``DIMSEMessage`` sub-class object. Parameters ---------- primitive : pdu_primitives.P_DATA The P-DATA service primitive to be decoded into a DIMSE message. assoc : association.Association, optional The association processing the message. This is required when: * :attr:`~pynetdicom._config.STORE_RECV_CHUNKED_DATASET` is ``True`` * The P-DATA primitive contains part of a C-STORE-RQ message In this case the association is consulted for its accepted transfer syntax, which is included in the File Meta Information of the stored dataset. Returns ------- bool ``True`` when the DIMSE message is completely decoded, ``False`` otherwise. References ---------- * DICOM Standard, Part 8, :dcm:`Annex E<part08/chapter_E.html>` """ # Make sure this is a P-DATA primitive if primitive.__class__ != P_DATA or primitive is None: return False for (context_id, data) in primitive.presentation_data_value_list: # The first byte of the P-DATA is the Message Control Header # See Part 8, Annex E.2 # The standard says that only the significant bits (ie the last # two) should be checked # xxxxxx00 - Message Dataset information, not the last fragment # xxxxxx01 - Command information, not the last fragment # xxxxxx10 - Message Dataset information, the last fragment # xxxxxx11 - Command information, the last fragment control_header_byte = data[0] # LOGGER.debug('Control header byte %s', control_header_byte) #print(f'Control header byte {control_header_byte}') # COMMAND SET # P-DATA fragment contains Command Set information # (control_header_byte is xxxxxx01 or xxxxxx11) if control_header_byte & 1: # The command set may be spread out over a number # of fragments and P-DATA primitives and we need to remember # the elements from previous fragments, hence the # encoded_command_set class attribute # This adds all the command set data to the class object self.encoded_command_set.write(data[1:]) # The final command set fragment (xxxxxx11) has been added # so decode the command set if control_header_byte & 2: # Presentation Context ID # Set this now as must only be one final command set # fragment and command set must always be present self.context_id = context_id # Command Set is always encoded Implicit VR Little Endian # decode(dataset, is_implicit_VR, is_little_endian) # pylint: disable=attribute-defined-outside-init self.command_set = decode(self.encoded_command_set, True, True) # Determine which DIMSE Message class to use self.__class__ = ( _MESSAGE_TYPES[self.command_set.CommandField][1]) # Determine if a Data Set is present by checking for # (0000, 0800) CommandDataSetType US 1. If the value is # 0x0101 no dataset present, otherwise one is. if self.command_set.CommandDataSetType == 0x0101: # By returning True we're indicating that the message # has been completely decoded return True # Data Set is present if (_config.STORE_RECV_CHUNKED_DATASET and isinstance(self, C_STORE_RQ)): # delete=False is a workaround for Windows # Setting delete=True prevents us from re-opening # the file after it is opened by NamedTemporaryFile # below. self._data_set_file = NamedTemporaryFile(delete=False, mode="wb", suffix=".dcm") self._data_set_path = Path(self._data_set_file.name) # Write the File Meta self._data_set_file.write(b'\x00' * 128) self._data_set_file.write(b'DICM') cs = self.command_set cx = assoc._accepted_cx[context_id] write_file_meta_info( self._data_set_file, create_file_meta( sop_class_uid=cs.AffectedSOPClassUID, sop_instance_uid=cs.AffectedSOPInstanceUID, transfer_syntax=cx.transfer_syntax[0])) # DATA SET # P-DATA fragment contains Data Set information # (control_header_byte is xxxxxx00 or xxxxxx10) else: # As with the command set, the data set may be spread over # a number of fragments in each P-DATA primitive and a # number of P-DATA primitives. if self._data_set_file: self._data_set_file.write(data[1:]) else: self.data_set.write(data[1:]) # The final data set fragment (xxxxxx10) has been added if control_header_byte & 2 != 0: # By returning True we're indicating that the message # has been completely decoded return True # We return False to indicate that the message isn't yet fully decoded return False
def test_assignment(self): """Check assignment works correctly""" primitive = N_GET() # AffectedSOPClassUID primitive.AffectedSOPClassUID = '1.1.1' assert primitive.AffectedSOPClassUID == UID('1.1.1') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPClassUID = UID('1.1.2') assert primitive.AffectedSOPClassUID == UID('1.1.2') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPClassUID = b'1.1.3' assert primitive.AffectedSOPClassUID == UID('1.1.3') assert isinstance(primitive.AffectedSOPClassUID, UID) # AffectedSOPInstanceUID primitive.AffectedSOPInstanceUID = b'1.2.1' assert primitive.AffectedSOPInstanceUID == UID('1.2.1') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPInstanceUID = UID('1.2.2') assert primitive.AffectedSOPInstanceUID == UID('1.2.2') assert isinstance(primitive.AffectedSOPClassUID, UID) primitive.AffectedSOPInstanceUID = '1.2.3' assert primitive.AffectedSOPInstanceUID == UID('1.2.3') assert isinstance(primitive.AffectedSOPClassUID, UID) # AttributeList ref_ds = Dataset() ref_ds.PatientID = '1234567' primitive.AttributeList = BytesIO(encode(ref_ds, True, True)) ds = decode(primitive.AttributeList, True, True) assert ds.PatientID == '1234567' # AttributeIdentifierList primitive.AttributeIdentifierList = [ 0x00001000, (0x0000, 0x1000), Tag(0x7fe0, 0x0010) ] assert [Tag(0x0000, 0x1000), Tag(0x0000, 0x1000), Tag(0x7fe0, 0x0010)] == primitive.AttributeIdentifierList primitive.AttributeIdentifierList = [(0x7fe0, 0x0010)] assert [Tag(0x7fe0, 0x0010)] == primitive.AttributeIdentifierList primitive.AttributeIdentifierList = (0x7fe0, 0x0010) assert [Tag(0x7fe0, 0x0010)] == primitive.AttributeIdentifierList elem = DataElement((0x0000, 0x0005), 'AT', [Tag(0x0000, 0x1000)]) assert isinstance(elem.value, MutableSequence) primitive.AttributeIdentifierList = elem.value assert [Tag(0x0000, 0x1000)] == primitive.AttributeIdentifierList # MessageID primitive.MessageID = 11 assert 11 == primitive.MessageID # MessageIDBeingRespondedTo primitive.MessageIDBeingRespondedTo = 13 assert 13 == primitive.MessageIDBeingRespondedTo # RequestedSOPClassUID primitive.RequestedSOPClassUID = '1.1.1' assert primitive.RequestedSOPClassUID == UID('1.1.1') assert isinstance(primitive.RequestedSOPClassUID, UID) primitive.RequestedSOPClassUID = UID('1.1.2') assert primitive.RequestedSOPClassUID == UID('1.1.2') assert isinstance(primitive.RequestedSOPClassUID, UID) primitive.RequestedSOPClassUID = b'1.1.3' assert primitive.RequestedSOPClassUID == UID('1.1.3') assert isinstance(primitive.RequestedSOPClassUID, UID) # RequestedSOPInstanceUID primitive.RequestedSOPInstanceUID = b'1.2.1' assert primitive.RequestedSOPInstanceUID == UID('1.2.1') assert isinstance(primitive.RequestedSOPInstanceUID, UID) primitive.RequestedSOPInstanceUID = UID('1.2.2') assert primitive.RequestedSOPInstanceUID == UID('1.2.2') assert isinstance(primitive.RequestedSOPInstanceUID, UID) primitive.RequestedSOPInstanceUID = '1.2.3' assert primitive.RequestedSOPInstanceUID == UID('1.2.3') assert isinstance(primitive.RequestedSOPInstanceUID, UID) # Status primitive.Status = 0x0000 assert primitive.Status == 0x0000
def test_failure(self): def dummy(): pass with pytest.raises(AttributeError): print(decode(dummy, False, True))