psychoinformatics-de / remodnav

Robust Eye Movement Detection for Natural Viewing
Other
61 stars 16 forks source link

Lower sampling rates can lead to invalid savgol filter parametrization #19

Closed adswa closed 3 years ago

adswa commented 3 years ago

Based on an inquiry via email.

Invoking remodnav on data with a low sampling rate (120, 250) results in errors such as

C:\Users\scb08600\AppData\Roaming\Python\Python39\Scripts>remodnav.exe C:\Users\scb08600\Desktop\Remodnav\test\test.tsv C:\Users\scb08600\Desktop\Remodnav\test\testausgabe.tsv 0.015 120.0
Traceback (most recent call last):
  File "c:\program files\python39\lib\runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "c:\program files\python39\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\scb08600\AppData\Roaming\Python\Python39\Scripts\remodnav.exe\__main__.py", line 7, in <module>
  File "C:\Users\scb08600\AppData\Roaming\Python\Python39\site-packages\remodnav\__init__.py", line 145, in main
    pp = clf.preproc(
  File "C:\Users\scb08600\AppData\Roaming\Python\Python39\site-packages\remodnav\clf.py", line 832, in preproc
    data[i] = savgol_filter(data[i], savgol_length, savgol_polyord)
  File "C:\Users\scb08600\AppData\Roaming\Python\Python39\site-packages\scipy\signal\_savitzky_golay.py", line 335, in savgol_filter
    coeffs = savgol_coeffs(window_length, polyorder, deriv=deriv, delta=delta)
  File "C:\Users\scb08600\AppData\Roaming\Python\Python39\site-packages\scipy\signal\_savitzky_golay.py", line 97, in savgol_coeffs
    raise ValueError("polyorder must be less than window_length.")
ValueError: polyorder must be less than window_length.

and

"ValueError: window_length must be odd." 

for data sampled with 250Hz.

This is due to the default window length of the savitzgy-golay filter (0.019) being multiplied with the sampling rate to determine the window length with which scipy's Savgol filter is parametrized. The interplay of window length and polynomial order has to comply to certain rules though, and we don't check whether they are fulfilled first.

From scipy's docs:

window_lengthint The length of the filter window (i.e., the number of coefficients). window_length must be a positive odd integer. If mode is ‘interp’, window_length must be less than or equal to the size of x.

polyorderint The order of the polynomial used to fit the samples. polyorder must be less than window_length.

For an immediate quick fix, when using a sampling rate of 120, the --savgol-length option needs to be changed to at least 0.025s (increasing the Window size) to reach a window length greater than the default polynomial order of 2. With a sampling rate of 250, one can increase it to 0.02s to achieve an odd window_length.

From the software side, we should provide a basic sanity check for window_lengths with a note to adjust the --savgol-filter defaults to prevent these errors in the future. From the event classification side, it is unclear what influence a change of the defaults (in the above case, they seem minuscule, in the order of single milliseconds, but there may be cases with larger adjustments), a low sampling rate, and the interaction of those two factors have. I think its very recommended to at least eyeball the raw data for plausibility. The function show_gaze in tests/utils.py could be used for this (there is no command line invocation for that function, it can be used in a Python session, though. Example usage can be found in tests/test_detect.py.

adswa commented 3 years ago

Safe-guarded with #20