Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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,
        )