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 file_meta(self) -> FileMetaDataset: r"""Return a *pydicom* :class:`~pydicom.dataset.Dataset` with the :dcm:`File Meta Information<part10/chapter_7.html#sect_7.1>` for a C-STORE request's `Data Set`. Contains the following File Meta Information elements: * (0002,0000) *File Meta Information Group Length* - set as ``0``, will be updated with the correct value during write * (0002,0001) *File Meta Information Version* - set as ``0x0001`` * (0002,0002) *Media Storage SOP Class UID* - set from the request's *Affected SOP Class UID* * (0002,0003) *Media Storage SOP Instance UID* - set from the request's *Affected SOP Instance UID* * (0002,0010) *Transfer Syntax UID* - set from the presentation context used to transfer the *Data Set* * (0002,0012) *Implementation Class UID* - set using :attr:`~pynetdicom.PYNETDICOM_IMPLEMENTATION_UID` * (0002,0013) *Implementation Version Name* - set using :attr:`~pynetdicom.PYNETDICOM_IMPLEMENTATION_VERSION` Examples -------- Add the File Meta Information to the decoded *Data Set* and save it to the :dcm:`DICOM File Format<part10/chapter_7.html>`. .. code-block:: python >>> ds = event.dataset >>> ds.file_meta = event.file_meta >>> ds.save_as('example.dcm', write_like_original=False) Encode the File Meta Information in a new file and append the encoded *Data Set* to it. This skips having to decode/re-encode the *Data Set* as in the previous example. .. code-block:: python >>> from pydicom.filewriter import write_file_meta_info >>> with open('example.dcm', 'wb') as f: ... f.write(b'\x00' * 128) ... f.write(b'DICM') ... write_file_meta_info(f, event.file_meta) ... f.write(event.request.DataSet.getvalue()) Returns ------- pydicom.dataset.Dataset The File Meta Information suitable for use with the decoded C-STORE request's *Data Set*. Raises ------ AttributeError If the corresponding event is not a C-STORE request. """ if not hasattr(self.request, "DataSet"): raise AttributeError( "The corresponding event is not a C-STORE request") # A C-STORE request must have AffectedSOPClassUID and # AffectedSOPInstanceUID self.request = cast("C_STORE", self.request) sop_class = cast(UID, self.request.AffectedSOPClassUID) sop_instance = cast(UID, self.request.AffectedSOPInstanceUID) return create_file_meta( sop_class_uid=sop_class, sop_instance_uid=sop_instance, transfer_syntax=self.context.transfer_syntax, )