Skip to content

Interlacer

interlacer #

Interlacer Module

This module defines the Interlacer class, which constructs and queries a hierarchical forest of DICOM series based on their metadata relationships. It enables efficient grouping, querying, and visualization of medical imaging series.

The Interlacer provides tools for analyzing complex DICOM relationships, such as connections between different imaging modalities (CT, MR, PT) and derived objects (RTSTRUCT, RTDOSE, SEG). It enables validation of relationships based on DICOM standards and medical imaging workflows.

Classes:

Name Description
SeriesNode

Represents an individual DICOM series and its hierarchical relationships.

Interlacer

Builds the hierarchy, processes queries, and visualizes the relationships.

InterlacerQueryError

Base exception for query validation errors.

UnsupportedModalityError

Raised when an unsupported modality is specified in a query.

MissingDependencyModalityError

Raised when modalities in a query are missing required dependencies.

ModalityHighlighter

Rich text highlighter for pretty-printing DICOM modalities.

Features
  • Hierarchical representation of DICOM relationships
  • Query for specific combinations of modalities with dependency validation
  • Interactive visualization of DICOM series relationships
  • Rich text console display of patient/series hierarchies
  • Validation of modality dependencies based on DICOM standards

DuplicateRowError #

DuplicateRowError()

Bases: imgtools.dicom.interlacer.InterlacerQueryError

Raised when the index.csv file contains duplicate rows.

Source code in src/imgtools/dicom/interlacer.py
def __init__(self) -> None:
    msg = "The input file contains duplicate rows."
    super().__init__(msg)

Interlacer dataclass #

Interlacer(
    crawl_index: str | pathlib.Path | pandas.DataFrame,
)

Builds and queries a forest of SeriesNode objects from DICOM series data.

Parameters:

Name Type Description Default

crawl_index #

str | pathlib.Path | pandas.DataFrame

Path to the CSV file or DataFrame containing the series data

required

Attributes:

Name Type Description
crawl_df pandas.DataFrame

DataFrame containing the data loaded from the CSV file or passed in crawl_index

series_nodes dict[str, imgtools.utils.interlacer_utils.SeriesNode]

Maps SeriesInstanceUID to SeriesNode objects

root_nodes list[imgtools.utils.interlacer_utils.SeriesNode]

List of root nodes in the forest

Methods:

Name Description
print_tree

Print a representation of the forest.

query

Query the forest for specific modalities.

query_all

Simply return ALL possible matches

visualize_forest

Visualize the forest as an interactive network graph.

valid_queries property #

valid_queries: list[str]

Compute all valid queries based on the current forest. Mostly for debugging and informing the user of what we have determined what we can query.

Essentially, traverse each possible branch path and add the modalities to a set, afterwards, permutate each element's subpaths that might be valid

print_tree #

print_tree(input_directory: pathlib.Path | None) -> None

Print a representation of the forest.

Source code in src/imgtools/dicom/interlacer.py
def print_tree(self, input_directory: Path | None) -> None:
    """Print a representation of the forest."""
    print_interlacer_tree(self.root_nodes, input_directory)

query #

query(
    query_string: str, group_by_root: bool = True
) -> list[list[imgtools.utils.interlacer_utils.SeriesNode]]

Query the forest for specific modalities.

Parameters:

Name Type Description Default
query_string #
str

Comma-separated string of modalities to query (e.g., 'CT,MR')

required
group_by_root #
bool

If True, group the returned SeriesNodes by their root CT/MR/PT node (i.e., avoid duplicate root nodes across results).

True

Returns:

Type Description
list[list[dict[str, str]]]

List of matched series groups where each series is represented by a dict containing 'Series' and 'Modality' keys

Notes

Supported modalities: - CT: Computed Tomography - PT: Positron Emission Tomography - MR: Magnetic Resonance Imaging - SEG: Segmentation - RTSTRUCT: Radiotherapy Structure - RTDOSE: Radiotherapy Dose

Source code in src/imgtools/dicom/interlacer.py
def query(
    self,
    query_string: str,
    group_by_root: bool = True,
) -> list[list[SeriesNode]]:
    """
    Query the forest for specific modalities.

    Parameters
    ----------
    query_string : str
        Comma-separated string of modalities to query (e.g., 'CT,MR')

    group_by_root : bool, default=True
        If True, group the returned SeriesNodes by their root CT/MR/PT
        node (i.e., avoid duplicate root nodes across results).

    Returns
    -------
    list[list[dict[str, str]]]
        List of matched series groups where each series is represented by a
        dict containing 'Series' and 'Modality' keys

    Notes
    -----
    Supported modalities:
    - CT: Computed Tomography
    - PT: Positron Emission Tomography
    - MR: Magnetic Resonance Imaging
    - SEG: Segmentation
    - RTSTRUCT: Radiotherapy Structure
    - RTDOSE: Radiotherapy Dose
    """
    if query_string in ["*", "all"]:
        query_results = self.query_all()
    else:
        queried_modalities = self._get_valid_query(query_string.split(","))
        query_results = self._query(queried_modalities)

    if not group_by_root:
        return query_results

    grouped: dict[SeriesNode, set[SeriesNode]] = defaultdict(set)
    # pretty much start with the root node, then add all branches
    for path in query_results:
        root = path[0]
        grouped[root].update(path[1:])

    # break each item into a list starting with key, then all the values
    return [[key] + list(value) for key, value in grouped.items()]

query_all #

query_all() -> (
    list[list[imgtools.utils.interlacer_utils.SeriesNode]]
)

Simply return ALL possible matches Note this has a different approach than query, since we dont care about the order of the modalities, just that they exist in the Branch

Source code in src/imgtools/dicom/interlacer.py
def query_all(self) -> list[list[SeriesNode]]:
    """Simply return ALL possible matches
    Note this has a different approach than query, since we dont care
    about the order of the modalities, just that they exist in the
    Branch
    """
    results: list[list[SeriesNode]] = []

    def dfs(node: SeriesNode, path: list[SeriesNode]) -> None:
        path.append(node)
        if len(node.children) == 0:
            # If this is a leaf node, check if the path is unique
            # but first, if the path has any 'RTPLAN' nodes, remove them
            # TODO:: create a global VALID_MODALITIES list instead of hardcoding
            cleaned_path = [n for n in path if n.Modality != "RTPLAN"]
            if cleaned_path not in results:
                results.append(cleaned_path)

        for child in node.children:
            dfs(child, path.copy())

    for root in self.root_nodes:
        dfs(root, [])
    return results

visualize_forest #

visualize_forest(
    save_path: str | pathlib.Path,
) -> pathlib.Path

Visualize the forest as an interactive network graph.

Creates an HTML visualization showing nodes for each SeriesNode and edges for parent-child relationships.

Parameters:

Name Type Description Default
save_path #
str | pathlib.Path

Path to save the HTML visualization.

required

Returns:

Type Description
pathlib.Path

Path to the saved HTML visualization

Source code in src/imgtools/dicom/interlacer.py
def visualize_forest(self, save_path: str | Path) -> Path:
    """
    Visualize the forest as an interactive network graph.

    Creates an HTML visualization showing nodes for each SeriesNode and
    edges for parent-child relationships.

    Parameters
    ----------
    save_path : str | Path
        Path to save the HTML visualization.

    Returns
    -------
    Path
        Path to the saved HTML visualization

    Raises
    ------
    OptionalImportError
        If pyvis package is not installed
    """
    return visualize_forest(
        self.root_nodes, save_path=save_path
    )  # call external method.

InterlacerQueryError #

Bases: Exception

Base exception for Interlacer query errors.

MissingDependencyModalityError #

MissingDependencyModalityError(
    missing_dependencies: dict[str, set[str]],
    query_set: set[str],
)

Bases: imgtools.dicom.interlacer.InterlacerQueryError

Raised when modalities are missing their required dependencies.

Source code in src/imgtools/dicom/interlacer.py
def __init__(
    self, missing_dependencies: dict[str, set[str]], query_set: set[str]
) -> None:
    self.missing_dependencies = missing_dependencies
    self.query_set = query_set
    message = self._build_error_message()
    super().__init__(message)

UnsupportedModalityError #

UnsupportedModalityError(
    query_set: set[str], valid_order: list[str]
)

Bases: imgtools.dicom.interlacer.InterlacerQueryError

Raised when an unsupported modality is specified in the query.

Source code in src/imgtools/dicom/interlacer.py
def __init__(self, query_set: set[str], valid_order: list[str]) -> None:
    self.unsupported_modalities = query_set - set(valid_order)
    self.valid_order = valid_order
    msg = (
        f"Invalid query: [{', '.join(query_set)}]. "
        f"The provided modalities [{', '.join(self.unsupported_modalities)}] "
        f"are not supported. "
        f"Supported modalities are: {', '.join(valid_order)}"
    )
    super().__init__(msg)