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_string = path.pop(-1) # Handle attributes used for real-time communication, which are neither # in DicomDictionary nor in RepeaterDictionary if any(p.startswith('0006') for p in path): logger.warning(f'skip attribute "{tag_string}"') continue logger.debug(f'add attribute "{tag_string}"') # Handle attributes that are in RepeatersDictionary tag_string = tag_string.replace('xx', '00') tag = Tag(tag_string) try: keyword = dictionary_keyword(tag) except KeyError: logger.error(f'keyword not found for attribute "{tag}"') continue mapping = { 'keyword': keyword, 'type': item['type'], 'path': [dictionary_keyword(t) for t in path], } modules[item['moduleId']].append(mapping) return modules
def code_dataelem( dataelem: DataElement, dataset_name: str = "ds", exclude_size: Optional[int] = None, include_private: bool = False ) -> str: """Code lines for a single DICOM data element Parameters ---------- dataelem : DataElement The DataElement instance to turn into code dataset_name : str The variable name of the Dataset containing `dataelem` exclude_size : Union[int, None] If specified, values longer than this (in bytes) will only have a commented string for a value, causing a syntax error when the code is run, and thus prompting the user to remove or fix that line. Returns ------- str A string containing code to recreate the data element If the data element is a sequence, calls code_sequence """ if dataelem.VR == VR.SQ: return code_sequence( dataelem, dataset_name, exclude_size, include_private ) # If in DICOM dictionary, set using the keyword # If not (e.g. is private element), set using add_new method have_keyword = True try: keyword = dictionary_keyword(dataelem.tag) except KeyError: have_keyword = False valuerep = repr(dataelem.value) if exclude_size: if ( dataelem.VR in (BYTES_VR | AMBIGUOUS_VR) - {VR.US_SS} and not isinstance(dataelem.value, (int, float)) and len(dataelem.value) > exclude_size ): valuerep = f"# XXX Array of {len(dataelem.value)} bytes excluded" if have_keyword: line = f"{dataset_name}.{keyword} = {valuerep}" else: tag = tag_repr(dataelem.tag) vr = dataelem.VR line = f"{dataset_name}.add_new({tag}, '{vr}', {valuerep})" return line
def keyword(self) -> str: """Return the element's keyword (if known) as :class:`str`. For officially registered DICOM Data Elements this will be the *Keyword* as given in :dcm:`Table 6-1<part06/chapter_6.html#table_6-1>`. For private or unknown elements this will return an empty string ``''``. """ if dictionary_has_tag(self.tag): return dictionary_keyword(self.tag) return ''
def code_dataelem(dataelem, dataset_name="ds", exclude_size=None, include_private=False): """Code lines for a single DICOM data element :arg dataelem: the DataElement instance to turn into code :arg dataset_name: variable name of the Dataset containing dataelem :arg exclude_size: if specified, values longer than this (in bytes) will only have a commented string for a value, causing a syntax error when the code is run, and thus prompting the user to remove or fix that line. :return: a string containing code to recreate the data element If the data element is a sequence, calls code_sequence """ if dataelem.VR == "SQ": return code_sequence(dataelem, dataset_name, exclude_size, include_private) # If in DICOM dictionary, set using the keyword # If not (e.g. is private element), set using add_new method have_keyword = True try: keyword = dictionary_keyword(dataelem.tag) except KeyError: have_keyword = False valuerep = repr(dataelem.value) if exclude_size: if (dataelem.VR in byte_VRs and len(dataelem.value) > exclude_size): valuerep = ( "# XXX Array of %d bytes excluded" % len(dataelem.value)) if have_keyword: format_str = "{ds_name}.{keyword} = {valuerep}" line = format_str.format( ds_name=dataset_name, keyword=keyword, valuerep=valuerep) else: format_str = "{ds_name}.add_new({tag}, '{VR}', {valuerep})" line = format_str.format( ds_name=dataset_name, tag=tag_repr(dataelem.tag), VR=dataelem.VR, valuerep=valuerep) return line
def code_dataelem(dataelem, dataset_name="ds", exclude_size=None, include_private=False): """Code lines for a single DICOM data element :arg dataelem: the DataElement instance to turn into code :arg dataset_name: variable name of the Dataset containing dataelem :arg exclude_size: if specified, values longer than this (in bytes) will only have a commented string for a value, causing a syntax error when the code is run, and thus prompting the user to remove or fix that line. :return: a string containing code to recreate the data element If the data element is a sequence, calls code_sequence """ if dataelem.VR == "SQ": return code_sequence(dataelem, dataset_name, exclude_size, include_private) # If in DICOM dictionary, set using the keyword # If not (e.g. is private element), set using add_new method have_keyword = True try: keyword = dictionary_keyword(dataelem.tag) except KeyError: have_keyword = False valuerep = repr(dataelem.value) if exclude_size: value_gr_size = len(dataelem.value) > exclude_size if (dataelem.VR in byte_VRs and value_gr_size): valuerep = ( "# XXX Array of %d bytes excluded" % len(dataelem.value)) if have_keyword: format_str = "{ds_name}.{keyword} = {valuerep}" line = format_str.format( ds_name=dataset_name, keyword=keyword, valuerep=valuerep) else: format_str = "{ds_name}.add_new({tag}, '{VR}', {valuerep})" line = format_str.format( ds_name=dataset_name, tag=tag_repr(dataelem.tag), VR=dataelem.VR, valuerep=valuerep) return line
def code_sequence(dataelem, dataset_name="ds", exclude_size=None, include_private=False, name_filter=default_name_filter): """Code lines for recreating a Sequence data element :arg dataelem: the DataElement instance of the Sequence :arg dataset_name: variable name of the dataset containing the Sequence :arg exclude_size: if specified, values longer than this (in bytes) will only have a commented string for a value, causing a syntax error when the code is run, and thus prompting the user to remove or fix that line. :arg include_private: If True, private data elements will be coded. If False, private elements are skipped :arg name_filter: a callable taking a sequence name or sequence item name, and returning a shorter name for easier code reading :return: a string containing code lines to recreate a DICOM sequence """ lines = [] seq = dataelem.value seq_name = dataelem.name seq_item_name = seq_name.replace(' Sequence', '') seq_keyword = dictionary_keyword(dataelem.tag) # Create comment line to document the start of Sequence lines.append('') lines.append("# " + seq_name) # Code line to create a new Sequence object if name_filter: seq_var = name_filter(seq_keyword) lines.append(seq_var + " = Sequence()") # Code line to add the sequence to its parent lines.append(dataset_name + "." + seq_keyword + " = " + seq_var) # Code lines to add sequence items to the Sequence for i, ds in enumerate(seq): # Determine index to use. If seq item has a data element with 'Index', # use that; if one with 'Number', use that, else start at 1 index_keyword = seq_keyword.replace("Sequence", "") + "Index" number_keyword = seq_keyword.replace("Sequence", "") + "Number" if index_keyword in ds: index_str = str(getattr(ds, index_keyword)) elif number_keyword in ds: index_str = str(getattr(ds, number_keyword)) else: index_str = str(i + 1) # Code comment line to mark start of sequence item lines.append('') lines.append("# " + seq_name + ": " + seq_item_name + " " + index_str) # Determine the variable name to use for the sequence item (dataset) ds_name = seq_var.replace("_sequence", "") + index_str # Code the sequence item code_item = code_dataset(ds, ds_name, exclude_size, include_private) lines.append(code_item) # Code the line to append the item to its parent sequence lines.append(seq_var + ".append(" + ds_name + ")") # Join the lines and return a single string return line_term.join(lines)
def keyword(self): """The data_element's keyword (if known)""" if dictionary_has_tag(self.tag): return dictionary_keyword(self.tag) else: return ''
def name(self) -> str: """Human readable name for this tag""" try: return dictionary_keyword(self.tag) except KeyError: return "Unknown Tag"
def keyword(self): """The element's keyword (if known).""" if dictionary_has_tag(self.tag): return dictionary_keyword(self.tag) else: return ''
def code_sequence( dataelem: DataElement, dataset_name: str = "ds", exclude_size: Optional[int] = None, include_private: bool = False, name_filter: Callable = default_name_filter, ) -> str: """Code lines for recreating a Sequence data element Parameters ---------- dataelem: DataElement The DataElement instance whose value is the Sequence dataset_name: str Variable name of the dataset containing the Sequence exclude_size: Union[int,None] If not None, values longer than this (in bytes) will only have a commented string for a value, causing a syntax error when the code is run, and thus prompting the user to remove or fix that line. include_private: bool If ``False`` (default), private elements are skipped If ``True``, private data elements will be coded. name_filter: Callable A callable taking a sequence name or sequence item name, and returning a shorter name for easier code reading Returns ------- str A string containing code lines to recreate a DICOM sequence """ lines = [] seq = dataelem.value seq_name = dataelem.name seq_item_name = seq_name.replace(" Sequence", "") try: seq_keyword = dictionary_keyword(dataelem.tag) except KeyError: seq_keyword = f"Tag{dataelem.tag:08x}" # Create comment line to document the start of Sequence lines.append("") lines.append("# " + seq_name) # Code line to create a new Sequence object if name_filter: seq_var = name_filter(seq_keyword) lines.append(seq_var + " = Sequence()") # Code line to add the sequence to its parent lines.append(dataset_name + "." + seq_keyword + " = " + seq_var) # Code lines to add sequence items to the Sequence for i, ds in enumerate(seq): # Determine index to use. If seq item has a data element with 'Index', # use that; if one with 'Number', use that, else start at 1 index_keyword = seq_keyword.replace("Sequence", "") + "Index" number_keyword = seq_keyword.replace("Sequence", "") + "Number" if hasattr(ds, index_keyword): index_str = str(getattr(ds, index_keyword)) elif hasattr(ds, number_keyword): index_str = str(getattr(ds, number_keyword)) else: index_str = str(i + 1) # Code comment line to mark start of sequence item lines.append("") lines.append("# " + seq_name + ": " + seq_item_name + " " + index_str) # Determine the variable name to use for the sequence item (dataset) ds_name = seq_var.replace("_sequence", "") + index_str # Code the sequence item code_item = code_dataset(ds, ds_name, exclude_size, include_private) lines.append(code_item) # Code the line to append the item to its parent sequence lines.append(seq_var + ".append(" + ds_name + ")") # Join the lines and return a single string return line_term.join(lines)
def keyword(self): """Return the element's keyword (if known) as :class:`str`.""" if dictionary_has_tag(self.tag): return dictionary_keyword(self.tag) else: return ''
series_names = [] for dcm in filenames: meta = {'subjectname': name} ds = pydicom.filereader.dcmread(str(dir.joinpath(dcm))) try: current_series = str(ds.data_element('SeriesDescription').value) except KeyError: # incase there is no series SeriesDescription tag. current_series = 'other' if current_series not in series_names: series_names.append(current_series) # extract requested dicom tags info to dict. meta[current_series] = {} for tag in tag_LUT: try: savevalue = ds[tag].value meta[current_series].update({dictionary_keyword(tag): str(ds[tag].value)}) except KeyError: # tag doesn't exist in this series pass meta_list.append(meta) allmeta.append(meta_list) print('Identified all dicom directories, their series and extracted metadata tags.') # convert dicoms to nifti files and save metadata dicts as json files. i = 0 for dicom_directory, name in zip(dcmdir, newnames): output_folder = output_dir.joinpath(name) jsonoutname = output_folder.joinpath(name+'.json') os.mkdir(output_folder) save_json(allmeta[i], str(jsonoutname)) dicom2nifti.convert_directory(str(dicom_directory), str(output_folder), compression=True, reorient=True)
def message_to_primitive(self): """ Convert the current DIMSE Message object to a DIMSE service parameters primitive Returns ------- primitive : pynetdicom.DIMSEparameters DIMSE service primitive The primitive generated from the current DIMSE Message """ cls_type_name = self.__class__.__name__ if 'C_ECHO' in cls_type_name: primitive = C_ECHO_ServiceParameters() elif 'C_STORE' in cls_type_name: primitive = C_STORE_ServiceParameters() elif 'C_FIND' in cls_type_name: primitive = C_FIND_ServiceParameters() elif 'C_GET' in cls_type_name: primitive = C_GET_ServiceParameters() elif 'C_MOVE' in cls_type_name: primitive = C_MOVE_ServiceParameters() elif 'N_EVENT' in cls_type_name: primitive = N_EVENT_REPORT_ServiceParameters() elif 'N_GET' in cls_type_name: primitive = N_GET_ServiceParameters() elif 'N_SET' in cls_type_name: primitive = N_SET_ServiceParameters() elif 'N_ACTION' in cls_type_name: primitive = N_ACTION_ServiceParameters() elif 'N_CREATE' in cls_type_name: primitive = N_CREATE_ServiceParameters() elif 'N_DELETE' in cls_type_name: primitive = N_DELETE_ServiceParameters() ## Command Set # For each parameter in the primitive, set the appropriate value # from the Message's Command Set for elem in self.command_set: if dictionary_has_tag(elem.tag): elem_name = dictionary_keyword(elem.tag) else: elem_name = elem.name.replace(' ', '') if hasattr(primitive, elem_name): try: setattr(primitive, elem_name, self.command_set.__getattr__(elem_name)) except: logger.error('DIMSE failed to convert message to primitive') ## Datasets if cls_type_name == 'C_STORE_RQ': setattr(primitive, 'DataSet', self.data_set) elif cls_type_name in ['C_FIND_RQ', 'C_FIND_RSP', 'C_GET_RQ', 'C_GET_RSP', 'C_MOVE_RQ', 'C_MOVE_RSP']: setattr(primitive, 'Identifier', self.data_set) elif cls_type_name == 'N_EVENT_REPORT_RQ': setattr(primitive, 'EventInformation', self.data_set) elif cls_type_name == 'N_EVENT_REPORT_RSP': setattr(primitive, 'EventReply', self.data_set) elif cls_type_name in ['N_GET_RSP', 'N_SET_RSP', 'N_CREATE_RQ', 'N_CREATE_RSP']: setattr(primitive, 'AttributeList', self.data_set) elif cls_type_name == 'N_SET_RQ': setattr(primitive, 'ModificationList', self.data_set) elif cls_type_name == 'N_ACTION_RQ': setattr(primitive, 'ActionInformation', self.data_set) elif cls_type_name == 'N_ACTION_RSP': setattr(primitive, 'ActionReply', self.data_set) return primitive
def primitive_to_message(self, primitive): """ Convert a DIMSE service parameters primitive to the current DIMSE message object Parameters ---------- primitive : pynetdicom.DIMSEparameters DIMSE service parameter The primitive to convert to the current DIMSE Message object """ ## Command Set for elem in self.command_set: # Use the short version of the element names as these should # match the parameter names in the primitive # # Nope, there's no guarantee that this will be the case # I think I'll add a keyword parameter to the pydicom Element # class and push upstream # elem_name = elem.keyword # # In the meantime we can add a workaround if dictionary_has_tag(elem.tag): elem_name = dictionary_keyword(elem.tag) else: elem_name = elem.name.replace(' ', '') # Old method if elem_name in primitive.__dict__.keys(): if primitive.__dict__[elem_name] is not None: elem.value = primitive.__dict__[elem_name] else: del self.command_set[elem.tag] # New method elif hasattr(primitive, elem_name): # If value hasn't been set for a parameter then delete # the corresponding element attr = getattr(primitive, elem_name) if attr is not None: elem.value = attr else: del self.command_set[elem.tag] # Theres a one-to-one relationship in the message_type dict, so invert # it for convenience rev_type = {} for value in message_type.keys(): rev_type[message_type[value]] = value cls_type_name = self.__class__.__name__.replace('_', '-') self.command_set.CommandField = rev_type[cls_type_name] ## Data Set # Default to no Data Set self.data_set = BytesIO() self.command_set.CommandDataSetType = 0x0101 # These message types should (except for C-FIND-RSP) always have # a Data Set cls_type_name = self.__class__.__name__ if cls_type_name == 'C_STORE_RQ': self.data_set = primitive.DataSet self.command_set.CommandDataSetType = 0x0001 elif cls_type_name in ['C_FIND_RQ', 'C_GET_RQ', 'C_GET_RSP', 'C_MOVE_RQ', 'C_MOVE_RSP']: self.data_set = primitive.Identifier self.command_set.CommandDataSetType = 0x0001 # C-FIND-RSP only has a Data Set when the Status is pending (0x0001) elif cls_type_name == 'C_FIND_RSP' and \ self.command_set.Status in [0xFF00, 0xFF01]: self.data_set = primitive.Identifier self.command_set.CommandDataSetType = 0x0001 elif cls_type_name == 'N_EVENT_REPORT_RQ': self.data_set = primitive.EventInformation self.command_set.CommandDataSetType = 0x0001 elif cls_type_name == 'N_EVENT_REPORT_RSP': self.data_set = primitive.EventReply self.command_set.CommandDataSetType = 0x0001 elif cls_type_name in ['N_GET_RSP', 'N_SET_RSP', 'N_CREATE_RQ', 'N_CREATE_RSP']: self.data_set = primitive.AttributeList self.command_set.CommandDataSetType = 0x0001 elif cls_type_name == 'N_SET_RQ': self.data_set = primitive.ModificationList self.command_set.CommandDataSetType = 0x0001 elif cls_type_name == 'N_ACTION_RQ': self.data_set = primitive.ActionInformation self.command_set.CommandDataSetType = 0x0001 elif cls_type_name == 'N_ACTION_RSP': self.data_set = primitive.ActionReply self.command_set.CommandDataSetType = 0x0001 elif cls_type_name in ['C_ECHO_RQ', 'C_ECHO_RSP', 'N_DELETE_RQ', 'C_STORE_RSP', 'C_CANCEL_RQ', 'N_DELETE_RSP', 'C_FIND_RSP']: pass else: logger.error("DIMSE - Can't convert primitive to message for " "unknown message type '%s'" %cls_type_name) # Set the Command Set length self._set_command_group_length()