Skip to content

Commit e700afc

Browse files
Add validation utilities and extract filter validation
1 parent a1ccb04 commit e700afc

2 files changed

Lines changed: 214 additions & 14 deletions

File tree

pyeyesweb/utils/signal_processing.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,50 @@
88
from scipy.signal import hilbert, butter, filtfilt
99

1010

11+
def validate_filter_params(lowcut, highcut, fs):
12+
"""Validate filter frequency parameters.
13+
14+
Centralized validation for filter parameters used in bandpass_filter
15+
and Synchronization class.
16+
17+
Parameters
18+
----------
19+
lowcut : float
20+
Low cutoff frequency in Hz
21+
highcut : float
22+
High cutoff frequency in Hz
23+
fs : float
24+
Sampling frequency in Hz
25+
26+
Returns
27+
-------
28+
tuple
29+
Validated (lowcut, highcut, fs)
30+
31+
Raises
32+
------
33+
ValueError
34+
If parameters are invalid
35+
"""
36+
# Validate individual parameters
37+
if fs <= 0:
38+
raise ValueError(f"Sampling frequency must be positive, got {fs}")
39+
if lowcut <= 0:
40+
raise ValueError(f"Low cutoff frequency must be positive, got {lowcut}")
41+
if highcut <= 0:
42+
raise ValueError(f"High cutoff frequency must be positive, got {highcut}")
43+
44+
# Validate relationships
45+
if lowcut >= highcut:
46+
raise ValueError(f"Low cutoff ({lowcut}) must be less than high cutoff ({highcut})")
47+
48+
nyquist = fs / 2
49+
if highcut >= nyquist:
50+
raise ValueError(f"High cutoff ({highcut}) must be less than Nyquist frequency ({nyquist})")
51+
52+
return lowcut, highcut, fs
53+
54+
1155
def bandpass_filter(data, filter_params):
1256
"""Apply a band-pass filter if filter_params is set.
1357
@@ -36,22 +80,10 @@ def bandpass_filter(data, filter_params):
3680
if filter_params is None:
3781
return data
3882

39-
lowcut, highcut, fs = filter_params
40-
41-
# Validate filter parameters
42-
if fs <= 0:
43-
raise ValueError(f"Sampling frequency must be positive, got {fs}")
44-
if lowcut <= 0:
45-
raise ValueError(f"Low cutoff frequency must be positive, got {lowcut}")
46-
if highcut <= 0:
47-
raise ValueError(f"High cutoff frequency must be positive, got {highcut}")
48-
if lowcut >= highcut:
49-
raise ValueError(f"Low cutoff ({lowcut}) must be less than high cutoff ({highcut})")
83+
# Use centralized validation
84+
lowcut, highcut, fs = validate_filter_params(*filter_params)
5085

5186
nyquist = 0.5 * fs
52-
if highcut >= nyquist:
53-
raise ValueError(f"High cutoff ({highcut}) must be less than Nyquist frequency ({nyquist})")
54-
5587
low = lowcut / nyquist
5688
high = highcut / nyquist
5789

pyeyesweb/utils/validators.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""Validation utilities for PyEyesWeb.
2+
3+
This module provides common validation functions used across multiple
4+
PyEyesWeb modules to ensure consistent error handling.
5+
"""
6+
7+
8+
def validate_numeric(value, name, min_val=None, max_val=None):
9+
"""Validate numeric parameter with optional bounds checking.
10+
11+
Parameters
12+
----------
13+
value : any
14+
Value to validate
15+
name : str
16+
Parameter name for error messages
17+
min_val : float, optional
18+
Minimum allowed value (inclusive)
19+
max_val : float, optional
20+
Maximum allowed value (inclusive)
21+
22+
Returns
23+
-------
24+
float
25+
Validated numeric value as float
26+
27+
Raises
28+
------
29+
TypeError
30+
If value is not numeric (int or float)
31+
ValueError
32+
If value is outside specified bounds
33+
34+
Examples
35+
--------
36+
>>> validate_numeric(50.0, 'rate_hz', min_val=0.1, max_val=100000)
37+
50.0
38+
>>> validate_numeric(-1, 'phase', min_val=0, max_val=1)
39+
ValueError: phase must be >= 0, got -1
40+
"""
41+
if not isinstance(value, (int, float)):
42+
raise TypeError(f"{name} must be a number, got {type(value).__name__}")
43+
44+
value = float(value)
45+
46+
if min_val is not None and value < min_val:
47+
raise ValueError(f"{name} must be >= {min_val}, got {value}")
48+
49+
if max_val is not None and value > max_val:
50+
raise ValueError(f"{name} must be <= {max_val}, got {value}")
51+
52+
return value
53+
54+
55+
def validate_integer(value, name, min_val=None, max_val=None):
56+
"""Validate integer parameter with optional bounds checking.
57+
58+
Parameters
59+
----------
60+
value : any
61+
Value to validate
62+
name : str
63+
Parameter name for error messages
64+
min_val : int, optional
65+
Minimum allowed value (inclusive)
66+
max_val : int, optional
67+
Maximum allowed value (inclusive)
68+
69+
Returns
70+
-------
71+
int
72+
Validated integer value
73+
74+
Raises
75+
------
76+
TypeError
77+
If value is not an integer
78+
ValueError
79+
If value is outside specified bounds
80+
81+
Examples
82+
--------
83+
>>> validate_integer(100, 'sensitivity', min_val=1, max_val=10000)
84+
100
85+
>>> validate_integer(0, 'max_length', min_val=1)
86+
ValueError: max_length must be >= 1, got 0
87+
"""
88+
if not isinstance(value, int):
89+
raise TypeError(f"{name} must be an integer, got {type(value).__name__}")
90+
91+
if min_val is not None and value < min_val:
92+
raise ValueError(f"{name} must be >= {min_val}, got {value}")
93+
94+
if max_val is not None and value > max_val:
95+
raise ValueError(f"{name} must be <= {max_val}, got {value}")
96+
97+
return value
98+
99+
100+
def validate_boolean(value, name):
101+
"""Validate boolean parameter.
102+
103+
Parameters
104+
----------
105+
value : any
106+
Value to validate
107+
name : str
108+
Parameter name for error messages
109+
110+
Returns
111+
-------
112+
bool
113+
Validated boolean value
114+
115+
Raises
116+
------
117+
TypeError
118+
If value is not a boolean
119+
120+
Examples
121+
--------
122+
>>> validate_boolean(True, 'use_filter')
123+
True
124+
>>> validate_boolean(1, 'output_phase')
125+
TypeError: output_phase must be boolean, got int
126+
"""
127+
if not isinstance(value, bool):
128+
raise TypeError(f"{name} must be boolean, got {type(value).__name__}")
129+
return value
130+
131+
132+
def validate_range(value, name, min_val, max_val):
133+
"""Validate that a value is within a specific range.
134+
135+
Useful for parameters that must be within a specific range like
136+
phase_threshold (0-1), percentages (0-100), etc.
137+
138+
Parameters
139+
----------
140+
value : float or int
141+
Value to validate
142+
name : str
143+
Parameter name for error messages
144+
min_val : float
145+
Minimum allowed value (inclusive)
146+
max_val : float
147+
Maximum allowed value (inclusive)
148+
149+
Returns
150+
-------
151+
float
152+
Validated value
153+
154+
Raises
155+
------
156+
ValueError
157+
If value is outside the specified range
158+
159+
Examples
160+
--------
161+
>>> validate_range(0.7, 'phase_threshold', 0, 1)
162+
0.7
163+
>>> validate_range(1.5, 'probability', 0, 1)
164+
ValueError: probability must be between 0 and 1, got 1.5
165+
"""
166+
if not min_val <= value <= max_val:
167+
raise ValueError(f"{name} must be between {min_val} and {max_val}, got {value}")
168+
return value

0 commit comments

Comments
 (0)