Skip to content

Commit cf0ef6b

Browse files
committed
ENH: Add validation to _check_edf_discontinuity for record times
- Validate n_records matches len(record_times), raise ValueError on mismatch - Validate record_times are numeric (no strings or invalid types) - Check for non-finite values (NaN, inf) and raise ValueError - Warn and sort if record_times are not monotonically increasing - Add comprehensive tests for all validation scenarios
1 parent 694fcaf commit cf0ef6b

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

mne/io/edf/edf.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2497,14 +2497,55 @@ def _check_edf_discontinuity(record_times, record_length, n_records, tolerance=1
24972497
True if gaps exist between records.
24982498
gaps : list of tuple
24992499
List of (onset, duration) tuples for each gap.
2500+
2501+
Raises
2502+
------
2503+
ValueError
2504+
If the number of record times does not match n_records, or if record_times
2505+
contains non-numeric values.
25002506
"""
2507+
# Handle empty/short cases early
25012508
if len(record_times) < 2:
2509+
# Validate n_records match even for edge cases
2510+
if len(record_times) != n_records:
2511+
raise ValueError(
2512+
f"Number of record times ({len(record_times)}) does not match "
2513+
f"expected number of records ({n_records})."
2514+
)
25022515
return False, []
25032516

2517+
# Validate n_records matches
2518+
if len(record_times) != n_records:
2519+
raise ValueError(
2520+
f"Number of record times ({len(record_times)}) does not match "
2521+
f"expected number of records ({n_records})."
2522+
)
2523+
2524+
# Convert to numpy array for validation and ensure numeric
2525+
try:
2526+
times = np.asarray(record_times, dtype=np.float64)
2527+
except (ValueError, TypeError) as e:
2528+
raise ValueError(
2529+
f"record_times must contain numeric values, got error: {e}"
2530+
) from e
2531+
2532+
# Check for NaN or inf values
2533+
if not np.all(np.isfinite(times)):
2534+
raise ValueError("record_times contains non-finite values (NaN or inf).")
2535+
2536+
# Check if times are sorted (monotonically increasing)
2537+
if not np.all(np.diff(times) >= 0):
2538+
warn(
2539+
"record_times are not sorted in monotonically increasing order. "
2540+
"Sorting a copy for gap detection."
2541+
)
2542+
times = np.sort(times)
2543+
2544+
# Detect gaps using validated/sorted times
25042545
gaps = []
2505-
for i in range(len(record_times) - 1):
2506-
expected_next = record_times[i] + record_length
2507-
actual_next = record_times[i + 1]
2546+
for i in range(len(times) - 1):
2547+
expected_next = times[i] + record_length
2548+
actual_next = times[i + 1]
25082549
gap = actual_next - expected_next
25092550

25102551
if gap > tolerance:

mne/io/edf/tests/test_edf.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,38 @@ def test_check_edf_discontinuity():
14481448
assert not has_gaps
14491449
assert gaps == []
14501450

1451+
# Test n_records mismatch validation
1452+
record_times = [0.0, 1.0, 2.0]
1453+
with pytest.raises(ValueError, match="does not match expected number of records"):
1454+
_check_edf_discontinuity(record_times, record_length, 5)
1455+
1456+
# Test n_records mismatch for single record
1457+
record_times = [0.0]
1458+
with pytest.raises(ValueError, match="does not match expected number of records"):
1459+
_check_edf_discontinuity(record_times, record_length, 2)
1460+
1461+
# Test non-numeric values
1462+
record_times = [0.0, "invalid", 2.0]
1463+
with pytest.raises(ValueError, match="must contain numeric values"):
1464+
_check_edf_discontinuity(record_times, record_length, 3)
1465+
1466+
# Test NaN values
1467+
record_times = [0.0, np.nan, 2.0]
1468+
with pytest.raises(ValueError, match="non-finite values"):
1469+
_check_edf_discontinuity(record_times, record_length, 3)
1470+
1471+
# Test inf values
1472+
record_times = [0.0, np.inf, 2.0]
1473+
with pytest.raises(ValueError, match="non-finite values"):
1474+
_check_edf_discontinuity(record_times, record_length, 3)
1475+
1476+
# Test unsorted record_times (should warn and sort)
1477+
record_times = [2.0, 0.0, 1.0, 3.0, 4.0] # Unsorted
1478+
with pytest.warns(RuntimeWarning, match="not sorted"):
1479+
has_gaps, gaps = _check_edf_discontinuity(record_times, record_length, 5)
1480+
assert not has_gaps # After sorting, should be continuous
1481+
assert gaps == []
1482+
14511483

14521484
def test_edf_plus_d_continuous_allowed(tmp_path):
14531485
"""Test that EDF+D files marked discontinuous but actually continuous load OK."""

0 commit comments

Comments
 (0)