Troubleshooting and diagnostics¶
Diagnose the earliest failing layer¶
A downstream error is often only a symptom. For example, an empty event-locked table can originate from missing TTL edges, mismatched clocks, wrong time units, or grouping identifiers that do not overlap. Changing the event window first can hide the cause rather than solve it.
Minimal triage scaffold¶
Start with a bounded copy of the same data that failed. Do not immediately rewrite the pipeline.
import gpbiometricspy as gp
# `dat` should be the same standardized table that exposed the problem.
schema = gp.detect_gazepoint_biometric_schema(dat)
timebase = gp.detect_gazepoint_biometric_timebase(
dat,
time_col="TIME",
counter_col="CNT",
)
missing = gp.summarize_gazepoint_missingness(
dat,
signal_cols=["GSR_US", "HR", "IBI", "LPMM"],
)
activity = gp.audit_gazepoint_signal_activity(
dat,
signal_cols=["GSR_US", "HR", "IBI", "LPMM"],
group_cols=["participant_id"],
)
resets = gp.audit_gazepoint_time_resets(
dat,
time_col="TIME",
group_cols=["participant_id"],
)
readiness = gp.run_gazepoint_biometrics_real_data_readiness(
dat,
min_rows=100,
)
Inspect the returned objects rather than reducing them to a single pass/fail label. In particular, audit_gazepoint_signal_activity() and audit_gazepoint_time_resets() return structured audit objects with detailed tables that explain the summary status.
Symptom → diagnostic → action¶
| Symptom | Check first | Useful route | Do not do first |
|---|---|---|---|
| Expected channel is absent | schema and source export | detect_gazepoint_biometric_schema() + new-dataset validation |
rename an unrelated column to satisfy code |
| Channel exists but is unusable | missingness and signal activity | summarize_gazepoint_missingness() + audit_gazepoint_signal_activity() |
tune detector thresholds |
| Timing looks irregular | observed time deltas/resets | detect_gazepoint_biometric_timebase() + audit_gazepoint_time_resets() |
resample immediately |
| Event table is empty | marker values and edge semantics | extract_gazepoint_ttl_events() + multimodal example |
enlarge response windows |
| Event-locked rows are empty | event clock, signal clock, group overlap | Timebase and alignment | assume equal clocks from matching labels |
| PPG/HRV result looks implausible | interval source, units, accepted/rejected peaks | PPG / HRV example | treat sampled HR as RR/NN intervals |
| Pupil/gaze summary changes unexpectedly | validity, interpolation, AOI denominator | Pupil / gaze / AOI example | interpret change as attention or preference |
| Model fails or predictions look strange | analysis unit, grouping, outcome support | Choose a modelling strategy | simplify grouping without scientific justification |
| Results cannot be reproduced later | settings, software identity, retained QC | Reporting and reproducibility | reconstruct settings from memory |
Scenario 1 — a signal is present but effectively inactive¶
A column can exist while containing only zeros, a constant, too few non-zero values, or almost entirely missing observations. Diagnose activity by group before processing.
activity = gp.audit_gazepoint_signal_activity(
dat,
signal_cols=["GSR_US", "HR", "IBI", "LPMM"],
group_cols=["participant_id"],
)
print(activity["overview"])
print(activity["signal_by_group"])
print(activity["inactive_groups"])
print(activity["inactive_signals"])
If a modality is inactive for an entire participant/session, downstream decomposition or feature extraction should not manufacture apparent evidence from the absence of signal variation. Preserve the inactive status as part of QC.
Scenario 2 — timestamps reset or repeat¶
Use both a broad timebase detector and explicit row/segment diagnostics.
timebase = gp.detect_gazepoint_biometric_timebase(
dat,
time_col="TIME",
counter_col="CNT",
)
resets = gp.audit_gazepoint_time_resets(
dat,
time_col="TIME",
group_cols=["participant_id"],
)
print(resets["overview"])
print(resets["segment_summary"])
print(resets["row_flags"].head())
A reset can indicate a new segment/session rather than a bad row. Keep the original clock evidence and determine the correct grouping/segment interpretation before creating a continuous time axis.
Scenario 3 — TTL events are missing or duplicated¶
Extract the event evidence before alignment.
events = gp.extract_gazepoint_ttl_events(
dat,
ttl_columns=["TTL0"],
group_columns=["participant_id"],
)
print(events.head())
print(events[["participant_id", "event_order", "ttl_value"]])
Check whether the channel encodes rising edges, changes, sustained active periods, or another acquisition-specific convention. A technically valid numeric marker is not automatically a scientifically valid event label.
Scenario 4 — event locking produces no rows¶
Diagnose four things in order:
- the event table actually contains the intended events;
- the event timestamps and signal timestamps use compatible units;
- the event and signal rows refer to the same participant/session/trial groups;
- the requested window overlaps recorded samples.
Only after those checks should you consider widening a window. If two streams come from different clocks, use explicit clock-alignment evidence rather than forcing them into one axis.
Scenario 5 — a model fits but answers the wrong question¶
Successful optimization does not guarantee a defensible estimand. Verify:
- the row represents the intended scientific analysis unit;
- repeated observations retain participant/item/trial identifiers;
- crossed and nested structures are not silently collapsed;
- holdout units match the claimed generalisation target;
- conditional predictions for observed groups are not reported as population predictions for unseen groups;
- outcome support matches the selected family.
Use the modelling decision guide before interpreting coefficients, feature importance, or predictive performance.
Build a useful diagnostic report¶
When asking for help or filing an issue, provide a minimal evidence bundle rather than only the final exception text.
diagnostic-report/
├── package-python-versions.txt
├── schema-summary.txt
├── timebase-summary.txt
├── signal-activity-overview.csv
├── signal-activity-by-group.csv
├── time-reset-overview.csv
├── time-reset-segments.csv
├── event-preview.csv
├── minimal-reproduction.py
└── error-traceback.txt
For private research data, do not attach participant-level raw exports merely to make a bug report complete. Prefer a minimal synthetic/reduced reproduction plus structural summaries unless the data can be shared under the applicable governance rules. See Private real-data validation.
What information makes a bug reproducible?¶
Include:
gpbiometricspyversion and Python version;- operating system when the behavior may be platform-specific;
- exact function call and relevant parameter values;
- shape and column names of the minimal input;
- grouping/time/event column names and units;
- complete exception type and message;
- whether the issue reproduces with bundled synthetic data;
- the smallest code path that still triggers it;
- any QC warning that appeared before the failure.
Exclude secrets, credentials, participant identifiers, and private raw data unless you have a legitimate approved route to share them.
Stop conditions¶
Recovery routes¶
- Unknown schema or channel identity: Validate a new dataset
- Clock/reset/alignment problem: Timebase and alignment
- EDA/SCR problem: EDA / GSR / SCR example
- PPG/HRV problem: PPG / HRV example
- Pupil/gaze/AOI problem: Pupil / gaze / AOI example
- QC/reporting problem: Quality control + reporting example
- Model/design problem: Choose a modelling strategy
- Replay/reproducibility problem: Reporting and reproducibility
- Full frozen R-companion troubleshooting workflow: Troubleshooting readiness
Want a known-good baseline?
Reproduce the synthetic demo or the runnable EDA example unchanged. If the bundled example works but the research export fails, the difference is evidence: compare schema, units, grouping, timing, event coverage, and signal activity before changing algorithms.