Example #1
0
    def dir(self, *filters):
        """Return an alphabetical list of DataElement keywords in the Dataset.

        Intended mainly for use in interactive Python sessions. Only lists the
        DataElement keywords in the current level of the Dataset (i.e. the
        contents of any Sequence elements are ignored).

        Parameters
        ----------
        filters : str
            Zero or more string arguments to the function. Used for
            case-insensitive match to any part of the DICOM keyword.

        Returns
        -------
        list of str
            The matching DataElement keywords in the dataset. If no filters are
            used then all DataElement keywords are returned.
        """
        allnames = [keyword_for_tag(tag) for tag in self.keys()]
        # remove blanks - tags without valid names (e.g. private tags)
        allnames = [x for x in allnames if x]
        # Store found names in a dict, so duplicate names appear only once
        matches = {}
        for filter_ in filters:
            filter_ = filter_.lower()
            match = [x for x in allnames if x.lower().find(filter_) != -1]
            matches.update(dict([(x, 1) for x in match]))
        if filters:
            names = sorted(matches.keys())
            return names
        else:
            return sorted(allnames)
Example #2
0
    def __next__(self):
        if self._i == self.length:
            raise StopIteration()

        fn = self.filenames[self._i]
        dcm = pydicom.dcmread(fn)
        fn = Path(fn).name

        for tag, new_value in self.replace_rules:
            if hasattr(new_value, '__call__'):
                new_value = new_value(dcm)
            if tag[0] == 0x0002:
                old_value = dcm.file_meta[tag].value
                dcm.file_meta[tag].value = new_value
            else:
                if tag in dcm:
                    old_value = dcm[tag].value
                    dcm[tag].value = new_value
                else:
                    old_value = ''
                    kw = keyword_for_tag(tag)
                    setattr(dcm, kw, new_value)
            self.replace_history[tag2str(tag)].append(
                (fn, (serialize_tag(old_value), new_value)))

        for tag in self.remove_rules:
            if tag in dcm:
                old_value = dcm[tag].value
                del dcm[tag]
                self.remove_history[tag2str(tag)].append(
                    (fn, serialize_tag(old_value)))

        self._i += 1
        return dcm
Example #3
0
    def dir(self, *filters):
        """Return an alphabetical list of DataElement keywords in the Dataset.

        Intended mainly for use in interactive Python sessions. Only lists the
        DataElement keywords in the current level of the Dataset (i.e. the
        contents of any Sequence elements are ignored).

        Parameters
        ----------
        filters : str
            Zero or more string arguments to the function. Used for
            case-insensitive match to any part of the DICOM keyword.

        Returns
        -------
        list of str
            The matching DataElement keywords in the dataset. If no filters are
            used then all DataElement keywords are returned.
        """
        allnames = [keyword_for_tag(tag) for tag in self.keys()]
        # remove blanks - tags without valid names (e.g. private tags)
        allnames = [x for x in allnames if x]
        # Store found names in a dict, so duplicate names appear only once
        matches = {}
        for filter_ in filters:
            filter_ = filter_.lower()
            match = [x for x in allnames if x.lower().find(filter_) != -1]
            matches.update(dict([(x, 1) for x in match]))
        if filters:
            names = sorted(matches.keys())
            return names
        else:
            return sorted(allnames)
Example #4
0
def decode(item, prefix):
	if isinstance(item, list):
		ii = 0
		for listitem in item:
			decode(listitem, prefix + 'list%d/' % ii)
			ii = ii + 1
		return
	elif isinstance(item, dict):
		for dictname, dictvalue in item.items():
			personal = False
			if ident_is_dicom_tag(dictname):
				personal = dicom_tagnum_is_personal(dictname)
				dictname = keyword_for_tag(dictname) + '('+dictname+')'
			if isinstance(dictvalue, dict) and 'vr' in dictvalue:
				# A vr without a val is just an empty dict XXX maybe not, just assumed!
				if not 'val' in dictvalue:
					continue
				# A vr with a string val
				if 'val' in dictvalue and isinstance(dictvalue['val'], str):
					personal = personal or vr_is_personal(dictvalue['vr'])
					printable_value = dictvalue['val'] if cfg_print_values else ''
					if personal:
						print('%s/%s [PERSONAL] = %s' % (prefix, dictname, printable_value))
					else:
						print('%s/%s = %s' % (prefix, dictname, printable_value))
					continue
				# A vr with a list value
				if 'val' in dictvalue and isinstance(dictvalue['val'], list):
					decode(dictvalue['val'], prefix + '%s!/' % dictname) # same as line below
					continue
			decode(dictvalue, prefix + '%s/' % dictname)
	else:
		print('%s = ? %s' % (prefix, item if cfg_print_values else ''))
Example #5
0
def dicom_tag_number_to_name(tag):
    match = re.match(r'^\({0,1}([0-9a-fA-F]{4}),{0,1}([0-9a-fA-F]{4})[\):]{0,1}', tag)
    if match:
        tagname = keyword_for_tag(match.group(1)+match.group(2))
        if tagname == "": tagname = "PrivateCreator"
        return tagname
    # If no number is found then assume it's already a name
    return tag
Example #6
0
def _create_modules(directory):
    filename = os.path.join(directory, 'module_to_attributes.json')
    module_to_attributes = _load_json_from_file(filename)
    modules = collections.defaultdict(list)
    for item in module_to_attributes:
        path = item['path'].split(':')[1:]
        tag = path.pop(-1)
        try:
            mapping = {
                'keyword': keyword_for_tag(tag),
                'type': item['type'],
                'path': [keyword_for_tag(t) for t in path],
            }
        except ValueError:
            logger.error('Keyword not found for attribute "{}"'.format(tag))
            continue
        modules[item['module']].append(mapping)
    return modules
Example #7
0
 def print_cb(ds, elem):
     if elem_filter:
         tag = elem.tag
         keyword = keyword_for_tag(tag)
         if not elem_filter(tag, keyword):
             return
     try:
         print(fmt.format(elem=elem, ds=ds))
     except Exception:
         log.warn("Couldn't apply format to elem: %s", elem)
Example #8
0
def normalize(data_set, elem_filter=None):
    """Convert a DICOM data set into basic python types that can be serialized"""
    res = OrderedDict()
    for tag in data_set.keys():
        key = keyword_for_tag(tag)
        if elem_filter and not elem_filter(tag, key):
            continue
        if key == "":
            key = "%04x,%04x" % (tag.group, tag.element)
        res[key] = norm_elem_val(data_set[tag])
    return res
Example #9
0
    def _get_elem_key(self, elem):
        '''Get the key for any non-translated elements.'''
        #Use standard DICOM keywords if possible
        key = keyword_for_tag(elem.tag)

        #For private tags we take elem.name and convert to camel case
        if key == '':
            key = elem.name
            if key.startswith('[') and key.endswith(']'):
                key = key[1:-1]
            tokens = [token[0].upper() + token[1:] for token in key.split()]
            key = ''.join(tokens)

        return key
    def replace_tags_with_keywords(self, worklist_dict):
        """
        dont ask, just enjoy
        iterate through json structure containing information on existing worklists
        and replace all occurrences of dicom tags with their corresponding keywords.

        :param worklist_dict: dictonary that contains the worklist elements
        """
        keyword_dict = copy.deepcopy(worklist_dict)
        for key, value in worklist_dict.items():
            if isinstance(value, dict):
                for dicom_tag, vr_dict in value.items():
                    dicom_keyword = keyword_for_tag(int(dicom_tag, 16))
                    keyword_dict[key][dicom_keyword] = keyword_dict[key].pop(dicom_tag)
                    if vr_dict["Value"] and isinstance(vr_dict["Value"][0], dict):
                        for index, nested_vr_dict in enumerate(vr_dict["Value"]):
                            for nested_dicom_tag, _ in nested_vr_dict.items():
                                try:
                                    nested_dicom_keyword = keyword_for_tag(int(nested_dicom_tag, 16))
                                except ValueError:
                                    continue
                                keyword_dict[key][dicom_keyword]["Value"][index][nested_dicom_keyword] = \
                                    keyword_dict[key][dicom_keyword]["Value"][index].pop(nested_dicom_tag)
        return keyword_dict
Example #11
0
    def _get_elem_key(self, elem):
        '''Get the key for any non-translated elements.'''
        #Use standard DICOM keywords if possible
        key = keyword_for_tag(elem.tag)

        #For private tags we take elem.name and convert to camel case
        if key == '':
            key = elem.name
            if key.startswith('[') and key.endswith(']'):
                key = key[1:-1]
            tokens = [token[0].upper() + token[1:]
                      for token in key.split()]
            key = ''.join(tokens)

        return key
Example #12
0
 def elem_filter(tag, keyword):
     if exclude_private and tag.group % 2 == 1:
         return False
     if tag in exclude_tags:
         return False
     if tag.group not in groups:
         if include and tag in include_tags:
             return True
         return False
     if kw_regex:
         keyword = keyword_for_tag(tag)
         if not any(r.search(keyword) for r in kw_regex):
             if include and tag in include_tags:
                 return True
             return False
     if tag in include_tags:
         return True
     return False
Example #13
0
def text_and_personal_content(item):

    is_personal = False

    vr = None
    val = None
    relationship_type = ''
    relationship_value_type = ''
    text_content = ''
    num_children = 0

    assert isinstance(item, dict)

    # Extract values from the dict
    if isinstance(item, dict):
        num_children = len(item)

        # Two common values in children items
        vr = item.get('vr', None)
        val = item.get('val', None)

        # Relationship to child item
        relationship_type = get_val(item, Tagnum_RelationshipType)
        relationship_value_type = get_val(item, Tagnum_ValueType)

        # Text strings
        text_content = get_val(item, Tagnum_TextValue)

        # Person name
        # (if it's the name of an organisation we will detect this later)
        if Tagnum_PersonName in item:
            text_content = decode_PNAME(get_val(item, Tagnum_PersonName))
            is_personal = True

        # Numeric values as text strings
        if Tagnum_MeasuredValueSequence in item:
            mvs = get_val(item, Tagnum_MeasuredValueSequence)[0]
            units = mvs[Tagnum_MeasurementUnitsCodeSequence]
            (units_short, units_long) = get_CodeValue_CodeMeaning(units)
            text_content = get_val(mvs, Tagnum_NumericValue)
            text_content = text_content + ' ' + units_short

    # First, can we return any content which is an attribute of this item
    if Tagnum_ReferringPhysicianName in item:
        val = get_val(item, Tagnum_ReferringPhysicianName)
        val = 'Referring Physician Name' + ': ' + decode_PNAME(val)
        yield val, True

    # Debug
    if isinstance(item, dict) and debug > 0:
        print("Is a dict with %d entries" % num_children)
        print("VR  %s" % vr)
        print("VAL %s" % val)

    # See if this dict has a 'meaning'
    (conceptname_name, conceptname_value) = get_ConceptNameCodeSequence(item)
    if conceptname_name and debug > 0:
        print("This item is a conceptName %s %s" %
              (conceptname_name, conceptname_value))
    (concept_name, concept_value) = get_ConceptCodeSequence(item)
    if concept_name and debug > 0:
        print("This item is a concept    %s %s" %
              (concept_name, concept_value))
    # See if this concept is personal data
    if conceptname_name and conceptname_name in ConceptName_personal_list:
        is_personal = True
    if conceptname_value and conceptname_value in ConceptValue_personal_list:
        is_personal = True

    # If it contains a CODE then extract the value of the code
    if relationship_type == 'CONTAINS':
        if relationship_value_type == 'CODE':
            text_content = concept_value

    # If it has a concept then prepend that to the front of the string value
    # unless it's the default Description as that appears multiple times.
    if conceptname_value and text_content and conceptname_value != 'Description':
        text_content = conceptname_value + ': ' + text_content

    # If we have a numeric value then extract the value as a string
    # but also append the units.

    # If we have a non empty string then we can return it now
    if text_content:
        yield text_content, is_personal
        return

    # If we have a content sequence child it's an array of similar items
    if Tagnum_ContentSequence in item:
        for seqitem in get_val(item, Tagnum_ContentSequence):
            yield from text_and_personal_content(seqitem)

    # Anything else is unexpected or unwanted
    #print("Finishing with %s" % item)
    return

    # Has a VR but no VAL or anything else
    if vr and not val and num_children < 2:
        yield None, False

    # Has a VR and a simple string VAL only
    elif vr and val and num_children == 2 and isinstance(item['val'], str):
        #print("yield string %s" % item['val'])
        # Data type may tell us if it's personal data
        if vr == 'PN':
            is_personal = True
            val = decode_PNAME(val)
        yield val, is_personal

    # --

    # Has a VR and a dict for VAL
    elif isinstance(item, dict) and vr and val and \
     isinstance(item['val'], dict):
        #print("Exploding val dict")
        yield from text_and_personal_content(item['val'])

    # Has a VR and an array for VAL
    elif isinstance(item, dict) and vr and val and \
     isinstance(item['val'], list):
        #print("Exploding val array")
        for listitem in item['val']:
            yield from text_and_personal_content(listitem)

    # Is a dict
    elif isinstance(item, dict):
        for dictitem in item:
            keyword = keyword_for_tag(dictitem)
            if debug > 0:
                print("Exploding dict item %s (%s)" % (dictitem, keyword))
            yield from text_and_personal_content(item[dictitem])
    else:
        print("Huh %s" % item)
Example #14
0
 def test_tag_not_found(self):
     """dicom_dictionary: CleanName returns blank string for unknown tag"""
     assert '' == keyword_for_tag(0x99991111)
Example #15
0
 def testTagNotFound(self):
     """dicom_dictionary: CleanName returns blank string for unknown tag"""
     self.assertTrue(keyword_for_tag(0x99991111) == "")
Example #16
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`.
    """
    # Check that no non-Group 2 Elements are present
    for elem in file_meta:
        if elem.tag.group != 0x0002:
            raise ValueError("Only File Meta Information Group (0002,eeee) "
                             "elements must be present in 'file_meta'.")

    # The Type 1 File Meta Elements are only required when `enforce_standard`
    #   is True, except for FileMetaInformationGroupLength and
    #   FileMetaInformationVersion, which may or may not be present
    if enforce_standard:
        # Will be updated with the actual length later
        if 'FileMetaInformationGroupLength' not in file_meta:
            file_meta.FileMetaInformationGroupLength = 0

        if 'FileMetaInformationVersion' not in file_meta:
            file_meta.FileMetaInformationVersion = b'\x00\x01'

        if 'ImplementationClassUID' not in file_meta:
            file_meta.ImplementationClassUID = PYDICOM_IMPLEMENTATION_UID

        if 'ImplementationVersionName' not in file_meta:
            file_meta.ImplementationVersionName = 'PYDICOM ' + __version__

        # Check that required File Meta Elements are present
        missing = []
        for element in [0x0002, 0x0003, 0x0010]:
            if Tag(0x0002, element) not in file_meta:
                missing.append(Tag(0x0002, element))
        if missing:
            msg = "Missing required File Meta Information elements from " \
                  "'file_meta':\n"
            for tag in missing:
                msg += '\t{0} {1}\n'.format(tag, keyword_for_tag(tag))
            raise ValueError(msg[:-1])  # Remove final newline

    # 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)
Example #17
0
 def testTagNotFound(self):
     """dicom_dictionary: CleanName returns blank string for unknown tag"""
     self.assertTrue(keyword_for_tag(0x99991111) == "")
 def test_tag_not_found(self):
     """dicom_dictionary: CleanName returns blank string for unknown tag"""
     assert '' == keyword_for_tag(0x99991111)
Example #19
0
#!/usr/bin/python3

# This script reads any text file (including JSON files) and replaces all
# strings such as "xxxxxxxx" (8 hex digits) with the corresponding DICOM tag name.
# Use it to make JSON files human-readable!
# Reads stdin, outputs to stdout.

import os
import sys
import re
from pydicom.datadict import keyword_for_tag

for line in sys.stdin:
    for match in re.findall('"[0-9a-fA-F]{8}"', line):
        line = line.replace(
            match, '"' + keyword_for_tag(match.replace('"', '')) + '"')
    print(line, end='')
Example #20
0
					personal = personal or vr_is_personal(dictvalue['vr'])
					printable_value = dictvalue['val'] if cfg_print_values else ''
					if personal:
						print('%s/%s [PERSONAL] = %s' % (prefix, dictname, printable_value))
					else:
						print('%s/%s = %s' % (prefix, dictname, printable_value))
					continue
				# A vr with a list value
				if 'val' in dictvalue and isinstance(dictvalue['val'], list):
					decode(dictvalue['val'], prefix + '%s!/' % dictname) # same as line below
					continue
			decode(dictvalue, prefix + '%s/' % dictname)
	else:
		print('%s = ? %s' % (prefix, item if cfg_print_values else ''))


print("OO %s" % keyword_for_tag('0020000D'))

with open(json_file_name) as fd:
	json_dict = json.loads(fd.read())

prefix='/'
decode(json_dict, prefix)
exit(0)
for name,value in json_dict.items():
	if isinstance(value, dict) and 'val' in value and isinstance(value['val'], str) and len(value['val']) > 0:
		print ('%s %s = %s' % (name, keyword_for_tag(name), value['val']))
	else:
		print ('%s %s' % (name, keyword_for_tag(name)))

Example #21
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`.
    """
    # Check that no non-Group 2 Elements are present
    for elem in file_meta:
        if elem.tag.group != 0x0002:
            raise ValueError("Only File Meta Information Group (0002,eeee) "
                             "elements must be present in 'file_meta'.")

    # The Type 1 File Meta Elements are only required when `enforce_standard`
    #   is True, except for FileMetaInformationGroupLength and
    #   FileMetaInformationVersion, which may or may not be present
    if enforce_standard:
        # Will be updated with the actual length later
        if 'FileMetaInformationGroupLength' not in file_meta:
            file_meta.FileMetaInformationGroupLength = 0

        if 'FileMetaInformationVersion' not in file_meta:
            file_meta.FileMetaInformationVersion = b'\x00\x01'

        if 'ImplementationClassUID' not in file_meta:
            file_meta.ImplementationClassUID = PYDICOM_IMPLEMENTATION_UID

        if 'ImplementationVersionName' not in file_meta:
            file_meta.ImplementationVersionName = (
                'PYDICOM ' + ".".join(str(x) for x in __version_info__))

        # Check that required File Meta Elements are present
        missing = []
        for element in [0x0002, 0x0003, 0x0010]:
            if Tag(0x0002, element) not in file_meta:
                missing.append(Tag(0x0002, element))
        if missing:
            msg = "Missing required File Meta Information elements from " \
                  "'file_meta':\n"
            for tag in missing:
                msg += '\t{0} {1}\n'.format(tag, keyword_for_tag(tag))
            raise ValueError(msg[:-1])  # Remove final newline

    # 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)