Skip to main content

fpm_rs/tabular/tables/
diagnostics.rs

1use std::collections::BTreeMap;
2
3use polars::prelude::*;
4
5use crate::{Result, diagnostics::ReconstructionDiagnostics};
6
7/// Builds one row per recorded convergence-diagnostic iteration.
8pub fn iteration_diagnostics_dataframe(
9    run_id: &str,
10    diagnostics: &ReconstructionDiagnostics,
11) -> Result<DataFrame> {
12    let length = diagnostics.iteration_diagnostics.len();
13    let mut run_ids = Vec::with_capacity(length);
14    let mut iterations = Vec::with_capacity(length);
15    let mut total_objectives = Vec::with_capacity(length);
16    let mut data_objectives = Vec::with_capacity(length);
17    let mut regularization_objectives = Vec::with_capacity(length);
18    let mut object_changes = Vec::with_capacity(length);
19    let mut pupil_changes = Vec::with_capacity(length);
20    let mut median_frame_objectives = Vec::with_capacity(length);
21    let mut worst_frame_objectives = Vec::with_capacity(length);
22    let mut elapsed_seconds = Vec::with_capacity(length);
23    for record in &diagnostics.iteration_diagnostics {
24        run_ids.push(run_id);
25        iterations.push(record.iteration as u64);
26        total_objectives.push(record.total_objective);
27        data_objectives.push(record.data_objective);
28        regularization_objectives.push(record.regularization_objective);
29        object_changes.push(record.object_relative_change);
30        pupil_changes.push(record.pupil_relative_change);
31        median_frame_objectives.push(record.median_frame_objective);
32        worst_frame_objectives.push(record.worst_frame_objective);
33        elapsed_seconds.push(record.elapsed_seconds);
34    }
35    Ok(df!(
36        "run_id" => run_ids,
37        "iteration" => iterations,
38        "total_objective" => total_objectives,
39        "data_objective" => data_objectives,
40        "regularization_objective" => regularization_objectives,
41        "object_relative_change" => object_changes,
42        "pupil_relative_change" => pupil_changes,
43        "median_frame_objective" => median_frame_objectives,
44        "worst_frame_objective" => worst_frame_objectives,
45        "elapsed_seconds" => elapsed_seconds,
46    )?)
47}
48
49/// Builds one row per predicted-versus-reference frame diagnostic record.
50pub fn frame_diagnostics_dataframe(
51    run_id: &str,
52    diagnostics: &ReconstructionDiagnostics,
53) -> Result<DataFrame> {
54    let length = diagnostics.frame_diagnostics.len();
55    let mut run_ids = Vec::with_capacity(length);
56    let mut iterations = Vec::with_capacity(length);
57    let mut frame_indices = Vec::with_capacity(length);
58    let mut illumination_indices = Vec::with_capacity(length);
59    let mut reference_sums = Vec::with_capacity(length);
60    let mut estimate_sums = Vec::with_capacity(length);
61    let mut residual_l1 = Vec::with_capacity(length);
62    let mut residual_l2 = Vec::with_capacity(length);
63    let mut residual_mean = Vec::with_capacity(length);
64    let mut residual_std = Vec::with_capacity(length);
65    let mut residual_max_abs = Vec::with_capacity(length);
66    let mut normalized_l2 = Vec::with_capacity(length);
67    let mut saturated_pixels = Vec::with_capacity(length);
68    for record in &diagnostics.frame_diagnostics {
69        run_ids.push(run_id);
70        iterations.push(record.iteration.map(|value| value as u64));
71        frame_indices.push(record.frame_index as u64);
72        illumination_indices.push(record.illumination_index as u64);
73        reference_sums.push(record.metrics.reference_sum);
74        estimate_sums.push(record.metrics.estimate_sum);
75        residual_l1.push(record.metrics.residual_l1);
76        residual_l2.push(record.metrics.residual_l2);
77        residual_mean.push(record.metrics.residual_mean);
78        residual_std.push(record.metrics.residual_std);
79        residual_max_abs.push(record.metrics.residual_max_abs);
80        normalized_l2.push(record.metrics.normalized_l2);
81        saturated_pixels.push(record.metrics.saturated_pixels.map(|value| value as u64));
82    }
83    Ok(df!(
84        "run_id" => run_ids,
85        "iteration" => iterations,
86        "frame_index" => frame_indices,
87        "illumination_index" => illumination_indices,
88        "reference_sum" => reference_sums,
89        "estimate_sum" => estimate_sums,
90        "residual_l1" => residual_l1,
91        "residual_l2" => residual_l2,
92        "residual_mean" => residual_mean,
93        "residual_std" => residual_std,
94        "residual_max_abs" => residual_max_abs,
95        "normalized_l2" => normalized_l2,
96        "saturated_pixels" => saturated_pixels,
97    )?)
98}
99
100/// Builds one row per raw measured-frame statistics record.
101pub fn raw_frame_statistics_dataframe(
102    run_id: &str,
103    diagnostics: &ReconstructionDiagnostics,
104) -> Result<DataFrame> {
105    let length = diagnostics.raw_frame_statistics.len();
106    let mut run_ids = Vec::with_capacity(length);
107    let mut frame_indices = Vec::with_capacity(length);
108    let mut means = Vec::with_capacity(length);
109    let mut stds = Vec::with_capacity(length);
110    let mut minima = Vec::with_capacity(length);
111    let mut maxima = Vec::with_capacity(length);
112    let mut sums = Vec::with_capacity(length);
113    let mut saturated = Vec::with_capacity(length);
114    let mut zeros = Vec::with_capacity(length);
115    for record in &diagnostics.raw_frame_statistics {
116        run_ids.push(run_id);
117        frame_indices.push(record.frame_index as u64);
118        means.push(record.metrics.mean);
119        stds.push(record.metrics.std);
120        minima.push(record.metrics.min);
121        maxima.push(record.metrics.max);
122        sums.push(record.metrics.sum);
123        saturated.push(record.metrics.saturated_pixels as u64);
124        zeros.push(record.metrics.zero_pixels as u64);
125    }
126    Ok(df!(
127        "run_id" => run_ids,
128        "frame_index" => frame_indices,
129        "mean" => means,
130        "std" => stds,
131        "min" => minima,
132        "max" => maxima,
133        "sum" => sums,
134        "saturated_pixels" => saturated,
135        "zero_pixels" => zeros,
136    )?)
137}
138
139/// Builds long-form run-ID/key/value rows from named scalar diagnostics.
140pub fn scalar_diagnostics_dataframe(
141    run_id: &str,
142    values: &BTreeMap<String, f64>,
143) -> Result<DataFrame> {
144    let length = values.len();
145    let mut run_ids = Vec::with_capacity(length);
146    let mut keys = Vec::with_capacity(length);
147    let mut scalar_values = Vec::with_capacity(length);
148    for (key, &value) in values {
149        run_ids.push(run_id);
150        keys.push(key.as_str());
151        scalar_values.push(value);
152    }
153    Ok(df!(
154        "run_id" => run_ids,
155        "key" => keys,
156        "value" => scalar_values,
157    )?)
158}
159
160/// Builds long-form non-promoted run metadata rows.
161pub fn metadata_dataframe(run_id: &str, values: &BTreeMap<String, String>) -> Result<DataFrame> {
162    const PROMOTED: &[&str] = &[
163        "case_id",
164        "dataset_name",
165        "dataset_version",
166        "preset_name",
167        "algorithm_configuration",
168        "random_seed",
169        "frame_count",
170    ];
171    let length = values.len();
172    let mut run_ids = Vec::with_capacity(length);
173    let mut keys = Vec::with_capacity(length);
174    let mut metadata_values = Vec::with_capacity(length);
175    for (key, value) in values {
176        if PROMOTED.contains(&key.as_str()) {
177            continue;
178        }
179        run_ids.push(run_id);
180        keys.push(key.as_str());
181        metadata_values.push(value.as_str());
182    }
183    Ok(df!(
184        "run_id" => run_ids,
185        "key" => keys,
186        "value" => metadata_values,
187    )?)
188}