Esempio n. 1
0
def create_roi(rtss,
               roi_name,
               roi_coordinates,
               data_set,
               rt_roi_interpreted_type="ORGAN"):
    """
        Create new ROI to rtss

        :param rtss: dataset of RTSS
        :param roi_name: ROIName
        :param roi_coordinates: Coordinates of pixels for new ROI
        :param data_set: Data Set of selected DICOM image file
        :return: rtss, with added ROI
        """

    patient_dict_container = PatientDictContainer()
    existing_rois = patient_dict_container.get("rois")
    roi_exists = False

    # This is for adding a new slice to an already existing ROI. For Future Development.
    # Check to see if the ROI already exists
    for key, value in existing_rois.items():
        if value["name"] == roi_name:
            roi_exists = True

    if not roi_exists:
        number_of_contour_points = len(roi_coordinates) / 3
        referenced_sop_class_uid = data_set.SOPClassUID
        referenced_sop_instance_uid = data_set.SOPInstanceUID

        referenced_frame_of_reference_uid = rtss[
            "StructureSetROISequence"].value[0].ReferencedFrameOfReferenceUID
        roi_number = rtss["StructureSetROISequence"].value[-1].ROINumber + 1

        # Colour TBC
        red = random.randint(0, 255)
        green = random.randint(0, 255)
        blue = random.randint(0, 255)
        rgb = [red, green, blue]

        # Saving a new StructureSetROISequence
        structure_set_sequence = Sequence([Dataset()])

        original_structure_set = rtss.StructureSetROISequence

        for structure_set in structure_set_sequence:
            structure_set.add_new(Tag("ROINumber"), 'IS', roi_number)
            structure_set.add_new(Tag("ReferencedFrameOfReferenceUID"), 'UI',
                                  referenced_frame_of_reference_uid)
            structure_set.add_new(Tag("ROIName"), 'LO', roi_name)
            structure_set.add_new(Tag("ROIGenerationAlgorithm"), 'CS', "")

        # Combine old and new structure set
        original_structure_set.extend(structure_set_sequence)
        rtss.add_new(Tag("StructureSetROISequence"), "SQ",
                     original_structure_set)

        # Saving a new ROIContourSequence, ContourSequence, ContourImageSequence
        roi_contour_sequence = Sequence([Dataset()])
        contour_sequence = Sequence([Dataset()])
        contour_image_sequence = Sequence([Dataset()])

        # Original File
        original_ROI_contour = rtss.ROIContourSequence

        # ROI Contour Sequence
        for roi_contour in roi_contour_sequence:
            roi_contour.add_new(Tag("ROIDisplayColor"), "IS", rgb)
            roi_contour.add_new(Tag("ContourSequence"), "SQ", contour_sequence)

            # ROI Sequence
            for contour in contour_sequence:
                # if data_set.get("ReferencedImageSequence"):
                contour.add_new(Tag("ContourImageSequence"), "SQ",
                                contour_image_sequence)

                # Contour Sequence
                for contour_image in contour_image_sequence:
                    contour_image.add_new(
                        Tag("ReferencedSOPClassUID"), "UI",
                        referenced_sop_class_uid)  # CT Image Storage
                    contour_image.add_new(Tag("ReferencedSOPInstanceUID"),
                                          "UI", referenced_sop_instance_uid)

                contour.add_new(Tag("ContourNumber"), "IS", 1)
                if not _is_closed_contour(roi_coordinates):
                    contour.add_new(Tag("ContourGeometricType"), "CS",
                                    "OPEN_PLANAR")
                    contour.add_new(Tag("NumberOfContourPoints"), "IS",
                                    number_of_contour_points)
                    contour.add_new(Tag("ContourData"), "DS", roi_coordinates)
                else:
                    contour.add_new(Tag("ContourGeometricType"), "CS",
                                    "CLOSED_PLANAR")
                    contour.add_new(Tag("NumberOfContourPoints"), "IS",
                                    number_of_contour_points - 1)
                    contour.add_new(Tag("ContourData"), "DS",
                                    roi_coordinates[0:-3])

            roi_contour.add_new(Tag("ReferencedROINumber"), "IS", roi_number)

        # Combine original ROIContourSequence with new
        original_ROI_contour.extend(roi_contour_sequence)

        rtss.add_new(Tag("ROIContourSequence"), "SQ", original_ROI_contour)

        # Saving a new RTROIObservationsSequence
        RT_ROI_observations_sequence = Sequence([Dataset()])

        original_ROI_observation_sequence = rtss.RTROIObservationsSequence

        for ROI_observations in RT_ROI_observations_sequence:
            # TODO: Check to make sure that there aren't multiple observations per ROI, e.g. increment from existing Observation Numbers?
            ROI_observations.add_new(Tag("ObservationNumber"), 'IS',
                                     roi_number)
            ROI_observations.add_new(Tag("ReferencedROINumber"), 'IS',
                                     roi_number)
            ROI_observations.add_new(Tag("RTROIInterpretedType"), 'CS',
                                     rt_roi_interpreted_type)

        original_ROI_observation_sequence.extend(RT_ROI_observations_sequence)
        rtss.add_new(Tag("RTROIObservationsSequence"), "SQ",
                     original_ROI_observation_sequence)

    else:
        # Add contour image data to existing ROI
        rtss = add_to_roi(rtss, roi_name, roi_coordinates, data_set)

    return rtss
Esempio n. 2
0
def read_dicom_image_series(image_folder, modality=None, series_uid=None):

    # Obtain a list with image files
    file_list = _find_dicom_image_series(image_folder=image_folder,
                                         allowed_modalities=["CT", "PT", "MR"],
                                         modality=modality,
                                         series_uid=series_uid)

    # Obtain slice positions for each file
    image_position_z = []
    for file_name in file_list:

        # Read DICOM header
        dcm = pydicom.dcmread(os.path.join(image_folder, file_name),
                              stop_before_pixels=True,
                              force=True,
                              specific_tags=[Tag(0x0020, 0x0032)])

        # Obtain the z position
        image_position_z += [
            get_pydicom_meta_tag(dcm_seq=dcm,
                                 tag=(0x0020, 0x0032),
                                 tag_type="mult_float")[2]
        ]

    # Order ascending position (DICOM: z increases from feet to head)
    file_table = pd.DataFrame({
        "file_name": file_list,
        "position_z": image_position_z
    }).sort_values(by="position_z")

    # Obtain DICOM metadata from the bottom slice. This will be used to fill out all the different details.
    dcm = pydicom.dcmread(os.path.join(image_folder,
                                       file_table.file_name.values[0]),
                          stop_before_pixels=True,
                          force=True)

    # Find the number of rows (y) and columns (x) in the data set.
    n_x = get_pydicom_meta_tag(dcm_seq=dcm,
                               tag=(0x0028, 0x011),
                               tag_type="int")
    n_y = get_pydicom_meta_tag(dcm_seq=dcm,
                               tag=(0x0028, 0x010),
                               tag_type="int")

    # Create an empty voxel grid. Use z, y, x ordering for consistency within MIRP.
    voxel_grid = np.zeros((len(file_table), n_y, n_x), dtype=np.float32)

    # Read all dicom slices in order.
    slice_dcm_list = [
        pydicom.dcmread(os.path.join(image_folder, file_name),
                        stop_before_pixels=False,
                        force=True)
        for file_name in file_table.file_name.values
    ]

    # Iterate over the different slices to fill out the voxel_grid.
    for ii, file_name in enumerate(file_table.file_name.values):

        # Read the dicom file and extract the slice grid
        slice_dcm = slice_dcm_list[ii]
        slice_grid = slice_dcm.pixel_array.astype(np.float32)

        # Update with scale and intercept. These may change per slice.
        rescale_intercept = get_pydicom_meta_tag(dcm_seq=slice_dcm,
                                                 tag=(0x0028, 0x1052),
                                                 tag_type="float",
                                                 default=0.0)
        rescale_slope = get_pydicom_meta_tag(dcm_seq=slice_dcm,
                                             tag=(0x0028, 0x1053),
                                             tag_type="float",
                                             default=1.0)
        slice_grid = slice_grid * rescale_slope + rescale_intercept

        # Convert all images to SUV at admin
        if get_pydicom_meta_tag(dcm_seq=dcm,
                                tag=(0x0008, 0x0060),
                                tag_type="str") == "PT":
            suv_conversion_object = SUVscalingObj(dcm=slice_dcm)
            scale_factor = suv_conversion_object.get_scale_factor(
                suv_normalisation="bw")

            # Convert to SUV
            slice_grid *= scale_factor

            # Update the DICOM header
            slice_dcm = suv_conversion_object.update_dicom_header(
                dcm=slice_dcm)

        # Store in voxel grid
        voxel_grid[ii, :, :] = slice_grid

    # Obtain the image origin from the dicom header (note: z, y, x order)
    image_origin = get_pydicom_meta_tag(dcm_seq=dcm,
                                        tag=(0x0020, 0x0032),
                                        tag_type="mult_float",
                                        default=np.array([0.0, 0.0,
                                                          0.0]))[::-1]

    # Obtain the image spacing from the dicom header and slice positions.
    image_pixel_spacing = get_pydicom_meta_tag(dcm_seq=dcm,
                                               tag=(0x0028, 0x0030),
                                               tag_type="mult_float")
    image_slice_thickness = get_pydicom_meta_tag(dcm_seq=dcm,
                                                 tag=(0x0018, 0x0050),
                                                 tag_type="float",
                                                 default=None)

    if len(file_table) > 1:
        # Slice spacing can be determined from the slice positions
        image_slice_spacing = np.median(
            np.abs(np.diff(file_table.position_z.values)))

        if image_slice_thickness is None:
            # TODO: Update slice thickness tag in dcm
            pass
        else:
            # Warn the user if there is a mismatch between slice thickness and the actual slice spacing.
            if not np.around(image_slice_thickness - image_slice_spacing,
                             decimals=5) == 0.0:
                warnings.warn(
                    f"Mismatch between slice thickness ({image_slice_thickness}) and actual slice spacing ({image_slice_spacing}). The actual slice spacing will be "
                    f"used.", UserWarning)

    elif image_slice_thickness is not None:
        # There is only one slice, and we use the slice thickness as parameter.
        image_slice_spacing = image_slice_thickness

    else:
        # There is only one slice and the slice thickness is unknown. In this situation, we use the pixel spacing
        image_slice_spacing = np.max(image_pixel_spacing)

    # Combine pixel spacing and slice spacing into the voxel spacing, using z, y, x order.
    image_spacing = np.array(
        [image_slice_spacing, image_pixel_spacing[1], image_pixel_spacing[0]])

    # Obtain image orientation and add the 3rd dimension
    image_orientation = get_pydicom_meta_tag(dcm_seq=dcm,
                                             tag=(0x0020, 0x0037),
                                             tag_type="mult_float")
    image_orientation += [0.0, 0.0, 1.0]

    # Revert to z, y, x order
    image_orientation = image_orientation[::-1]

    # Create an ImageClass object and store dicom meta-data
    img_obj = ImageClass(voxel_grid=voxel_grid,
                         origin=image_origin,
                         spacing=image_spacing,
                         slice_z_pos=file_table.position_z.values,
                         orientation=image_orientation,
                         modality=get_pydicom_meta_tag(dcm_seq=dcm,
                                                       tag=(0x0008, 0x0060),
                                                       tag_type="str"),
                         spat_transform="base",
                         no_image=False,
                         metadata=slice_dcm_list[0],
                         metadata_sop_instances=[
                             get_pydicom_meta_tag(dcm_seq=slice_dcm,
                                                  tag=(0x0008, 0x0018),
                                                  tag_type="str")
                             for slice_dcm in slice_dcm_list
                         ])

    return img_obj
Esempio n. 3
0
    def c_find(cls, ds: pydicom.Dataset):
        """C-FIND request handler for Study level

        :param ds: C-FIND request
        :type ds: pydicom.Dataset
        :yield: C-FIND result
        :rtype: pydicom.Dataset
        """
        joins = set()

        response_attrs = []
        select = [Study]
        upper_level_filters = []

        patient_attrs = [e for e in ds if e.tag in Patient.mapping]
        skipped = set(e.tag for e in patient_attrs)
        if patient_attrs:
            upper_level_filters.extend(
                _filter_upper_level(Patient, patient_attrs))
            for tag, attr, vr, _, attr_name in upper_level_filters:
                select.append(attr)
                response_attrs.append((tag, ('patient', attr_name), vr, None))
            joins.add((Study, Patient))

        if 'ModalitiesInStudy' in ds:
            _tag = Tag(0x0008, 0x0061)
            skipped.add(_tag)
            # TODO: Add modalities in study filter
            agg_fun = db.string_agg_func()
            select.append(
                agg_fun(Series.modality, '\\').alias('modalities_in_study'))
            func = lambda v: '\\'.join(set(v.split('\\'))) if v else v
            response_attrs.append((_tag, 'modalities_in_study', 'CS', func))
            joins.add((Study, Series))
        if 'SOPClassesInStudy' in ds:
            _tag = Tag(0x0008, 0x0062)
            skipped.add(_tag)
            agg_fun = db.string_agg_func()
            select.append(
                agg_fun(Instance.sop_class_uid,
                        '\\').alias('sop_classes_in_study'))
            func = lambda v: '\\'.join(set(v.split('\\'))) if v else v
            response_attrs.append((_tag, 'sop_classes_in_study', 'UI', func))
            joins.update([(Study, Series), (Series, Instance)])
        if 'NumberOfStudyRelatedSeries' in ds:
            _tag = Tag(0x0020, 0x1206)
            skipped.add(_tag)
            select.append(
                peewee.fn.Count(Series.id)\
                    .alias('number_of_study_related_series')  # pylint: disable=no-member
            )
            response_attrs.append(
                (_tag, 'number_of_study_related_series', 'IS', None))
            joins.add((Study, Series))
        if 'NumberOfStudyRelatedInstances' in ds:
            _tag = Tag((0x0020, 0x1208))
            skipped.add(_tag)
            select.append(
                peewee.fn.Count(Instance.id)\
                    .alias('number_of_study_related_instances')  # pylint: disable=no-member
            )
            response_attrs.append(
                (_tag, 'number_of_study_related_instances', 'IS', None))
            joins.update([(Study, Series), (Series, Instance)])

        query = Study.select(*select)

        for join in joins:
            query = query.join_from(*join)

        query, _response_attrs = _build_filters(cls, query, ds, skipped)
        response_attrs.extend(_response_attrs)
        for _, attr, vr, elem, _ in upper_level_filters:
            if not elem.value:
                continue
            query = _build_filter(query, attr, vr, elem)

        encoding = getattr(ds, 'SpecificCharacterSet', 'ISO-IR 6')
        if not query.count():
            return []
        yield from (_encode_response(s, response_attrs, encoding)
                    for s in query)
Esempio n. 4
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.

    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
    -------
    list of int
        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 offsets
Esempio n. 5
0
 def write_tag(self, tag):
     """Write a dicom tag (two unsigned shorts) to the file."""
     tag = Tag(tag)  # make sure is an instance of class, not just a tuple or int
     self.write_US(tag.group)
     self.write_US(tag.element)
Esempio n. 6
0
 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)
Esempio n. 7
0
    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
Esempio n. 8
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')
Esempio n. 9
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))
Esempio n. 10
0
def data_element_generator(
    fp: BinaryIO,
    is_implicit_VR: bool,
    is_little_endian: bool,
    stop_when: Optional[Callable[[BaseTag, Optional[str], int], bool]] = None,
    defer_size: Optional[Union[int, str, float]] = None,
    encoding: Union[str, MutableSequence[str]] = default_encoding,
    specific_tags: Optional[List[BaseTag]] = None
) -> Iterator[Union[RawDataElement, DataElement]]:
    """Create a generator to efficiently return the raw data elements.

    .. note::

        This function is used internally - usually there is no need to call it
        from user code. To read data from a DICOM file, :func:`dcmread`
        shall be used instead.

    Parameters
    ----------
    fp : file-like
        The file-like to read from.
    is_implicit_VR : bool
        ``True`` if the data is encoded as implicit VR, ``False`` otherwise.
    is_little_endian : bool
        ``True`` if the data is encoded as little endian, ``False`` otherwise.
    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 or float, optional
        See :func:`dcmread` for parameter info.
    encoding : Union[str, MutableSequence[str]]
        Encoding scheme
    specific_tags : list or None
        See :func:`dcmread` for parameter info.

    Yields
    -------
    RawDataElement or DataElement
        Yields DataElement for undefined length UN or SQ, RawDataElement
        otherwise.
    """
    # 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
    from pydicom.values import convert_string

    if is_little_endian:
        endian_chr = "<"
    else:
        endian_chr = ">"

    # assign implicit VR struct to variable as use later if VR assumed missing
    implicit_VR_struct = Struct(endian_chr + "HHL")
    if is_implicit_VR:
        element_struct = implicit_VR_struct
    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 = {Tag(tag) for tag in specific_tags} if specific_tags else set()
    has_tag_set = bool(tag_set)
    if has_tag_set:
        tag_set.add(Tag(0x00080005))  # Specific Character Set

    while True:
        # VR: Optional[str]

        # 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 = f"{fp.tell() - 8:08x}: {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)
            # defend against switching to implicit VR, some writer do in SQ's
            # issue 1067, issue 1035

            if not (b'AA' <= VR <= b'ZZ') and config.assume_implicit_vr_switch:
                # invalid VR, must be 2 cap chrs, assume implicit and continue
                VR = None
                group, elem, length = implicit_VR_struct.unpack(bytes_read)
            else:
                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 length > 0 else cast(
                    Optional[bytes], empty_value_for_VR(VR, raw=True)))
                if debugging:
                    dotdot = "..." if length > 20 else "   "
                    displayed_value = value[:20] if value else b''
                    logger_debug("%08x: %-34s %s %r %s" %
                                 (value_tell, bytes2hex(displayed_value),
                                  dotdot, displayed_value, dotdot))

            # If the tag is (0008,0005) Specific Character Set, then store it
            if tag == BaseTag(0x00080005):
                # *Specific Character String* is b'' for empty value
                encoding = convert_string(
                    cast(bytes, value) or b'', is_little_endian)
                # Store the encoding value in the generator
                # for use with future elements (SQs)
                encoding = convert_encodings(encoding)

            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:
            # VR UN with undefined length shall be handled as SQ
            # see PS 3.5, section 6.2.2
            if VR == 'UN':
                VR = 'SQ'
            # 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 or VR == 'UN' and config.replace_un_with_known_vr:
                try:
                    VR = dictionary_VR(tag)
                except KeyError:
                    # Look ahead to see if it consists of items
                    # and is thus a SQ
                    next_tag = _unpack_tag(fp_read(4), endian_chr)
                    # Rewind the file
                    fp.seek(fp_tell() - 4)
                    if next_tag == ItemTag:
                        VR = 'SQ'

            if VR == 'SQ':
                if debugging:
                    logger_debug(
                        f"{fp_tell():08X}: Reading/parsing undefined length "
                        "sequence")

                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)

                # 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)
Esempio n. 11
0
def dcmread(
    fp: Union[PathType, BinaryIO, DicomFileLike],
    defer_size: Optional[Union[str, int, float]] = None,
    stop_before_pixels: bool = False,
    force: bool = False,
    specific_tags: Optional[TagListType] = None
) -> Union[FileDataset, DicomDir]:
    """Read and parse a DICOM dataset stored in the DICOM File Format.

    Read a DICOM dataset stored in accordance with the :dcm:`DICOM File
    Format <part10/chapter_7.html>`. If the dataset is not stored in
    accordance with the File Format (i.e. the preamble and prefix are missing,
    there are missing required Type 1 *File Meta Information Group* elements
    or the entire *File Meta Information* is missing) then you will have to
    set `force` to ``True``.

    .. deprecated:: 2.2

        Returning a :class:`~pydicom.dicomdir.DicomDir` is deprecated and
        will be removed in v3.0. Use :class:`~pydicom.fileset.FileSet` instead.


    Examples
    --------
    Read and return a dataset stored in accordance with the DICOM File Format:

    >>> ds = pydicom.dcmread("CT_small.dcm")
    >>> ds.PatientName

    Read and return a dataset not in accordance with the DICOM File Format:

    >>> ds = pydicom.dcmread("rtplan.dcm", force=True)
    >>> ds.PatientName

    Use within a context manager:

    >>> with pydicom.dcmread("rtplan.dcm") as ds:
    ...     ds.PatientName

    Parameters
    ----------
    fp : str or PathLike or file-like
        Either a file-like object, a string containing the file name or the
        path to the file. The file-like object must have ``seek()``,
        ``read()`` and ``tell()`` methods and the caller is responsible for
        closing it (if required).
    defer_size : int, str or float, optional
        If not used then all elements are read into memory. If specified,
        then if a data element's stored value is larger than `defer_size`, the
        value is not read into memory until it is accessed in code. Should be
        the number of bytes to be read as :class:`int` or as a :class:`str`
        with units, e.g. ``'512 KB'``, ``'2 MB'``.
    stop_before_pixels : bool, optional
        If ``False`` (default), the full file will be read and parsed. Set
        ``True`` to stop before reading (7FE0,0010) *Pixel Data* (and all
        subsequent elements).
    force : bool, optional
        If ``False`` (default), raises an
        :class:`~pydicom.errors.InvalidDicomError` if the file is
        missing the *File Meta Information* header. Set to ``True`` to force
        reading even if no *File Meta Information* header is found.
    specific_tags : list of (int or str or 2-tuple of int), optional
        If used the only the supplied tags will be returned. The supplied
        elements can be tags or keywords. Note that the element (0008,0005)
        *Specific Character Set* is always returned if present - this ensures
        correct decoding of returned text values.

    Returns
    -------
    FileDataset or DicomDir
        An instance of :class:`~pydicom.dataset.FileDataset` that represents
        a parsed DICOM file, unless the dataset is a *Media Storage Directory*
        instance in which case it will be a
        :class:`~pydicom.dicomdir.DicomDir`.

    Raises
    ------
    InvalidDicomError
        If `force` is ``False`` and the file is not a valid DICOM file.
    TypeError
        If `fp` is ``None`` or of an unsupported type.

    See Also
    --------
    pydicom.dataset.FileDataset
        Data class that is returned.
    pydicom.filereader.read_partial
        Only read part of a DICOM file, stopping on given conditions.
    """
    # Open file if not already a file object
    caller_owns_file = True
    fp = path_from_pathlike(fp)
    if isinstance(fp, str):
        # caller provided a file name; we own the file handle
        caller_owns_file = False
        logger.debug("Reading file '{0}'".format(fp))
        fp = open(fp, 'rb')
    elif fp is None or not hasattr(fp, "read") or not hasattr(fp, "seek"):
        raise TypeError("dcmread: Expected a file path or a file-like, "
                        "but got " + type(fp).__name__)

    if config.debugging:
        logger.debug("\n" + "-" * 80)
        logger.debug("Call to dcmread()")
        msg = ("filename:'%s', defer_size='%s', "
               "stop_before_pixels=%s, force=%s, specific_tags=%s")
        logger.debug(
            msg %
            (fp.name, defer_size, stop_before_pixels, force, specific_tags))
        if caller_owns_file:
            logger.debug("Caller passed file object")
        else:
            logger.debug("Caller passed file name")
        logger.debug("-" * 80)

    if specific_tags:
        specific_tags = [Tag(t) for t in specific_tags]

    specific_tags = cast(Optional[List[BaseTag]], specific_tags)

    # Iterate through all items and store them --include file meta if present
    stop_when = None
    if stop_before_pixels:
        stop_when = _at_pixel_data
    try:
        dataset = read_partial(
            fp,
            stop_when,
            defer_size=size_in_bytes(defer_size),
            force=force,
            specific_tags=specific_tags,
        )
    finally:
        if not caller_owns_file:
            fp.close()
    # XXX need to store transfer syntax etc.
    return dataset
Esempio n. 12
0
class DataElementFactory(factory.Factory):
    """Generates pydicom DataElements.

    Will always match VR and random value to given values

    >>> DataElementFactory(tag='PatientName').vr = 'PN'
    >>> DataElementFactory(tag='PatientName').value = 'JONES^Sarah'

    You can still set custom values as well:
    >>> DataElementFactory(tag='PatientName', value='123').value = '123'

    Notes
    -----
    For an unknown tag without an explicit VR, this factory will assign a
    LongString (LO) VR:
    >>> DataElementFactory(tag=('ee011020')).VR = 'LO'

    If this is not what you want, pass an explicit VR:
    >>> DataElementFactory(tag=('ee011020'), VR='SL', value=-10.2)
    """
    class Meta:
        model = pydicom.dataelem.DataElement

    tag = Tag('PatientID')

    @factory.lazy_attribute
    def VR(self):
        """Find the correct Value Representation for this tag from pydicom"""
        try:
            return dictionary_VR(Tag(self.tag))
        except KeyError as e:
            # unknown tag. Just return set value, assuming user want to just
            # get on with it
            return VRs.LongString.short_name

    @factory.lazy_attribute
    def value(self):
        """Generate a valid mock value for this type of VR

        Raises
        ------
        DataElementFactoryException
            If a value cannot be generated
        """
        faker = Faker()
        faker.add_provider(DICOMVRProvider)
        vr = VRs.short_name_to_vr(self.VR)
        if vr == VRs.ApplicationEntity:
            return "MockEntity"
        elif vr == VRs.AgeString:
            return f"{factory.random.randgen.randint(0,120):03d}Y"
        elif vr == VRs.AttributeTag:
            return 0x0010, 0x0010
        elif vr == VRs.CodeString:
            return "MockCodeString"
        elif vr == VRs.Date:
            return faker.dicom_date()
        elif vr == VRs.DecimalString:
            return "+10.4"
        elif vr == VRs.DateTime:
            return faker.dicom_date() + faker.dicom_time()
        elif vr == VRs.FloatingPointSingle:
            return 1.1
        elif vr == VRs.FloatingPointDouble:
            return 1.123
        elif vr == VRs.IntegerString:
            return f"{factory.random.randgen.randint(-2**31,2**31)}"
        elif vr == VRs.LongString:
            return faker.sentence()[:64]
        elif vr == VRs.LongText:
            return faker.text()[:10240]
        elif vr == VRs.OtherByteString:
            return b'\x13\00'
        elif vr == VRs.OtherDoubleString:
            return "MockDoubleString"
        elif vr == VRs.OtherFloatString:
            return "MockFloatString"
        elif vr == VRs.OtherWordString:
            return "MockOtherWordString"
        elif vr == VRs.PersonName:
            return faker.dicom_person_name()
        elif vr == VRs.ShortString:
            return "MockShortString"
        elif vr == VRs.SignedLong:
            return factory.random.randgen.randint(-2**32, 2**32)
        elif vr == VRs.Sequence:
            return []
        elif vr == VRs.SignedShort:
            return factory.random.randgen.randint(-2**16, 2**16)
        elif vr == VRs.ShortText:
            return faker.sentence()
        elif vr == VRs.Time:
            return faker.dicom_time()
        elif vr == VRs.UniqueIdentifier:
            return faker.dicom_ui()
        elif vr == VRs.UnsignedLong:
            return factory.random.randgen.randint(0, 2**32)
        elif vr == VRs.Unknown:
            return "MockUnknown"
        elif vr == VRs.UnsignedShort:
            return factory.random.randgen.randint(0, 2**16)
        elif vr == VRs.UnlimitedText:
            return faker.text()
        else:
            raise DataElementFactoryException(
                f"I dont know how to generate a mock value for"
                f" {vr}, the VR of '{self.tag}'")
Esempio n. 13
0
def add_to_roi(rtss, roi_name, roi_coordinates, data_set):
    """
        Add new contour image sequence ROI to rtss

        :param rtss: dataset of RTSS
        :param roi_name: ROIName
        :param roi_coordinates: Coordinates of pixels for new ROI
        :param data_set: Data Set of selected DICOM image file
        :return: rtss, with added ROI
    """

    # Creating a new ROIContourSequence, ContourSequence, ContourImageSequence
    contour_sequence = Sequence([Dataset()])
    contour_image_sequence = Sequence([Dataset()])

    number_of_contour_points = len(roi_coordinates) / 3
    referenced_sop_class_uid = data_set.SOPClassUID
    referenced_sop_instance_uid = data_set.SOPInstanceUID

    existing_roi_number = None
    for item in rtss["StructureSetROISequence"]:
        if item.ROIName == roi_name:
            existing_roi_number = item.ROINumber

    position = None

    # Get the index of the ROI
    for index, contour in enumerate(rtss.ROIContourSequence):
        if contour.ReferencedROINumber == existing_roi_number:
            position = index

    new_contour_number = len(
        rtss.ROIContourSequence[position].ContourSequence) + 1

    # ROI Sequence
    for contour in contour_sequence:
        # if data_set.get("ReferencedImageSequence"):
        contour.add_new(Tag("ContourImageSequence"), "SQ",
                        contour_image_sequence)

        # Contour Sequence
        for contour_image in contour_image_sequence:
            contour_image.add_new(Tag("ReferencedSOPClassUID"), "UI",
                                  referenced_sop_class_uid)  # CT Image Storage
            contour_image.add_new(Tag("ReferencedSOPInstanceUID"), "UI",
                                  referenced_sop_instance_uid)

        contour.add_new(Tag("ContourNumber"), "IS", new_contour_number)
        if not _is_closed_contour(roi_coordinates):
            contour.add_new(Tag("ContourGeometricType"), "CS", "OPEN_PLANAR")
            contour.add_new(Tag("NumberOfContourPoints"), "IS",
                            number_of_contour_points)
            contour.add_new(Tag("ContourData"), "DS", roi_coordinates)
        else:
            contour.add_new(Tag("ContourGeometricType"), "CS", "CLOSED_PLANAR")
            contour.add_new(Tag("NumberOfContourPoints"), "IS",
                            number_of_contour_points - 1)
            contour.add_new(Tag("ContourData"), "DS", roi_coordinates[0:-3])

    rtss.ROIContourSequence[position].ContourSequence.extend(contour_sequence)

    return rtss
Esempio n. 14
0
def create_initial_rtss_from_ct(img_ds: pydicom.dataset.Dataset,
                                ct_uid_list=[]) -> pydicom.dataset.Dataset:
    """Pre-populate an RT Structure Set based on a single CT (or MR) and a list of image UIDs
        The caller should update the Structure Set Label, Name, and Description, which are
        set to "OnkoDICOM" plus the StudyID from the CT, and must add 
        Structure Set ROI Sequence, ROI Contour Sequence, and RT ROI Observations Sequence

    Parameters
    ----------
    img_ds : pydicom.dataset.Dataset
        A CT or MR image that the RT Structure Set will be "drawn" on
    ct_uid_list : list, optional
        list of UIDs (as strings) of the entire image volume that the RT SS references, by default []

    Returns
    -------
    pydicom.dataset.Dataset
        the half-baked RT SS, ready for Structure Set ROI Sequence, ROI Contour Sequence,
        and RT ROI Observations Sequence

    Raises
    ------
    ValueError
        [description]
    """
    if (img_ds is None):
        raise ValueError("No CT data to initialize RT SS")

    now = datetime.datetime.now()
    dicom_date = now.strftime("%Y%m%d")
    dicom_time = now.strftime("%H%M")

    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("FrameOfReferenceUID"),
        Tag("PositionReferenceIndicator"),
        Tag("InstitutionName"),
        Tag("InstitutionAddress")
    ]

    rt_ss = pydicom.dataset.Dataset()

    for tag in top_level_tags_to_copy:
        print("Tag ", tag)
        if tag in img_ds:
            print("value of tag in image: ", img_ds[tag])
            rt_ss[tag] = deepcopy(img_ds[tag])

    # Best to modify the Structure Set Lable with something more interesting in the application.
    # and populate the Name and Description from the application also.
    print("Study ID is ", rt_ss.StudyID)
    rt_ss.StructureSetLabel = "OnkoDICOM rtss of " + rt_ss.StudyID
    rt_ss.StructureSetName = rt_ss.StructureSetLabel
    rt_ss.StructureSetDescription = rt_ss.StructureSetLabel

    # referenced_study_sequence_item = pydicom.dataset.Dataset()
    # referenced_study_sequence_item["ReferencedSOPInstanceUID"] = img_ds.StudyInstanceUID
    # referenced_study_sequence_item["ReferencedSOPClassUID"] = img_ds.SOPClassUID

    # rt_ss.ReferencedStudySequence = [referenced_study_sequence_item]

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

    # RT Series Module
    rt_ss.SeriesInstanceUID = pydicom.uid.generate_uid()
    rt_ss.Modality = "RTSTRUCT"
    rt_ss.SeriesDate = dicom_date
    rt_ss.SeriesTime = dicom_time
    rt_ss.SeriesNumber = 1

    # RT Referenced Frame Of Reference Sequence, Structure Set Module
    rt_ref_frame_of_ref_sequence_item = pydicom.dataset.Dataset()
    rt_ref_frame_of_ref_sequence_item.FrameOfReferenceUID = img_ds.FrameOfReferenceUID
    rt_ss.ReferencedFrameOfReferenceSequence = [
        rt_ref_frame_of_ref_sequence_item
    ]

    rt_ref_study_sequence_item = pydicom.dataset.Dataset()
    rt_ref_study_sequence_item.ReferencedSOPInstanceUID = img_ds.StudyInstanceUID
    rt_ref_study_sequence_item.ReferencedSOPClassUID = img_ds.SOPClassUID

    rt_ref_series_sequence_item = pydicom.dataset.Dataset()
    rt_ref_series_sequence_item.SeriesInstanceUID = img_ds.SeriesInstanceUID

    contour_image_sequence = []
    referenced_image_sequence = []
    for uid in ct_uid_list:
        contour_image_sequence_item = pydicom.dataset.Dataset()
        referenced_image_sequence_item = pydicom.dataset.Dataset()
        referenced_image_sequence_item.ReferencedSOPClassUID = img_ds.SOPClassUID
        contour_image_sequence_item.ReferencedSOPClassUID = img_ds.SOPClassUID
        referenced_image_sequence_item.ReferencedSOPInstanceUID = uid
        contour_image_sequence_item.ReferencedSOPInstanceUID = uid
        contour_image_sequence.append(contour_image_sequence_item)
        referenced_image_sequence.append(referenced_image_sequence_item)

    rt_ref_frame_of_ref_sequence_item.ContourImageSequence = contour_image_sequence
    rt_ss.ReferencedImageSequence = referenced_image_sequence

    referenced_series_sequence_item = pydicom.dataset.Dataset()
    referenced_series_sequence_item.SeriesInstanceUID = img_ds.SeriesInstanceUID
    # acceptable to copy because the contents of each reference sequence item only include
    # SOP Class and SOP Instance, and not additional elements that are in referenced image seq
    # but not referenced instance seq
    referenced_series_sequence_item.ReferencedInstanceSequence = shallowcopy(
        referenced_image_sequence)
    rt_ss.ReferencedSeriesSequence = [referenced_series_sequence_item]
    rt_ref_study_sequence_item.RTReferencedSeriesSequence = [
        rt_ref_series_sequence_item
    ]
    rt_ref_frame_of_ref_sequence_item.RTReferencedStudySequence = [
        rt_ref_study_sequence_item
    ]

    rt_ss.StructureSetROISequence = []
    rt_ss.ROIContourSequence = []
    rt_ss.RTROIObservationsSequence = []
    rt_ss.SOPClassUID = "1.2.840.10008.5.1.4.1.1.481.3"
    rt_ss.SOPInstanceUID = pydicom.uid.generate_uid()

    rt_ss.InstanceCreationDate = rt_ss.StructureSetDate = dicom_date

    rt_ss.InstanceCreationTime = rt_ss.StructureSetTime = dicom_time
    rt_ss.is_little_endian = True
    rt_ss.is_implicit_VR = True
    return rt_ss
Esempio n. 15
0
 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)
Esempio n. 16
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:
    """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()
        +-------------+   --->    +-----------+   --->   +-------------+
Esempio n. 17
0
 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)
Esempio n. 18
0
from pydicom.filebase import DicomBytesIO
import warnings

# filename = Path(r'D:\Users\user\Desktop\kwmri\DICOM\ST000000\SE000005\MR000003.TRUE')
filename = Path(
    r'D:\Users\user\Desktop\NTUCT\8252\Ct_Without_ContrastBrain - 16683\IAC_2\IM-0008-0002.dcm'
)
filename = Path(
    r'D:\Users\user\Desktop\NTUCT\8252\Ct_Without_ContrastBrain - 16683\AXIAL_303\IM-0009-0003.dcm'
)
# coding = 'big5'
coding = 'utf8'
ds = pydicom.dcmread(str(filename))
DcmTagValDict = {}
DcmTagNameDict = {
    Tag(0x33, 0x1013): 'NTUPatientName',
    Tag(0x33, 0x1014): 'NTUPatientId',
    Tag(0x33, 0x1018): 'NTUDoctorName',
    Tag(0x0028, 0x1050): 'wl',
    Tag(0x0028, 0x1051): 'ww',
    Tag(0x20, 0x10): 'StudyID',
    Tag(0x20, 0x11): 'Series#',
    Tag(0x20, 0x13): 'Instance#',
    Tag(0x10, 0x10): 'PatientName',
    Tag(0x10, 0x20): 'PatientID',
    Tag(0x10, 0x1010): 'PatientAge',
    Tag(0x10, 0x40): 'PatientSex',
    Tag(0x10, 0x30): 'PatientBirthDate',
    Tag(0x08, 0x20): 'StudyDate',
    Tag(0x08, 0x30): 'StudyTime',
    Tag(0x08, 0x1030): 'studyDescription',
Esempio n. 19
0
 def setUp(self):
     # raw data element -> tag VR length value
     #                       value_tell is_implicit_VR is_little_endian'
     # Unknown (not in DICOM dict), non-private, non-group 0 for this test
     self.raw1 = RawDataElement(Tag(0x88880002), None, 4, 0x1111,
                                0, True, True)
Esempio n. 20
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
Esempio n. 21
0
    def test_exceptions(self):
        """ Check incorrect types/values for properties raise exceptions """
        primitive = N_GET()

        # MessageID
        with pytest.raises(TypeError):
            primitive.MessageID = 'halp'

        with pytest.raises(TypeError):
            primitive.MessageID = 1.111

        with pytest.raises(ValueError):
            primitive.MessageID = 65536

        with pytest.raises(ValueError):
            primitive.MessageID = -1

        # MessageIDBeingRespondedTo
        with pytest.raises(TypeError):
            primitive.MessageIDBeingRespondedTo = 'halp'

        with pytest.raises(TypeError):
            primitive.MessageIDBeingRespondedTo = 1.111

        with pytest.raises(ValueError):
            primitive.MessageIDBeingRespondedTo = 65536

        with pytest.raises(ValueError):
            primitive.MessageIDBeingRespondedTo = -1

        # AffectedSOPClassUID
        with pytest.raises(TypeError):
            primitive.AffectedSOPClassUID = 45.2

        with pytest.raises(TypeError):
            primitive.AffectedSOPClassUID = 100

        with pytest.raises(ValueError):
            primitive.AffectedSOPClassUID = 'abc'

        # AffectedSOPInstanceUID
        with pytest.raises(TypeError):
            primitive.AffectedSOPInstanceUID = 45.2

        with pytest.raises(TypeError):
            primitive.AffectedSOPInstanceUID = 100

        with pytest.raises(ValueError):
            primitive.AffectedSOPInstanceUID = 'abc'

        # RequestedSOPClassUID
        with pytest.raises(TypeError):
            primitive.RequestedSOPClassUID = 45.2

        with pytest.raises(TypeError):
            primitive.RequestedSOPClassUID = 100

        with pytest.raises(ValueError):
            primitive.RequestedSOPClassUID = 'abc'

        # RequestedSOPInstanceUID
        with pytest.raises(TypeError):
            primitive.RequestedSOPInstanceUID = 45.2

        with pytest.raises(TypeError):
            primitive.RequestedSOPInstanceUID = 100

        with pytest.raises(ValueError):
            primitive.RequestedSOPInstanceUID = 'abc'

        # AttributeIdentifierList
        with pytest.raises(ValueError):
            primitive.AttributeIdentifierList = Tag(0x0000, 0x0000)

        with pytest.raises(ValueError, match="Attribute Identifier List must"):
            primitive.AttributeIdentifierList = ['ijk', 'abc']

        with pytest.raises(ValueError, match="Attribute Identifier List must"):
            primitive.AttributeIdentifierList = []

        # AttributeList
        msg = r"'AttributeList' parameter must be a BytesIO object"
        with pytest.raises(TypeError, match=msg):
            primitive.AttributeList = 'halp'

        with pytest.raises(TypeError):
            primitive.AttributeList = 1.111

        with pytest.raises(TypeError):
            primitive.AttributeList = 50

        with pytest.raises(TypeError):
            primitive.AttributeList = [30, 10]

        # Status
        with pytest.raises(TypeError):
            primitive.Status = 19.4
Esempio n. 22
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)
Esempio n. 23
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))
Esempio n. 24
0
def read_3d_dicom(dicomFile, flip=False):
    """

    Read dicom file and return an float 3D image
    """
    files = []
    try:
        files.append(pydicom.read_file(dicomFile[0]))
    except pydicom.errors.InvalidDicomError:
        ds = pydicom.read_file(dicomFile[0], force=True)
        ds.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
        files.append(ds)

    # skip files with no SliceLocation (eg scout views)
    slices = []
    if len(files) == 1:
        slices.append(files[0])
    else:
        logger.error('no file available')
        return

    dicomProperties = dicom_properties()
    dicomProperties.read_dicom_properties(slices[0])

    # create 3D array
    if len(slices[0].pixel_array.shape) == 2:
        dicomProperties.img_shape = [1] + dicomProperties.img_shape
    img3d = np.zeros(dicomProperties.img_shape)

    # fill 3D array with the images from the files
    if len(slices[0].pixel_array.shape) == 2:
        img3d[0, :, :] = slices[0].pixel_array
    else:
        img3d[:, :, :] = slices[0].pixel_array
    img3d = dicomProperties.rs * img3d + dicomProperties.ri

    img_result = itk.image_from_array(np.float32(img3d))
    img_result.SetSpacing(dicomProperties.spacing)
    img_result.SetOrigin(dicomProperties.origin)
    arrayDirection = np.zeros([3, 3], np.float64)
    arrayDirection[0, 0] = dicomProperties.io[0]
    arrayDirection[0, 1] = dicomProperties.io[1]
    arrayDirection[0, 2] = dicomProperties.io[2]
    arrayDirection[1, 0] = dicomProperties.io[3]
    arrayDirection[1, 1] = dicomProperties.io[4]
    arrayDirection[1, 2] = dicomProperties.io[5]
    arrayDirection[2, :] = np.cross(arrayDirection[0, :], arrayDirection[1, :])
    if Tag(0x18, 0x88) in slices[0] and slices[0][0x18, 0x88].value < 0:
        arrayDirection[2, 2] = -1.0
    else:
        flip = False
        arrayDirection[2, 2] = 1.0
    matrixItk = itk.Matrix[itk.D, 3,
                           3](itk.GetVnlMatrixFromArray(arrayDirection))
    img_result.SetDirection(matrixItk)
    if flip:
        flipFilter = itk.FlipImageFilter.New(Input=img_result)
        flipFilter.SetFlipAxes((False, False, True))
        flipFilter.SetFlipAboutOrigin(False)
        flipFilter.Update()
        img_result = flipFilter.GetOutput()
    return img_result
Esempio n. 25
0
def _create_bvecs(sorted_dicoms, bvec_file):
    """
    Calculate the bvecs and write the to a bvec file
    # inspired by dicom2nii from mricron
    # see  http://users.fmrib.ox.ac.uk/~robson/internal/Dicom2Nifti111.m
    """
    if type(sorted_dicoms[0]) is list:
        dicom_headers = sorted_dicoms[0][0]
    else:
        dicom_headers = sorted_dicoms[0]

    # get the patient orientation
    image_orientation = dicom_headers.ImageOrientationPatient
    read_vector = numpy.array([
        float(image_orientation[0]),
        float(image_orientation[1]),
        float(image_orientation[2])
    ])
    phase_vector = numpy.array([
        float(image_orientation[3]),
        float(image_orientation[4]),
        float(image_orientation[5])
    ])
    mosaic_vector = numpy.cross(read_vector, phase_vector)

    # normalize the vectors
    read_vector /= numpy.linalg.norm(read_vector)
    phase_vector /= numpy.linalg.norm(phase_vector)
    mosaic_vector /= numpy.linalg.norm(mosaic_vector)
    # create an empty array for the new bvecs
    bvecs = numpy.zeros([len(sorted_dicoms), 3])
    # for each slice calculate the new bvec
    for index in range(0, len(sorted_dicoms)):
        if type(sorted_dicoms[0]) is list:
            dicom_headers = sorted_dicoms[index][0]
        else:
            dicom_headers = sorted_dicoms[index]

        # get the bval als this is needed in some checks
        bval = common.get_is_value(dicom_headers[Tag(0x0019, 0x100c)])
        # get the bvec if it exists in the headers
        bvec = numpy.array([0, 0, 0])
        if Tag(0x0019, 0x100e) in dicom_headers:
            # in case of implicit VR the private field cannot be split into an array, we do this here
            bvec = numpy.array(
                common.get_fd_array_value(dicom_headers[Tag(0x0019, 0x100e)],
                                          3))
        # if bval is 0 or the vector is 0 no projection is needed and the vector is 0,0,0
        new_bvec = numpy.array([0, 0, 0])

        if bval > 0 and not (bvec == [0, 0, 0]).all():
            # project the bvec and invert the y direction
            new_bvec = numpy.array([
                numpy.dot(bvec, read_vector), -numpy.dot(bvec, phase_vector),
                numpy.dot(bvec, mosaic_vector)
            ])
            # normalize the bvec
            new_bvec /= numpy.linalg.norm(new_bvec)
        bvecs[index, :] = new_bvec
        # save the found bvecs to the file
        common.write_bvec_file(bvecs, bvec_file)
    return numpy.array(bvecs)
Esempio n. 26
0
    def read_dicom_properties(self, slice, nextSlice=None):
        # pixel aspects, assuming all slices are the same

        ps = [1.0, 1.0]
        if Tag(0x28, 0x30) in slice:
            ps = slice.PixelSpacing
        ss = None
        if Tag(0x18, 0x88) in slice:
            ss = abs(float(slice[(0x0018, 0X0088)].value))
        if ss == '' or ss is None:
            if Tag(0x3004, 0x000c) in slice:
                ss = slice[0x3004, 0x000c][1] - slice[0x3004, 0x000c][0]
        if ss == '' or ss is None:
            if not nextSlice is None and Tag(0x0020, 0x0032) in slice and Tag(
                    0x0020, 0x0032) in nextSlice:
                ss = abs(nextSlice[0x0020, 0x0032][2] -
                         slice[0x0020, 0x0032][2])
            if ss == '' or ss is None:
                ss = 1.0
        self.spacing = [ps[1], ps[0], ss]
        ip = [0.0, 0.0, 0.0]
        if Tag(0x20, 0x32) in slice:
            ip = slice[0x20, 0x32].value  #Image Position
        elif Tag(0x54, 0x22) in slice and Tag(0x20, 0x32) in slice[0x54,
                                                                   0x22][0]:
            ip = slice[0x54, 0x22][0][0x20, 0x32].value  #Image Position
        self.origin = [ip[0], ip[1], ip[2]]
        if Tag(0x20, 0x37) in slice:
            self.io = slice[0x20, 0x37].value  #Image Orientation
        elif Tag(0x54, 0x22) in slice and Tag(0x20, 0x37) in slice[0x54,
                                                                   0x22][0]:
            self.io = slice[0x54, 0x22][0][0x20,
                                           0x37].value  #Image Orientation
        if self.io == "" or self.io == None:
            self.io = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
        #orientation = [io[0], io[1], io[2], io[3], io[4], io[5]]
        self.read_dicom_slop_intercept(slice)

        self.img_shape = list(slice.pixel_array.shape)
Esempio n. 27
0
def dicom_to_nifti(dicom_input, output_file):
    """
    This function will convert an anatomical dicom series to a nifti

    Examples: See unit test

    :param output_file: filepath to the output nifti
    :param dicom_input: directory with the dicom files for a single scan, or list of read in dicoms
    """
    if len(dicom_input) <= 0:
        raise ConversionError('NO_DICOM_FILES_FOUND')

    # remove duplicate slices based on position and data
    dicom_input = remove_duplicate_slices(dicom_input)

    # remove localizers based on image type
    dicom_input = remove_localizers_by_imagetype(dicom_input)
    # if no dicoms remain we should raise exception
    if len(dicom_input) < 1:
        raise ConversionValidationError('TOO_FEW_SLICES/LOCALIZER')

    if settings.validate_slicecount:
        common.validate_slicecount(dicom_input)
        # remove_localizers based on image orientation (only valid if slicecount is validated)
        dicom_input = remove_localizers_by_orientation(dicom_input)

        # validate all the dicom files for correct orientations
        common.validate_slicecount(dicom_input)
    if settings.validate_orientation:
        # validate that all slices have the same orientation
        common.validate_orientation(dicom_input)
    if settings.validate_orthogonal:
        # validate that we have an orthogonal image (to detect gantry tilting etc)
        common.validate_orthogonal(dicom_input)

    # sort the dicoms
    dicom_input = common.sort_dicoms(dicom_input)

    # validate slice increment inconsistent
    slice_increment_inconsistent = False
    if settings.validate_slice_increment:
        # validate that all slices have a consistent slice increment
        common.validate_slice_increment(dicom_input)
    elif common.is_slice_increment_inconsistent(dicom_input):
        slice_increment_inconsistent = True

    if settings.validate_instance_number:
        # validate that all slices have a consistent instance_number
        common.validate_instance_number(dicom_input)

    # if inconsistent increment and we allow resampling then do the resampling based conversion to maintain the correct geometric shape
    if slice_increment_inconsistent and settings.resample:
        nii_image, max_slice_increment = _convert_slice_incement_inconsistencies(
            dicom_input)
    # do the normal conversion
    else:
        # Get data; originally z,y,x, transposed to x,y,z
        data = common.get_volume_pixeldata(dicom_input)

        affine, max_slice_increment = common.create_affine(dicom_input)

        # Convert to nifti
        nii_image = nibabel.Nifti1Image(data.squeeze(), affine)

    # Set TR and TE if available
    if Tag(0x0018, 0x0080) in dicom_input[0] and Tag(0x0018,
                                                     0x0081) in dicom_input[0]:
        common.set_tr_te(nii_image, float(dicom_input[0].RepetitionTime),
                         float(dicom_input[0].EchoTime))

    # Save to disk
    if output_file is not None:
        logger.info('Saving nifti to disk %s' % output_file)
        nii_image.to_filename(output_file)

    return {
        'NII_FILE': output_file,
        'NII': nii_image,
        'MAX_SLICE_INCREMENT': max_slice_increment
    }
Esempio n. 28
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.

    .. note::

        This function is used internally - usually there is no need to call it
        from user code. To read data from a DICOM file, :func:`dcmread`
        shall be used instead.

    Parameters
    ----------
    fp : file-like
        The file-like to read from.
    is_implicit_VR : bool
        ``True`` if the data is encoded as implicit VR, ``False`` otherwise.
    is_little_endian : bool
        ``True`` if the data is encoded as little endian, ``False`` otherwise.
    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 :func:`dcmread` for parameter info.
    encoding :
        Encoding scheme
    specific_tags : list or None
        See :func:`dcmread` for parameter info.

    Returns
    -------
    VR : str or None
        ``None`` if implicit VR, otherwise the VR read from the file.
    length : int
        The length of the DICOM data element (could be DICOM "undefined
        length" ``0xFFFFFFFFL``)
    value_bytes : bytes or str
        The raw bytes from the DICOM file (not parsed into Python types)
    is_little_endian : bool
        ``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()
    if specific_tags is not None:
        for tag in specific_tags:
            if isinstance(tag, str):
                tag = Tag(tag_for_keyword(tag))
            if isinstance(tag, BaseTag):
                tag_set.add(tag)
        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)
            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 length > 0 else empty_value_for_VR(VR, raw=True))
                if debugging:
                    dotdot = "..." if length > 12 else "   "
                    displayed_value = value[:12] if value else b''
                    logger_debug("%08x: %-34s %s %r %s" %
                                 (value_tell, bytes2hex(displayed_value),
                                  dotdot, displayed_value, 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 or b'', is_little_endian)
                # Store the encoding value in the generator
                # for use with future elements (SQs)
                encoding = convert_encodings(encoding)

            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)

                # 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)
Esempio n. 29
0
    def c_find(cls, ds: pydicom.Dataset):
        """C-FIND handler

        :param ds: C-FIND request
        :type ds: pydicom.Dataset
        :yield: C-FIND results
        :rtype: pydicom.Dataset
        """
        joins = set()

        response_attrs = []
        select = [Series]
        upper_level_filters = []

        skipped = set()

        patient_attrs = [e for e in ds if e.tag in Patient.mapping]
        skipped.update(e.tag for e in patient_attrs)
        if patient_attrs:
            _upper_level_filters = list(
                _filter_upper_level(Patient, patient_attrs))
            upper_level_filters.extend(_upper_level_filters)
            for tag, attr, vr, _, attr_name in _upper_level_filters:
                select.append(attr)
                response_attrs.append(
                    (tag, ('study', 'patient', attr_name), vr, None))
            joins.update([(Series, Study), (Study, Patient)])

        study_attrs = [e for e in ds if e.tag in Study.mapping]
        skipped.update(e.tag for e in study_attrs)
        if study_attrs:
            _upper_level_filters = list(_filter_upper_level(
                Study, study_attrs))
            upper_level_filters.extend(_upper_level_filters)
            for tag, attr, vr, _, attr_name in _upper_level_filters:
                select.append(attr)
                response_attrs.append((tag, ('study', attr_name), vr, None))
            joins.update([(Series, Study)])

        if 'NumberOfSeriesRelatedInstances' in ds:
            _tag = Tag((0x0020, 0x1209))
            skipped.add(_tag)
            select.append(
                peewee.fn.Count(Instance.id)\
                    .alias('number_of_study_related_series')  # pylint: disable=no-member
            )
            response_attrs.append(
                (_tag, 'number_of_series_related_instances', 'IS', None))
            joins.add((Series, Instance))

        query = Series.select(*select)

        for join in joins:
            query = query.join_from(*join)

        query, _response_attrs = _build_filters(cls, query, ds, skipped)
        response_attrs.extend(_response_attrs)
        for _, attr, vr, elem, _ in upper_level_filters:
            if not elem.value:
                continue
            query = _build_filter(query, attr, vr, elem)

        encoding = getattr(ds, 'SpecificCharacterSet', 'ISO-IR 6')
        if not query.count():
            return []

        yield from (_encode_response(s, response_attrs, encoding)
                    for s in query)
Esempio n. 30
0
 def test_wrong_bytes_length_exception(self, accept_wrong_length):
     """Check exception when number of raw bytes is not correct."""
     raw = RawDataElement(Tag(0x00190000), 'FD', 1, b'1', 0, False, True)
     with pytest.raises(BytesLengthException):
         DataElement_from_raw(raw)