An easy-to-use platform for EEG experimentation in the classroom
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Tidyed up pyodide files and removed some jupyter dependencies

authored by

jdpigeon and committed by
Teon L Brooks
9881c7b2 d0210156

+247 -139
+38 -40
app/epics/pyodideEpics.js
··· 1 - import { combineEpics } from "redux-observable"; 2 - import { of } from "rxjs"; 3 - import { 4 - map, 5 - mergeMap, 6 - tap, 7 - pluck, 8 - filter 9 - } from "rxjs/operators"; 10 - import { getWorkspaceDir } from "../utils/filesystem/storage"; 11 - import { parseSingleQuoteJSON } from "../utils/pyodide/functions" 12 - import { readFiles } from "../utils/filesystem/read"; 1 + import { combineEpics } from 'redux-observable'; 2 + import { of } from 'rxjs'; 3 + import { map, mergeMap, tap, pluck, filter } from 'rxjs/operators'; 4 + import { getWorkspaceDir } from '../utils/filesystem/storage'; 5 + import { parseSingleQuoteJSON } from '../utils/pyodide/functions'; 6 + import { readFiles } from '../utils/filesystem/read'; 13 7 import { 14 8 LAUNCH, 15 9 LOAD_EPOCHS, ··· 20 14 CLEAN_EPOCHS, 21 15 loadTopo, 22 16 loadERP 23 - } from "../actions/pyodideActions"; 17 + } from '../actions/pyodideActions'; 24 18 import { 25 19 loadPackages, 26 20 utils, ··· 34 28 plotPSD, 35 29 plotERP, 36 30 plotTopoMap, 37 - saveEpochs, 38 - } from "../utils/pyodide/commands"; 31 + saveEpochs 32 + } from '../utils/pyodide'; 39 33 import { 40 34 EMOTIV_CHANNELS, 41 35 EVENTS, ··· 43 37 MUSE_CHANNELS, 44 38 PYODIDE_VARIABLE_NAMES, 45 39 PYODIDE_STATUS 46 - } from "../constants/constants"; 40 + } from '../constants/constants'; 47 41 48 - export const GET_EPOCHS_INFO = "GET_EPOCHS_INFO"; 49 - export const GET_CHANNEL_INFO = "GET_CHANNEL_INFO"; 50 - export const SET_MAIN_CHANNEL = "SET_MAIN_CHANNEL"; 51 - export const SET_EPOCH_INFO = "SET_EPOCH_INFO"; 52 - export const SET_CHANNEL_INFO = "SET_CHANNEL_INFO"; 53 - export const SET_PSD_PLOT = "SET_PSD_PLOT"; 54 - export const SET_ERP_PLOT = "SET_ERP_PLOT"; 55 - export const SET_TOPO_PLOT = "SET_TOPO_PLOT"; 56 - export const SET_PYODIDE_STATUS = "SET_PYODIDE_STATUS"; 57 - export const RECEIVE_EXECUTE_REPLY = "RECEIVE_EXECUTE_REPLY"; 58 - export const RECEIVE_EXECUTE_RESULT = "RECEIVE_EXECUTE_RESULT"; 59 - export const RECEIVE_STREAM = "RECEIVE_STREAM"; 60 - export const RECEIVE_DISPLAY_DATA = "RECEIVE_DISPLAY_DATA"; 42 + export const GET_EPOCHS_INFO = 'GET_EPOCHS_INFO'; 43 + export const GET_CHANNEL_INFO = 'GET_CHANNEL_INFO'; 44 + export const SET_MAIN_CHANNEL = 'SET_MAIN_CHANNEL'; 45 + export const SET_EPOCH_INFO = 'SET_EPOCH_INFO'; 46 + export const SET_CHANNEL_INFO = 'SET_CHANNEL_INFO'; 47 + export const SET_PSD_PLOT = 'SET_PSD_PLOT'; 48 + export const SET_ERP_PLOT = 'SET_ERP_PLOT'; 49 + export const SET_TOPO_PLOT = 'SET_TOPO_PLOT'; 50 + export const SET_PYODIDE_STATUS = 'SET_PYODIDE_STATUS'; 51 + export const RECEIVE_EXECUTE_REPLY = 'RECEIVE_EXECUTE_REPLY'; 52 + export const RECEIVE_EXECUTE_RESULT = 'RECEIVE_EXECUTE_RESULT'; 53 + export const RECEIVE_STREAM = 'RECEIVE_STREAM'; 54 + export const RECEIVE_DISPLAY_DATA = 'RECEIVE_DISPLAY_DATA'; 61 55 62 56 // ------------------------------------------------------------------------- 63 57 // Action Creators ··· 101 95 102 96 const launchEpic = action$ => 103 97 action$.ofType(LAUNCH).pipe( 104 - tap(() => console.log("launching")), 98 + tap(() => console.log('launching')), 105 99 mergeMap(loadPackages), 106 100 mergeMap(utils), 107 101 map(() => setPyodideStatus(PYODIDE_STATUS.LOADED)) ··· 109 103 110 104 const loadEpochsEpic = (action$, state$) => 111 105 action$.ofType(LOAD_EPOCHS).pipe( 112 - pluck("payload"), 106 + pluck('payload'), 113 107 filter(filePathsArray => filePathsArray.length >= 1), 114 108 tap(files => console.log('files:', files)), 115 - map((filePathsArray => readFiles(filePathsArray))), 109 + map(filePathsArray => readFiles(filePathsArray)), 116 110 tap(csvArray => console.log('csvs:', csvArray)), 117 111 mergeMap(csvArray => loadCSV(csvArray)), 118 112 mergeMap(() => filterIIR(1, 30)), ··· 131 125 map(() => getEpochsInfo(PYODIDE_VARIABLE_NAMES.RAW_EPOCHS)) 132 126 ); 133 127 134 - const loadCleanedEpochsEpic = (action$) => 128 + const loadCleanedEpochsEpic = action$ => 135 129 action$.ofType(LOAD_CLEANED_EPOCHS).pipe( 136 - pluck("payload"), 130 + pluck('payload'), 137 131 filter(filePathsArray => filePathsArray.length >= 1), 138 132 map(filePathsArray => loadCleanedEpochs(filePathsArray)), 139 133 mergeMap(() => ··· 157 151 map(() => getEpochsInfo(PYODIDE_VARIABLE_NAMES.RAW_EPOCHS)) 158 152 ); 159 153 160 - const getEpochsInfoEpic = (action$) => 154 + const getEpochsInfoEpic = action$ => 161 155 action$.ofType(GET_EPOCHS_INFO).pipe( 162 - pluck("payload"), 156 + pluck('payload'), 163 157 tap(payload => console.log('payload: ', payload)), 164 158 mergeMap(requestEpochsInfo), 165 159 map(epochInfoArray => ··· 171 165 map(setEpochInfo) 172 166 ); 173 167 174 - const getChannelInfoEpic = (action$, state$) => 168 + const getChannelInfoEpic = action$ => 175 169 action$.ofType(GET_CHANNEL_INFO).pipe( 176 170 map(requestChannelInfo), 177 171 map(channelInfoString => ··· 179 173 ) 180 174 ); 181 175 182 - const loadPSDEpic = (action$, state$) => 176 + const loadPSDEpic = action$ => 183 177 action$.ofType(LOAD_PSD).pipe( 184 178 map(plotPSD), 185 179 map(setPSDPlot) ··· 198 192 ) 199 193 ); 200 194 201 - const loadERPEpic = (action$, state$) => 195 + const loadERPEpic = action$ => 202 196 action$.ofType(LOAD_ERP).pipe( 197 + <<<<<<< HEAD 203 198 <<<<<<< HEAD 204 199 pluck('payload'), 205 200 map((channelName) => { 206 201 ======= 207 202 pluck("payload"), 203 + ======= 204 + pluck('payload'), 205 + >>>>>>> Tidyed up pyodide files and removed some jupyter dependencies 208 206 map(channelName => { 209 207 >>>>>>> Added loading of pyodide within app epics 210 208 if (MUSE_CHANNELS.includes(channelName)) { ··· 216 214 console.warn('channel name supplied to loadERPEpic does not belong to either device'); 217 215 ======= 218 216 console.warn( 219 - "channel name supplied to loadERPEpic does not belong to either device" 217 + 'channel name supplied to loadERPEpic does not belong to either device' 220 218 ); 221 219 >>>>>>> Added loading of pyodide within app epics 222 220 return EMOTIV_CHANNELS[0];
+1 -4
app/package.json
··· 19 19 "@neurosity/pipes": "^3.2.3", 20 20 "@babel/runtime": "7.10.2", 21 21 "@babel/runtime-corejs2": "^7.10.2", 22 - "enchannel-zmq-backend": "^9.1.22", 23 - "kernelspecs": "^2.0.0", 24 - "node-pre-gyp": "^0.15.0", 25 - "spawnteract": "^5.0.1" 22 + "node-pre-gyp": "^0.15.0" 26 23 }, 27 24 "devDependencies": { 28 25 "@babel/register": "^7.10.1"
-95
app/utils/pyodide/commands.js
··· 1 - import * as path from "path"; 2 - import { readFileSync } from "fs"; 3 - import { languagePluginLoader } from "./pyodide"; 4 - 5 - 6 - let pyodide; 7 - // ----------------------------- 8 - // Imports and Utility functions 9 - 10 - export const loadPackages = async () => { 11 - await languagePluginLoader; 12 - console.log("loaded language plugin"); 13 - // using window.pyodide instead of pyodide to get linter to stop yelling ;) 14 - await window.pyodide.loadPackage(["matplotlib", "mne", "pandas"]); 15 - await window.pyodide.runPython("import js"); 16 - console.log("loaded mne package"); 17 - 18 - } 19 - 20 - export const utils = async () => 21 - window.pyodide.runPython( 22 - readFileSync(path.join(__dirname, "/utils/pyodide/utils.py"), "utf8") 23 - ); 24 - 25 - export const loadCSV = async (csvArray: Array<any>) => { 26 - window.csvArray = csvArray; 27 - // TODO: Pass attached variable name as parameter to load_data 28 - await window.pyodide.runPython( 29 - `raw = load_data()`) 30 - } 31 - 32 - // --------------------------- 33 - // MNE-Related Data Processing 34 - export const loadCleanedEpochs = (epocsArray: Array<any>) => 35 - [ 36 - `clean_epochs = concatenate_epochs([read_epochs(file) for file in files])`, 37 - `conditions = OrderedDict({key: [value] for (key, value) in clean_epochs.event_id.items()})` 38 - ].join("\n"); 39 - 40 - // NOTE: this command includes a ';' to prevent returning data 41 - export const filterIIR = async (lowCutoff: number, highCutoff: number) => 42 - window.pyodide.runPython(`raw.filter(${lowCutoff}, ${highCutoff}, method='iir');`) 43 - 44 - export const epochEvents = async ( 45 - eventIDs: { [string]: number }, 46 - tmin: number, 47 - tmax: number, 48 - reject?: Array<string> | string = "None" 49 - ) => window.pyodide.runPython([ 50 - `event_id = ${JSON.stringify(eventIDs)}`, 51 - `tmin=${tmin}`, 52 - `tmax=${tmax}`, 53 - `baseline= (tmin, tmax)`, 54 - `picks = None`, 55 - `reject = ${reject}`, 56 - "events = find_events(raw)", 57 - `raw_epochs = Epochs(raw, events=events, event_id=event_id, 58 - tmin=tmin, tmax=tmax, baseline=baseline, reject=reject, preload=True, 59 - verbose=False, picks=picks)`, 60 - `conditions = OrderedDict({key: [value] for (key, value) in raw_epochs.event_id.items()})` 61 - ].join("\n")) 62 - 63 - export const requestEpochsInfo = async (variableName: string) => { 64 - const pyodideReturn = await window.pyodide.runPython(`get_epochs_info(${variableName})`); 65 - return pyodideReturn 66 - } 67 - 68 - export const requestChannelInfo = () => 69 - `[ch for ch in clean_epochs.ch_names if ch != 'Marker']`; 70 - 71 - // ----------------------------- 72 - // Plot functions 73 - 74 - export const cleanEpochsPlot = () => 75 - `raw_epochs.plot(scalings='auto', n_epochs=6, title="Clean Data", events=None)`; 76 - 77 - export const plotPSD = () => `raw.plot_psd(fmin=1, fmax=30)`; 78 - 79 - export const plotTopoMap = () => `plot_topo(clean_epochs, conditions)`; 80 - 81 - export const plotERP = (channelIndex: number) => 82 - `X, y = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions, 83 - ci=97.5, n_boot=1000, title='', diff_waveform=None)`; 84 - 85 - export const saveEpochs = (workspaceDir: string, subject: string) => 86 - `raw_epochs.save(${formatFilePath( 87 - path.join( 88 - workspaceDir, 89 - "Data", 90 - subject, 91 - "EEG", 92 - `${subject}-cleaned-epo.fif` 93 - ) 94 - )})`; 95 -
+6
app/utils/pyodide/functions.js
··· 1 + // ------------------------------------------- 2 + // Helper & utility functions 3 + 1 4 export const parseSingleQuoteJSON = (string: string) => 2 5 JSON.parse(string.replace(/'/g, '"')); 3 6 ··· 31 34 } 32 35 return `${msg.channel} ${content}`; 33 36 }; 37 + 38 + export const formatFilePath = (filePath: string) => 39 + `"${filePath.replace(/\\/g, '/')}"`;
+118
app/utils/pyodide/index.js
··· 1 + import * as path from 'path'; 2 + import { readFileSync } from 'fs'; 3 + import { languagePluginLoader } from './pyodide'; 4 + import { formatFilePath } from './functions'; 5 + 6 + // --------------------------------- 7 + // This file contains the JS functions that allow the app to access python-wasm through pyodide 8 + // These functions wrap the python strings defined in the 9 + 10 + // ----------------------------- 11 + // Imports and Utility functions 12 + 13 + // Note: this takes an incredibly long time 14 + export const loadPackages = async () => { 15 + await languagePluginLoader; 16 + console.log('loaded language plugin'); 17 + // using window.pyodide instead of pyodide to get linter to stop yelling ;) 18 + await window.pyodide.loadPackage(['matplotlib', 'mne', 'pandas']); 19 + await window.pyodide.runPython('import js'); 20 + console.log('loaded mne package'); 21 + }; 22 + 23 + export const loadUtils = async () => 24 + window.pyodide.runPython( 25 + readFileSync(path.join(__dirname, '/utils/pyodide/utils.py'), 'utf8') 26 + ); 27 + 28 + export const loadCSV = async (csvArray: Array<any>) => { 29 + window.csvArray = csvArray; 30 + // TODO: Pass attached variable name as parameter to load_data 31 + await window.pyodide.runPython(`raw = load_data()`); 32 + }; 33 + 34 + // --------------------------- 35 + // MNE-Related Data Processing 36 + 37 + // export const loadCleanedEpochs = (epocsArray: Array<any>) => 38 + // [ 39 + // `clean_epochs = concatenate_epochs([read_epochs(file) for file in files])`, 40 + // `conditions = OrderedDict({key: [value] for (key, value) in clean_epochs.event_id.items()})` 41 + // ].join("\n"); 42 + 43 + // NOTE: this command includes a ';' to prevent returning data 44 + export const filterIIR = async (lowCutoff: number, highCutoff: number) => 45 + window.pyodide.runPython( 46 + `raw.filter(${lowCutoff}, ${highCutoff}, method='iir');` 47 + ); 48 + 49 + export const epochEvents = async ( 50 + eventIDs: { [string]: number }, 51 + tmin: number, 52 + tmax: number, 53 + reject?: Array<string> | string = 'None' 54 + ) => 55 + window.pyodide.runPython( 56 + [ 57 + `event_id = ${JSON.stringify(eventIDs)}`, 58 + `tmin=${tmin}`, 59 + `tmax=${tmax}`, 60 + `baseline= (tmin, tmax)`, 61 + `picks = None`, 62 + `reject = ${reject}`, 63 + 'events = find_events(raw)', 64 + `raw_epochs = Epochs(raw, events=events, event_id=event_id, 65 + tmin=tmin, tmax=tmax, baseline=baseline, reject=reject, preload=True, 66 + verbose=False, picks=picks)`, 67 + `conditions = OrderedDict({key: [value] for (key, value) in raw_epochs.event_id.items()})` 68 + ].join('\n') 69 + ); 70 + 71 + export const requestEpochsInfo = async (variableName: string) => { 72 + const pyodideReturn = await window.pyodide.runPython( 73 + `get_epochs_info(${variableName})` 74 + ); 75 + return pyodideReturn; 76 + }; 77 + 78 + export const requestChannelInfo = async () => 79 + window.pyodide.runPython( 80 + `[ch for ch in clean_epochs.ch_names if ch != 'Marker']` 81 + ); 82 + 83 + // ----------------------------- 84 + // Plot functions 85 + 86 + export const cleanEpochsPlot = async () => { 87 + // TODO: Figure out how to get image results from pyodide 88 + window.pyodide.runPython( 89 + `raw_epochs.plot(scalings='auto', n_epochs=6, title="Clean Data", events=None)` 90 + ); 91 + }; 92 + 93 + export const plotPSD = async () => { 94 + // TODO: Figure out how to get image results from pyodide 95 + window.pyodide.runPython(`raw.plot_psd(fmin=1, fmax=30)`); 96 + }; 97 + 98 + export const plotTopoMap = async () => { 99 + // TODO: Figure out how to get image results from pyodide 100 + window.pyodide.runPython(`plot_topo(clean_epochs, conditions)`); 101 + }; 102 + 103 + export const plotERP = (channelIndex: number) => 104 + `X, y = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions, 105 + ci=97.5, n_boot=1000, title='', diff_waveform=None)`; 106 + 107 + export const saveEpochs = (workspaceDir: string, subject: string) => 108 + window.pyodide.runPython( 109 + `raw_epochs.save(${formatFilePath( 110 + path.join( 111 + workspaceDir, 112 + 'Data', 113 + subject, 114 + 'EEG', 115 + `${subject}-cleaned-epo.fif` 116 + ) 117 + )}` 118 + );
+84
app/utils/pyodide/pythonStrings.js
··· 1 + // DEPRECATED 2 + 3 + // import * as path from "path"; 4 + // import { readFileSync } from "fs"; 5 + 6 + // // The output of the functions contained in this file are python commands encoded as strings 7 + // // that would be run in a notebook environment in order to perform the experimental analyses underlying BrainWaves 8 + 9 + // export const utils = () => 10 + // readFileSync(path.join(__dirname, "/utils/pyodide/utils.py"), "utf8"); 11 + 12 + // // export const loadCSV = (filePathArray: Array<string>) => 13 + // // [ 14 + // // `files = [${filePathArray.map(filePath => formatFilePath(filePath))}]`, 15 + // // `replace_ch_names = None`, 16 + // // `raw = load_data(files, replace_ch_names)` 17 + // // ].join("\n"); 18 + 19 + // // export const loadCleanedEpochs = (filePathArray: Array<string>) => 20 + // // [ 21 + // // `files = [${filePathArray.map(filePath => formatFilePath(filePath))}]`, 22 + // // `clean_epochs = concatenate_epochs([read_epochs(file) for file in files])`, 23 + // // `conditions = OrderedDict({key: [value] for (key, value) in clean_epochs.event_id.items()})` 24 + // // ].join("\n"); 25 + 26 + // // NOTE: this command includes a ';' to prevent returning data 27 + // export const filterIIR = (lowCutoff: number, highCutoff: number) => 28 + // `raw.filter(${lowCutoff}, ${highCutoff}, method='iir');`; 29 + 30 + // export const plotPSD = () => 31 + // [`%matplotlib inline`, `raw.plot_psd(fmin=1, fmax=30)`].join("\n"); 32 + 33 + // export const epochEvents = ( 34 + // eventIDs: { [string]: number }, 35 + // tmin: number, 36 + // tmax: number, 37 + // reject?: Array<string> | string = "None" 38 + // ) => 39 + // [ 40 + // `event_id = ${JSON.stringify(eventIDs)}`, 41 + // `tmin=${tmin}`, 42 + // `tmax=${tmax}`, 43 + // `baseline= (tmin, tmax)`, 44 + // `picks = None`, 45 + // `reject = ${reject}`, 46 + // "events = find_events(raw)", 47 + // `raw_epochs = Epochs(raw, events=events, event_id=event_id, 48 + // tmin=tmin, tmax=tmax, baseline=baseline, reject=reject, preload=True, 49 + // verbose=False, picks=picks)`, 50 + // `conditions = OrderedDict({key: [value] for (key, value) in raw_epochs.event_id.items()})` 51 + // ].join("\n"); 52 + 53 + // export const requestEpochsInfo = (variableName: string) => 54 + // `get_epochs_info(${variableName})`; 55 + 56 + // export const requestChannelInfo = () => 57 + // `[ch for ch in clean_epochs.ch_names if ch != 'Marker']`; 58 + 59 + // export const cleanEpochsPlot = () => 60 + // [ 61 + // `%matplotlib`, 62 + // `raw_epochs.plot(scalings='auto', n_epochs=6, title="Clean Data", events=None)` 63 + // ].join("\n"); 64 + 65 + // export const plotTopoMap = () => 66 + // [`%matplotlib inline`, `plot_topo(clean_epochs, conditions)`].join("\n"); 67 + 68 + // export const plotERP = (channelIndex: number) => 69 + // [ 70 + // `%matplotlib inline`, 71 + // `X, y = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions, 72 + // ci=97.5, n_boot=1000, title='', diff_waveform=None)` 73 + // ].join("\n"); 74 + 75 + // export const saveEpochs = (workspaceDir: string, subject: string) => 76 + // `raw_epochs.save(${formatFilePath( 77 + // path.join( 78 + // workspaceDir, 79 + // "Data", 80 + // subject, 81 + // "EEG", 82 + // `${subject}-cleaned-epo.fif` 83 + // ) 84 + // )})`;