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.

overhaul continues

+147 -128
-1
app/actions/pyodideActions.js
··· 8 8 export const LOAD_ERP = "LOAD_ERP"; 9 9 export const LOAD_TOPO = "LOAD_TOPO"; 10 10 export const CLEAN_EPOCHS = "CLEAN_EPOCHS"; 11 - export const CLOSE_KERNEL = "CLOSE_KERNEL"; 12 11 13 12 // ------------------------------------------------------------------------- 14 13 // Actions
+4 -17
app/components/CleanComponent/index.tsx
··· 18 18 import { isNil, isArray, isString } from 'lodash'; 19 19 import styles from '../styles/collect.css'; 20 20 import commonStyles from '../styles/common.css'; 21 - import { EXPERIMENTS, DEVICES, KERNEL_STATUS } from '../../constants/constants'; 22 - import { Kernel } from '../../constants/interfaces'; 21 + import { EXPERIMENTS, DEVICES } from '../../constants/constants'; 23 22 import { readWorkspaceRawEEGData } from '../../utils/filesystem/storage'; 24 23 import CleanSidebar from './CleanSidebar'; 25 24 import { JupyterActions, ExperimentActions } from '../../actions'; ··· 29 28 title: string; 30 29 deviceType: DEVICES; 31 30 mainChannel?: any; 32 - kernel?: Kernel; 33 - kernelStatus: KERNEL_STATUS; 34 31 epochsInfo: Array<{ 35 32 [key: string]: number | string; 36 33 }>; 37 - JupyterActions: typeof JupyterActions; 34 + PyodideActions: typeof PyodideActions; 38 35 ExperimentActions: typeof ExperimentActions; 39 36 subject: string; 40 37 session: number; ··· 72 69 73 70 async componentDidMount() { 74 71 const workspaceRawData = await readWorkspaceRawEEGData(this.props.title); 75 - if (this.props.kernelStatus === KERNEL_STATUS.OFFLINE) { 76 - this.props.JupyterActions.LaunchKernel(); 77 - } 78 72 this.setState({ 79 73 subjects: workspaceRawData 80 74 .map( ··· 116 110 117 111 handleLoadData() { 118 112 this.props.ExperimentActions.SetSubject(this.state.selectedSubject); 119 - this.props.JupyterActions.LoadEpochs(this.state.selectedFilePaths); 113 + this.props.PyodideActions.LoadEpochs(this.state.selectedFilePaths); 120 114 } 121 115 122 116 handleSidebarToggle() { ··· 232 226 <Grid.Column> 233 227 <Button 234 228 secondary 235 - disabled={ 236 - this.props.kernelStatus !== KERNEL_STATUS.IDLE 237 - } 238 - loading={ 239 - this.props.kernelStatus === KERNEL_STATUS.STARTING || 240 - this.props.kernelStatus === KERNEL_STATUS.BUSY 241 - } 242 229 onClick={this.handleLoadData} 243 230 > 244 231 Load Dataset ··· 248 235 <Button 249 236 primary 250 237 disabled={isNil(this.props.epochsInfo)} 251 - onClick={this.props.JupyterActions.CleanEpochs} 238 + onClick={this.props.PyodideActions.CleanEpochs} 252 239 > 253 240 Clean Data 254 241 </Button>
+4 -4
app/components/JupyterPlotWidget.tsx
··· 6 6 standardTransforms, 7 7 } from '@nteract/transforms'; 8 8 import { isNil } from 'lodash'; 9 - import { storeJupyterImage } from '../utils/filesystem/storage'; 9 + import { storePyodideImage } from '../utils/filesystem/storage'; 10 10 11 11 interface Props { 12 12 title: string; ··· 24 24 mimeType: string; 25 25 } 26 26 27 - export default class JupyterPlotWidget extends Component<Props, State> { 27 + export default class PyodidePlotWidget extends Component<Props, State> { 28 28 // state: State; 29 29 constructor(props: Props) { 30 30 super(props); ··· 55 55 } 56 56 57 57 handleSave() { 58 - const buf = Buffer.from(this.state.rawData, 'base64'); 59 - storeJupyterImage(this.props.title, this.props.imageTitle, buf); 58 + const buf = Buffer.from(this.state.rawData, "base64"); 59 + storePyodideImage(this.props.title, this.props.imageTitle, buf); 60 60 } 61 61 62 62 renderResults() {
+3 -3
app/containers/AnalyzeContainer.ts
··· 1 1 import { bindActionCreators } from 'redux'; 2 2 import { connect } from 'react-redux'; 3 3 import Analyze from '../components/AnalyzeComponent'; 4 - import { JupyterActions, ExperimentActions } from '../actions'; 4 + import { PyodideActions, ExperimentActions } from '../actions'; 5 5 6 6 function mapStateToProps(state) { 7 7 return { ··· 9 9 type: state.experiment.type, 10 10 deviceType: state.device.deviceType, 11 11 isEEGEnabled: state.experiment.isEEGEnabled, 12 - ...state.jupyter, 12 + ...state.pyodide 13 13 }; 14 14 } 15 15 16 16 function mapDispatchToProps(dispatch) { 17 17 return { 18 18 ExperimentActions: bindActionCreators(ExperimentActions, dispatch), 19 - JupyterActions: bindActionCreators(JupyterActions, dispatch), 19 + PyodideActions: bindActionCreators(PyodideActions, dispatch), 20 20 }; 21 21 } 22 22
+3 -3
app/containers/CleanContainer.ts
··· 1 1 import { bindActionCreators } from 'redux'; 2 2 import { connect } from 'react-redux'; 3 3 import CleanComponent from '../components/CleanComponent'; 4 - import { JupyterActions, ExperimentActions } from '../actions'; 4 + import { PyodideActions, ExperimentActions } from '../actions'; 5 5 6 6 function mapStateToProps(state) { 7 7 return { ··· 11 11 group: state.experiment.group, 12 12 session: state.experiment.session, 13 13 deviceType: state.device.deviceType, 14 - ...state.jupyter, 14 + ...state.pyodide 15 15 }; 16 16 } 17 17 18 18 function mapDispatchToProps(dispatch) { 19 19 return { 20 20 ExperimentActions: bindActionCreators(ExperimentActions, dispatch), 21 - JupyterActions: bindActionCreators(JupyterActions, dispatch), 21 + PyodideActions: bindActionCreators(PyodideActions, dispatch), 22 22 }; 23 23 } 24 24
+5 -10
app/containers/HomeContainer.ts
··· 1 1 import { connect } from 'react-redux'; 2 2 import { bindActionCreators } from 'redux'; 3 3 import Home from '../components/HomeComponent'; 4 - import { DeviceActions, ExperimentActions, JupyterActions } from '../actions'; 5 - 6 - function mapStateToProps(state) { 7 - return { 8 - ...state.device, 9 - kernelStatus: state.jupyter.kernelStatus, 10 - }; 11 - } 4 + import { DeviceActions, ExperimentActions, PyodideActions } from '../actions'; 12 5 13 6 function mapDispatchToProps(dispatch) { 14 7 return { 15 8 DeviceActions: bindActionCreators(DeviceActions, dispatch), 16 - JupyterActions: bindActionCreators(JupyterActions, dispatch), 9 + PyodideActions: bindActionCreators(PyodideActions, dispatch), 17 10 ExperimentActions: bindActionCreators(ExperimentActions, dispatch), 18 11 }; 19 12 } 20 13 21 - export default connect(mapStateToProps, mapDispatchToProps)(Home); 14 + export default connect( 15 + mapDispatchToProps 16 + )(Home);
+2 -2
app/epics/index.ts
··· 1 1 import { combineEpics } from 'redux-observable'; 2 - import jupyter from './jupyterEpics'; 2 + import pyodide from './pyodideEpics'; 3 3 import device from './deviceEpics'; 4 4 import experiment from './experimentEpics'; 5 5 6 - export default combineEpics(device, experiment, jupyter); 6 + export default combineEpics(device, experiment, pyodide);
+22 -44
app/epics/pyodideEpics.js
··· 48 48 49 49 export const GET_EPOCHS_INFO = 'GET_EPOCHS_INFO'; 50 50 export const GET_CHANNEL_INFO = 'GET_CHANNEL_INFO'; 51 - export const SET_KERNEL = 'SET_KERNEL'; 52 - export const SET_KERNEL_STATUS = 'SET_KERNEL_STATUS'; 53 - export const SET_KERNEL_INFO = 'SET_KERNEL_INFO'; 54 51 export const SET_MAIN_CHANNEL = 'SET_MAIN_CHANNEL'; 55 52 export const SET_EPOCH_INFO = 'SET_EPOCH_INFO'; 56 53 export const SET_CHANNEL_INFO = 'SET_CHANNEL_INFO'; ··· 69 66 70 67 const getChannelInfo = () => ({ type: GET_CHANNEL_INFO }); 71 68 72 - const setKernel = (payload) => ({ 73 - payload, 74 - type: SET_KERNEL, 75 - }); 76 - 77 - const setKernelStatus = (payload) => ({ 78 - payload, 79 - type: SET_KERNEL_STATUS, 80 - }); 81 - 82 - const setKernelInfo = (payload) => ({ 83 - payload, 84 - type: SET_KERNEL_INFO, 85 - }); 86 - 87 - const setMainChannel = (payload) => ({ 69 + const setMainChannel = payload => ({ 88 70 payload, 89 71 type: SET_MAIN_CHANNEL, 90 72 }); ··· 140 122 const loadEpochsEpic = (action$, state$) => 141 123 action$.ofType(LOAD_EPOCHS).pipe( 142 124 pluck('payload'), 143 - filter((filePathsArray) => filePathsArray.length >= 1), 144 - map((filePathsArray) => 145 - state$.value.jupyter.mainChannel.next(executeRequest(loadCSV(filePathsArray))) 125 + filter(filePathsArray => filePathsArray.length >= 1), 126 + map(filePathsArray => 127 + state$.value.pyodide.mainChannel.next( 128 + executeRequest(loadCSV(filePathsArray)) 129 + ) 146 130 ), 147 131 awaitOkMessage(action$), 148 132 execute(filterIIR(1, 30), state$), ··· 159 143 0.8 160 144 ) 161 145 ), 162 - tap((e)=> {console.log('e', e)}), 163 - map((epochEventsCommand) => 164 - state$.value.jupyter.mainChannel.next(executeRequest(epochEventsCommand)) 146 + map(epochEventsCommand => 147 + state$.value.pyodide.mainChannel.next(executeRequest(epochEventsCommand)) 165 148 ), 166 149 awaitOkMessage(action$), 167 150 map(() => getEpochsInfo(PYODIDE_VARIABLE_NAMES.RAW_EPOCHS)) ··· 170 153 const loadCleanedEpochsEpic = (action$, state$) => 171 154 action$.ofType(LOAD_CLEANED_EPOCHS).pipe( 172 155 pluck('payload'), 173 - filter((filePathsArray) => filePathsArray.length >= 1), 174 - map((filePathsArray) => 175 - state$.value.jupyter.mainChannel.next(executeRequest(loadCleanedEpochs(filePathsArray))) 156 + filter(filePathsArray => filePathsArray.length >= 1), 157 + map(filePathsArray => 158 + state$.value.pyodide.mainChannel.next( 159 + executeRequest(loadCleanedEpochs(filePathsArray)) 160 + ) 176 161 ), 177 162 awaitOkMessage(action$), 178 163 mergeMap(() => ··· 197 182 ) 198 183 ), 199 184 map(() => 200 - state$.value.jupyter.mainChannel.next( 185 + state$.value.pyodide.mainChannel.next( 201 186 executeRequest( 202 187 saveEpochs( 203 188 getWorkspaceDir(state$.value.experiment.title), ··· 213 198 const getEpochsInfoEpic = (action$, state$) => 214 199 action$.ofType(GET_EPOCHS_INFO).pipe( 215 200 pluck('payload'), 216 - map((variableName) => 217 - state$.value.jupyter.mainChannel.next(executeRequest(requestEpochsInfo(variableName))) 201 + map(variableName => 202 + state$.value.pyodide.mainChannel.next( 203 + executeRequest(requestEpochsInfo(variableName)) 204 + ) 218 205 ), 219 206 mergeMap(() => 220 207 action$.ofType(RECEIVE_EXECUTE_RESULT).pipe( ··· 293 280 console.warn('channel name supplied to loadERPEpic does not belong to either device'); 294 281 return EMOTIV_CHANNELS[0]; 295 282 }), 296 - map((channelIndex) => 297 - state$.value.jupyter.mainChannel.next(executeRequest(plotERP(channelIndex))) 283 + map(channelIndex => 284 + state$.value.pyodide.mainChannel.next( 285 + executeRequest(plotERP(channelIndex)) 286 + ) 298 287 ), 299 288 mergeMap(() => 300 289 action$.ofType(RECEIVE_DISPLAY_DATA).pipe( ··· 308 297 map(setERPPlot) 309 298 ); 310 299 311 - const closeKernelEpic = (action$, state$) => 312 - action$.ofType(CLOSE_KERNEL).pipe( 313 - map(() => { 314 - state$.value.jupyter.kernel.spawn.kill(); 315 - state$.value.jupyter.mainChannel.complete(); 316 - }), 317 - ignoreElements() 318 - ); 319 - 320 300 export default combineEpics( 321 301 launchEpic, 322 302 setUpChannelEpic, 323 - requestKernelInfoEpic, 324 303 receiveChannelMessageEpic, 325 304 loadEpochsEpic, 326 305 loadCleanedEpochsEpic, ··· 330 309 loadPSDEpic, 331 310 loadTopoEpic, 332 311 loadERPEpic, 333 - closeKernelEpic 334 312 );
+15
app/reducers/index.js
··· 1 + // @flow 2 + import { combineReducers } from 'redux'; 3 + import { routerReducer as router } from 'react-router-redux'; 4 + import pyodide from './pyodideReducer'; 5 + import device from './deviceReducer'; 6 + import experiment from './experimentReducer'; 7 + 8 + const rootReducer = combineReducers({ 9 + pyodide, 10 + device, 11 + experiment, 12 + router, 13 + }); 14 + 15 + export default rootReducer;
+6 -26
app/reducers/pyodideReducer.js
··· 1 1 // @flow 2 2 import { 3 - SET_KERNEL, 4 - SET_KERNEL_STATUS, 5 3 SET_MAIN_CHANNEL, 6 - SET_KERNEL_INFO, 7 4 SET_EPOCH_INFO, 8 5 SET_CHANNEL_INFO, 9 6 SET_PSD_PLOT, ··· 11 8 SET_ERP_PLOT, 12 9 RECEIVE_EXECUTE_RETURN 13 10 } from '../epics/pyodideEpics'; 14 - import { ActionType, Kernel } from '../constants/interfaces'; 15 - import { KERNEL_STATUS } from '../constants/constants'; 11 + import { ActionType } from '../constants/interfaces'; 16 12 import { EXPERIMENT_CLEANUP } from '../epics/experimentEpics'; 17 13 18 - export interface JupyterStateType { 19 - +kernel: ?Kernel; 20 - +kernelStatus: KERNEL_STATUS; 14 + export interface PyodideStateType { 21 15 +mainChannel: ?any; 22 16 +epochsInfo: ?Array<{ [string]: number | string }>; 23 17 +channelInfo: ?Array<string>; ··· 27 21 } 28 22 29 23 const initialState = { 30 - kernel: null, 31 - kernelStatus: KERNEL_STATUS.OFFLINE, 32 24 mainChannel: null, 33 25 epochsInfo: null, 34 26 channelInfo: [], ··· 37 29 erpPlot: null, 38 30 }; 39 31 40 - export default function jupyter(state: JupyterStateType = initialState, action: ActionType) { 32 + export default function pyodide( 33 + state: PyodideStateType = initialState, 34 + action: ActionType 35 + ) { 41 36 switch (action.type) { 42 - case SET_KERNEL: 43 - return { 44 - ...state, 45 - kernel: action.payload, 46 - }; 47 - 48 - case SET_KERNEL_STATUS: 49 - return { 50 - ...state, 51 - kernelStatus: action.payload, 52 - }; 53 - 54 37 case SET_MAIN_CHANNEL: 55 38 return { 56 39 ...state, 57 40 mainChannel: action.payload, 58 41 }; 59 - 60 - case SET_KERNEL_INFO: 61 - return state; 62 42 63 43 case SET_EPOCH_INFO: 64 44 return {
+77
app/store/configureStore.dev.js
··· 1 + import { createStore, applyMiddleware, compose } from 'redux'; 2 + import thunk from 'redux-thunk'; 3 + import { createEpicMiddleware } from 'redux-observable'; 4 + import { createHashHistory } from 'history'; 5 + import { routerMiddleware, routerActions } from 'react-router-redux'; 6 + import { createLogger } from 'redux-logger'; 7 + import rootReducer from '../reducers'; 8 + import rootEpic from '../epics'; 9 + import * as pyodideActions from '../actions/pyodideActions'; 10 + import * as deviceActions from '../actions/deviceActions'; 11 + 12 + const history = createHashHistory(); 13 + 14 + const configureStore = (initialState?: AppState) => { 15 + // Redux Configuration 16 + const middleware = []; 17 + const enhancers = []; 18 + 19 + // Thunk Middleware 20 + middleware.push(thunk); 21 + 22 + // Redux Observable (Epic) Middleware 23 + const epicMiddleware = createEpicMiddleware(); 24 + middleware.push(epicMiddleware); 25 + 26 + // Logging Middleware 27 + const logger = createLogger({ 28 + level: 'info', 29 + collapsed: true, 30 + }); 31 + 32 + // Skip redux logs in console during the tests 33 + if (process.env.NODE_ENV !== 'test') { 34 + middleware.push(logger); 35 + } 36 + 37 + // Router Middleware 38 + const router = routerMiddleware(history); 39 + middleware.push(router); 40 + 41 + // Redux DevTools Configuration 42 + const actionCreators = { 43 + ...deviceActions, 44 + ...pyodideActions, 45 + ...routerActions 46 + }; 47 + // If Redux DevTools Extension is installed use it, otherwise use Redux compose 48 + /* eslint-disable no-underscore-dangle */ 49 + const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 50 + ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 51 + // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html 52 + actionCreators, 53 + }) 54 + : compose; 55 + /* eslint-enable no-underscore-dangle */ 56 + 57 + // Apply Middleware & Compose Enhancers 58 + enhancers.push(applyMiddleware(...middleware)); 59 + const enhancer = composeEnhancers(...enhancers); 60 + 61 + // Create Store 62 + const store = createStore(rootReducer, initialState, enhancer); 63 + 64 + if (module.hot) { 65 + module.hot.accept( 66 + '../reducers', 67 + () => store.replaceReducer(require('../reducers')) // eslint-disable-line global-require 68 + ); 69 + } 70 + 71 + // Redux Observable 72 + epicMiddleware.run(rootEpic); 73 + 74 + return store; 75 + }; 76 + 77 + export default { configureStore, history };
+1 -1
app/utils/filesystem/storage.ts
··· 75 75 }; 76 76 77 77 // Stores an image to workspace dir 78 - export const storeJupyterImage = ( 78 + export const storePyodideImage = ( 79 79 title: string, 80 80 imageTitle: string, 81 81 rawData: Buffer
+1
app/utils/jupyter/cells.ts
··· 91 91 export const plotERP = (channelIndex: number | string) => 92 92 [ 93 93 `%matplotlib inline`, 94 + `X, y = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions`, 94 95 `X, y = plot_conditions(clean_epochs, ch_ind=${channelIndex}, conditions=conditions, 95 96 ci=97.5, n_boot=1000, title='', diff_waveform=None)`, 96 97 ].join('\n');
-14
app/utils/jupyter/functions.ts
··· 1 - import { KERNEL_STATUS } from '../../constants/constants'; 2 - 3 1 export const parseSingleQuoteJSON = (string: string) => 4 2 JSON.parse(string.replace(/'/g, '"')); 5 - 6 - export const parseKernelStatus = (msg: Record<string, any>) => { 7 - switch (msg.content.execution_state) { 8 - case 'busy': 9 - return KERNEL_STATUS.BUSY; 10 - case 'idle': 11 - return KERNEL_STATUS.IDLE; 12 - case 'starting': 13 - default: 14 - return KERNEL_STATUS.STARTING; 15 - } 16 - }; 17 3 18 4 export const debugParseMessage = (msg: Record<string, any>) => { 19 5 let content = '';
+4 -3
app/utils/jupyter/pipes.ts
··· 1 1 import { pipe } from 'rxjs'; 2 2 import { map, pluck, filter, take, mergeMap } from 'rxjs/operators'; 3 3 import { executeRequest } from '@nteract/messaging'; 4 - import { JupyterActions } from '../../actions'; 4 + import { PyodideActions } from '../../actions'; 5 + import { RECEIVE_EXECUTE_REPLY } from '../../epics/pyodideEpics'; 5 6 6 7 // Refactor this so command can be calculated either up stream or inside pipe 7 8 export const execute = (command, state$) => 8 9 pipe( 9 - map(() => state$.value.jupyter.mainChannel.next(executeRequest(command))) 10 + map(() => state$.value.pyodide.mainChannel.next(executeRequest(command))) 10 11 ); 11 12 12 13 export const awaitOkMessage = (action$) => 13 14 pipe( 14 15 mergeMap(() => 15 - action$.ofType(JupyterActions.ReceiveExecuteReply.type).pipe( 16 + action$.ofType(PyodideActions.ReceiveExecuteReply.type).pipe( 16 17 pluck('payload'), 17 18 filter<any>( 18 19 (msg) => msg.channel === 'shell' && msg.content.status === 'ok'