blinklinmult.fit

Fit an operating point for a model that never had one.

BlinkCNN’s thresholds were fitted on validation during training. The 1.x models were released as weights alone, so MODELS records the plain 0.5 default for them – a placeholder, not a measurement.

That gap makes event comparisons between the generations unfair: v2’s operating point was tuned and theirs was not. This module closes it. Give it scored recordings with their annotated blinks and it sweeps the same grids the training callback uses, returning the pair that maximises event F1:

from blinklinmult.fit import fit_operating_point

point = fit_operating_point(signals, annotations, fps=30.0)
detector.spec = point.applied_to(detector.spec)

Fit on validation, never on test. A threshold chosen on the same recordings it is reported on is not a measurement, it is a description of that split – and it will not survive contact with new data. The function does not enforce this; it cannot see where the signals came from.

Hysteresis usually wins, so it is searched by default. A single cut has to be low enough to catch the shallow start of a closure and high enough to ignore noise, and no one value does both. Two thresholds split those jobs: the high one decides whether a run is a blink, the low one decides where it starts and ends. On BlinkCNN this was worth 0.19 to 0.52 event F1.

Attributes

logger

Module-level logger.

DEFAULT_LOW_RATIOS

Low-threshold fractions searched, mirroring the training callback's grid.

Exceptions

FitError

Raised when there is not enough data to fit an operating point.

Classes

OperatingPoint

A fitted decision rule and the score it achieved.

Functions

fit_operating_point(→ OperatingPoint)

Search for the threshold pair that maximises event F1.

Module Contents

blinklinmult.fit.logger

Module-level logger.

blinklinmult.fit.DEFAULT_LOW_RATIOS: tuple[float | None, ...]

Low-threshold fractions searched, mirroring the training callback’s grid.

None comes first so a single threshold wins ties: hysteresis costs a second parameter, and it should have to earn its place rather than be adopted on a rounding difference.

exception blinklinmult.fit.FitError[source]

Bases: Exception

Raised when there is not enough data to fit an operating point.

Initialize self. See help(type(self)) for accurate signature.

class blinklinmult.fit.OperatingPoint[source]

A fitted decision rule and the score it achieved.

Parameters:
  • threshold (float) – The high threshold – whether a run is a blink.

  • low_ratio (float | None) – Low threshold as a fraction of threshold, or None for a single cut.

  • f1 (float) – Event F1 at this point, on the data it was fitted to.

  • precision (float) – Event precision there.

  • recall (float) – Event recall there.

property low_threshold: float | None

The absolute low threshold, or None for a single cut.

applied_to(model_spec: blinklinmult.registry.ModelSpec) → blinklinmult.registry.ModelSpec[source]

Copy a spec with this operating point substituted in.

Parameters:

model_spec (ModelSpec) – The spec to update.

Returns:

A new spec carrying the fitted thresholds.

Return type:

ModelSpec

blinklinmult.fit.fit_operating_point(signals: list[numpy.ndarray], annotations: list[list[blinklinmult.train.events.Interval]], thresholds: numpy.ndarray | None = None, low_ratios: tuple[float | None, ...] | None = None) → OperatingPoint[source]

Search for the threshold pair that maximises event F1.

Parameters:
  • signals (list[np.ndarray]) – One per-frame score array per recording, in [0, 1]. Produced by score().

  • annotations (list[list[Interval]]) – The annotated blink intervals for each, in the same order.

  • thresholds (np.ndarray | None) – High thresholds to try; DEFAULT_THRESHOLDS by default.

  • low_ratios (tuple[float | None, ...] | None) – Low ratios to try; DEFAULT_LOW_RATIOS by default.

Returns:

The best pair found, with its scores.

Return type:

OperatingPoint

Raises:

FitError – If the inputs disagree in length, are empty, or hold no annotated blink to fit against.