예제 #1
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))
예제 #2
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)}")
예제 #3
0
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)
예제 #4
0
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_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]
예제 #6
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)
예제 #7
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)
예제 #8
0
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))
예제 #9
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())
예제 #10
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))
예제 #11
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
예제 #12
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))