Skip to content

nabu.app.parse_reconstruction_log

[docs] module nabu.app.parse_reconstruction_log

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# ruff: noqa
import re
import numpy as np
from collections import defaultdict
from datetime import datetime
from ..utils import check_supported, convert_str_to_tuple
from .utils import parse_params_values
from .cli_configs import ShowReconstructionTimingsConfig

try:
    import matplotlib.pyplot as plt

    __have_matplotlib__ = True
except ImportError:
    __have_matplotlib__ = False

steps_to_measure = [
    "Reading data",
    "Applying flat-field",
    "Applying double flat-field",
    "Applying CCD corrections",
    "Rotating projections",
    "Performing phase retrieval",
    "Performing unsharp mask",
    "Taking logarithm",
    "Applying radios movements",
    "Normalizing sinograms",
    "Building sinograms",  # deprecated
    "Removing rings on sinograms",
    "Reconstruction",
    "Computing histogram",
    "Saving data",
]


def extract_timings_from_volume_reconstruction_lines(lines):
    regexp = re.compile(r"^(?P<timestamp>\d+-\d+-\d+ \d+:\d+:\d+) - .* - \[0\] (?P<step>.*)$")

    start_timestamp = None
    current_step = None

    res = defaultdict(list)
    for line in lines:
        match = regexp.match(line.strip())
        if match is None:
            continue
        timestamp = datetime.strptime(match["timestamp"], "%d-%m-%Y %H:%M:%S")
        step = match["step"]
        if step not in steps_to_measure:
            continue
        if current_step is not None:
            res[current_step].append((timestamp - start_timestamp).total_seconds())
        start_timestamp = timestamp
        current_step = step

    res[current_step].append((timestamp - start_timestamp).total_seconds())

    return res


def parse_logfile(fname):
    """
    Returns
    -------
    timings: list of dict
        List of dictionaries: one dict per reconstruction in the log file.
        For each dict, the key is the pipeline step name, and the value is the list of timings for the different chunks.
    """
    with open(fname, "r") as f:
        lines = f.readlines()

    start_text = "Going to reconstruct slices"
    end_text = "Reconstruction completed"

    start_line = None
    rec_log_bounds = []
    for i, line in enumerate(lines):
        if start_text in line:
            start_line = i
        if end_text in line:
            if start_line is None:
                raise ValueError("Could not find reconstruction start string indicator")
            rec_log_bounds.append((start_line, i))

    results = []
    for bounds in rec_log_bounds:
        start, end = bounds
        timings = {}
        res = extract_timings_from_volume_reconstruction_lines(lines[start:end])
        for step in steps_to_measure:
            if step in res:
                timings[step] = res[step]
        results.append(timings)
    return results


def display_timings_pie(timings, reduce_function=None, cutoffs=None):
    reduce_function = reduce_function or np.median
    cutoffs = cutoffs or (0, np.inf)

    def _format_pie_text(pct, allvals):
        # https://matplotlib.org/stable/gallery/pie_and_polar_charts/pie_and_donut_labels.html
        absolute = int(np.round(pct / 100.0 * np.sum(allvals)))
        return f"{pct:.1f}%\n({absolute:d} s)"

    for run in timings:
        fig = plt.figure()
        pie_labels = []
        pie_sizes = []
        for step_name, step_timings in run.items():
            t = reduce_function(step_timings)
            if t > cutoffs[0] and t < cutoffs[1]:
                # pie_labels.append(step_name)
                pie_labels.append(step_name + "\n(%d s)" % t)
                pie_sizes.append(t)
        ax = fig.subplots()
        # ax.pie(pie_sizes, labels=pie_labels, autopct=lambda pct: _format_pie_text(pct, pie_sizes)) # autopct='%1.1f%%')
        ax.pie(pie_sizes, labels=pie_labels, autopct="%1.1f%%")

        fig.show()
    input("Press any key to continue")


def parse_reclog_cli():
    args = parse_params_values(
        ShowReconstructionTimingsConfig, parser_description="Display reconstruction performances from a log file"
    )
    if not (__have_matplotlib__):
        print("Need matplotlib to use this utility")
        exit(1)

    display_functions = {
        "pie": display_timings_pie,
    }

    logfile = args["logfile"]
    cutoff = args["cutoff"]
    display_type = args["type"]

    check_supported(display_type, display_functions.keys(), "Graphics display type")
    if cutoff is not None:
        cutoff = list(map(float, convert_str_to_tuple(cutoff)))

    timings = parse_logfile(logfile)
    display_functions[display_type](timings, cutoffs=cutoff)

    return 0


if __name__ == "__main__":
    parse_reclog_cli()
    exit(0)