コード例 #1
0
def write_file_meta_info(fp, file_meta, enforce_standard=True):
    """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())
コード例 #2
0
ファイル: test_dataset.py プロジェクト: erikced/pydicom
    def test_validate_and_correct_file_meta(self):
        file_meta = Dataset()
        validate_file_meta(file_meta, enforce_standard=False)
        with pytest.raises(ValueError):
            validate_file_meta(file_meta, enforce_standard=True)

        file_meta.PatientID = 'PatientID'
        for enforce_standard in (True, False):
            with pytest.raises(ValueError,
                               match=r'Only File Meta Information Group '
                               r'\(0002,eeee\) elements must be present .*'):
                validate_file_meta(file_meta,
                                   enforce_standard=enforce_standard)

        file_meta = Dataset()
        file_meta.MediaStorageSOPClassUID = '1.2.3'
        file_meta.MediaStorageSOPInstanceUID = '1.2.4'
        # still missing TransferSyntaxUID
        with pytest.raises(ValueError):
            validate_file_meta(file_meta, enforce_standard=True)

        file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
        validate_file_meta(file_meta, enforce_standard=True)

        # check the default created values
        assert file_meta.FileMetaInformationVersion == b'\x00\x01'
        assert file_meta.ImplementationClassUID == PYDICOM_IMPLEMENTATION_UID
        assert file_meta.ImplementationVersionName.startswith('PYDICOM ')

        file_meta.ImplementationClassUID = '1.2.3.4'
        file_meta.ImplementationVersionName = 'ACME LTD'
        validate_file_meta(file_meta, enforce_standard=True)
        # check that existing values are left alone
        assert file_meta.ImplementationClassUID == '1.2.3.4'
        assert file_meta.ImplementationVersionName == 'ACME LTD'
コード例 #3
0
def write_file_meta_info(fp, file_meta, enforce_standard=True):
    """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 Section 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 Table 7.1-1):
        * (0002,0000) FileMetaInformationGroupLength, UL, 4
        * (0002,0001) FileMetaInformationVersion, OB, 2
        * (0002,0002) MediaStorageSOPClassUID, UI, N
        * (0002,0003) MediaStorageSOPInstanceUID, UI, N
        * (0002,0010) TransferSyntaxUID, UI, N
        * (0002,0012) ImplementationClassUID, 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) ImplementationVersionName, SH, N
        * (0002,0016) SourceApplicationEntityTitle, AE, N
        * (0002,0017) SendingApplicationEntityTitle, AE, N
        * (0002,0018) ReceivingApplicationEntityTitle, AE, N
        * (0002,0100) PrivateInformationCreatorUID, UI, N
        * (0002,0102) PrivateInformation, OB, 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 DataElements.
    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

    # Only used if FileMetaInformationGroupLength is present.
    #   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.
    end_group_length_elem = fp.tell() + 12

    # The 'is_little_endian' and 'is_implicit_VR' attributes will need to be
    #   set correctly after the File Meta Info has been written.
    fp.is_little_endian = True
    fp.is_implicit_VR = False

    # Write the File Meta Information Group elements to `fp`
    write_dataset(fp, 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:
        # Save end of file meta to go back to
        end_of_file_meta = fp.tell()

        # 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
        group_length = int(end_of_file_meta - end_group_length_elem)
        file_meta.FileMetaInformationGroupLength = group_length
        fp.seek(end_group_length_elem - 12)
        write_data_element(fp, file_meta[0x00020000])

        # Return to end of the file meta, ready to write remainder of the file
        fp.seek(end_of_file_meta)
コード例 #4
0
def generate_dicom_sr(file_path, img_ds, data, series_description):
    """
    Generates DICOM Structured Report files for the given file path.
    :param file_path: the file name and directory to save the DICOM
                      SR file in.
    :param img_ds: A CT or MR image from the dataset used to pull
                   general information for the DICOM SR.
    :param data: Text data to be written to the DICOM SR file.
    :param series_description: Description of text data written to SR.
    :return: dicom_sr, a dataset for the new DICOM SR file.
    """
    if img_ds is None:
        raise ValueError("No CT data to initialize RT SS")

    # Create file meta
    file_meta = FileMetaDataset()
    file_meta.FileMetaInformationGroupLength = 238
    file_meta.FileMetaInformationVersion = b'\x00\x01'
    file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.88.33'
    file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid()
    file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
    validate_file_meta(file_meta)

    # Create dataset
    dicom_sr = pydicom.dataset.FileDataset(file_path, {},
                                           preamble=b"\0" * 128,
                                           file_meta=file_meta)
    dicom_sr.fix_meta_info()

    # Get current date and time
    now = datetime.datetime.now()
    dicom_date = now.strftime("%Y%m%d")
    dicom_time = now.strftime("%H%M")

    # List of tags to copy from CT/MR image
    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("InstitutionName"),
        Tag("InstitutionAddress")
    ]

    # Copy tags from CT/MR image
    for tag in top_level_tags_to_copy:
        if tag in img_ds:
            dicom_sr[tag] = deepcopy(img_ds[tag])

    dicom_sr.AccessionNumber = ""

    # == SR Document Series Module
    dicom_sr.SeriesDate = dicom_date
    dicom_sr.SeriesTime = dicom_time
    dicom_sr.Modality = "SR"
    dicom_sr.SeriesDescription = series_description
    # Can be empty
    referenced_performed_procedure_step_sequence = Sequence()
    dicom_sr.ReferencedPerformedProcedureStepSequence = \
        referenced_performed_procedure_step_sequence
    dicom_sr.SeriesInstanceUID = pydicom.uid.generate_uid()
    dicom_sr.SeriesNumber = 1

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

    # == SR Document General Module
    dicom_sr.ContentDate = dicom_date
    dicom_sr.ContentTime = dicom_time

    dicom_sr.InstanceNumber = 1

    # Empty if unknown
    performed_procedure_code_sequence = Sequence()

    dicom_sr.PerformedProcedureCodeSequence = performed_procedure_code_sequence

    # Do not want to mark as complete in case it isn't!
    dicom_sr.CompletionFlag = "PARTIAL"
    dicom_sr.VerificationFlag = "UNVERIFIED"

    # == SR Document Content Module
    referenced_sop_sequence = Sequence([Dataset()])
    referenced_sop_sequence[0].ReferencedSOPClassUID = ''
    referenced_sop_sequence[0].ReferencedSOPInstanceUID = ''

    dicom_sr.ReferencedSOPSequence = referenced_sop_sequence
    dicom_sr.ValueType = "CONTAINER"

    dicom_sr.ContinuityOfContent = "CONTINUOUS"
    dicom_sr.TemporalRangeTime = ""
    dicom_sr.ReferencedTimeOffsets = ""
    dicom_sr.ReferencedDateTime = ""

    dicom_sr.MeasuredValueSequence = Sequence()
    og_frame_of_reference_UID = \
        deepcopy(img_ds[Tag("FrameOfReferenceUID")].value)
    dicom_sr.ReferencedFrameOfReferenceUID = og_frame_of_reference_UID

    # == Content Sequence
    content_sequence = Sequence([Dataset()])
    content_sequence[0].RelationshipType = 'CONTAINS'
    content_sequence[0].ValueType = 'TEXT'

    concept_name_code_sequence = Sequence([Dataset()])
    concept_name_code_sequence[0].CodeValue = ''
    concept_name_code_sequence[0].CodingSchemeDesignator = ''
    concept_name_code_sequence[0].CodeMeaning = ''
    content_sequence[0].ConceptNameCodeSequence = concept_name_code_sequence

    content_sequence[0].TextValue = data

    dicom_sr.ContentSequence = content_sequence

    # == SOP Common Module
    dicom_sr.SOPClassUID = '1.2.840.10008.5.1.4.1.1.88.33'
    dicom_sr.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID

    dicom_sr.is_little_endian = True
    dicom_sr.is_implicit_VR = True

    return dicom_sr
コード例 #5
0
    def __init__(self, dataset, memmap_pixel_array=False):
        """Initialize DicomParser from a pydicom Dataset or DICOM file.

        Parameters
        ----------
        dataset : pydicom Dataset or filename
            pydicom dataset object or DICOM file location
        memmap_pixel_array : bool, optional
            Enable pixel array access via memory mapping, by default False

        Raises
        ------
        AttributeError
            Raised if SOPClassUID is not present in the pydicom Dataset
        AttributeError
            Raised if the DICOM file or pydicom Dataset cannot be read
        """
        self.memmap_pixel_array = memmap_pixel_array
        if isinstance(dataset, Dataset):
            self.ds = dataset
        elif isinstance(dataset, (string_types, BytesIO)):
            try:
                with open(dataset, "rb") as fp:
                    self.ds = read_file(fp,
                                        defer_size=100,
                                        force=True,
                                        stop_before_pixels=memmap_pixel_array)
                    if memmap_pixel_array:
                        self.offset = fp.tell() + 8
            except Exception:
                # Raise the error for the calling method to handle
                raise
            else:
                # Sometimes DICOM files may not have headers,
                # but they should always have a SOPClassUID
                # to declare what type of file it is.
                # If the file doesn't have a SOPClassUID,
                # then it probably isn't DICOM.
                if "SOPClassUID" not in self.ds:
                    raise AttributeError
        else:
            raise AttributeError

        # Fix dataset file_meta if incorrect
        try:
            validate_file_meta(self.ds.file_meta)
        except ValueError:
            logger.debug('Fixing invalid File Meta for ' +
                         str(self.ds.SOPInstanceUID))
            self.ds.fix_meta_info()

        # Remove the PixelData attribute if it is not set.
        # i.e. RTStruct does not contain PixelData and its presence can confuse
        # the parser
        if "PixelData" in self.ds and self.ds.PixelData is None:
            delattr(self.ds, 'PixelData')
        if memmap_pixel_array:
            self.filename = dataset
            self.pixel_array = self.get_pixel_array
        else:
            if "PixelData" in self.ds:
                self.pixel_array = self.ds.pixel_array
コード例 #6
0
def test_valid_filemeta(new_rtstruct: RTStruct):
    try:
        validate_file_meta(new_rtstruct.ds.file_meta)
    except Exception:
        pytest.fail("Invalid file meta in RTStruct dataset")
コード例 #7
0
ファイル: ROI.py プロジェクト: celeron533/OnkoDICOM
def create_initial_rtss_from_ct(img_ds: pydicom.dataset.Dataset,
                                filepath: Path,
                                uid_list: list) -> pydicom.dataset.FileDataset:
    """
    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
    uid_list : list
        list of UIDs (as strings) of the entire image volume that the
        RT SS references
    filepath: str
        A path where the RTStruct will be saved
    Returns
    -------
    pydicom.dataset.FileDataset
        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")

    # File Meta module
    file_meta = FileMetaDataset()
    file_meta.FileMetaInformationGroupLength = 238
    file_meta.FileMetaInformationVersion = b'\x00\x01'
    file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3'
    file_meta.MediaStorageSOPInstanceUID = generate_uid()
    file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
    validate_file_meta(file_meta)

    rt_ss = pydicom.dataset.FileDataset(filepath, {},
                                        preamble=b"\0" * 128,
                                        file_meta=file_meta)
    rt_ss.fix_meta_info()

    top_level_tags_to_copy: list = [
        Tag("PatientName"),
        Tag("PatientID"),
        Tag("PatientBirthDate"),
        Tag("PatientSex"),
        Tag("StudyDate"),
        Tag("StudyTime"),
        Tag("AccessionNumber"),
        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"),
        Tag("OperatorsName")
    ]

    for tag in top_level_tags_to_copy:
        if tag in img_ds:
            rt_ss[tag] = deepcopy(img_ds[tag])

    if rt_ss.StudyInstanceUID == "":
        raise ValueError(
            "The given dataset is missing a required tag 'StudyInstanceUID'")

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

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

    # Frame of Reference module
    rt_ss.FrameOfReferenceUID = img_ds.FrameOfReferenceUID
    rt_ss.PositionReferenceIndicator = ""

    # Structure Set module
    # Best to modify the Structure Set Label with something more
    # interesting in the application. and populate the Name and
    # Description from the application also.
    rt_ss.StructureSetLabel = "OnkoDICOM rtss"
    rt_ss.StructureSetName = rt_ss.StructureSetLabel
    rt_ss.StructureSetDescription = "OnkoDICOM rtss of " + rt_ss.StudyID
    rt_ss.StructureSetDate = dicom_date
    rt_ss.StructureSetTime = dicom_time

    # Contour Image Sequence
    contour_image_sequence = []
    for uid in uid_list:
        contour_image_sequence_item = pydicom.dataset.Dataset()
        contour_image_sequence_item.ReferencedSOPClassUID = img_ds.SOPClassUID
        contour_image_sequence_item.ReferencedSOPInstanceUID = uid
        contour_image_sequence.append(contour_image_sequence_item)

    # RT Referenced Series Sequence
    rt_referenced_series = pydicom.dataset.Dataset()
    rt_referenced_series.SeriesInstanceUID = img_ds.SeriesInstanceUID
    rt_referenced_series.ContourImageSequence = contour_image_sequence
    rt_referenced_series_sequence = [rt_referenced_series]

    # RT Referenced Study Sequence
    rt_referenced_study = pydicom.dataset.Dataset()
    rt_referenced_study.ReferencedSOPClassUID = "1.2.840.10008.3.1.2.3.1"
    rt_referenced_study.ReferencedSOPInstanceUID = img_ds.StudyInstanceUID
    rt_referenced_study.RTReferencedSeriesSequence = \
        rt_referenced_series_sequence
    rt_referenced_study_sequence = [rt_referenced_study]

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

    # Sequence modules
    rt_ss.StructureSetROISequence = []
    rt_ss.ROIContourSequence = []
    rt_ss.RTROIObservationsSequence = []

    # SOP Common module
    rt_ss.SOPClassUID = rt_ss.file_meta.MediaStorageSOPClassUID
    rt_ss.SOPInstanceUID = rt_ss.file_meta.MediaStorageSOPInstanceUID

    # Add required elements
    rt_ss.InstanceCreationDate = dicom_date
    rt_ss.InstanceCreationTime = dicom_time

    rt_ss.is_little_endian = True
    rt_ss.is_implicit_VR = True
    return rt_ss
コード例 #8
0
ファイル: filewriter.py プロジェクト: pieper/pydicom
def write_file_meta_info(fp, file_meta, enforce_standard=True):
    """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 Section 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 Table 7.1-1):
        * (0002,0000) FileMetaInformationGroupLength, UL, 4
        * (0002,0001) FileMetaInformationVersion, OB, 2
        * (0002,0002) MediaStorageSOPClassUID, UI, N
        * (0002,0003) MediaStorageSOPInstanceUID, UI, N
        * (0002,0010) TransferSyntaxUID, UI, N
        * (0002,0012) ImplementationClassUID, 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) ImplementationVersionName, SH, N
        * (0002,0016) SourceApplicationEntityTitle, AE, N
        * (0002,0017) SendingApplicationEntityTitle, AE, N
        * (0002,0018) ReceivingApplicationEntityTitle, AE, N
        * (0002,0100) PrivateInformationCreatorUID, UI, N
        * (0002,0102) PrivateInformation, OB, 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 DataElements.
    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())