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))
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)}")
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)
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)
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]
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)
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)
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))
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())
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))
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
def write_OBvalue(fp: DicomIO, elem: DataElement) -> None: """Write a data_element with VR of 'other byte' (OB).""" fp.write(cast(bytes, elem.value))