gaze-eye-metrics (Processing Node)
The Gaze Eye Metrics node classifies 3D gaze direction data into spatial zones (Far, Near, Left, Right) using KMeans clustering with Mahalanobis distance augmentation. It produces an overall gaze distribution, raw gaze vectors, and a per-second timeline of zone percentages. When an optional phase-boundaries parent is connected, results are also computed per phase.
This node is domain-agnostic — it operates on any upstream payload that supplies three gaze direction columns and a timestamp — and is currently hosted inside the Aviation Metrics runner.
Single-user scope: The classification pipeline assumes all gaze samples belong to a single participant. If the upstream payload contains gaze data from multiple users, the clustering will conflate them and produce incorrect results. For multi-user sessions, pre-filter the data to a single entity upstream (e.g. via
json-templateortabular-query) before feeding it into this node.
Configuration Schema
All properties are optional. The node works with an empty configuration ({}).
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
gazeColumns | object | No | COMBINED columns | Column name overrides for the three gaze direction axes. Object with keys x, y, z mapping to column names in the bio payload. |
gazeColumns.x | string | No | "COMBINED GazeDirection X (Float)" | Column for the X (lateral) gaze axis. Used for Left/Right classification. |
gazeColumns.y | string | No | "COMBINED GazeDirection Y (Float)" | Column for the Y (vertical) gaze axis. |
gazeColumns.z | string | No | "COMBINED GazeDirection Z (Float)" | Column for the Z (depth) gaze axis. Used for Far/Near classification. |
timestampColumn | string | No | "CAPTURED DATE TIME (IS0 8601)" | Name of the ISO 8601 timestamp column. Falls back to TIMESTAMP if the primary column is absent. |
nClusters | integer (>= 4) | No | 5 | Number of KMeans clusters. The four gaze labels (Far, Near, Left, Right) are assigned to the most representative clusters; any extra clusters are merged into the nearest label. |
timelineResolution | string | No | "1S" | Pandas-compatible frequency string for timeline bucketing (e.g. "1S", "500ms", "2S"). |
primaryInputNode | string | No | "rapid-preprocess" | Name of the parent node supplying the bio payload (the key used in input_map). Override this when reusing the node outside a RAPID context. |
phaseInputNode | string | No | "phase-segment" | Name of the optional parent node supplying phase boundaries. Override when using a different phase-segmentation node. |
Expected Upstream Input
Parent 1 (required): bio input (default: rapid-preprocess)
A JSON object containing a bio_input key with an array of sample objects. Each sample must include the configured gaze direction columns and a timestamp:
{
"bio_input": [
{
"COMBINED GazeDirection X (Float)": 0.12,
"COMBINED GazeDirection Y (Float)": -0.03,
"COMBINED GazeDirection Z (Float)": 0.99,
"CAPTURED DATE TIME (IS0 8601)": "2025-06-15T10:00:00.000Z"
}
]
}Parent 2 (optional): phase boundaries (default: phase-segment)
A JSON object keyed by phase name. Each value has the same shape as the root payload above (containing its own bio_input array). When present, the node produces per-phase results in addition to the full-session result.
{
"take_off": { "bio_input": [ ... ] },
"downwind": { "bio_input": [ ... ] },
"final_approach": { "bio_input": [ ... ] }
}Required Upstream Columns
| Column (default name) | Type | Notes |
|---|---|---|
COMBINED GazeDirection X (Float) | float | Lateral axis. Override via gazeColumns.x. |
COMBINED GazeDirection Y (Float) | float | Vertical axis. Override via gazeColumns.y. |
COMBINED GazeDirection Z (Float) | float | Depth axis. Override via gazeColumns.z. |
CAPTURED DATE TIME (IS0 8601) | ISO 8601 string | Override via timestampColumn. Falls back to TIMESTAMP if absent. |
Behaviour
- The processor reads the upstream bio payload (from
primaryInputNode, defaultrapid-preprocess) and extractsbio_input. - Raw gaze vectors (
x,y,z,timestamp) are assembled into thegaze_data.valuesoutput. - The bio samples are passed to the gaze classification pipeline:
a. A
Circuit_Numbercolumn is added if absent (defaults to circuit 1). b. Data is split by circuit number. c. For each circuit, gaze feature columns are cleaned: coerced to numeric, rows with all-NaN gaze are dropped, remaining NaNs are interpolated then median-imputed. d. Features are standardised and a Mahalanobis distance from the global centroid is appended as a fourth feature. e. KMeans clustering is run on the augmented feature space (default 5 clusters). f. Clusters are labelled: highest mean Z = Far, lowest Z = Near, highest mean X = Right, lowest X = Left. Extra clusters are assigned to their nearest label. g. If any of the four labels is missing, ~1% of data points are reassigned from the nearest feature-space neighbours. - Per-bucket (default per-second) gaze label percentages are computed to form the timeline.
- Overall gaze percentages are computed across all samples.
- If a phase-boundaries parent is connected, steps 1—5 are repeated independently for each phase slice.
Output Artefact
The node outputs JSON. The top-level key full_session is always present. When phase data is available, additional keys are added for each phase name.
{
"full_session": {
"gaze_data": {
"values": [
{ "x": 0.12, "y": -0.03, "z": 0.99, "timestamp": "2025-06-15T10:00:00.000Z" }
]
},
"gaze_percentages": {
"Near": 25.0,
"Left": 15.0,
"Right": 20.0,
"Far": 40.0
},
"gaze_timeline": [
{
"second": "2025-06-15T10:00:00+00:00",
"Near": 20.0,
"Left": 10.0,
"Right": 30.0,
"Far": 40.0
}
]
},
"take_off": { "..." : "same structure as full_session" },
"downwind": { "..." : "same structure as full_session" }
}When no valid gaze data is available for a block, the output uses stable defaults: gaze_data: {}, all percentages at 0.0, and an empty gaze_timeline.
Example Configuration
Minimal (all defaults):
{}Customised (non-RAPID context):
{
"gazeColumns": {
"x": "EyeTracker_GazeX",
"y": "EyeTracker_GazeY",
"z": "EyeTracker_GazeZ"
},
"timestampColumn": "sample_timestamp",
"nClusters": 6,
"timelineResolution": "500ms",
"primaryInputNode": "my-bio-preprocess",
"phaseInputNode": "my-phase-splitter"
}Integration in a Session Template
- Place a
gaze-eye-metricsnode in your session template. - Connect a parent node that produces a
{ "bio_input": [...] }JSON payload with gaze direction columns. By default the processor looks forrapid-preprocess; setprimaryInputNodein config to use a different parent. - Optionally connect a phase-boundaries parent to enable per-phase scoring. By default the processor looks for
phase-segment; setphaseInputNodein config to use a different parent. - Supply configuration to override gaze column names, cluster count, timeline resolution, or parent node names. Leave the config empty (
{}) for default RAPID behaviour. - Connect downstream consumers:
- Dashboard transforms (e.g.
am-transform-circuits-dashboard,rapid-transform-intercept-dashboard) — readgaze_percentagesfor eye-tracking chart panels. timeline-metrics— can consumegaze_timelinefor unified timeline rendering.
- Dashboard transforms (e.g.