Example #1
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_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
Example #2
0
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 ''
Example #4
0
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
Example #5
0
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
Example #6
0
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)
Example #7
0
 def keyword(self):
     """The data_element's keyword (if known)"""
     if dictionary_has_tag(self.tag):
         return dictionary_keyword(self.tag)
     else:
         return ''
Example #8
0
 def name(self) -> str:
     """Human readable name for this tag"""
     try:
         return dictionary_keyword(self.tag)
     except KeyError:
         return "Unknown Tag"
Example #9
0
 def keyword(self):
     """The element's keyword (if known)."""
     if dictionary_has_tag(self.tag):
         return dictionary_keyword(self.tag)
     else:
         return ''
Example #10
0
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)
Example #11
0
 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)
Example #13
0
    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
Example #14
0
    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()