Source code for pyfli.data_text.msg_display

"""
Format fitting parameters, session settings, and pixel summaries for display or logging.

This module belongs to :mod:`pyfli.data_text` and is part of PyFLI text display helpers
used by interactive fitting workflows. Public API includes classes
:class:`MessageDisplay`.
"""

from typing import Any, ClassVar

import numpy as np

from pyfli import logging


[docs] class MessageDisplay: """ Format fitting parameters, session settings, and pixel summaries for notebook or console display. An optional saver can persist the same messages alongside analysis outputs. Parameters ---------- saver : Any | None Optional object responsible for persisting display text or outputs. """ def __init__(self, saver: Any | None = None) -> None: self.saver = saver def _internal_log(self, message: Any) -> None: """ Run the internal log routine. Parameters ---------- message : Any Message text displayed to the user. Returns ------- None No object is returned; the function perform internal log. """ if self.saver: self.saver.log(message) else: logging.info(message)
[docs] def disp_params( self, res_px: np.ndarray, model_type: str = "bi-exponential" ) -> None: """ Run the disp params routine. Parameters ---------- res_px : np.ndarray Fit result dictionary for one pixel. model_type : str FLI model family, such as mono- or bi-exponential. Returns ------- None No object is returned; the function perform disp params. """ if not res_px: raise ValueError("Data was not provided (res_px is empty or None)") try: p, err = res_px[0], res_px[1] r2, chi2, red_chi2 = res_px[2], res_px[3], res_px[4] conv = res_px[6] except IndexError: raise IndexError("res_px does not have the expected number of elements.") # Build output string output = [] output.append("\n" + "=" * 30) output.append(f"FIT PARAMETERS ({model_type.upper()})") output.append("-" * 30) labels = ( ["photon_counts", "alpha1", "tau1", "tau2", "v-shift"] if model_type == "bi-exponential" else ["photon_counts", "tau", "v-shift"] ) for i, label in enumerate(labels): output.append(f"{label:8}: {p[i]:.4f} \u00b1 {err[i]:.4f}") output.append("-" * 30) output.append(f"R2 : {r2:.4f}") output.append(f"chi2 : {chi2:.4f}") output.append(f"Reduced chi2 : {red_chi2:.4f}") output.append(f"Convergence : {conv}") output.append("=" * 30 + "\n") # Display and Log full_msg = "\n".join(output) self._internal_log(full_msg)
[docs] def fit_session(self, **kwargs: Any) -> None: """ Fit session. Parameters ---------- **kwargs : Any Additional keyword options forwarded to the underlying implementation. Returns ------- None No object is returned; the function fit session. """ pretty_labels = { "model_type": "Decay Model", "processor_name": "Processor", "fitter_name": "Fitting Method", "p0": "Initial Guesses (p0)", "use_initial_guess": "Using Guess", "use_bounds": "Using Bounds", } header = "\n" + "-" * 60 + f"\n{'SESSION CONFIGURATION':^60}\n" + "-" * 60 self._internal_log(header) # Log parameters via save_params if saver exists for structured logging if self.saver: self.saver.save_params(**kwargs) for key, value in kwargs.items(): label = pretty_labels.get(key, key.replace("_", " ").capitalize()) self._internal_log(f"{label:25}: {value}") footer = "-" * 60 + f"\n{'Session Initialized':^60}\n" + "-" * 60 + "\n" self._internal_log(footer)
# Fixed display order: label → candidate map keys (first match wins) _PIXEL_FIELDS: ClassVar[list[tuple[str, list[str]]]] = [ ("A", ["photon_count_map"]), ("α", ["alpha1_map", "alpha_map"]), ("τ₁", ["tau1_map", "tau_map"]), ("τ₂", ["tau2_map"]), ("R²", ["R2_map"]), ("Red.χ²", ["reduced_chi2_map"]), ("Raw.χ²", ["chi2_map"]), ("Pearson", ["pearson_reduced_chi2_map"]), ("v-shift", ["v_shift_map"]), ("h-shift", ["h_shift_map"]), ]
[docs] def get_pixel_summary(self, data_maps: np.ndarray, px: np.ndarray) -> np.ndarray: """ Return pixel summary. Parameters ---------- data_maps : np.ndarray Dictionary of parameter maps used to summarize a pixel. px : np.ndarray Pixel column coordinate. Returns ------- np.ndarray Per-pixel summary values for the requested coordinate. """ x, y = px rows = [] for label, candidates in self._PIXEL_FIELDS: val = "—" for key in candidates: m = data_maps.get(key) if isinstance(m, np.ndarray) and m.ndim == 2: try: v = m[x, y] val = f"{float(v):.4f}" except Exception: val = "error" break rows.append((label, val)) label_w = max(len(lbl) for lbl, _ in rows) rule = "─" * (label_w + 14) lines = [f"\n Pixel {px}", f" {rule}"] for label, val in rows: lines.append(f" {label:<{label_w}} {val}") lines.append(f" {rule}\n") output = "\n".join(lines) logging.info(output) if self.saver: self.saver.log(output) return rows