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