- Please, if you will use scripts, information or anything from this repository, please give us some credits.
+ We will appreciate it!
- https://www.linkedin.com/in/lucasbaldezzari/
+ Thanks!
Haackathon de BCI para comandar dispositivos utilizando Imaginería Motora.
El siguiente repositorio será utilizado para almacenar bibliografía, set de datos, firmware, hardware, imágenes, entre otros recursos correspondiente al hackathon de BCI 2022/2023 de la UTEC, Uruguay.
NOTA: Queda pendiente mejorar el resumen del proyecto.
Demostración del sistema al día 23/5/2023.
Puedes visitarnos en Discord en nuestro canal Uruguay BCI.
En esta sección se encuentra el código fuente de los diferentes scripts en python para implementar la ICC.
La siguiente imagen muestra el diagrama de bloques V1.4 de la BCI.
A continuación, se resume lo contenido dentro de cada directorio.
Este módulo es el gestor y administrador de los principales procesos de la BCI, entre los que podemos destacar la escritura/lectura de datos -EEG, clasificadores-, gestión/escritura/lectura de eventos relevantes, procesamiento de EEG (online), gestión de las diferentes etapas de un trial. Se comunica con todos los módulos.
Este módulo es el encargado de gestionar los siguientes procesos,
Bloque para comunicación con placas OpenBCI a través de Brainflow. Este bloque permite la adquisición y registro de señales de EEG (rawEEG), información del acelerómetro, entre otros. Se comunica con el bloque de procesamiento de señal.
Esta gestión se hace con la clase EEGLogger.py.
A continuación de mencionan las funcionalidades y clases del bloque,
Se aplican filtros pasabanda y notch a señal proveniente del bloque EEGLogger con la clase Filter.py.
Esta clase aplica un filtrado espacial a través de Common Spatial Pattern. La misma hace uso de la clase CSP, pero se le agregan algunos métodos adicionales. La clase engargada es CSPMulticlass.
La clase recibe los datos filtrados por Filter.py y genera una lista de filtros CSP. La cantidad de filtros espaciales creados depende, en primera instancia de la cantidad de clases que se tenga, y también del tipo de comparación que se quiere hacer.
Se pueden utilizar dos enfoques para generar y aplicar los filtros espaciales. Estos son,
Cuando se llama a transform(signal) el método aplica cada filtro dentro csplist a los datos dentro de signal y retorna un array con estas proyecciones concatenadas de tal forma que el array retornado tiene la forma [n_trials, n_components x n_filters, n_samples].
A modo de ejemplo, si tenemos 4 clases, y entrenamos un CSPMulticlass para dos componentes y con método ovo, deberíamos tener $2{componentes}\times6{filtros} = 12$ nuevas dimensiones, entonces el array que entregará CSPMulticlass al aplicar transform es [n_trials, 12, n_samples].
La extracción de características está a cargo del módulo FeatureExtractor.py.
La extracción de caraterísticas puede hacerse por dos métodos, uno es obteniendo la densidad espectral de potencia por método de Welch y la otra es la envolvente de la señal de EEG a través de la transformada de Hilbert.
Se plantean dos enfoques para la aplicación del CSP y la extracción de características. Una es la estrategia OneVsOne y la otra es OneVsRest.
La siguiente figura (adaptada de Multiclass Classification Based on Combined Motor Imageries) muestra un diagrama de aplicación de CSP y extracción de características para la fase de entrenamiento, las fases de feedback y online son similares, sólo que los CSP no se entrenan, sino que se utilizan filtros previamente entrenados durante la fase de entrenamiento.
Se utilizan las señales de EEG previamente filtradas (pasabanda y notch), trials y labels para obtener los filtros espaciales que proyectarán el EEG a un nuevo espacio. La cantidad de filtros espaciales a obtener está en función del número de clases según la cantidad de clases y el método de comparación seleccionado.
A partir de las salidas de estos filtros se extraen sus características con FeatureExtractor.py y se concatenan cada una de estas para formar el feature vector final.
El entrenamiento y aplicación de filtrado por CSP está a cargo de CSPMulticlass.py.
La concatenación de las features en un único feature se hace con la clase RavelTransformer.
La clase CSPMulticlass posee métodos para graficar los mapas topográficos referentes a los filtros y patrones obtenidos a partir de entrenar la clase con fit().
La cantidad de patrones o filtros a graficar depende de la cantidad de clases, de la cantidad de componentes y de si los CSP se obtienen a partir de entrenar las clases one vs one o one vs all.
Las siguientes figuras muestran ejemplos de patrones y filtros para el caso de entrenar el CSPMulticlass para 5 clases y tres componentes por clase.
Patrones
Filtros
Se entrenan y utilizan clasificadores de la librería Scipy.
Al momento se implementa una clase para intentar mejorar la extracción de características a través de Common Spatial Pattern. La clase es CommonSpatialPatter.
NOTA: Las clases dentro del bloque SignalProcessor se implementan como si fueran _Transformers de ScikitLearn (heredan de BaseEstimator, TransformerMixin). La idea es poder utilizar estos objetos en un Pipeline_, lo que nos da la ventaja de probar diferentes estrategias de manera rápida y sencilla.
En el classifierPipeline.py se muestra una aplicación completa para el análisis de las señales de EEG registradas sobre uno de nuestros voluntarios.
Se muestra un resumen debajo,
### ********** Creamos el pipeline para LDA **********
pipeline_lda = Pipeline([
('pasabanda', filter),
('cspmulticlase', cspmulticlass),
('featureExtractor', featureExtractor),
('ravelTransformer', ravelTransformer),
('lda', lda)
])
### ********** Grilla de ejemplo **********
param_grid_lda = {
'pasabanda__lowcut': [5, 8],
'pasabanda__highcut': [12],
'cspmulticlase__n_components': [2],
'cspmulticlase__method': ["ovo","ova"],
'cspmulticlase__n_classes': [len(np.unique(labels))],
'cspmulticlase__reg': [0.01],
'cspmulticlase__log': [None],
'cspmulticlase__norm_trace': [False],
'featureExtractor__method': ["welch", "hilbert"],
'featureExtractor__sample_rate': [fm],
'featureExtractor__band_values': [[8,12]],
'lda__solver': ['svd'],
'lda__shrinkage': [None],
'lda__priors': [None],
'lda__n_components': [None],
'lda__store_covariance': [False],
'lda__tol': [0.0001, 0.001],
}
#Creamos el GridSearch para el LDA
grid_lda = GridSearchCV(pipeline_lda, param_grid_lda, cv=5, n_jobs=1, verbose=1)
### ********** Entrenamos el modelo **********
grid_lda.fit(eeg_train, labels_train)
### ******************************************
Bloque para comunicación entre PC y el ejecutor (que será un brazo robótico y una silla de ruedas). Los comandos obtenidos por el bloque de clasificación son enviados al dispositivo a controlar. El bloque de mensajería tambien puede recibir mensajes desde el dispositivo controlado.
Existen 3 GUIs o APPs
La versión de python a utilizar será >= 3.10.1