Skip to content

SpecPrinter

dcmspec.spec_printer.SpecPrinter

Printer for DICOM specification models.

Provides methods to print a SpecModel as a hierarchical tree or as a flat table, using rich formatting for console output. Supports colorized output and customizable logging.

Source code in src/dcmspec/spec_printer.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
class SpecPrinter:
    """Printer for DICOM specification models.

    Provides methods to print a SpecModel as a hierarchical tree or as a flat table,
    using rich formatting for console output. Supports colorized output and customizable logging.
    """

    def __init__(self, model: object, logger: Optional[logging.Logger] = None) -> None:
        """Initialize the input handler with an optional logger.

        Args:
            model (object): An instance of SpecModel.
            logger (Optional[logging.Logger]): Logger instance to use. If None, a default logger is created.

        """
        if logger is not None and not isinstance(logger, logging.Logger):
            raise TypeError("logger must be an instance of logging.Logger or None")
        self.logger = logger or logging.getLogger(self.__class__.__name__)

        self.model = model
        self.console = Console(highlight=False)

    def print_tree(
        self,
        attr_names: Optional[Union[str, List[str]]] = None,
        attr_widths: Optional[List[int]] = None,
        colorize: bool = False,
    ) -> None:
        """Print the specification model as a hierarchical tree to the console.

        Args:
            attr_names (Optional[Union[str, list[str]]]): Attribute name(s) to display for each node.
                If None, only the node's name is displayed.
                If a string, displays that single attribute.
                If a list of strings, displays all specified attributes.
            attr_widths (Optional[list[int]]): List of widths for each attribute in attr_names.
                If provided, each attribute will be padded/truncated to the specified width.
            colorize (bool): Whether to colorize the output by node depth.

        Example:
            # This will nicely align the tag, type, and name values in the tree output:
            printer.print_tree(attr_names=["elem_tag", "elem_type", "elem_name"], attr_widths=[11, 2, 64])

        Returns:
            None

        """
        for pre, fill, node in RenderTree(self.model.content):
            style = LEVEL_COLORS[node.depth % len(LEVEL_COLORS)] if colorize else "default"
            pre_text = Text(pre)
            if attr_names is None:
                node_text = Text(str(node.name), style=style)
            else:
                if isinstance(attr_names, str):
                    attr_names = [attr_names]
                values = [str(getattr(node, attr, "")) for attr in attr_names]
                if attr_widths:
                    # Pad/truncate each value to the specified width
                    values = [
                        v.ljust(w)[:w] if w is not None else v
                        for v, w in zip(values, attr_widths)
                    ]
                attr_text = " ".join(values)
                node_text = Text(attr_text, style=style)
            self.console.print(pre_text + node_text)

    def print_table(self, colorize: bool = False) -> None:
        """Print the specification model as a flat table to the console.

        Traverses the content tree and prints each node's attributes in a flat table,
        using column headers from the metadata node. Optionally colorizes rows.

        Args:
            colorize (bool): Whether to colorize the output by node depth.

        Returns:
            None

        """
        table = Table(show_header=True, header_style="bold magenta", show_lines=True, box=box.ASCII_DOUBLE_HEAD)

        # Define the columns using the extracted headers
        for header in self.model.metadata.header:
            table.add_column(header, width=20)

        # Traverse the tree and add rows to the table
        for node in PreOrderIter(self.model.content):
            # skip the root node
            if node.name == "content":
                continue

            row = [getattr(node, attr, "") for attr in self.model.metadata.column_to_attr.values()]
            # Skip row if all values are empty or whitespace
            if all(not str(cell).strip() for cell in row):
                continue
            row_style = None
            if colorize:
                row_style = (
                    "yellow"
                    if self.model._is_include(node)
                    else "magenta"
                    if self.model._is_title(node)
                    else LEVEL_COLORS[(node.depth - 1) % len(LEVEL_COLORS)]
                )
            table.add_row(*row, style=row_style)

        self.console.print(table)

__init__(model, logger=None)

Initialize the input handler with an optional logger.

PARAMETER DESCRIPTION
model

An instance of SpecModel.

TYPE: object

logger

Logger instance to use. If None, a default logger is created.

TYPE: Optional[Logger] DEFAULT: None

Source code in src/dcmspec/spec_printer.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def __init__(self, model: object, logger: Optional[logging.Logger] = None) -> None:
    """Initialize the input handler with an optional logger.

    Args:
        model (object): An instance of SpecModel.
        logger (Optional[logging.Logger]): Logger instance to use. If None, a default logger is created.

    """
    if logger is not None and not isinstance(logger, logging.Logger):
        raise TypeError("logger must be an instance of logging.Logger or None")
    self.logger = logger or logging.getLogger(self.__class__.__name__)

    self.model = model
    self.console = Console(highlight=False)

print_table(colorize=False)

Print the specification model as a flat table to the console.

Traverses the content tree and prints each node's attributes in a flat table, using column headers from the metadata node. Optionally colorizes rows.

PARAMETER DESCRIPTION
colorize

Whether to colorize the output by node depth.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
None

None

Source code in src/dcmspec/spec_printer.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def print_table(self, colorize: bool = False) -> None:
    """Print the specification model as a flat table to the console.

    Traverses the content tree and prints each node's attributes in a flat table,
    using column headers from the metadata node. Optionally colorizes rows.

    Args:
        colorize (bool): Whether to colorize the output by node depth.

    Returns:
        None

    """
    table = Table(show_header=True, header_style="bold magenta", show_lines=True, box=box.ASCII_DOUBLE_HEAD)

    # Define the columns using the extracted headers
    for header in self.model.metadata.header:
        table.add_column(header, width=20)

    # Traverse the tree and add rows to the table
    for node in PreOrderIter(self.model.content):
        # skip the root node
        if node.name == "content":
            continue

        row = [getattr(node, attr, "") for attr in self.model.metadata.column_to_attr.values()]
        # Skip row if all values are empty or whitespace
        if all(not str(cell).strip() for cell in row):
            continue
        row_style = None
        if colorize:
            row_style = (
                "yellow"
                if self.model._is_include(node)
                else "magenta"
                if self.model._is_title(node)
                else LEVEL_COLORS[(node.depth - 1) % len(LEVEL_COLORS)]
            )
        table.add_row(*row, style=row_style)

    self.console.print(table)

print_tree(attr_names=None, attr_widths=None, colorize=False)

Print the specification model as a hierarchical tree to the console.

PARAMETER DESCRIPTION
attr_names

Attribute name(s) to display for each node. If None, only the node's name is displayed. If a string, displays that single attribute. If a list of strings, displays all specified attributes.

TYPE: Optional[Union[str, list[str]]] DEFAULT: None

attr_widths

List of widths for each attribute in attr_names. If provided, each attribute will be padded/truncated to the specified width.

TYPE: Optional[list[int]] DEFAULT: None

colorize

Whether to colorize the output by node depth.

TYPE: bool DEFAULT: False

Example

This will nicely align the tag, type, and name values in the tree output:

printer.print_tree(attr_names=["elem_tag", "elem_type", "elem_name"], attr_widths=[11, 2, 64])

RETURNS DESCRIPTION
None

None

Source code in src/dcmspec/spec_printer.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def print_tree(
    self,
    attr_names: Optional[Union[str, List[str]]] = None,
    attr_widths: Optional[List[int]] = None,
    colorize: bool = False,
) -> None:
    """Print the specification model as a hierarchical tree to the console.

    Args:
        attr_names (Optional[Union[str, list[str]]]): Attribute name(s) to display for each node.
            If None, only the node's name is displayed.
            If a string, displays that single attribute.
            If a list of strings, displays all specified attributes.
        attr_widths (Optional[list[int]]): List of widths for each attribute in attr_names.
            If provided, each attribute will be padded/truncated to the specified width.
        colorize (bool): Whether to colorize the output by node depth.

    Example:
        # This will nicely align the tag, type, and name values in the tree output:
        printer.print_tree(attr_names=["elem_tag", "elem_type", "elem_name"], attr_widths=[11, 2, 64])

    Returns:
        None

    """
    for pre, fill, node in RenderTree(self.model.content):
        style = LEVEL_COLORS[node.depth % len(LEVEL_COLORS)] if colorize else "default"
        pre_text = Text(pre)
        if attr_names is None:
            node_text = Text(str(node.name), style=style)
        else:
            if isinstance(attr_names, str):
                attr_names = [attr_names]
            values = [str(getattr(node, attr, "")) for attr in attr_names]
            if attr_widths:
                # Pad/truncate each value to the specified width
                values = [
                    v.ljust(w)[:w] if w is not None else v
                    for v, w in zip(values, attr_widths)
                ]
            attr_text = " ".join(values)
            node_text = Text(attr_text, style=style)
        self.console.print(pre_text + node_text)