pyfli.data_vnp.model_selector#

Per-pixel mono- vs bi-exponential model selection (nested-model F-test + AICc).

Classes

ModelSelector(b_bool_mask[, fdr_q, ...])

Per-pixel mono- versus bi-exponential model selection using a nested-model F-test (BH-FDR corrected) combined with an AICc criterion.

class ModelSelector(b_bool_mask, fdr_q=0.05, daicc_min=4.0, tau_bound=12.5, k_mono=4, k_bi=6)[source]#

Bases: object

Per-pixel mono- versus bi-exponential model selection using a nested-model F-test (BH-FDR corrected) combined with an AICc criterion.

Pixels are assigned to one of four classes: mono sufficient, bi justified, bi significant but with a parameter at a fit limit, and bi fit worse than mono (optimiser failure). Class 0 marks pixels outside the ROI or invalid.

Parameters:
  • b_bool_mask (np.ndarray) – ROI mask; only pixels where it is truthy are analysed.

  • fdr_q (float) – False-discovery rate for the F-test.

  • daicc_min (float) – Minimum ΔAICc additionally required for a pixel to be classified as bi.

  • tau_bound (float) – Upper τ fit bound in ns; τ2 near it (or τ1 ≈ 0) flags a parameter at a limit.

  • k_mono (int) – Number of fitted parameters in the mono-exponential model (A, τ, v, h).

  • k_bi (int) – Number of fitted parameters in the bi-exponential model (A, α, τ1, τ2, v, h).

DEFAULT_FITS: ClassVar[dict[str, str]] = {'MLE': 'CPU_MLE_poisson', 'NLSF': 'CPU_NLSF_least_squares'}#
CLASS_NAMES: ClassVar[list[str]] = ['Mono sufficient', 'Bi justified', 'Bi, parameter at limit', 'Bi fit worse than mono']#
CLASS_COLORS: ClassVar[list[str]] = ['#56B4E9', '#D55E00', '#E69F00', '#7f7f7f']#
RC: ClassVar[dict[str, Any]] = {'axes.grid': False, 'axes.labelsize': 7, 'axes.linewidth': 0.6, 'axes.titlesize': 8, 'font.size': 7, 'mathtext.default': 'regular', 'pdf.fonttype': 42, 'svg.fonttype': 'none', 'xtick.labelsize': 6, 'ytick.labelsize': 6}#
static bh_fdr(p)[source]#

Benjamini–Hochberg adjusted p-values (q-values) for a 1-D array.

Parameters:

p (np.ndarray) – Raw p-values.

Returns:

q-values in the original order, clipped to [0, 1].

Return type:

np.ndarray

compare(label, maps_mono, maps_bi)[source]#

Run model selection for one pair of mono/bi fit result maps.

Parameters:
  • label (str) – Name of the fit (e.g. "MLE"); used as the key in results.

  • maps_mono (dict) – Mono-exponential result maps; must contain chi2_map.

  • maps_bi (dict) – Bi-exponential result maps; must contain chi2_map, reduced_chi2_map, tau1_map and tau2_map.

Returns:

class_map, bi_justified, F_map, p_map, q_map, dAICc_map, dDev_map and n_points.

Return type:

dict

compare_from_dir(save_dir, fits=None)[source]#

Load saved mono/bi fit results from save_dir and run compare() on each.

Files are expected as {prefix}_mono-exponential.npy and {prefix}_bi-exponential.npy. Fits missing either file are skipped.

Parameters:
  • save_dir (str) – Directory containing the saved fit results (e.g. saver.save_dir).

  • fits (dict[str, str] | None) – Mapping of label to file prefix; defaults to DEFAULT_FITS.

Returns:

results, keyed by fit label.

Return type:

dict

summary()[source]#

Per-fit summary of class fractions and test statistics.

Returns:

One row per compared fit, indexed by fit label.

Return type:

pd.DataFrame

agreement()[source]#

Jaccard index of the ‘Bi justified’ masks when exactly two fits were compared.

Returns:

Jaccard index, or None if the number of compared fits is not two.

Return type:

float | None

plot(saver=None, name='model_selection_mono_vs_bi')[source]#

Plot one row per fit: ΔAICc map, −log10 q map, class map and class fractions.

Parameters:
  • saver (Any | None) – Optional object with a save_plot(name, fig=..., close=...) method (e.g. pyfli.io.data_saving.DataSaver).

  • name (str) – File name passed to saver.save_plot.

Returns:

The figure, or None if nothing has been compared.

Return type:

matplotlib.figure.Figure | None