def read_file_meta_info(filename): """Read and return the DICOM file meta information only. This function is meant to be used in user code, for quickly going through a series of files to find one which is referenced to a particular SOP, without having to read the entire files. """ with DicomFile(filename, 'rb') as fp: read_preamble(fp, False) # if no header, raise exception return _read_file_meta_info(fp)
def write_file(filename, dataset, write_like_original=True): """Store a FileDataset to the filename specified. Parameters ---------- filename : str Name of file to save new DICOM file to. dataset : FileDataset Dataset holding the DICOM information; e.g. an object read with read_file(). write_like_original : boolean If True (default), preserves the following information from the dataset: -preamble -- if no preamble in read file, than not used here -hasFileMeta -- if writer did not do file meta information, then don't write here either -seq.is_undefined_length -- if original had delimiters, write them now too, instead of the more sensible length characters - is_undefined_length_sequence_item -- for datasets that belong to a sequence, write the undefined length delimiters if that is what the original had. If False, produces a "nicer" DICOM file for other readers, where all lengths are explicit. See Also -------- pydicom.dataset.FileDataset Dataset class with relevant attrs and information. pydicom.dataset.Dataset.save_as Write a DICOM file from a dataset that was read in with read_file(). save_as wraps write_file. Notes ----- Set dataset.preamble if you want something other than 128 0-bytes. If the dataset was read from an existing dicom file, then its preamble was stored at read time. It is up to the user to ensure the preamble is still correct for its purposes. If there is no Transfer Syntax tag in the dataset, then set dataset.is_implicit_VR and dataset.is_little_endian to determine the transfer syntax used to write the file. """ # Decide whether to write DICOM preamble. Should always do so unless trying to mimic the original file read in preamble = getattr(dataset, "preamble", None) if not preamble and not write_like_original: preamble = b"\0" * 128 file_meta = dataset.file_meta if file_meta is None: file_meta = Dataset() if 'TransferSyntaxUID' not in file_meta: if dataset.is_little_endian and dataset.is_implicit_VR: file_meta.add_new((2, 0x10), 'UI', ImplicitVRLittleEndian) elif dataset.is_little_endian and not dataset.is_implicit_VR: file_meta.add_new((2, 0x10), 'UI', ExplicitVRLittleEndian) elif not dataset.is_little_endian and not dataset.is_implicit_VR: file_meta.add_new((2, 0x10), 'UI', ExplicitVRBigEndian) else: raise NotImplementedError( "pydicom has not been verified for Big Endian with Implicit VR" ) caller_owns_file = True # Open file if not already a file object if isinstance(filename, compat.string_types): fp = DicomFile(filename, 'wb') # caller provided a file name; we own the file handle caller_owns_file = False else: fp = DicomFileLike(filename) try: if preamble: fp.write(preamble) # blank 128 byte preamble _write_file_meta_info(fp, file_meta) # Set file VR, endian. MUST BE AFTER writing META INFO (which changes to Explicit LittleEndian) fp.is_implicit_VR = dataset.is_implicit_VR fp.is_little_endian = dataset.is_little_endian write_dataset(fp, dataset) finally: if not caller_owns_file: fp.close()
def dcmwrite(filename, dataset, write_like_original=True): """Write `dataset` to the `filename` specified. If `write_like_original` is ``True`` then `dataset` will be written as is (after minimal validation checking) and may or may not contain all or parts of the File Meta Information (and hence may or may not be conformant with the DICOM File Format). If `write_like_original` is ``False``, `dataset` will be stored in the :dcm:`DICOM File Format <part10/chapter_7.html>`. To do so requires that the ``Dataset.file_meta`` attribute exists and contains a :class:`Dataset` with the required (Type 1) *File Meta Information Group* elements. The byte stream of the `dataset` will be placed into the file after the DICOM *File Meta Information*. If `write_like_original` is ``True`` then the :class:`Dataset` will be written as is (after minimal validation checking) and may or may not contain all or parts of the *File Meta Information* (and hence may or may not be conformant with the DICOM File Format). **File Meta Information** The *File Meta Information* consists of a 128-byte preamble, followed by a 4 byte ``b'DICM'`` prefix, followed by the *File Meta Information Group* elements. **Preamble and Prefix** The ``dataset.preamble`` attribute shall be 128-bytes long or ``None`` and is available for use as defined by the Application Profile or specific implementations. If the preamble is not used by an Application Profile or specific implementation then all 128 bytes should be set to ``0x00``. The actual preamble written depends on `write_like_original` and ``dataset.preamble`` (see the table below). +------------------+------------------------------+ | | write_like_original | +------------------+-------------+----------------+ | dataset.preamble | True | False | +==================+=============+================+ | None | no preamble | 128 0x00 bytes | +------------------+-------------+----------------+ | 128 bytes | dataset.preamble | +------------------+------------------------------+ The prefix shall be the bytestring ``b'DICM'`` and will be written if and only if the preamble is present. **File Meta Information Group Elements** The preamble and prefix are followed by a set of DICOM elements from the (0002,eeee) group. Some of these elements are required (Type 1) while others are optional (Type 3/1C). If `write_like_original` is ``True`` then the *File Meta Information Group* elements are all optional. See :func:`~pydicom.filewriter.write_file_meta_info` for more information on which elements are required. The *File Meta Information Group* elements should be included within their own :class:`~pydicom.dataset.Dataset` in the ``dataset.file_meta`` attribute. If (0002,0010) *Transfer Syntax UID* is included then the user must ensure its value is compatible with the values for the ``dataset.is_little_endian`` and ``dataset.is_implicit_VR`` attributes. For example, if ``is_little_endian`` and ``is_implicit_VR`` are both ``True`` then the Transfer Syntax UID must be 1.2.840.10008.1.2 *Implicit VR Little Endian*. See the DICOM Standard, Part 5, :dcm:`Section 10<part05/chapter_10.html>` for more information on Transfer Syntaxes. *Encoding* The preamble and prefix are encoding independent. The File Meta elements are encoded as *Explicit VR Little Endian* as required by the DICOM Standard. **Dataset** A DICOM Dataset representing a SOP Instance related to a DICOM Information Object Definition. It is up to the user to ensure the `dataset` conforms to the DICOM Standard. *Encoding* The `dataset` is encoded as specified by the ``dataset.is_little_endian`` and ``dataset.is_implicit_VR`` attributes. It's up to the user to ensure these attributes are set correctly (as well as setting an appropriate value for ``dataset.file_meta.TransferSyntaxUID`` if present). Parameters ---------- filename : str or file-like Name of file or the file-like to write the new DICOM file to. dataset : pydicom.dataset.FileDataset Dataset holding the DICOM information; e.g. an object read with :func:`~pydicom.filereader.dcmread`. write_like_original : bool, optional If ``True`` (default), preserves the following information from the Dataset (and may result in a non-conformant file): - preamble -- if the original file has no preamble then none will be written. - file_meta -- if the original file was missing any required *File Meta Information Group* elements then they will not be added or written. If (0002,0000) *File Meta Information Group Length* is present then it may have its value updated. - seq.is_undefined_length -- if original had delimiters, write them now too, instead of the more sensible length characters - is_undefined_length_sequence_item -- for datasets that belong to a sequence, write the undefined length delimiters if that is what the original had. If ``False``, produces a file conformant with the DICOM File Format, with explicit lengths for all elements. Raises ------ AttributeError If either ``dataset.is_implicit_VR`` or ``dataset.is_little_endian`` have not been set. ValueError If group 2 elements are in ``dataset`` rather than ``dataset.file_meta``, or if a preamble is given but is not 128 bytes long, or if Transfer Syntax is a compressed type and pixel data is not compressed. See Also -------- pydicom.dataset.FileDataset Dataset class with relevant attributes and information. pydicom.dataset.Dataset.save_as Write a DICOM file from a dataset that was read in with ``dcmread()``. ``save_as()`` wraps ``dcmwrite()``. """ # Ensure is_little_endian and is_implicit_VR are set if None in (dataset.is_little_endian, dataset.is_implicit_VR): has_tsyntax = False try: tsyntax = dataset.file_meta.TransferSyntaxUID if not tsyntax.is_private: dataset.is_little_endian = tsyntax.is_little_endian dataset.is_implicit_VR = tsyntax.is_implicit_VR has_tsyntax = True except AttributeError: pass if not has_tsyntax: raise AttributeError( "'{0}.is_little_endian' and '{0}.is_implicit_VR' must be " "set appropriately before saving.".format( dataset.__class__.__name__)) # Try and ensure that `is_undefined_length` is set correctly try: tsyntax = dataset.file_meta.TransferSyntaxUID if not tsyntax.is_private: dataset['PixelData'].is_undefined_length = tsyntax.is_compressed except (AttributeError, KeyError): pass # Check that dataset's group 0x0002 elements are only present in the # `dataset.file_meta` Dataset - user may have added them to the wrong # place if dataset.group_dataset(0x0002) != Dataset(): raise ValueError("File Meta Information Group Elements (0002,eeee) " "should be in their own Dataset object in the " "'{0}.file_meta' " "attribute.".format(dataset.__class__.__name__)) # A preamble is required under the DICOM standard, however if # `write_like_original` is True we treat it as optional preamble = getattr(dataset, 'preamble', None) if preamble and len(preamble) != 128: raise ValueError("'{0}.preamble' must be 128-bytes " "long.".format(dataset.__class__.__name__)) if not preamble and not write_like_original: # The default preamble is 128 0x00 bytes. preamble = b'\x00' * 128 # File Meta Information is required under the DICOM standard, however if # `write_like_original` is True we treat it as optional if not write_like_original: # the checks will be done in write_file_meta_info() dataset.fix_meta_info(enforce_standard=False) else: dataset.ensure_file_meta() # Check for decompression, give warnings if inconsistencies # If decompressed, then pixel_array is now used instead of PixelData if dataset.is_decompressed: xfer = dataset.file_meta.TransferSyntaxUID if xfer not in UncompressedPixelTransferSyntaxes: raise ValueError("file_meta transfer SyntaxUID is compressed type " "but pixel data has been decompressed") # Force PixelData to the decompressed version dataset.PixelData = dataset.pixel_array.tobytes() caller_owns_file = True # Open file if not already a file object if isinstance(filename, compat.string_types): fp = DicomFile(filename, 'wb') # caller provided a file name; we own the file handle caller_owns_file = False else: fp = DicomFileLike(filename) # if we want to write with the same endianess 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__ try: # WRITE FILE META INFORMATION if preamble: # Write the 'DICM' prefix if and only if we write the preamble fp.write(preamble) fp.write(b'DICM') if dataset.file_meta: # May be an empty Dataset # If we want to `write_like_original`, don't enforce_standard write_file_meta_info(fp, dataset.file_meta, enforce_standard=not write_like_original) # 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 = dataset.is_implicit_VR fp.is_little_endian = dataset.is_little_endian # Write non-Command Set elements now write_dataset(fp, get_item(dataset, slice(0x00010000, None))) finally: if not caller_owns_file: fp.close()
def dcmwrite(filename: Union[PathType, BinaryIO], dataset: Dataset, write_like_original: bool = True) -> None: """Write `dataset` to the `filename` specified. If `write_like_original` is ``True`` then the :class:`Dataset` will be written as is (after minimal validation checking) and may or may not contain all or parts of the *File Meta Information* (and hence may or may not be conformant with the DICOM File Format). If `write_like_original` is ``False``, `dataset` will be stored in the :dcm:`DICOM File Format <part10/chapter_7.html>`. To do so requires that the ``Dataset.file_meta`` attribute exists and contains a :class:`Dataset` with the required (Type 1) *File Meta Information Group* elements. The byte stream of the `dataset` will be placed into the file after the DICOM *File Meta Information*. **File Meta Information** The *File Meta Information* consists of a 128-byte preamble, followed by a 4 byte ``b'DICM'`` prefix, followed by the *File Meta Information Group* elements. **Preamble and Prefix** The ``dataset.preamble`` attribute shall be 128-bytes long or ``None`` and is available for use as defined by the Application Profile or specific implementations. If the preamble is not used by an Application Profile or specific implementation then all 128 bytes should be set to ``0x00``. The actual preamble written depends on `write_like_original` and ``dataset.preamble`` (see the table below). +------------------+------------------------------+ | | write_like_original | +------------------+-------------+----------------+ | dataset.preamble | True | False | +==================+=============+================+ | None | no preamble | 128 0x00 bytes | +------------------+-------------+----------------+ | 128 bytes | dataset.preamble | +------------------+------------------------------+ The prefix shall be the bytestring ``b'DICM'`` and will be written if and only if the preamble is present. **File Meta Information Group Elements** The preamble and prefix are followed by a set of DICOM elements from the (0002,eeee) group. Some of these elements are required (Type 1) while others are optional (Type 3/1C). If `write_like_original` is ``True`` then the *File Meta Information Group* elements are all optional. See :func:`~pydicom.filewriter.write_file_meta_info` for more information on which elements are required. The *File Meta Information Group* elements should be included within their own :class:`~pydicom.dataset.Dataset` in the ``dataset.file_meta`` attribute. If (0002,0010) *Transfer Syntax UID* is included then the user must ensure its value is compatible with the values for the ``dataset.is_little_endian`` and ``dataset.is_implicit_VR`` attributes. For example, if ``is_little_endian`` and ``is_implicit_VR`` are both ``True`` then the Transfer Syntax UID must be 1.2.840.10008.1.2 *Implicit VR Little Endian*. See the DICOM Standard, Part 5, :dcm:`Section 10<part05/chapter_10.html>` for more information on Transfer Syntaxes. *Encoding* The preamble and prefix are encoding independent. The File Meta elements are encoded as *Explicit VR Little Endian* as required by the DICOM Standard. **Dataset** A DICOM Dataset representing a SOP Instance related to a DICOM Information Object Definition. It is up to the user to ensure the `dataset` conforms to the DICOM Standard. *Encoding* The `dataset` is encoded as specified by the ``dataset.is_little_endian`` and ``dataset.is_implicit_VR`` attributes. It's up to the user to ensure these attributes are set correctly (as well as setting an appropriate value for ``dataset.file_meta.TransferSyntaxUID`` if present). Parameters ---------- filename : str or PathLike or file-like Name of file or the file-like to write the new DICOM file to. dataset : pydicom.dataset.FileDataset Dataset holding the DICOM information; e.g. an object read with :func:`~pydicom.filereader.dcmread`. write_like_original : bool, optional If ``True`` (default), preserves the following information from the Dataset (and may result in a non-conformant file): - preamble -- if the original file has no preamble then none will be written. - file_meta -- if the original file was missing any required *File Meta Information Group* elements then they will not be added or written. If (0002,0000) *File Meta Information Group Length* is present then it may have its value updated. - seq.is_undefined_length -- if original had delimiters, write them now too, instead of the more sensible length characters - is_undefined_length_sequence_item -- for datasets that belong to a sequence, write the undefined length delimiters if that is what the original had. If ``False``, produces a file conformant with the DICOM File Format, with explicit lengths for all elements. Raises ------ AttributeError If either ``dataset.is_implicit_VR`` or ``dataset.is_little_endian`` have not been set. ValueError If group 2 elements are in ``dataset`` rather than ``dataset.file_meta``, or if a preamble is given but is not 128 bytes long, or if Transfer Syntax is a compressed type and pixel data is not compressed. See Also -------- pydicom.dataset.Dataset Dataset class with relevant attributes and information. pydicom.dataset.Dataset.save_as Write a DICOM file from a dataset that was read in with ``dcmread()``. ``save_as()`` wraps ``dcmwrite()``. """ # Ensure is_little_endian and is_implicit_VR are set if None in (dataset.is_little_endian, dataset.is_implicit_VR): has_tsyntax = False try: tsyntax = dataset.file_meta.TransferSyntaxUID if not tsyntax.is_private: dataset.is_little_endian = tsyntax.is_little_endian dataset.is_implicit_VR = tsyntax.is_implicit_VR has_tsyntax = True except AttributeError: pass if not has_tsyntax: name = dataset.__class__.__name__ raise AttributeError( f"'{name}.is_little_endian' and '{name}.is_implicit_VR' must " f"be set appropriately before saving") # Try and ensure that `is_undefined_length` is set correctly try: tsyntax = dataset.file_meta.TransferSyntaxUID if not tsyntax.is_private: dataset['PixelData'].is_undefined_length = tsyntax.is_compressed except (AttributeError, KeyError): pass # Check that dataset's group 0x0002 elements are only present in the # `dataset.file_meta` Dataset - user may have added them to the wrong # place if dataset.group_dataset(0x0002) != Dataset(): raise ValueError( f"File Meta Information Group Elements (0002,eeee) should be in " f"their own Dataset object in the " f"'{dataset.__class__.__name__}.file_meta' attribute.") # A preamble is required under the DICOM standard, however if # `write_like_original` is True we treat it as optional preamble = getattr(dataset, 'preamble', None) if preamble and len(preamble) != 128: raise ValueError( f"'{dataset.__class__.__name__}.preamble' must be 128-bytes long.") if not preamble and not write_like_original: # The default preamble is 128 0x00 bytes. preamble = b'\x00' * 128 # File Meta Information is required under the DICOM standard, however if # `write_like_original` is True we treat it as optional if not write_like_original: # the checks will be done in write_file_meta_info() dataset.fix_meta_info(enforce_standard=False) else: dataset.ensure_file_meta() # Check for decompression, give warnings if inconsistencies # If decompressed, then pixel_array is now used instead of PixelData if dataset.is_decompressed: if dataset.file_meta.TransferSyntaxUID.is_compressed: raise ValueError( f"The Transfer Syntax UID element in " f"'{dataset.__class__.__name__}.file_meta' is compressed " f"but the pixel data has been decompressed") # Force PixelData to the decompressed version dataset.PixelData = dataset.pixel_array.tobytes() caller_owns_file = True # Open file if not already a file object filename = path_from_pathlike(filename) if isinstance(filename, str): fp = DicomFile(filename, 'wb') # caller provided a file name; we own the file handle caller_owns_file = False else: try: fp = DicomFileLike(filename) except AttributeError: raise TypeError("dcmwrite: Expected a file path or a file-like, " "but got " + type(filename).__name__) try: # WRITE FILE META INFORMATION if preamble: # Write the 'DICM' prefix if and only if we write the preamble fp.write(preamble) fp.write(b'DICM') tsyntax: Optional[UID] = None # type: ignore[no-redef] if dataset.file_meta: # May be an empty Dataset # If we want to `write_like_original`, don't enforce_standard write_file_meta_info(fp, dataset.file_meta, enforce_standard=not write_like_original) tsyntax = getattr(dataset.file_meta, "TransferSyntaxUID", None) if (tsyntax == DeflatedExplicitVRLittleEndian): # See PS3.5 section A.5 # when writing, the entire dataset following # the file metadata is prepared the normal way, # then "deflate" compression applied. buffer = DicomBytesIO() _write_dataset(buffer, dataset, write_like_original) # Compress the encoded data and write to file compressor = zlib.compressobj(wbits=-zlib.MAX_WBITS) deflated = compressor.compress( buffer.parent.getvalue() # type: ignore[union-attr] ) deflated += compressor.flush() if len(deflated) % 2: deflated += b'\x00' fp.write(deflated) else: _write_dataset(fp, dataset, write_like_original) finally: if not caller_owns_file: fp.close()
def dcmwrite(filename, dataset, write_like_original=True): """Write `dataset` to the `filename` specified. If `write_like_original` is True then `dataset` will be written as is (after minimal validation checking) and may or may not contain all or parts of the File Meta Information (and hence may or may not be conformant with the DICOM File Format). If `write_like_original` is False, `dataset` will be stored in the DICOM File Format in accordance with DICOM Standard Part 10 Section 7. The byte stream of the `dataset` will be placed into the file after the DICOM File Meta Information. File Meta Information --------------------- The File Meta Information consists of a 128-byte preamble, followed by a 4 byte DICOM prefix, followed by the File Meta Information Group elements. Preamble and Prefix ~~~~~~~~~~~~~~~~~~~ The `dataset.preamble` attribute shall be 128-bytes long or None and is available for use as defined by the Application Profile or specific implementations. If the preamble is not used by an Application Profile or specific implementation then all 128 bytes should be set to 0x00. The actual preamble written depends on `write_like_original` and `dataset.preamble` (see the table below). +------------------+------------------------------+ | | write_like_original | +------------------+-------------+----------------+ | dataset.preamble | True | False | +==================+=============+================+ | None | no preamble | 128 0x00 bytes | +------------------+------------------------------+ | 128 bytes | dataset.preamble | +------------------+------------------------------+ The prefix shall be the string 'DICM' and will be written if and only if the preamble is present. File Meta Information Group Elements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The preamble and prefix are followed by a set of DICOM Elements from the (0002,eeee) group. Some of these elements are required (Type 1) while others are optional (Type 3/1C). If `write_like_original` is True then the File Meta Information Group elements are all optional. See pydicom.filewriter.write_file_meta_info for more information on which elements are required. The File Meta Information Group elements should be included within their own Dataset in the `dataset.file_meta` attribute. If (0002,0010) 'Transfer Syntax UID' is included then the user must ensure it's value is compatible with the values for the `dataset.is_little_endian` and `dataset.is_implicit_VR` attributes. For example, if is_little_endian and is_implicit_VR are both True then the Transfer Syntax UID must be 1.2.840.10008.1.2 'Implicit VR Little Endian'. See the DICOM standard Part 5 Section 10 for more information on Transfer Syntaxes. Encoding ~~~~~~~~ The preamble and prefix are encoding independent. The File Meta Elements are encoded as Explicit VR Little Endian as required by the DICOM standard. Dataset ------- A DICOM Dataset representing a SOP Instance related to a DICOM Information Object Definition. It is up to the user to ensure the `dataset` conforms to the DICOM standard. Encoding ~~~~~~~~ The `dataset` is encoded as specified by the `dataset.is_little_endian` and `dataset.is_implicit_VR` attributes. It's up to the user to ensure these attributes are set correctly (as well as setting an appropriate value for `dataset.file_meta.TransferSyntaxUID` if present). Parameters ---------- filename : str or file-like Name of file or the file-like to write the new DICOM file to. dataset : pydicom.dataset.FileDataset Dataset holding the DICOM information; e.g. an object read with pydicom.dcmread(). write_like_original : bool If True (default), preserves the following information from the Dataset (and may result in a non-conformant file): - preamble -- if the original file has no preamble then none will be written. - file_meta -- if the original file was missing any required File Meta Information Group elements then they will not be added or written. If (0002,0000) 'File Meta Information Group Length' is present then it may have its value updated. - seq.is_undefined_length -- if original had delimiters, write them now too, instead of the more sensible length characters - is_undefined_length_sequence_item -- for datasets that belong to a sequence, write the undefined length delimiters if that is what the original had. If False, produces a file conformant with the DICOM File Format, with explicit lengths for all elements. See Also -------- pydicom.dataset.FileDataset Dataset class with relevant attributes and information. pydicom.dataset.Dataset.save_as Write a DICOM file from a dataset that was read in with dcmread(). save_as wraps dcmwrite. """ # Check that dataset's group 0x0002 elements are only present in the # `dataset.file_meta` Dataset - user may have added them to the wrong # place if dataset.group_dataset(0x0002) != Dataset(): raise ValueError("File Meta Information Group Elements (0002,eeee) " "should be in their own Dataset object in the " "'{0}.file_meta' " "attribute.".format(dataset.__class__.__name__)) # A preamble is required under the DICOM standard, however if # `write_like_original` is True we treat it as optional preamble = getattr(dataset, 'preamble', None) if preamble and len(preamble) != 128: raise ValueError("'{0}.preamble' must be 128-bytes " "long.".format(dataset.__class__.__name__)) if not preamble and not write_like_original: # The default preamble is 128 0x00 bytes. preamble = b'\x00' * 128 # File Meta Information is required under the DICOM standard, however if # `write_like_original` is True we treat it as optional file_meta = getattr(dataset, 'file_meta', None) if not file_meta and not write_like_original: dataset.file_meta = Dataset() file_meta = dataset.file_meta # If enforcing the standard, correct the TransferSyntaxUID where possible, # noting that the transfer syntax for is_implicit_VR = False and # is_little_endian = True is ambiguous as it may be an encapsulated # transfer syntax if not write_like_original: if dataset.is_little_endian and dataset.is_implicit_VR: file_meta.TransferSyntaxUID = ImplicitVRLittleEndian elif not dataset.is_little_endian and not dataset.is_implicit_VR: file_meta.TransferSyntaxUID = ExplicitVRBigEndian elif not dataset.is_little_endian and dataset.is_implicit_VR: raise NotImplementedError("Implicit VR Big Endian is not a" "supported Transfer Syntax.") if 'SOPClassUID' in dataset: file_meta.MediaStorageSOPClassUID = dataset.SOPClassUID if 'SOPInstanceUID' in dataset: file_meta.MediaStorageSOPInstanceUID = dataset.SOPInstanceUID caller_owns_file = True # Open file if not already a file object if isinstance(filename, compat.string_types): fp = DicomFile(filename, 'wb') # caller provided a file name; we own the file handle caller_owns_file = False else: fp = DicomFileLike(filename) try: # WRITE FILE META INFORMATION if preamble: # Write the 'DICM' prefix if and only if we write the preamble fp.write(preamble) fp.write(b'DICM') if file_meta is not None: # May be an empty Dataset # If we want to `write_like_original`, don't enforce_standard write_file_meta_info(fp, file_meta, enforce_standard=not write_like_original) # 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 = dataset[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 = dataset.is_implicit_VR fp.is_little_endian = dataset.is_little_endian # Write non-Command Set elements now write_dataset(fp, dataset[0x00010000:]) finally: if not caller_owns_file: fp.close()
def test_read(self): """Test the function""" with DicomFile(TEST_FILE, 'rb') as fp: assert not fp.parent.closed assert 'CT_small.dcm' in fp.name assert fp.read(2) == b'\x49\x49'
def open(self) -> None: """Opens file and reads metadata from it. Raises ------ FileNotFoundError When file cannot be found OSError When file cannot be opened IOError When DICOM metadata cannot be read from file ValueError When DICOM dataset contained in file does not represent an image Note ---- Builds a Basic Offset Table to speed up subsequent frame-level access. """ logger.debug('read File Meta Information') file_meta = read_file_meta_info(self.filename) transfer_syntax_uid = UID(file_meta.TransferSyntaxUID) try: self._fp = DicomFile(str(self.filename), mode='rb') self._fp.is_little_endian = transfer_syntax_uid.is_little_endian self._fp.is_implicit_VR = transfer_syntax_uid.is_implicit_VR except FileNotFoundError: raise FileNotFoundError(f'File not found: "{self.filename}"') except Exception: raise OSError( f'Could not open file for reading: "{self.filename}"') logger.debug('read metadata elements') try: self._metadata = dcmread(self._fp, stop_before_pixels=True) except Exception as err: raise IOError( f'DICOM metadata cannot be read from file "{self.filename}": ' f'"{err}"') self._pixel_data_offset = self._fp.tell() # Determine whether dataset contains a Pixel Data element try: tag = TupleTag(self._fp.read_tag()) except EOFError: raise ValueError( 'Dataset does not represent an image information entity.') if int(tag) not in _PIXEL_DATA_TAGS: raise ValueError( 'Dataset does not represent an image information entity.') self._as_float = False if int(tag) in _FLOAT_PIXEL_DATA_TAGS: self._as_float = True # Reset the file pointer to the beginning of the Pixel Data element self._fp.seek(self._pixel_data_offset, 0) # Build the ICC Transformation object. This takes some time and should # be done only once to speedup subsequent color corrections. if self.metadata.SamplesPerPixel == 1: self._color_manager = None else: try: icc_profile = self.metadata.ICCProfile except AttributeError: try: if len(self.metadata.OpticalPathSequence) > 1: # This should not happen in case of a color image. logger.warning( 'color image contains more than one optical path') optical_path_item = self.metadata.OpticalPathSequence[0] icc_profile = optical_path_item.ICCProfile except (IndexError, AttributeError): raise AttributeError( 'No ICC Profile found in image metadata.') try: self._color_manager = ColorManager(icc_profile) except ValueError: logger.warning('could not read ICC Profile') self._color_manager = None logger.debug('build Basic Offset Table') transfer_syntax_uid = self.metadata.file_meta.TransferSyntaxUID if transfer_syntax_uid.is_encapsulated: try: self._basic_offset_table = _get_bot( self._fp, number_of_frames=self.number_of_frames) except Exception as err: raise IOError(f'Failed to build Basic Offset Table: "{err}"') self._first_frame_offset = self._fp.tell() else: if self._fp.is_implicit_VR: header_offset = 4 + 4 # tag and length else: header_offset = 4 + 2 + 2 + 4 # tag, VR, reserved and length self._first_frame_offset = self._pixel_data_offset + header_offset n_pixels = self._pixels_per_frame bits_allocated = self.metadata.BitsAllocated if bits_allocated == 1: self._basic_offset_table = [ int(np.floor(i * n_pixels / 8)) for i in range(self.number_of_frames) ] else: self._basic_offset_table = [ i * self._bytes_per_frame_uncompressed for i in range(self.number_of_frames) ] if len(self._basic_offset_table) != self.number_of_frames: raise ValueError( 'Length of Basic Offset Table does not match Number of Frames.' )