示例#1
0
    def get_item(self, key):
        """Return the raw data element if possible.

        It will be raw if the user has never accessed the value, or set their
        own value. Note if the data element is a deferred-read element,
        then it is read and converted before being returned.

        Parameters
        ----------
        key
            The DICOM (group, element) tag in any form accepted by
            pydicom.tag.Tag such as [0x0010, 0x0010], (0x10, 0x10), 0x00100010,
            etc.

        Returns
        -------
        pydicom.dataelem.DataElement
        """
        tag = Tag(key)
        data_elem = dict.__getitem__(self, tag)
        # If a deferred read, return using __getitem__ to read and convert it
        if isinstance(data_elem, tuple) and data_elem.value is None:
            return self[key]
        return data_elem
示例#2
0
def _get_grouped_dicoms(dicom_input):
    """
    Search all dicoms in the dicom directory, sort and validate them

    fast_read = True will only read the headers not the data
    """
    # if all dicoms have an instance number try sorting by instance number else by position
    if [d for d in dicom_input if 'InstanceNumber' in d]:
        dicoms = sorted(dicom_input, key=lambda x: x.InstanceNumber)
    else:
        dicoms = common.sort_dicoms(dicom_input)
    # now group per stack
    grouped_dicoms = [[]]  # list with first element a list
    timepoint_index = 0
    previous_stack_position = -1

    # loop over all sorted dicoms
    stack_position_tag = Tag(
        0x2001, 0x100a)  # put this there as this is a slow step and used a lot
    for index in range(0, len(dicoms)):
        dicom_ = dicoms[index]
        stack_position = 0
        if stack_position_tag in dicom_:
            stack_position = common.get_is_value(dicom_[stack_position_tag])
        if previous_stack_position == stack_position:
            # if the stack number is the same we move to the next timepoint
            timepoint_index += 1
            if len(grouped_dicoms) <= timepoint_index:
                grouped_dicoms.append([])
        else:
            # if it changes move back to the first timepoint
            timepoint_index = 0
        grouped_dicoms[timepoint_index].append(dicom_)
        previous_stack_position = stack_position

    return grouped_dicoms
示例#3
0
def find_delimiter(
    fp: BinaryIO,
    delimiter: BaseTag,
    is_little_endian: bool,
    read_size: int = 128,
    rewind: bool = True
) -> Optional[int]:
    """Return file position where 4-byte delimiter is located.

    Parameters
    ----------
    delimiter : int
        The delimiter to search for.
    is_little_endian : bool
        ``True`` if little endian, ``False`` otherwise.
    read_size : int
        See :func:`find_bytes` for parameter info.
    rewind : bool
        Flag to rewind to initial position after searching.

    Returns
    -------
    int or None
        Returns ``None`` if end of file is reached without finding the
        delimiter, otherwise the byte offset to the delimiter.
    """
    struct_format = "<H"
    if not is_little_endian:
        struct_format = ">H"
    delimiter = Tag(delimiter)
    bytes_to_find = (
        pack(struct_format, delimiter.group)
        + pack(struct_format, delimiter.elem)
    )

    return find_bytes(fp, bytes_to_find, read_size=read_size, rewind=rewind)
def _create_affine_multiframe(multiframe_dicom):
    """
    Function to create the affine matrix for a siemens mosaic dataset
    This will work for siemens dti and 4D if in mosaic format
    """
    first_frame = multiframe_dicom[Tag(0x5200, 0x9230)][0]
    # Create affine matrix (http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-slice-affine)
    image_orient1 = numpy.array(first_frame.PlaneOrientationSequence[0].
                                ImageOrientationPatient)[0:3].astype(float)
    image_orient2 = numpy.array(first_frame.PlaneOrientationSequence[0].
                                ImageOrientationPatient)[3:6].astype(float)

    normal = numpy.cross(image_orient1, image_orient2)

    delta_r = float(first_frame[0x2005, 0x140f][0].PixelSpacing[0])
    delta_c = float(first_frame[0x2005, 0x140f][0].PixelSpacing[1])

    image_pos = numpy.array(
        first_frame.PlanePositionSequence[0].ImagePositionPatient).astype(
            float)

    delta_s = float(multiframe_dicom.SpacingBetweenSlices)
    return numpy.matrix([[
        -image_orient1[0] * delta_c, -image_orient2[0] * delta_r,
        -delta_s * normal[0], -image_pos[0]
    ],
                         [
                             -image_orient1[1] * delta_c,
                             -image_orient2[1] * delta_r, -delta_s * normal[1],
                             -image_pos[1]
                         ],
                         [
                             image_orient1[2] * delta_c,
                             image_orient2[2] * delta_r, delta_s * normal[2],
                             image_pos[2]
                         ], [0, 0, 0, 1]])
示例#5
0
    def _convert(self, val: Any) -> Any:
        """Convert `val` to an appropriate type for the element's VR."""
        # If the value is bytes and has a VR that can only be encoded
        # using the default character repertoire, convert it to a string
        if self.VR in DEFAULT_CHARSET_VR and isinstance(val, bytes):
            val = val.decode()

        if self.VR == VR_.IS:
            return pydicom.valuerep.IS(val, self.validation_mode)

        if self.VR == VR_.DA and config.datetime_conversion:
            return pydicom.valuerep.DA(val,
                                       validation_mode=self.validation_mode)

        if self.VR == VR_.DS:
            return pydicom.valuerep.DS(val, False, self.validation_mode)

        if self.VR == VR_.DT and config.datetime_conversion:
            return pydicom.valuerep.DT(val,
                                       validation_mode=self.validation_mode)

        if self.VR == VR_.TM and config.datetime_conversion:
            return pydicom.valuerep.TM(val,
                                       validation_mode=self.validation_mode)

        if self.VR == VR_.UI:
            return UID(val, self.validation_mode) if val is not None else None

        if self.VR == VR_.PN:
            return PersonName(val, validation_mode=self.validation_mode)

        if self.VR == VR_.AT and (val == 0 or val):
            return val if isinstance(val, BaseTag) else Tag(val)

        self.validate(val)
        return val
示例#6
0
def test_identifier_comparison():
    """Tag Identifiers are hashable and should be sortable as well"""
    # these should equal each other
    assert SingleTag(tag=Tag("PatientID")) == SingleTag(tag=Tag("PatientID"))
    # You can use any initialization that pydicom.tag.Tag allows, still equal
    assert SingleTag(tag=Tag(0x0010, 0x0020)) == SingleTag(
        tag=Tag("PatientID"))

    assert RepeatingGroup(tag="0010,10xx") == RepeatingGroup(tag="0010,10xx")

    # it should be possible to sort them as well
    taglist = [
        SingleTag(tag=Tag("0020000e")),
        SingleTag(tag=Tag("00100020")),
        RepeatingGroup(tag="001010xx"),
    ]

    taglist.sort(reverse=True)
    assert str(taglist[1]) == "(0010, 10xx)"
示例#7
0
    def test_tag_single_list(self):
        """Test creating a Tag from a single list."""
        assert Tag([0x0000, 0x0000]) == BaseTag(0x00000000)
        assert Tag([0x99, 0xFE]) == BaseTag(0x009900FE)
        assert Tag([15, 0xE]) == BaseTag(0x000F000E)
        assert Tag([0x1000, 0x2000]) == BaseTag(0x10002000)
        assert Tag(['0x01', '0x02']) == BaseTag(0x00010002)

        # Must be 2 list
        pytest.raises(ValueError, Tag, [0x1000, 0x2000, 0x0030])
        pytest.raises(ValueError, Tag, ['0x10', '0x20', '0x03'])
        pytest.raises(ValueError, Tag, [0x1000])
        pytest.raises(ValueError, Tag, ['0x10'])

        # Must be int or string
        msg = (r"Unable to create an element tag from '\[1.0, 2.0\]': both "
               r"arguments must be the same type and str or int")
        with pytest.raises(TypeError, match=msg):
            Tag([1., 2.])

        # Must be 32-bit
        pytest.raises(OverflowError, Tag, [65536, 0])
        pytest.raises(OverflowError, Tag, [0, 65536])
        pytest.raises(OverflowError, Tag, ('0xFFFF', '0xFFFF1'))
        # Must be positive
        pytest.raises(ValueError, Tag, [-1, 0])
        pytest.raises(ValueError, Tag, [0, -1])
        pytest.raises(ValueError, Tag, ('0x0', '-0x1'))
        pytest.raises(ValueError, Tag, ('-0x1', '0x0'))
        # Can't have second parameter
        msg = r"Unable to create an element tag from '\(\[1, 2\], 1\)'"
        with pytest.raises(TypeError, match=msg):
            Tag([0x01, 0x02], 0x01)

        pytest.raises(TypeError, Tag, [0x01, 0x02], '0x01')
        pytest.raises(TypeError, Tag, ['0x01', '0x02'], '0x01')
        pytest.raises(TypeError, Tag, ['0x01', '0x02'], 0x01)
示例#8
0
def test_core_deidentify_safe_private(a_dataset, a_safe_private_definition):
    """Private elements marked as safe should not be removed by Clean()"""

    assert Tag("00b10010") in a_dataset  # a private creator tag
    assert Tag("00b11001") in a_dataset  # and a private tag

    # A core instance that should clean() private tags, but one tag is deemed safe
    ruleset = RuleSet(
        [Rule(PrivateTags(), Clean(safe_private=a_safe_private_definition))])
    core = Core(profile=Profile([ruleset]))

    # One tag should be kept
    deltas = extract_signature(deidentifier=core, dataset=a_dataset)
    assert {x.tag: x for x in deltas}[Tag("00b10010")].status == "REMOVED"
    assert {x.tag: x for x in deltas}[Tag("00b11001")].status == "UNCHANGED"

    # but only so long as dataset has modality = CT
    a_dataset.Modality = "US"
    deltas = extract_signature(deidentifier=core, dataset=a_dataset)
    assert {x.tag: x for x in deltas}[Tag("00b10010")].status == "REMOVED"
    assert {x.tag: x for x in deltas}[Tag("00b11001")].status == "REMOVED"
示例#9
0
def _create_singleframe_bvals_bvecs(grouped_dicoms, bval_file, bvec_file, nifti, nifti_file):
    """
    Write the bvals from the sorted dicom files to a bval file
    """

    # create the empty arrays
    bvals = numpy.zeros([len(grouped_dicoms)], dtype=numpy.int32)
    bvecs = numpy.zeros([len(grouped_dicoms), 3])

    # loop over all timepoints and create a list with all bvals and bvecs
    if _is_bval_type_a(grouped_dicoms):
        bval_tag = Tag(0x2001, 0x1003)
        bvec_x_tag = Tag(0x2005, 0x10b0)
        bvec_y_tag = Tag(0x2005, 0x10b1)
        bvec_z_tag = Tag(0x2005, 0x10b2)
        for stack_index in range(0, len(grouped_dicoms)):
            bvals[stack_index] = common.get_fl_value(grouped_dicoms[stack_index][0][bval_tag])
            bvecs[stack_index, :] = [common.get_fl_value(grouped_dicoms[stack_index][0][bvec_x_tag]),
                                     common.get_fl_value(grouped_dicoms[stack_index][0][bvec_y_tag]),
                                     common.get_fl_value(grouped_dicoms[stack_index][0][bvec_z_tag])]
    elif _is_bval_type_b(grouped_dicoms):
        bval_tag = Tag(0x0018, 0x9087)
        bvec_tag = Tag(0x0018, 0x9089)
        for stack_index in range(0, len(grouped_dicoms)):
            bvals[stack_index] = common.get_fd_value(grouped_dicoms[stack_index][0][bval_tag])
            bvecs[stack_index, :] = common.get_fd_array_value(grouped_dicoms[stack_index][0][bvec_tag], 3)

    # truncate nifti if needed
    nifti, bvals, bvecs = _fix_diffusion_images(bvals, bvecs, nifti, nifti_file)

    # save the found bvecs to the file
    if numpy.count_nonzero(bvals) > 0 or numpy.count_nonzero(bvecs) > 0:
        common.write_bval_file(bvals, bval_file)
        common.write_bvec_file(bvecs, bvec_file)
    else:
        bval_file = None
        bvec_file = None
        bvals = None
        bvecs = None
    return nifti, bvals, bvecs, bval_file, bvec_file
示例#10
0
 def test_data_element_without_encoding(self):
     """RawDataElement: no encoding needed."""
     raw = RawDataElement(Tag(0x00104000), 'LT', 23,
                          b'comment\\comment2\\comment3', 0, False, True)
     element = DataElement_from_raw(raw)
     assert 'Patient Comments' == element.name
示例#11
0
def generate_pixel_data_fragment(fp):
    """Yield the encapsulated pixel data fragments.

    For compressed (encapsulated) Transfer Syntaxes, the (7FE0,0010) *Pixel
    Data* element is encoded in an encapsulated format.

    **Encapsulation**

    The encoded pixel data stream is fragmented into one or more Items. The
    stream may represent a single or multi-frame image.

    Each *Data Stream Fragment* shall have tag of (FFFE,E000), followed by a 4
    byte *Item Length* field encoding the explicit number of bytes in the Item.
    All Items containing an encoded fragment shall have an even number of bytes
    greater than or equal to 2, with the last fragment being padded if
    necessary.

    The first Item in the Sequence of Items shall be a 'Basic Offset Table',
    however the Basic Offset Table item value is not required to be present.
    It is assumed that the Basic Offset Table item has already been read prior
    to calling this function (and that `fp` is positioned past this item).

    The remaining items in the Sequence of Items are the pixel data fragments
    and it is these items that will be read and returned by this function.

    The Sequence of Items is terminated by a (FFFE,E0DD) *Sequence Delimiter
    Item* with an Item Length field of value ``0x00000000``. The presence
    or absence of the *Sequence Delimiter Item* in `fp` has no effect on the
    returned fragments.

    *Encoding*

    The encoding of the data shall be little endian.

    Parameters
    ----------
    fp : filebase.DicomBytesIO
        The encoded (7FE0,0010) *Pixel Data* element value, positioned at the
        start of the item tag for the first item after the Basic Offset Table
        item. ``fp.is_little_endian`` should be set to ``True``.

    Yields
    ------
    bytes
        A pixel data fragment.

    Raises
    ------
    ValueError
        If the data contains an item with an undefined length or an unknown
        tag.

    References
    ----------
    DICOM Standard Part 5, :dcm:`Annex A.4 <part05/sect_A.4.html>`
    """
    if not fp.is_little_endian:
        raise ValueError("'fp.is_little_endian' must be True")

    # We should be positioned at the start of the Item Tag for the first
    # fragment after the Basic Offset Table
    while True:
        try:
            tag = Tag(fp.read_tag())
        except EOFError:
            break

        if tag == 0xFFFEE000:
            # Item
            length = fp.read_UL()
            if length == 0xFFFFFFFF:
                raise ValueError("Undefined item length at offset {} when "
                                 "parsing the encapsulated pixel data "
                                 "fragments.".format(fp.tell() - 4))
            yield fp.read(length)
        elif tag == 0xFFFEE0DD:
            # Sequence Delimiter
            # Behave nicely and rewind back to the end of the items
            fp.seek(-4, 1)
            break
        else:
            raise ValueError("Unexpected tag '{0}' at offset {1} when parsing "
                             "the encapsulated pixel data fragment items."
                             .format(tag, fp.tell() - 4))
示例#12
0
def test_command_set_keywords():
    """Check the keywords are valid and unique"""
    for kw_list in _COMMAND_SET_KEYWORDS.values():
        assert len(kw_list) == len(list(set(kw_list)))
        for kw in kw_list:
            Tag(kw)
示例#13
0
 def test_mixed_long_int(self):
     assert Tag([0x1000, long(0x2000)]) == BaseTag(0x10002000)
     assert Tag([long(0x1000), 0x2000]) == BaseTag(0x10002000)
     assert Tag([long(0x1000), long(0x2000)]) == BaseTag(0x10002000)
示例#14
0
 def testTagWithoutEncodingPython2(self):
     """RawDataElement: no encoding needed in Python 2."""
     raw = RawDataElement(Tag(0x00104000), 'LT', 23,
                          b'comment\\comment2\\comment3', 0, False, True)
     element = DataElement_from_raw(raw)
     self.assertEqual(element.name, 'Patient Comments')
 def test_offset(self):
     """Test convert_tag with an offset"""
     bytestring = b'\x12\x23\x10\x00\x20\x00\x34\x45'
     assert convert_tag(bytestring, True, 0) == Tag(0x2312, 0x0010)
     assert convert_tag(bytestring, True, 2) == Tag(0x0010, 0x0020)
 def test_big_endian(self):
     """Test convert_tag with a big endian byte string"""
     bytestring = b'\x00\x10\x00\x20'
     assert convert_tag(bytestring, False) == Tag(0x0010, 0x0020)
 def test_little_endian(self):
     """Test convert_tag with a little endian byte string"""
     bytestring = b'\x10\x00\x20\x00'
     assert convert_tag(bytestring, True) == Tag(0x0010, 0x0020)
示例#18
0
_MSG_TO_PRIMITVE = {
    'C_ECHO': C_ECHO,
    'C_STORE': C_STORE,
    'C_FIND': C_FIND,
    'C_GET': C_GET,
    'C_MOVE': C_MOVE,
    'C_CANCEL': C_CANCEL,
    'N_EVENT_REPORT': N_EVENT_REPORT,
    'N_GET': N_GET,
    'N_SET': N_SET,
    'N_ACTION': N_ACTION,
    'N_CREATE': N_CREATE,
    'N_DELETE': N_DELETE,
}

_MULTIVALUE_TAGS = [Tag('OffendingElement'), Tag('AttributeIdentifierList')]


class DIMSEMessage(object):
    """Represents a DIMSE Message.

    Information is communicated across the DICOM network interface in a DICOM
    Message. A Message is composed of a Command Set followed by a
    conditional Data Set. The Command Set is used to indicate the
    operations/notifications to be performed on or with the Data Set (see
    PS3.7 6.2-3).

    ::

                primitive_to_message()       encode_msg()
        +-------------+   --->    +-----------+   --->   +-------------+
示例#19
0
def write_file_meta_info(fp, file_meta, enforce_standard=True):
    """Write the File Meta Information elements in `file_meta` to `fp`.

    If `enforce_standard` is True then the file-like `fp` should be positioned
    past the 128 byte preamble + 4 byte prefix (which should already have been
    written).

    DICOM File Meta Information Group Elements
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    From the DICOM standard, Part 10 Section 7.1, any DICOM file shall contain
    a 128-byte preamble, a 4-byte DICOM prefix 'DICM' and (at a minimum) the
    following Type 1 DICOM Elements (from Table 7.1-1):
        * (0002,0000) FileMetaInformationGroupLength, UL, 4
        * (0002,0001) FileMetaInformationVersion, OB, 2
        * (0002,0002) MediaStorageSOPClassUID, UI, N
        * (0002,0003) MediaStorageSOPInstanceUID, UI, N
        * (0002,0010) TransferSyntaxUID, UI, N
        * (0002,0012) ImplementationClassUID, UI, N

    If `enforce_standard` is True then (0002,0000) will be added/updated,
    (0002,0001) and (0002,0012) will be added if not already present and the
    other required elements will be checked to see if they exist. If
    `enforce_standard` is False then `file_meta` will be written as is after
    minimal validation checking.

    The following Type 3/1C Elements may also be present:
        * (0002,0013) ImplementationVersionName, SH, N
        * (0002,0016) SourceApplicationEntityTitle, AE, N
        * (0002,0017) SendingApplicationEntityTitle, AE, N
        * (0002,0018) ReceivingApplicationEntityTitle, AE, N
        * (0002,0100) PrivateInformationCreatorUID, UI, N
        * (0002,0102) PrivateInformation, OB, N

    If `enforce_standard` is True then (0002,0013) will be added/updated.

    Encoding
    ~~~~~~~~
    The encoding of the File Meta Information shall be Explicit VR Little
    Endian

    Parameters
    ----------
    fp : file-like
        The file-like to write the File Meta Information to.
    file_meta : pydicom.dataset.Dataset
        The File Meta Information DataElements.
    enforce_standard : bool
        If False, then only the File Meta Information elements already in
        `file_meta` will be written to `fp`. If True (default) then a DICOM
        Standards conformant File Meta will be written to `fp`.

    Raises
    ------
    ValueError
        If `enforce_standard` is True and any of the required File Meta
        Information elements are missing from `file_meta`, with the
        exception of (0002,0000), (0002,0001) and (0002,0012).
    ValueError
        If any non-Group 2 Elements are present in `file_meta`.
    """
    # Check that no non-Group 2 Elements are present
    for elem in file_meta:
        if elem.tag.group != 0x0002:
            raise ValueError("Only File Meta Information Group (0002,eeee) "
                             "elements must be present in 'file_meta'.")

    # The Type 1 File Meta Elements are only required when `enforce_standard`
    #   is True, except for FileMetaInformationGroupLength and
    #   FileMetaInformationVersion, which may or may not be present
    if enforce_standard:
        # Will be updated with the actual length later
        if 'FileMetaInformationGroupLength' not in file_meta:
            file_meta.FileMetaInformationGroupLength = 0

        if 'FileMetaInformationVersion' not in file_meta:
            file_meta.FileMetaInformationVersion = b'\x00\x01'

        if 'ImplementationClassUID' not in file_meta:
            file_meta.ImplementationClassUID = PYDICOM_IMPLEMENTATION_UID

        if 'ImplementationVersionName' not in file_meta:
            file_meta.ImplementationVersionName = 'PYDICOM ' + __version__

        # Check that required File Meta Elements are present
        missing = []
        for element in [0x0002, 0x0003, 0x0010]:
            if Tag(0x0002, element) not in file_meta:
                missing.append(Tag(0x0002, element))
        if missing:
            msg = "Missing required File Meta Information elements from " \
                  "'file_meta':\n"
            for tag in missing:
                msg += '\t{0} {1}\n'.format(tag, keyword_for_tag(tag))
            raise ValueError(msg[:-1])  # Remove final newline

    # Only used if FileMetaInformationGroupLength is present.
    #   FileMetaInformationGroupLength has a VR of 'UL' and so has a value that
    #   is 4 bytes fixed. The total length of when encoded as Explicit VR must
    #   therefore be 12 bytes.
    end_group_length_elem = fp.tell() + 12

    # The 'is_little_endian' and 'is_implicit_VR' attributes will need to be
    #   set correctly after the File Meta Info has been written.
    fp.is_little_endian = True
    fp.is_implicit_VR = False

    # Write the File Meta Information Group elements to `fp`
    write_dataset(fp, file_meta)

    # If FileMetaInformationGroupLength is present it will be the first written
    #   element and we must update its value to the correct length.
    if 'FileMetaInformationGroupLength' in file_meta:
        # Save end of file meta to go back to
        end_of_file_meta = fp.tell()

        # Update the FileMetaInformationGroupLength value, which is the number
        #   of bytes from the end of the FileMetaInformationGroupLength element
        #   to the end of all the File Meta Information elements
        group_length = int(end_of_file_meta - end_group_length_elem)
        file_meta.FileMetaInformationGroupLength = group_length
        fp.seek(end_group_length_elem - 12)
        write_data_element(fp, file_meta[0x00020000])

        # Return to end of the file meta, ready to write remainder of the file
        fp.seek(end_of_file_meta)
示例#20
0
 def testCleanName(self):
     """dicom_dictionary: CleanName returns correct strings............."""
     self.assertTrue(CleanName(0x00100010) == "PatientsName")
     self.assertTrue(CleanName(Tag((0x0010, 0x0010))) == "PatientsName")
示例#21
0
 def testTagWithoutEncodingPython3(self):
     """RawDataElement: raises if no encoding given in Python 3."""
     self.assertRaises(
         TypeError,
         RawDataElement(Tag(0x00104000), 'LT', 14, b'comment1\\comment2', 0,
                        False, True))
示例#22
0
 def testPrivate1(self):
     """private dict: """
     self.assertTrue(CleanName(0x00100010) == "PatientsName")
     self.assertTrue(CleanName(Tag((0x0010, 0x0010))) == "PatientsName")
示例#23
0
 def test():
     tag = Tag(0x00100010)
     with tag_in_exception(tag) as tag:
         raise ValueError('Test message.')
示例#24
0
    def primitive_to_message(self, primitive):
        """Convert a DIMSE `primitive` to the current DIMSEMessage object.

        Parameters
        ----------
        primitive
            A DIMSE message primitive from pynetdicom3.dimse_primitives
            to convert to the current DIMSEMessage object.
        """
        # pylint: disable=too-many-branches,too-many-statements
        # Command Set
        # Due to the del self.command_set[elem.tag] line below this may
        #   end up permanently removing the element from the DIMSE message class
        #   so we refresh the command set elements
        cls_type_name = self.__class__.__name__.replace('_', '-')
        command_set_tags = [elem.tag for elem in self.command_set]

        if cls_type_name not in _COMMAND_SET_ELEM:
            raise ValueError("Can't convert primitive to message for unknown "
                             "DIMSE message type '{}'".format(cls_type_name))

        for tag in _COMMAND_SET_ELEM[cls_type_name]:
            if tag not in command_set_tags:
                tag = Tag(tag)
                vr = dcm_dict[tag][0]
                try:
                    self.command_set.add_new(tag, vr, None)
                except TypeError:
                    self.command_set.add_new(tag, vr, '')

        # Convert the message command set to the primitive attributes
        for elem in self.command_set:
            # Use the short version of the element names as these should
            #   match the parameter names in the primitive
            if hasattr(primitive, elem.keyword):
                # If value hasn't been set for a parameter then delete
                #   the corresponding element
                attr = getattr(primitive, elem.keyword)

                if attr is not None:
                    elem.value = attr
                else:
                    del self.command_set[elem.tag]  # Careful!

        # Theres a one-to-one relationship in the _MESSAGE_TYPES dict, so
        #   invert it for convenience
        rev_type = {}
        for value in _MESSAGE_TYPES:
            rev_type[_MESSAGE_TYPES[value]] = value

        self.command_set.CommandField = rev_type[cls_type_name]

        # Data Set
        # Default to no Data Set
        self.data_set = BytesIO()
        self.command_set.CommandDataSetType = 0x0101

        try:
            # These message types *may* have a dataset
            dataset_keyword = _DATASET_KEYWORDS[self.__class__.__name__]
            self.data_set = getattr(primitive, dataset_keyword)
            if self.data_set:
                self.command_set.CommandDataSetType = 0x0001
        except KeyError:
            # The following message types never have a dataset
            # 'C_ECHO_RQ', 'C_ECHO_RSP', 'N_DELETE_RQ', 'C_STORE_RSP',
            # 'C_CANCEL_RQ', 'N_DELETE_RSP', 'C_FIND_RSP', 'N_GET_RQ'
            pass

        # Set the Command Set length
        self._set_command_group_length()
示例#25
0
def get_frame_offsets(fp):
    """Return a list of the fragment offsets from the Basic Offset Table.

    **Basic Offset Table**

    The Basic Offset Table Item must be present and have a tag (FFFE,E000) and
    a length, however it may or may not have a value.

    Basic Offset Table with no value
    ::

        Item Tag   | Length    |
        FE FF 00 E0 00 00 00 00

    Basic Offset Table with value (2 frames)
    ::

        Item Tag   | Length    | Offset 1  | Offset 2  |
        FE FF 00 E0 08 00 00 00 00 00 00 00 10 00 00 00

    For single or multi-frame images with only one frame, the Basic Offset
    Table may or may not have a value. When it has no value then its length
    shall be ``0x00000000``.

    For multi-frame images with more than one frame, the Basic Offset Table
    should have a value containing concatenated 32-bit unsigned integer values
    that are the byte offsets to the first byte of the Item tag of the first
    fragment of each frame as measured from the first byte of the first item
    tag following the Basic Offset Table Item.

    All decoders, both for single and multi-frame images should accept both
    an empty Basic Offset Table and one containing offset values.

    .. versionchanged:: 1.4

        Changed to return (is BOT empty, list of offsets).

    Parameters
    ----------
    fp : filebase.DicomBytesIO
        The encapsulated pixel data positioned at the start of the Basic Offset
        Table. ``fp.is_little_endian`` should be set to ``True``.

    Returns
    -------
    bool, list of int
        Whether or not the BOT is empty, and a list of the byte offsets
        to the first fragment of each frame, as measured from the start of the
        first item following the Basic Offset Table item.

    Raises
    ------
    ValueError
        If the Basic Offset Table item's tag is not (FFEE,E000) or if the
        length in bytes of the item's value is not a multiple of 4.

    References
    ----------
    DICOM Standard, Part 5, :dcm:`Annex A.4 <part05/sect_A.4.html>`
    """
    if not fp.is_little_endian:
        raise ValueError("'fp.is_little_endian' must be True")

    tag = Tag(fp.read_tag())

    if tag != 0xfffee000:
        raise ValueError("Unexpected tag '{}' when parsing the Basic Table "
                         "Offset item.".format(tag))

    length = fp.read_UL()
    if length % 4:
        raise ValueError("The length of the Basic Offset Table item is not "
                         "a multiple of 4.")

    offsets = []
    # Always return at least a 0 offset
    if length == 0:
        offsets.append(0)

    for ii in range(length // 4):
        offsets.append(fp.read_UL())

    return bool(length), offsets
示例#26
0
    def primitive_to_message(self, primitive):
        """Convert a DIMSE `primitive` to the current DIMSEMessage object.

        Parameters
        ----------
        primitive
            The pynetdicom3.dimse_primitives DIMSE service primitive to convert
            to the current DIMSEMessage object.
        """
        # pylint: disable=too-many-branches,too-many-statements
        # Command Set
        # Due to the del self.command_set[elem.tag] line below this may
        #   end up permanently removing the element from the DIMSE message class
        #   so we refresh the command set elements
        cls_type_name = self.__class__.__name__.replace('_', '-')
        command_set_tags = [elem.tag for elem in self.command_set]

        if cls_type_name not in _COMMAND_SET_ELEM:
            raise ValueError("Can't convert primitive to message for unknown "
                             "DIMSE message type '{}'".format(cls_type_name))

        for tag in _COMMAND_SET_ELEM[cls_type_name]:
            if tag not in command_set_tags:
                tag = Tag(tag)
                vr = dcm_dict[tag][0]
                try:
                    self.command_set.add_new(tag, vr, None)
                except TypeError:
                    self.command_set.add_new(tag, vr, '')

        # Convert the message command set to the primitive attributes
        for elem in self.command_set:
            # Use the short version of the element names as these should
            #   match the parameter names in the primitive
            if hasattr(primitive, elem.keyword):
                # If value hasn't been set for a parameter then delete
                #   the corresponding element
                attr = getattr(primitive, elem.keyword)

                if attr is not None:
                    elem.value = attr
                else:
                    del self.command_set[elem.tag]  # Careful!

        # Theres a one-to-one relationship in the _MESSAGE_TYPES dict, so invert
        #   it for convenience
        rev_type = {}
        for value in _MESSAGE_TYPES:
            rev_type[_MESSAGE_TYPES[value]] = value

        self.command_set.CommandField = rev_type[cls_type_name]

        # Data Set
        # Default to no Data Set
        self.data_set = BytesIO()
        self.command_set.CommandDataSetType = 0x0101

        # TODO: This can probably be refactored to be cleaner
        #   dict = {['C_STORE_RQ'] : 'DataSet',
        #           ['C_FIND_RQ', 'C_GET_RQ'] : 'Identifier'}
        #   for cls_names in dict.keys():
        #       if cls_type_name in cls_names and hasattr(primitive, dict[cls_type_name]):
        #           self.data_set = getattr(primitive.dict[cls_type_name])
        #           self.command_set.CommandDataSetType = 0x0001
        cls_type_name = self.__class__.__name__
        if cls_type_name == 'C_STORE_RQ':
            self.data_set = primitive.DataSet
            self.command_set.CommandDataSetType = 0x0001
        elif cls_type_name in ['C_FIND_RQ', 'C_GET_RQ', 'C_MOVE_RQ',
                               'C_FIND_RSP', 'C_GET_RSP', 'C_MOVE_RSP'] and \
                primitive.Identifier:
            self.data_set = primitive.Identifier
            self.command_set.CommandDataSetType = 0x0001
        elif cls_type_name == 'N_EVENT_REPORT_RQ':
            self.data_set = primitive.EventInformation
            self.command_set.CommandDataSetType = 0x0001
        elif cls_type_name == 'N_EVENT_REPORT_RSP':
            self.data_set = primitive.EventReply
            self.command_set.CommandDataSetType = 0x0001
        elif cls_type_name in [
                'N_GET_RSP', 'N_SET_RSP', 'N_CREATE_RQ', 'N_CREATE_RSP'
        ]:
            self.data_set = primitive.AttributeList
            self.command_set.CommandDataSetType = 0x0001
        elif cls_type_name == 'N_SET_RQ':
            self.data_set = primitive.ModificationList
            self.command_set.CommandDataSetType = 0x0001
        elif cls_type_name == 'N_ACTION_RQ':
            self.data_set = primitive.ActionInformation
            self.command_set.CommandDataSetType = 0x0001
        elif cls_type_name == 'N_ACTION_RSP':
            self.data_set = primitive.ActionReply
            self.command_set.CommandDataSetType = 0x0001

        # The following message types don't have a dataset
        # 'C_ECHO_RQ', 'C_ECHO_RSP', 'N_DELETE_RQ', 'C_STORE_RSP',
        # 'C_CANCEL_RQ', 'N_DELETE_RSP', 'C_FIND_RSP'

        # Set the Command Set length
        self._set_command_group_length()
示例#27
0
def data_element_generator(fp,
                           is_implicit_VR,
                           is_little_endian,
                           stop_when=None,
                           defer_size=None,
                           encoding=default_encoding,
                           specific_tags=None):
    """Create a generator to efficiently return the raw data elements.

    Parameters
    ----------
    fp : file-like object
    is_implicit_VR : boolean
    is_little_endian : boolean
    stop_when : None, callable, optional
        If None (default), then the whole file is read.
        A callable which takes tag, VR, length,
        and returns True or False. If it returns True,
        read_data_element will just return.
    defer_size : int, str, None, optional
        See ``dcmread`` for parameter info.
    encoding :
        Encoding scheme
    specific_tags : list or None
        See ``dcmread`` for parameter info.

    Returns
    -------
    VR : None if implicit VR, otherwise the VR read from the file
    length :
        the length as in the DICOM data element (could be
        DICOM "undefined length" 0xffffffffL)
    value_bytes :
        the raw bytes from the DICOM file
        (not parsed into python types)
    is_little_endian : boolean
        True if transfer syntax is little endian; else False.
    """
    # Summary of DICOM standard PS3.5-2008 chapter 7:
    # If Implicit VR, data element is:
    #    tag, 4-byte length, value.
    #        The 4-byte length can be FFFFFFFF (undefined length)*
    #
    # If Explicit VR:
    #    if OB, OW, OF, SQ, UN, or UT:
    #       tag, VR, 2-bytes reserved (both zero), 4-byte length, value
    #           For all but UT, the length can be FFFFFFFF (undefined length)*
    #   else: (any other VR)
    #       tag, VR, (2 byte length), value
    # * for undefined length, a Sequence Delimitation Item marks the end
    #        of the Value Field.
    # Note, except for the special_VRs, both impl and expl VR use 8 bytes;
    #    the special VRs follow the 8 bytes with a 4-byte length

    # With a generator, state is stored, so we can break down
    #    into the individual cases, and not have to check them again for each
    #    data element

    if is_little_endian:
        endian_chr = "<"
    else:
        endian_chr = ">"
    if is_implicit_VR:
        element_struct = Struct(endian_chr + "HHL")
    else:  # Explicit VR
        # tag, VR, 2-byte length (or 0 if special VRs)
        element_struct = Struct(endian_chr + "HH2sH")
        extra_length_struct = Struct(endian_chr + "L")  # for special VRs
        extra_length_unpack = extra_length_struct.unpack  # for lookup speed

    # Make local variables so have faster lookup
    fp_read = fp.read
    fp_tell = fp.tell
    logger_debug = logger.debug
    debugging = config.debugging
    element_struct_unpack = element_struct.unpack
    defer_size = size_in_bytes(defer_size)

    tag_set = set()
    has_specific_char_set = True
    if specific_tags is not None:
        for tag in specific_tags:
            if isinstance(tag, (str, compat.text_type)):
                tag = Tag(tag_for_keyword(tag))
            if isinstance(tag, BaseTag):
                tag_set.add(tag)
        has_specific_char_set = Tag(0x08, 0x05) in tag_set
        tag_set.add(Tag(0x08, 0x05))
    has_tag_set = len(tag_set) > 0

    while True:
        # Read tag, VR, length, get ready to read value
        bytes_read = fp_read(8)
        if len(bytes_read) < 8:
            return  # at end of file
        if debugging:
            debug_msg = "{0:08x}: {1}".format(fp.tell() - 8,
                                              bytes2hex(bytes_read))

        if is_implicit_VR:
            # must reset VR each time; could have set last iteration (e.g. SQ)
            VR = None
            group, elem, length = element_struct_unpack(bytes_read)
        else:  # explicit VR
            group, elem, VR, length = element_struct_unpack(bytes_read)
            if not in_py2:
                VR = VR.decode(default_encoding)
            if VR in extra_length_VRs:
                bytes_read = fp_read(4)
                length = extra_length_unpack(bytes_read)[0]
                if debugging:
                    debug_msg += " " + bytes2hex(bytes_read)
        if debugging:
            debug_msg = "%-47s  (%04x, %04x)" % (debug_msg, group, elem)
            if not is_implicit_VR:
                debug_msg += " %s " % VR
            if length != 0xFFFFFFFF:
                debug_msg += "Length: %d" % length
            else:
                debug_msg += "Length: Undefined length (FFFFFFFF)"
            logger_debug(debug_msg)

        # Positioned to read the value, but may not want to -- check stop_when
        value_tell = fp_tell()
        tag = TupleTag((group, elem))
        if stop_when is not None:
            # XXX VR may be None here!! Should stop_when just take tag?
            if stop_when(tag, VR, length):
                if debugging:
                    logger_debug("Reading ended by stop_when callback. "
                                 "Rewinding to start of data element.")
                rewind_length = 8
                if not is_implicit_VR and VR in extra_length_VRs:
                    rewind_length += 4
                fp.seek(value_tell - rewind_length)
                return

        # Reading the value
        # First case (most common): reading a value with a defined length
        if length != 0xFFFFFFFF:
            # don't defer loading of Specific Character Set value as it is
            # needed immediately to get the character encoding for other tags
            if has_tag_set and tag not in tag_set:
                # skip the tag if not in specific tags
                fp.seek(fp_tell() + length)
                continue

            if (defer_size is not None and length > defer_size
                    and tag != BaseTag(0x00080005)):
                # Flag as deferred by setting value to None, and skip bytes
                value = None
                logger_debug("Defer size exceeded. "
                             "Skipping forward to next data element.")
                fp.seek(fp_tell() + length)
            else:
                value = fp_read(length)
                if debugging:
                    dotdot = "   "
                    if length > 12:
                        dotdot = "..."
                    logger_debug("%08x: %-34s %s %r %s" %
                                 (value_tell, bytes2hex(
                                     value[:12]), dotdot, value[:12], dotdot))

            # If the tag is (0008,0005) Specific Character Set, then store it
            if tag == BaseTag(0x00080005):
                from pydicom.values import convert_string
                encoding = convert_string(value,
                                          is_little_endian,
                                          encoding=default_encoding)
                # Store the encoding value in the generator
                # for use with future elements (SQs)
                encoding = convert_encodings(encoding)
                if not has_specific_char_set:
                    continue

            yield RawDataElement(tag, VR, length, value, value_tell,
                                 is_implicit_VR, is_little_endian)

        # Second case: undefined length - must seek to delimiter,
        # unless is SQ type, in which case is easier to parse it, because
        # undefined length SQs and items of undefined lengths can be nested
        # and it would be error-prone to read to the correct outer delimiter
        else:
            # Try to look up type to see if is a SQ
            # if private tag, won't be able to look it up in dictionary,
            #   in which case just ignore it and read the bytes unless it is
            #   identified as a Sequence
            if VR is None:
                try:
                    VR = dictionary_VR(tag)
                except KeyError:
                    # Look ahead to see if it consists of items
                    # and is thus a SQ
                    next_tag = TupleTag(unpack(endian_chr + "HH", fp_read(4)))
                    # Rewind the file
                    fp.seek(fp_tell() - 4)
                    if next_tag == ItemTag:
                        VR = 'SQ'

            if VR == 'SQ':
                if debugging:
                    msg = "{0:08x}: Reading/parsing undefined length sequence"
                    logger_debug(msg.format(fp_tell()))
                seq = read_sequence(fp, is_implicit_VR, is_little_endian,
                                    length, encoding)
                if has_tag_set and tag not in tag_set:
                    continue
                yield DataElement(tag,
                                  VR,
                                  seq,
                                  value_tell,
                                  is_undefined_length=True)
            else:
                delimiter = SequenceDelimiterTag
                if debugging:
                    logger_debug("Reading undefined length data element")
                value = read_undefined_length_value(fp, is_little_endian,
                                                    delimiter, defer_size)

                # If the tag is (0008,0005) Specific Character Set,
                # then store it
                if tag == (0x08, 0x05):
                    from pydicom.values import convert_string
                    encoding = convert_string(value,
                                              is_little_endian,
                                              encoding=default_encoding)
                    # Store the encoding value in the generator for use
                    # with future elements (SQs)
                    encoding = convert_encodings(encoding)
                    if not has_specific_char_set:
                        continue

                # tags with undefined length are skipped after read
                if has_tag_set and tag not in tag_set:
                    continue
                yield RawDataElement(tag, VR, length, value, value_tell,
                                     is_implicit_VR, is_little_endian)
示例#28
0
def _build_message_classes(message_name):
    """
    Create a new subclass instance of DIMSEMessage for the given DIMSE
    `message_name`.

    Parameters
    ----------
    message_name : str
        The name/type of message class to construct, one of the following:
        * C-ECHO-RQ
        * C-ECHO-RSP
        * C-STORE-RQ
        * C-STORE-RSP
        * C-FIND-RQ
        * C-FIND-RSP
        * C-GET-RQ
        * C-GET-RSP
        * C-MOVE-RQ
        * C-MOVE-RSP
        * C-CANCEL-RQ
        * N-EVENT-REPORT-RQ
        * N-EVENT-REPORT-RSP
        * N-GET-RQ
        * N-GET-RSP
        * N-SET-RQ
        * N-SET-RSP
        * N-ACTION-RQ
        * N-ACTION-RSP
        * N-CREATE-RQ
        * N-CREATE-RSP
        * N-DELETE-RQ
        * N-DELETE-RSP
    """
    def __init__(self):
        DIMSEMessage.__init__(self)

    # Create new subclass of DIMSE Message using the supplied name
    #   but replace hyphens with underscores
    cls = type(message_name.replace('-', '_'), (DIMSEMessage, ),
               {"__init__": __init__})

    # Create a new Dataset object for the command_set attributes
    ds = Dataset()
    for elem_tag in _COMMAND_SET_ELEM[message_name]:
        tag = Tag(elem_tag)
        vr = dcm_dict[elem_tag][0]

        # If the required command set elements are expanded this will need
        #   to be checked to ensure it functions OK
        try:
            ds.add_new(tag, vr, None)
        except TypeError:
            ds.add_new(tag, vr, '')

    # Add the Command Set dataset to the class
    cls.command_set = ds

    # Add the class to the module
    globals()[cls.__name__] = cls

    return cls
示例#29
0
 def test_unknown_vr(self):
     """Test converting a raw element with unknown VR"""
     raw = RawDataElement(Tag(0x00080000), 'AA', 8, b'20170101', 0, False,
                          True)
     with pytest.raises(NotImplementedError):
         DataElement_from_raw(raw, default_encoding)
示例#30
0
def generate_dicom_sr(file_path, img_ds, data, series_description):
    """
    Generates DICOM Structured Report files for the given file path.
    :param file_path: the file name and directory to save the DICOM
                      SR file in.
    :param img_ds: A CT or MR image from the dataset used to pull
                   general information for the DICOM SR.
    :param data: Text data to be written to the DICOM SR file.
    :param series_description: Description of text data written to SR.
    :return: dicom_sr, a dataset for the new DICOM SR file.
    """
    if img_ds is None:
        raise ValueError("No CT data to initialize RT SS")

    # Create file meta
    file_meta = FileMetaDataset()
    file_meta.FileMetaInformationGroupLength = 238
    file_meta.FileMetaInformationVersion = b'\x00\x01'
    file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.88.33'
    file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
    file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
    validate_file_meta(file_meta)

    # Create dataset
    dicom_sr = pydicom.dataset.FileDataset(file_path, {},
                                           preamble=b"\0" * 128,
                                           file_meta=file_meta)
    dicom_sr.fix_meta_info()

    # Get current date and time
    now = datetime.datetime.now()
    dicom_date = now.strftime("%Y%m%d")
    dicom_time = now.strftime("%H%M")

    # List of tags to copy from CT/MR image
    top_level_tags_to_copy: list = [
        Tag("PatientName"),
        Tag("PatientID"),
        Tag("PatientBirthDate"),
        Tag("PatientSex"),
        Tag("StudyDate"),
        Tag("StudyTime"),
        Tag("ReferringPhysicianName"),
        Tag("StudyDescription"),
        Tag("StudyInstanceUID"),
        Tag("StudyID"),
        Tag("RequestingService"),
        Tag("PatientAge"),
        Tag("PatientSize"),
        Tag("PatientWeight"),
        Tag("MedicalAlerts"),
        Tag("Allergies"),
        Tag("PregnancyStatus"),
        Tag("InstitutionName"),
        Tag("InstitutionAddress")
    ]

    # Copy tags from CT/MR image
    for tag in top_level_tags_to_copy:
        if tag in img_ds:
            dicom_sr[tag] = deepcopy(img_ds[tag])

    dicom_sr.AccessionNumber = ""

    # == SR Document Series Module
    dicom_sr.SeriesDate = dicom_date
    dicom_sr.SeriesTime = dicom_time
    dicom_sr.Modality = "SR"
    dicom_sr.SeriesDescription = series_description
    # Can be empty
    referenced_performed_procedure_step_sequence = Sequence()
    dicom_sr.ReferencedPerformedProcedureStepSequence = \
        referenced_performed_procedure_step_sequence
    dicom_sr.SeriesInstanceUID = pydicom.uid.generate_uid()
    dicom_sr.SeriesNumber = 1

    # == General Equipment Module
    dicom_sr.Manufacturer = "OnkoDICOM"
    dicom_sr.ManufacturersModelName = "OnkoDICOM"
    # TODO: Pull this off build information in some way
    dicom_sr.SoftwareVersions = "2021"

    # == SR Document General Module
    dicom_sr.ContentDate = dicom_date
    dicom_sr.ContentTime = dicom_time

    dicom_sr.InstanceNumber = 1

    # Empty if unknown
    performed_procedure_code_sequence = Sequence()

    dicom_sr.PerformedProcedureCodeSequence = performed_procedure_code_sequence

    # Do not want to mark as complete in case it isn't!
    dicom_sr.CompletionFlag = "PARTIAL"
    dicom_sr.VerificationFlag = "UNVERIFIED"

    # == SR Document Content Module
    referenced_sop_sequence = Sequence([Dataset()])
    referenced_sop_sequence[0].ReferencedSOPClassUID = ''
    referenced_sop_sequence[0].ReferencedSOPInstanceUID = ''

    dicom_sr.ReferencedSOPSequence = referenced_sop_sequence
    dicom_sr.ValueType = "CONTAINER"

    dicom_sr.ContinuityOfContent = "CONTINUOUS"
    dicom_sr.TemporalRangeTime = ""
    dicom_sr.ReferencedTimeOffsets = ""
    dicom_sr.ReferencedDateTime = ""

    dicom_sr.MeasuredValueSequence = Sequence()
    og_frame_of_reference_UID = \
        deepcopy(img_ds[Tag("FrameOfReferenceUID")].value)
    dicom_sr.ReferencedFrameOfReferenceUID = og_frame_of_reference_UID

    # == Content Sequence
    content_sequence = Sequence([Dataset()])
    content_sequence[0].RelationshipType = 'CONTAINS'
    content_sequence[0].ValueType = 'TEXT'

    concept_name_code_sequence = Sequence([Dataset()])
    concept_name_code_sequence[0].CodeValue = ''
    concept_name_code_sequence[0].CodingSchemeDesignator = ''
    concept_name_code_sequence[0].CodeMeaning = ''
    content_sequence[0].ConceptNameCodeSequence = concept_name_code_sequence

    content_sequence[0].TextValue = data

    dicom_sr.ContentSequence = content_sequence

    # == SOP Common Module
    dicom_sr.SOPClassUID = '1.2.840.10008.5.1.4.1.1.88.33'
    dicom_sr.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID

    dicom_sr.is_little_endian = True
    dicom_sr.is_implicit_VR = True

    return dicom_sr