blinklinmult.live¶
Rendering a live view: the frame, and a scrolling plot per eye.
What the batch pipeline draws
(draw()) needs a finished
Result – a whole-recording object that does not
exist while frames are still arriving. So the live view draws its own, reusing
blinklinmult.overlay’s colours so the two look like the same product.
Everything here is cv2 into a numpy canvas, not matplotlib. Measured, a
matplotlib redraw of two small subplots costs 6.1 ms per frame against
0.13 ms for a cv2 polyline – a 47x difference, and the display loop only has
about 6 ms of slack once detection and imshow are paid. A plotting library
would spend a third of the remaining headroom drawing two lines.
The layout is the frame on top, and beneath it two subplots at half the frame width each: the viewer’s left eye on the left. A wink then spikes the plot on the same side as the eye that closed, which is the whole point of the view.
Attributes¶
Seconds of score history each plot shows. |
|
Height of one eye's subplot, in pixels. |
|
Near-white behind a plot, matching the readout panel's family. |
|
Light grey for the frame around a plot. |
|
Muted blue for the operating-point rule. |
|
The zero line, so the trace has something to sit on. |
|
The score trace. |
|
Polyline thickness. Two pixels reads clearly without hiding detail. |
|
Padding inside a plot, so the trace never touches the border. |
|
Rows reserved at the top for the label and the current reading. |
Classes¶
A bounded, per-eye history of closure scores. |
Functions¶
|
Draw one eye's scrolling score plot. |
|
Stack the frame above the two eye plots. |
|
Annotate a live frame with the face, the eyes and a readout. |
|
Cut both eye crops into the layout the models take. |
Module Contents¶
- blinklinmult.live.DEFAULT_SECONDS = 10.0¶
Seconds of score history each plot shows.
- blinklinmult.live.PLOT_HEIGHT = 130¶
Height of one eye’s subplot, in pixels.
- blinklinmult.live.PLOT_BACKGROUND = (248, 248, 248)¶
Near-white behind a plot, matching the readout panel’s family.
- blinklinmult.live.GRID_COLOUR = (215, 215, 215)¶
Light grey for the frame around a plot.
- blinklinmult.live.THRESHOLD_COLOUR = (120, 120, 200)¶
Muted blue for the operating-point rule.
Blue rather than grey: on a near-white panel a mid grey line is easy to miss, and a viewer who cannot see the threshold cannot tell whether a spike counted as a blink – which is the whole reason the rule is drawn.
- blinklinmult.live.AXIS_COLOUR = (205, 205, 205)¶
The zero line, so the trace has something to sit on.
Without it an open eye’s near-zero score looks like an empty plot rather than a measured one.
- blinklinmult.live.TRACE_COLOUR = (200, 60, 60)¶
The score trace.
One colour for the whole line. Colouring it by whether the score is above the threshold sounds informative and is not: a single spike at the end repaints the entire history, so the plot’s colour reports only the newest sample while appearing to describe all ten seconds. The threshold rule already shows where the operating point is, and the eye boxes on the frame carry the open/closed state.
- blinklinmult.live.TRACE_WIDTH = 2¶
Polyline thickness. Two pixels reads clearly without hiding detail.
- blinklinmult.live.PLOT_MARGIN = 6¶
Padding inside a plot, so the trace never touches the border.
- blinklinmult.live.LABEL_HEIGHT = 16¶
Rows reserved at the top for the label and the current reading.
- class blinklinmult.live.ScoreHistory[source]¶
A bounded, per-eye history of closure scores.
A
dequewithmaxlenrather than a list: a demo left running for an hour uses exactly the same memory as one left running for a second, because the oldest sample falls off as each new one arrives.- Parameters:
capacity (int) – Samples retained – normally
fps * seconds.
- push(value: float) None[source]¶
Add one score.
- Parameters:
value (float) – Closure score in
[0, 1], ornanfor a frame with no usable eye. A gap, not a zero: zero means “open”, which is a claim the pipeline cannot make when it never saw the eye.
- property latest: float¶
The most recent score.
- Returns:
The last value pushed, or
nanwhen empty.- Return type:
float
- blinklinmult.live.draw_plot(history: ScoreHistory, threshold: float, label: str, width: int, height: int = PLOT_HEIGHT) numpy.ndarray[source]¶
Draw one eye’s scrolling score plot.
The trace fills only the fraction of the width its samples cover, so a two-second-old session does not stretch to look like a full ten seconds of data. Gaps – frames where no eye was found – break the line rather than dropping it to zero.
- Parameters:
history (ScoreHistory) – The scores to draw.
threshold (float) – Operating point, drawn as a horizontal rule so a spike crossing it is visibly a blink rather than just a bump.
label (str) – Shown top-left, e.g.
"left eye".width (int) – Canvas width in pixels.
height (int) – Canvas height in pixels.
- Returns:
(height, width, 3)uint8 RGB.- Return type:
np.ndarray
- blinklinmult.live.compose(frame: numpy.ndarray, left: numpy.ndarray, right: numpy.ndarray) numpy.ndarray[source]¶
Stack the frame above the two eye plots.
- Parameters:
frame (np.ndarray) –
(H, W, 3)uint8 RGB, already annotated.left (np.ndarray) – The viewer’s-left eye plot.
right (np.ndarray) – The viewer’s-right eye plot.
- Returns:
(H + plot_height, W, 3)uint8 RGB, the plots side by side beneath the frame at half its width each.- Return type:
np.ndarray
- blinklinmult.live.draw_overlay(frame: numpy.ndarray, detection, scores: dict[str, float], threshold: float, lines: list[str] | None = None) numpy.ndarray[source]¶
Annotate a live frame with the face, the eyes and a readout.
- Parameters:
frame (np.ndarray) –
(H, W, 3)uint8 RGB. Not modified.detection – A
FaceDetectionfromdetect(), orNonewhen no face was found.scores (dict[str, float]) – Score per side;
nanwhere unknown.threshold (float) – Operating point, deciding an eye box’s colour.
lines (list[str] | None) – Extra readout lines, e.g. fps and head pose.
- Returns:
A new annotated frame.
- Return type:
np.ndarray
- blinklinmult.live.eye_crops(detection, frame: numpy.ndarray, image_size: int = 64) numpy.ndarray | None[source]¶
Cut both eye crops into the layout the models take.
- Parameters:
detection – A
FaceDetectionfromdetect().frame (np.ndarray) –
(H, W, 3)RGB.image_size (int) – Crop side the model expects.
- Returns:
(2, 3, size, size)in[0, 1], orNoneunless both eyes were located. A one-eye batch is refused rather than scored: the caller reads the two scores positionally, so a missing side would silently shift the right eye’s score onto the left.- Return type:
np.ndarray | None