pyfli.bayes_utils.param_combinations#

Classes

ParamSelector(freq_acq, irf, decay[, ...])

Evaluate a stack of posterior-sample parameter combinations against measured decay data, and select the best-fitting combination per pixel by a chosen goodness-of-fit metric.

class ParamSelector(freq_acq, irf, decay, model_type='bi-exponential', backend='compute_detailed_results', bool_mask=None)[source]#

Bases: object

Evaluate a stack of posterior-sample parameter combinations against measured decay data, and select the best-fitting combination per pixel by a chosen goodness-of-fit metric.

Parameters:
  • freq_acq (float) – Acquisition frequency (e.g. 80 MHz), passed straight through to compute_detailed_results’ freq_acq argument.

  • irf (np.ndarray) – Passed through to compute_detailed_results unchanged for every sample and for the final best-combination re-fit.

  • decay (np.ndarray) – Passed through to compute_detailed_results unchanged for every sample and for the final best-combination re-fit.

  • bool_mask (np.ndarray | None) – Optional (H, W) boolean mask – e.g. the same ROI mask passed to BiPipeline.run_inference. Not used during sample evaluation/selection itself (compute_detailed_results’ own pixel_health_map is derived from decay, not this mask, so a pixel with background counts outside the real ROI can otherwise look “healthy” even though its params are meaningless placeholders). Stored as the default for compute_best_model_fit_result’s own bool_mask argument, which uses it to NaN-out excluded pixels in the final result’s output maps.

  • model_type (str) – “bi-exponential” or “mono-exponential”.

  • backend (str) – Which implementation reconstructs each sample’s fit/residual/ goodness-of-fit maps: "compute_detailed_results" (default, the rescale-to-measured-totals implementation in pyfli.reconstruction.detailed_results) or "reconstructor" (pyfli.reconstruction.ParamToDecay’s vectorized path). Both return the same {"name", "method", "results": {"maps", "error_maps", "TR_maps"}} shape, so switching backends doesn’t change any downstream code.

METRICS: ClassVar[dict[str, tuple[str, str]]] = {'R2': ('r2_stack', 'max'), 'RMSE': ('rmse_stack', 'min'), 'chi2': ('chi2_stack', 'min'), 'reduced_chi2': ('reduced_chi2_stack', 'min')}#

(stack_key, “min” | “max”)} – whether the metric should be minimized (chi2/reduced_chi2/RMSE) or maximized (R2) to find the best-fitting sample per pixel.

Type:

Registry of {metric

BACKENDS: tuple[str, ...] = ('compute_detailed_results', 'reconstructor')#

Valid values for the backend constructor argument.

REDUCERS: ClassVar[dict[str, Callable]] = {'mean': <function mean>, 'median': <function median>}#

numpy function} for compute_aggregate_model_fit_result’s non-“best” methods – collapses the NUM_SAMPLES axis of each output_combination array to a single per-pixel value. Add an entry here to support another reduction (e.g. “mode”) without touching the method itself.

Type:

Registry of {reducer

evaluate_all_samples(output_combination, keep_per_sample_results=False, progress=True)[source]#

Run compute_detailed_results once per posterior sample.

Parameters:
  • output_combination (dict[str, np.ndarray]) – e.g. {'tau1': (H,W,NUM_SAMPLES), 'tau2': (H,W,NUM_SAMPLES), 'alpha1': (H,W,NUM_SAMPLES)} for bi-exponential, or {'tau': (H,W,NUM_SAMPLES)} for mono-exponential.

  • keep_per_sample_results (bool) – If True, also returns the full compute_detailed_results() dict for every sample (memory-heavy for large images/NUM_SAMPLES). Default False – only the scalar metric stacks are kept.

  • progress (bool) – Show a tqdm progress bar over the NUM_SAMPLES loop. Default True; pass False for quiet use (e.g. a 1x1-pixel crop). The per-sample backend log line is suppressed regardless, so it never competes with the bar.

Returns:

Dictionary with keys:

  • chi2_stack, reduced_chi2_stack, rmse_stack, r2_stack : (H, W, NUM_SAMPLES)

  • per_sample_results : list[dict] or None

Return type:

dict

select_best_combination(output_combination, stacks, metric='RMSE')[source]#

For each pixel independently, pick the sample index that optimizes the chosen metric (minimizes chi2/reduced_chi2/RMSE, maximizes R2), and build the corresponding best-per-pixel parameter maps.

Parameters:
  • output_combination (dict[str, np.ndarray]) – Same dict passed to evaluate_all_samples (each array (H, W, NUM_SAMPLES)).

  • stacks (dict) – Output of evaluate_all_samples.

  • metric (str) – One of “chi2”, “reduced_chi2”, “RMSE”, “R2”.

Returns:

Dictionary with keys:

  • best_params : dict[str, np.ndarray] – best (H, W) map per key in output_combination (e.g. tau1/tau2/alpha1)

  • best_sample_idx : (H, W) int array – which sample index won per pixel

  • best_score : (H, W) float array – the winning metric value per pixel

Return type:

dict

compute_best_model_fit_result(output_combination, metric='RMSE', data_name='BI_MODEL_bi_best', bool_mask=None, stacks=None)[source]#

Full pipeline: evaluate every sample, pick the best per-pixel combination by metric, then re-run compute_detailed_results once more on that best combination.

Parameters:
  • output_combination (dict[str, np.ndarray]) – Same dict passed to evaluate_all_samples.

  • metric (str) – One of “chi2”, “reduced_chi2”, “RMSE”, “R2”.

  • data_name (str) – Dataset name recorded in the returned result dict.

  • stacks (dict | None) – Precomputed evaluate_all_samples() output for this exact output_combination. When given, the per-sample evaluation loop (NUM_SAMPLES reconstructions) is skipped and these stacks are used directly – so a caller that already ran evaluate_all_samples (e.g. to inspect the raw stacks or try several metrics) doesn’t pay for it twice. When None (default) it is computed internally. The stacks must come from the same output_combination; passing mismatched stacks yields wrong selections.

  • bool_mask (np.ndarray | None) – Optional (H, W) boolean mask; defaults to self.bool_mask (set at construction) when not given. Pixels where the mask is False are NaN’d out in every array under result[‘results’][‘maps’], [‘TR_maps’], and [‘error_maps’] – so excluded pixels (e.g. outside the real ROI) can’t be mistaken for ordinary fits downstream, since compute_detailed_results’ own pixel_health_map is derived from decay, not this mask. No masking is applied if both this argument and self.bool_mask are None.

Notes

The return value is exactly the dict compute_detailed_results itself returns – {"name", "method", "results": {"maps", "error_maps", "TR_maps"}} – so it plugs directly into the rest of the workflow (e.g. result['results']['maps'].keys(), result['results']['TR_maps']['fit_map'], saver.save_npy(...), DataViewer, etc.) exactly like any other compute_detailed_results output.

Two extra maps are folded into result['results']['maps']:

  • best_sample_idx_map : (H, W) – which posterior sample (0..NUM_SAMPLES-1) won at each pixel

  • <metric>_selection_map : (H, W) – the winning metric value at each pixel (e.g. reduced_chi2_selection_map)

Per-sample diagnostics (the full metric stacks across all samples, and the chosen metric name) are attached as an additional top-level key, sample_selection, without disturbing the primary "name"/"method"/"results" structure.

Returns:

Same shape as compute_detailed_results()’s return value, plus a top-level sample_selection key holding the raw per-sample stacks and best_params used to produce the final maps.

Return type:

dict

compute_aggregate_model_fit_result(output_combination, method='best', metric='RMSE', data_name='BI_MODEL_bi_aggregate', bool_mask=None, stacks=None)[source]#

Collapse the stack of posterior-sample parameter combinations down to a single per-pixel parameter map via method, then run the configured backend once on that combination.

Parameters:
  • output_combination (dict[str, np.ndarray]) – Same dict passed to evaluate_all_samples/compute_best_model_fit_result (each array (H, W, NUM_SAMPLES)).

  • method (str) –

    How to reduce the NUM_SAMPLES axis to a single per-pixel value:
    • ”best”per-pixel sample that optimizes metric

      (delegates to compute_best_model_fit_result; see that method for the extra ‘best_sample_idx_map’/’sample_selection’ diagnostics only this option adds).

    • any key in REDUCERS (default “mean”, “median”) :

      per-pixel reduction across samples, e.g. the per-pixel mean or median tau/alpha1.

  • metric (str) – Only used when method=”best”; one of “chi2”, “reduced_chi2”, “RMSE”, “R2” (see METRICS).

  • data_name (str) – Dataset name recorded in the returned result dict.

  • bool_mask (np.ndarray | None) – Optional (H, W) boolean mask; defaults to self.bool_mask (set at construction) when not given. Forwarded to compute_best_model_fit_result for method=”best”; for the reducer methods, applied the same way directly here – see compute_best_model_fit_result’s bool_mask parameter for what it does.

  • stacks (dict | None) – Only used when method=”best”; forwarded to compute_best_model_fit_result to skip the per-sample evaluation loop when the caller already has its output. Ignored by the reducer methods, which never score individual samples.

Returns:

dict – compute_best_model_fit_result for the exact structure notes). For method=”best” this is exactly compute_best_model_fit_result’s return value, including its extra ‘sample_selection’ key; the other methods have no per-sample selection to report, so that key is simply absent.

Return type:

same shape as compute_detailed_results()'s return value (see