kirtonBCIlab / bci-essentials-python

Python backend for BCI Essentials
Mozilla Public License 2.0
11 stars 7 forks source link

Make Bessy more resilient to bad data #88

Closed gregbci closed 10 months ago

gregbci commented 10 months ago

B4K-156 B4K-243

I investigated how Bessy handled reading no data or invalid data from a source. I found that bad data (or empty arrays) would end up causing issues related to array dimensions.

For example, when [] is used to init NDArray, the result is an empty 1D array. This is a valid array, but it is not valid for eeg or markers - they must be 2D, even if empty. The existing code would allow this invalid array to flow through Bessy where it would eventually cause a problem. The error message in B4K-156 is an example. I was able to crash mi_unity_backend in a similar way just by pausing eeg_lsl_sim for a few seconds (Bessy would get [] from the eeg source).

Since bad data can come from any source, I refactored EegData such that Invalid data is discarded with a warning. No data is just ignored (not an error). There are now some unit tests to check that this works.

I updated the LSL and XDF sources to properly return [[]] when there are no samples available (or pull_chunk() returns None).

To help future Bessy devs, I cleaned up the EegSource and MarkerSource interface to call out where a 2D list is required.

To make unit tests more stable, I broke the dependency on examples/data, preferring to give the unit tests their own copy. This way examples can change without breaking tests.

ekinney-lang commented 10 months ago

Wondering about if there is any potential edge case issues that might happen since we are putting "0" values in instead of empty values for Lines 121-124 in the initialization step. Is there a downside to using np.empty() here instead?

        # Initialize data and timestamp arrays to the right dimensions, but zero elements
        self.marker_data = np.zeros((0, 1))
        self.marker_timestamps = np.zeros((0))
        self.eeg_data = np.zeros((0, self.nchannels))
        self.eeg_timestamps = np.zeros((0))