コード例 #1
0
def _write_dataset(fp: DicomIO, dataset: Dataset,
                   write_like_original: bool) -> None:
    """Write the Data Set to a file-like. Assumes the file meta information,
    if any, has been written.
    """

    # if we want to write with the same endianness and VR handling as
    # the read dataset we want to preserve raw data elements for
    # performance reasons (which is done by get_item);
    # otherwise we use the default converting item getter
    if dataset.is_original_encoding:
        get_item = Dataset.get_item
    else:
        get_item = Dataset.__getitem__  # type: ignore[assignment]

    # WRITE DATASET
    # The transfer syntax used to encode the dataset can't be changed
    #   within the dataset.
    # Write any Command Set elements now as elements must be in tag order
    #   Mixing Command Set with other elements is non-conformant so we
    #   require `write_like_original` to be True
    command_set = get_item(dataset, slice(0x00000000, 0x00010000))
    if command_set and write_like_original:
        fp.is_implicit_VR = True
        fp.is_little_endian = True
        write_dataset(fp, command_set)

    # Set file VR and endianness. MUST BE AFTER writing META INFO (which
    #   requires Explicit VR Little Endian) and COMMAND SET (which requires
    #   Implicit VR Little Endian)
    fp.is_implicit_VR = cast(bool, dataset.is_implicit_VR)
    fp.is_little_endian = cast(bool, dataset.is_little_endian)

    # Write non-Command Set elements now
    write_dataset(fp, get_item(dataset, slice(0x00010000, None)))
コード例 #2
0
def write_dataset(
        fp: DicomIO,
        dataset: Dataset,
        parent_encoding: Union[str, List[str]] = default_encoding) -> int:
    """Write a Dataset dictionary to the file. Return the total length written.
    """
    _harmonize_properties(dataset, fp)

    if None in (dataset.is_little_endian, dataset.is_implicit_VR):
        name = dataset.__class__.__name__
        raise AttributeError(
            f"'{name}.is_little_endian' and '{name}.is_implicit_VR' must "
            f"be set appropriately before saving")

    if not dataset.is_original_encoding:
        dataset = correct_ambiguous_vr(dataset, fp.is_little_endian)

    dataset_encoding = cast(
        Union[None, str, List[str]],
        dataset.get('SpecificCharacterSet', parent_encoding))

    fpStart = fp.tell()
    # data_elements must be written in tag order
    tags = sorted(dataset.keys())

    for tag in tags:
        # do not write retired Group Length (see PS3.5, 7.2)
        if tag.element == 0 and tag.group > 6:
            continue

        with tag_in_exception(tag):
            write_data_element(fp, dataset.get_item(tag), dataset_encoding)

    return fp.tell() - fpStart
コード例 #3
0
def write_OWvalue(fp: DicomIO, elem: DataElement) -> None:
    """Write a data_element with VR of 'other word' (OW).

    Note: This **does not currently do the byte swapping** for Endian state.
    """
    # XXX for now just write the raw bytes without endian swapping
    fp.write(cast(bytes, elem.value))
コード例 #4
0
ファイル: filewriter.py プロジェクト: naterichman/pydicom
def write_text(
    fp: DicomIO, elem: DataElement, encodings: Optional[List[str]] = None
) -> None:
    """Write a single or multivalued text string."""
    encodings = encodings or [default_encoding]
    val = elem.value
    if val is not None:
        if _is_multi_value(val):
            val = cast(Union[Sequence[bytes], Sequence[str]], val)
            if isinstance(val[0], str):
                val = cast(Sequence[str], val)
                val = b'\\'.join(
                    [encode_string(val, encodings) for val in val]
                )
            else:
                val = cast(Sequence[bytes], val)
                val = b'\\'.join([val for val in val])
        else:
            val = cast(Union[bytes, str], val)
            if isinstance(val, str):
                val = encode_string(val, encodings)

        if len(val) % 2 != 0:
            val = val + b' '  # pad to even length
        fp.write(val)
コード例 #5
0
def write_numbers(fp: DicomIO, elem: DataElement, struct_format: str) -> None:
    """Write a "value" of type struct_format from the dicom file.

    "Value" can be more than one number.

    Parameters
    ----------
    fp : file-like
        The file-like to write the encoded data to.
    elem : dataelem.DataElement
        The element to encode.
    struct_format : str
        The character format as used by the struct module.
    """
    endianChar = '><'[fp.is_little_endian]
    value = elem.value
    if value == "":
        return  # don't need to write anything for empty string

    format_string = endianChar + struct_format
    try:
        try:
            # works only if list, not if string or number
            value.append
        except AttributeError:  # is a single value - the usual case
            fp.write(pack(format_string, value))
        else:
            for val in cast(Iterable[Any], value):
                fp.write(pack(format_string, val))
    except Exception as e:
        raise IOError(f"{str(e)}\nfor data_element:\n{str(elem)}")
コード例 #6
0
ファイル: test_filebase.py プロジェクト: jrkerns/pydicom
 def test_getter_is_little_endian(self):
     """Test DicomIO.is_little_endian getter"""
     fp = DicomIO()
     fp.is_little_endian = True
     assert fp.is_little_endian
     fp.is_little_endian = False
     assert not fp.is_little_endian
コード例 #7
0
 def test_getter_is_little_endian(self):
     """Test DicomIO.is_little_endian getter"""
     fp = DicomIO()
     fp.is_little_endian = True
     assert fp.is_little_endian
     fp.is_little_endian = False
     assert not fp.is_little_endian
コード例 #8
0
 def test_is_implicit_vr(self):
     """Test DicomIO.is_implicit_VR"""
     fp = DicomIO()
     fp.is_implicit_VR = True
     assert fp.is_implicit_VR
     fp.is_implicit_VR = False
     assert not fp.is_implicit_VR
コード例 #9
0
ファイル: filewriter.py プロジェクト: naterichman/pydicom
def write_number_string(fp: DicomIO, elem: DataElement) -> None:
    """Handle IS or DS VR - write a number stored as a string of digits."""
    # If the DS or IS has an original_string attribute, use that, so that
    # unchanged data elements are written with exact string as when read from
    # file
    val = elem.value
    if _is_multi_value(val):
        val = cast(Union[Sequence[IS], Sequence[DSclass]], val)
        val = "\\".join(
            (
                x.original_string if hasattr(x, 'original_string')
                else str(x) for x in val
            )
        )
    else:
        val = cast(Union[IS, DSclass], val)
        if hasattr(val, 'original_string'):
            val = val.original_string
        else:
            val = str(val)

    if len(val) % 2 != 0:
        val = val + ' '  # pad to even length

    val = bytes(val, default_encoding)

    fp.write(val)
コード例 #10
0
ファイル: test_filebase.py プロジェクト: jrkerns/pydicom
 def test_is_implicit_vr(self):
     """Test DicomIO.is_implicit_VR"""
     fp = DicomIO()
     fp.is_implicit_VR = True
     assert fp.is_implicit_VR
     fp.is_implicit_VR = False
     assert not fp.is_implicit_VR
コード例 #11
0
def write_string(fp: DicomIO, elem: DataElement, padding: str = ' ') -> None:
    """Write a single or multivalued ASCII string."""
    val = multi_string(cast(Union[str, Iterable[str]], elem.value))
    if val is not None:
        if len(val) % 2 != 0:
            val += padding  # pad to even length

        if isinstance(val, str):
            val = val.encode(default_encoding)  # type: ignore[assignment]

        fp.write(val)  # type: ignore[arg-type]
コード例 #12
0
def _harmonize_properties(ds: Dataset, fp: DicomIO) -> None:
    """Make sure the properties in the dataset and the file pointer are
    consistent, so the user can set both with the same effect.
    Properties set on the destination file object always have preference.
    """
    # ensure preference of fp over dataset
    if hasattr(fp, 'is_little_endian'):
        ds.is_little_endian = fp.is_little_endian
    if hasattr(fp, 'is_implicit_VR'):
        ds.is_implicit_VR = fp.is_implicit_VR

    # write the properties back to have a consistent state
    fp.is_implicit_VR = cast(bool, ds.is_implicit_VR)
    fp.is_little_endian = cast(bool, ds.is_little_endian)
コード例 #13
0
def write_ATvalue(fp: DicomIO, elem: DataElement) -> None:
    """Write a data_element tag to a file."""
    try:
        iter(cast(Sequence[Any], elem.value))  # see if is multi-valued AT;
        # Note will fail if Tag ever derived from true tuple rather than being
        # a long
    except TypeError:
        # make sure is expressed as a Tag instance
        tag = Tag(cast(int, elem.value))
        fp.write_tag(tag)
    else:
        tags = [Tag(tag) for tag in cast(Sequence[int], elem.value)]
        for tag in tags:
            fp.write_tag(tag)
コード例 #14
0
    def test_setter_is_little_endian(self):
        """Test DicomIO.is_little_endian setter"""
        fp = DicomIO()
        fp.is_little_endian = True
        assert fp.read_US == fp.read_leUS
        assert fp.read_UL == fp.read_leUL
        assert fp.write_US == fp.write_leUS
        assert fp.write_UL == fp.write_leUL
        assert fp.read_tag == fp.read_le_tag

        fp.is_little_endian = False
        assert fp.read_US == fp.read_beUS
        assert fp.read_UL == fp.read_beUL
        assert fp.write_US == fp.write_beUS
        assert fp.write_UL == fp.write_beUL
        assert fp.read_tag == fp.read_be_tag
コード例 #15
0
ファイル: test_filebase.py プロジェクト: jrkerns/pydicom
    def test_setter_is_little_endian(self):
        """Test DicomIO.is_little_endian setter"""
        fp = DicomIO()
        fp.is_little_endian = True
        assert fp.read_US == fp.read_leUS
        assert fp.read_UL == fp.read_leUL
        assert fp.write_US == fp.write_leUS
        assert fp.write_UL == fp.write_leUL
        assert fp.read_tag == fp.read_le_tag

        fp.is_little_endian = False
        assert fp.read_US == fp.read_beUS
        assert fp.read_UL == fp.read_beUL
        assert fp.write_US == fp.write_beUS
        assert fp.write_UL == fp.write_beUL
        assert fp.read_tag == fp.read_be_tag
コード例 #16
0
def write_PN(fp: DicomIO,
             elem: DataElement,
             encodings: Optional[List[str]] = None) -> None:
    if not encodings:
        encodings = [default_encoding]

    val: List[PersonName]
    if elem.VM == 1:
        val = [cast(PersonName, elem.value)]
    else:
        val = cast(List[PersonName], elem.value)

    enc = b'\\'.join([elem.encode(encodings) for elem in val])
    if len(enc) % 2 != 0:
        enc += b' '

    fp.write(enc)
コード例 #17
0
def write_TM(fp: DicomIO, elem: DataElement) -> None:
    val = elem.value
    if isinstance(val, str):
        write_string(fp, elem)
    else:
        if _is_multi_value(val):
            val = cast(Sequence[TM], val)
            val = "\\".join(
                (x if isinstance(x, str) else _format_TM(x) for x in val))
        else:
            val = _format_TM(cast(TM, val))

        if len(val) % 2 != 0:
            val = val + ' '  # pad to even length

        if isinstance(val, str):
            val = val.encode(default_encoding)

        fp.write(val)
コード例 #18
0
ファイル: filewriter.py プロジェクト: naterichman/pydicom
def write_OBvalue(fp: DicomIO, elem: DataElement) -> None:
    """Write a data_element with VR of 'other byte' (OB)."""
    if len(elem.value) % 2:
        # Pad odd length values
        fp.write(cast(bytes, elem.value))
        fp.write(b'\x00')
    else:
        fp.write(cast(bytes, elem.value))
コード例 #19
0
 def test_init(self):
     """Test __init__"""
     # All the subclasses override this anyway
     fp = DicomIO()
     assert fp.is_implicit_VR
コード例 #20
0
def write_OBvalue(fp: DicomIO, elem: DataElement) -> None:
    """Write a data_element with VR of 'other byte' (OB)."""
    fp.write(cast(bytes, elem.value))
コード例 #21
0
def write_data_element(
        fp: DicomIO,
        elem: Union[DataElement, RawDataElement],
        encodings: Optional[Union[str, List[str]]] = None) -> None:
    """Write the data_element to file fp according to
    dicom media storage rules.
    """
    # Write element's tag
    fp.write_tag(elem.tag)

    # write into a buffer to avoid seeking back which can be expansive
    buffer = DicomBytesIO()
    buffer.is_little_endian = fp.is_little_endian
    buffer.is_implicit_VR = fp.is_implicit_VR

    VR: Optional[str] = elem.VR
    if not fp.is_implicit_VR and VR and len(VR) != 2:
        msg = (f"Cannot write ambiguous VR of '{VR}' for data element with "
               f"tag {repr(elem.tag)}.\nSet the correct VR before "
               f"writing, or use an implicit VR transfer syntax")
        raise ValueError(msg)

    if elem.is_raw:
        elem = cast(RawDataElement, elem)
        # raw data element values can be written as they are
        buffer.write(cast(bytes, elem.value))
        is_undefined_length = elem.length == 0xFFFFFFFF
    else:
        elem = cast(DataElement, elem)
        if VR not in writers:
            raise NotImplementedError(
                f"write_data_element: unknown Value Representation '{VR}'")

        encodings = encodings or [default_encoding]
        encodings = convert_encodings(encodings)
        fn, param = writers[VR]
        is_undefined_length = elem.is_undefined_length
        if not elem.is_empty:
            if VR in text_VRs or VR in ('PN', 'SQ'):
                fn(buffer, elem, encodings=encodings)  # type: ignore[operator]
            else:
                # Many numeric types use the same writer but with
                # numeric format parameter
                if param is not None:
                    fn(buffer, elem, param)  # type: ignore[operator]
                else:
                    fn(buffer, elem)  # type: ignore[operator]

    # valid pixel data with undefined length shall contain encapsulated
    # data, e.g. sequence items - raise ValueError otherwise (see #238)
    if is_undefined_length and elem.tag == 0x7fe00010:
        encap_item = b'\xfe\xff\x00\xe0'
        if not fp.is_little_endian:
            # Non-conformant endianness
            encap_item = b'\xff\xfe\xe0\x00'
        if not cast(bytes, elem.value).startswith(encap_item):
            raise ValueError(
                "(7FE0,0010) Pixel Data has an undefined length indicating "
                "that it's compressed, but the data isn't encapsulated as "
                "required. See pydicom.encaps.encapsulate() for more "
                "information")

    value_length = buffer.tell()
    if (not fp.is_implicit_VR and VR not in extra_length_VRs
            and not is_undefined_length and value_length > 0xffff):
        # see PS 3.5, section 6.2.2 for handling of this case
        msg = (
            f"The value for the data element {elem.tag} exceeds the "
            f"size of 64 kByte and cannot be written in an explicit transfer "
            f"syntax. The data element VR is changed from '{VR}' to 'UN' "
            f"to allow saving the data.")
        warnings.warn(msg)
        VR = 'UN'

    # write the VR for explicit transfer syntax
    if not fp.is_implicit_VR:
        VR = cast(str, VR)
        fp.write(bytes(VR, default_encoding))

        if VR in extra_length_VRs:
            fp.write_US(0)  # reserved 2 bytes

    if (not fp.is_implicit_VR and VR not in extra_length_VRs
            and not is_undefined_length):
        fp.write_US(value_length)  # Explicit VR length field is 2 bytes
    else:
        # write the proper length of the data_element in the length slot,
        # unless is SQ with undefined length.
        fp.write_UL(0xFFFFFFFF if is_undefined_length else value_length)

    fp.write(buffer.getvalue())
    if is_undefined_length:
        fp.write_tag(SequenceDelimiterTag)
        fp.write_UL(0)  # 4-byte 'length' of delimiter data item
コード例 #22
0
def write_file_meta_info(fp: DicomIO,
                         file_meta: FileMetaDataset,
                         enforce_standard: bool = True) -> None:
    """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,
    :dcm:`Section 7.1<part10/chapter_7.html#sect_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
    :dcm:`Table 7.1-1<part10/chapter_7.html#table_7.1-1>`):

    * (0002,0000) *File Meta Information Group Length*, UL, 4
    * (0002,0001) *File Meta Information Version*, OB, 2
    * (0002,0002) *Media Storage SOP Class UID*, UI, N
    * (0002,0003) *Media Storage SOP Instance UID*, UI, N
    * (0002,0010) *Transfer Syntax UID*, UI, N
    * (0002,0012) *Implementation Class UID*, 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) *Implementation Version Name*, SH, N
    * (0002,0016) *Source Application Entity Title*, AE, N
    * (0002,0017) *Sending Application Entity Title*, AE, N
    * (0002,0018) *Receiving Application Entity Title*, AE, N
    * (0002,0102) *Private Information*, OB, N
    * (0002,0100) *Private Information Creator UID*, UI, 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 elements.
    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`.
    """
    validate_file_meta(file_meta, enforce_standard)

    if enforce_standard and 'FileMetaInformationGroupLength' not in file_meta:
        # Will be updated with the actual length later
        file_meta.FileMetaInformationGroupLength = 0

    # Write the File Meta Information Group elements
    # first write into a buffer to avoid seeking back, that can be
    # expansive and is not allowed if writing into a zip file
    buffer = DicomBytesIO()
    buffer.is_little_endian = True
    buffer.is_implicit_VR = False
    write_dataset(buffer, 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:
        # 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.
        # 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.
        file_meta.FileMetaInformationGroupLength = buffer.tell() - 12
        buffer.seek(0)
        write_data_element(buffer, file_meta[0x00020000])

    fp.write(buffer.getvalue())
コード例 #23
0
def write_sequence_item(fp: DicomIO, dataset: Dataset,
                        encodings: List[str]) -> None:
    """Write a `dataset` in a sequence to the file-like `fp`.

    This is similar to writing a data_element, but with a specific tag for
    Sequence Item.

    See DICOM Standard, Part 5, :dcm:`Section 7.5<sect_7.5.html>`.

    Parameters
    ----------
    fp : file-like
        The file-like to write the encoded data to.
    dataset : Dataset
        The :class:`Dataset<pydicom.dataset.Dataset>` to write to `fp`.
    encodings : list of str
        The character encodings to use on text values.
    """
    fp.write_tag(ItemTag)  # marker for start of Sequence Item
    length_location = fp.tell()  # save location for later.
    # will fill in real value later if not undefined length
    fp.write_UL(0xffffffff)
    write_dataset(fp, dataset, parent_encoding=encodings)
    if getattr(dataset, "is_undefined_length_sequence_item", False):
        fp.write_tag(ItemDelimiterTag)
        fp.write_UL(0)  # 4-bytes 'length' field for delimiter item
    else:  # we will be nice and set the lengths for the reader of this file
        location = fp.tell()
        fp.seek(length_location)
        fp.write_UL(location - length_location - 4)  # 4 is length of UL
        fp.seek(location)  # ready for next data_element
コード例 #24
0
def write_UN(fp: DicomIO, elem: DataElement) -> None:
    """Write a byte string for an DataElement of value 'UN' (unknown)."""
    fp.write(cast(bytes, elem.value))