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.

Tweaks for polish

jdpigeon 0423783b 154246bc

+264 -33
+1 -1
app/components/AnalyzeComponent.js
··· 185 185 </p> 186 186 <ClickableHeadDiagramSVG 187 187 channelinfo={this.props.channelInfo} 188 - onclick={this.handleChannelSelect} 188 + onChannelClick={this.handleChannelSelect} 189 189 /> 190 190 <Divider hidden /> 191 191 {this.renderEpochLabels()}
+5 -6
app/components/CollectComponent/ConnectModal.js
··· 4 4 Modal, 5 5 Button, 6 6 Segment, 7 - Image, 7 + // Image, 8 8 List, 9 9 Grid, 10 10 Divider ··· 16 16 SCREENS 17 17 } from '../../constants/constants'; 18 18 import styles from '../styles/collect.css'; 19 - import blake from '../../assets/face_house/faces/Blake_3.jpg'; 20 19 21 20 interface Props { 22 21 history: Object; ··· 132 131 if (this.props.deviceAvailability === DEVICE_AVAILABILITY.SEARCHING) { 133 132 return ( 134 133 <React.Fragment> 135 - <Modal.Content image> 134 + {/* <Modal.Content image> 136 135 <Image src={blake} size="tiny" centered /> 137 - </Modal.Content> 136 + </Modal.Content> */} 138 137 <Modal.Content className={styles.searchingText}> 139 138 Searching for available headset(s)... 140 139 </Modal.Content> ··· 144 143 if (this.props.connectionStatus === CONNECTION_STATUS.CONNECTING) { 145 144 return ( 146 145 <React.Fragment> 147 - <Modal.Content image> 146 + {/* <Modal.Content image> 148 147 <Image src={blake} size="tiny" centered /> 149 - </Modal.Content> 148 + </Modal.Content> */} 150 149 <Modal.Content className={styles.searchingText}> 151 150 Connecting to{' '} 152 151 {ConnectModal.getDeviceName(this.state.selectedDevice)}
+6 -1
app/components/DesignComponent/StimuliDesignColumn.js
··· 85 85 </Grid.Column> 86 86 <Grid.Column verticalAlign="bottom" floated="left" width={4}> 87 87 <Segment basic compact> 88 - <em>{this.props.dir}</em> 88 + { 89 + // Kind of hacky. Set to empty string if dir is inside app itself 90 + } 91 + <em> 92 + {this.props.dir.includes('app') ? '' : this.props.dir} 93 + </em> 89 94 </Segment> 90 95 </Grid.Column> 91 96 </Grid>
+3 -3
app/components/HomeComponent/index.js
··· 169 169 </Button> 170 170 </Segment> 171 171 </Grid.Column> 172 - <Grid.Column> 172 + {/* <Grid.Column> 173 173 <Segment basic className={styles.descriptionContainer}> 174 174 <Image size="huge" src={faceHouseIcon} /> 175 175 <Header as="h1">Stroop</Header> ··· 189 189 > 190 190 Start Experiment 191 191 </Button> 192 - </Segment> 193 - </Grid.Column> 192 + </Segment> */} 193 + {/* </Grid.Column> */} 194 194 <Grid.Column> 195 195 <Segment basic className={styles.descriptionContainer}> 196 196 <Image size="huge" src={faceHouseIcon} />
+20 -20
app/components/svgs/ClickableHeadDiagramSVG.js
··· 2 2 3 3 interface Props { 4 4 channelinfo: Array<string>; 5 - onclick: string => void; 5 + onChannelClick: string => void; 6 6 } 7 7 8 8 const SvgComponent = (props: Props) => ( ··· 93 93 className="channelCircle" 94 94 id="T7" 95 95 visibility={props.channelinfo.includes('T7') ? 'show' : 'hidden'} 96 - onClick={() => props.onclick('T7')} 96 + onClick={() => props.onChannelClick('T7')} 97 97 > 98 98 <circle 99 99 cx={124.37} ··· 116 116 <g 117 117 className="channelCircle" 118 118 id="FC5" 119 - onClick={() => props.onclick('FC5')} 119 + onClick={() => props.onChannelClick('FC5')} 120 120 visibility={props.channelinfo.includes('FC5') ? 'show' : 'hidden'} 121 121 > 122 122 <circle ··· 140 140 <g 141 141 className="channelCircle" 142 142 id="FC6" 143 - onClick={() => props.onclick('FC6')} 143 + onClick={() => props.onChannelClick('FC6')} 144 144 visibility={props.channelinfo.includes('FC6') ? 'show' : 'hidden'} 145 145 > 146 146 <circle ··· 164 164 <g 165 165 className="channelCircle" 166 166 id="F3" 167 - onClick={() => props.onclick('F3')} 167 + onClick={() => props.onChannelClick('F3')} 168 168 visibility={props.channelinfo.includes('F3') ? 'show' : 'hidden'} 169 169 > 170 170 <circle ··· 188 188 <g 189 189 className="channelCircle" 190 190 id="F4" 191 - onClick={() => props.onclick('F4')} 191 + onClick={() => props.onChannelClick('F4')} 192 192 visibility={props.channelinfo.includes('F4') ? 'show' : 'hidden'} 193 193 > 194 194 <circle ··· 212 212 <g 213 213 className="channelCircle" 214 214 id="AF3" 215 - onClick={() => props.onclick('AF3')} 215 + onClick={() => props.onChannelClick('AF3')} 216 216 visibility={props.channelinfo.includes('AF3') ? 'show' : 'hidden'} 217 217 > 218 218 <circle ··· 236 236 <g 237 237 className="channelCircle" 238 238 id="AF4" 239 - onClick={() => props.onclick('AF4')} 239 + onClick={() => props.onChannelClick('AF4')} 240 240 visibility={props.channelinfo.includes('AF4') ? 'show' : 'hidden'} 241 241 > 242 242 <circle ··· 260 260 <g 261 261 className="channelCircle" 262 262 id="M1" 263 - onClick={() => props.onclick('M1')} 263 + onClick={() => props.onChannelClick('M1')} 264 264 visibility={props.channelinfo.includes('M1') ? 'show' : 'hidden'} 265 265 > 266 266 <circle ··· 284 284 <g 285 285 className="channelCircle" 286 286 id="P7" 287 - onClick={() => props.onclick('P7')} 287 + onClick={() => props.onChannelClick('P7')} 288 288 visibility={props.channelinfo.includes('P7') ? 'show' : 'hidden'} 289 289 > 290 290 <circle ··· 308 308 <g 309 309 className="channelCircle" 310 310 id="O1" 311 - onClick={() => props.onclick('O1')} 311 + onClick={() => props.onChannelClick('O1')} 312 312 visibility={props.channelinfo.includes('O1') ? 'show' : 'hidden'} 313 313 > 314 314 <circle ··· 332 332 <g 333 333 className="channelCircle" 334 334 id="O2" 335 - onClick={() => props.onclick('O2')} 335 + onClick={() => props.onChannelClick('O2')} 336 336 visibility={props.channelinfo.includes('O2') ? 'show' : 'hidden'} 337 337 > 338 338 <circle ··· 356 356 <g 357 357 className="channelCircle" 358 358 id="P8" 359 - onClick={() => props.onclick('P8')} 359 + onClick={() => props.onChannelClick('P8')} 360 360 visibility={props.channelinfo.includes('P8') ? 'show' : 'hidden'} 361 361 > 362 362 <circle ··· 380 380 <g 381 381 className="channelCircle" 382 382 id="T8" 383 - onClick={() => props.onclick('T8')} 383 + onClick={() => props.onChannelClick('T8')} 384 384 visibility={props.channelinfo.includes('T8') ? 'show' : 'hidden'} 385 385 > 386 386 <circle ··· 404 404 <g 405 405 className="channelCircle" 406 406 id="M2" 407 - onClick={() => props.onclick('M2')} 407 + onClick={() => props.onChannelClick('M2')} 408 408 visibility={props.channelinfo.includes('M2') ? 'show' : 'hidden'} 409 409 > 410 410 <circle ··· 428 428 <g 429 429 className="channelCircle" 430 430 id="TP10" 431 - onClick={() => props.onclick('TP10')} 431 + onClick={() => props.onChannelClick('TP10')} 432 432 visibility={props.channelinfo.includes('TP10') ? 'show' : 'hidden'} 433 433 > 434 434 <circle ··· 452 452 <g 453 453 className="channelCircle" 454 454 id="Fpz" 455 - onClick={() => props.onclick('Fpz')} 455 + onClick={() => props.onChannelClick('Fpz')} 456 456 visibility={props.channelinfo.includes('Fpz') ? 'show' : 'hidden'} 457 457 > 458 458 <circle ··· 479 479 <g 480 480 className="channelCircle" 481 481 id="TP9" 482 - onClick={() => props.onclick('TP9')} 482 + onClick={() => props.onChannelClick('TP9')} 483 483 visibility={props.channelinfo.includes('TP9') ? 'show' : 'hidden'} 484 484 > 485 485 <circle ··· 503 503 <g 504 504 className="channelCircle" 505 505 id="AF7" 506 - onClick={() => props.onclick('AF7')} 506 + onClick={() => props.onChannelClick('AF7')} 507 507 visibility={props.channelinfo.includes('AF7') ? 'show' : 'hidden'} 508 508 > 509 509 <circle ··· 527 527 <g 528 528 className="channelCircle" 529 529 id="AF8" 530 - onClick={() => props.onclick('AF8')} 530 + onClick={() => props.onChannelClick('AF8')} 531 531 visibility={props.channelinfo.includes('AF8') ? 'show' : 'hidden'} 532 532 > 533 533 <circle
-2
app/utils/jupyter/utils.py
··· 78 78 79 79 evoked_topo = viz.plot_evoked_topo( 80 80 evokeds, vline=None, color=palette[0:len(conditions)], show=False) 81 - #evoked_topo.facecolor = 'lightslategray' 82 81 evoked_topo.patch.set_alpha(0) 83 82 evoked_topo.set_size_inches(10, 8) 84 83 for axis in evoked_topo.axes: ··· 88 87 legend_loc = 0 89 88 labels = [e.comment if e.comment else 'Unknown' for e in evokeds] 90 89 legend = plt.legend(labels, loc=legend_loc, prop={'size': 20}) 91 - legend.get_frame().set_facecolor(axis_facecolor) 92 90 txts = legend.get_texts() 93 91 for txt, col in zip(txts, palette): 94 92 txt.set_color(col)
+229
app/utils/jupyter/utils.py.12f8fbcd3baa0d3aad47fff879ef68cf.py
··· 1 + from glob import glob 2 + import os 3 + from collections import OrderedDict 4 + from mne import create_info, concatenate_raws, viz 5 + from mne.io import RawArray 6 + from mne.channels import read_montage 7 + import pandas as pd 8 + import numpy as np 9 + import seaborn as sns 10 + from matplotlib import pyplot as plt 11 + 12 + sns.set_context('talk') 13 + sns.set_style('white') 14 + 15 + 16 + def load_data(fnames, sfreq=128., replace_ch_names=None): 17 + """Load CSV files from the /data directory into a Raw object. 18 + 19 + Args: 20 + fnames (array): CSV filepaths from which to load data 21 + 22 + Keyword Args: 23 + sfreq (float): EEG sampling frequency 24 + replace_ch_names (dict or None): dictionary containing a mapping to 25 + rename channels. Useful when an external electrode was used. 26 + 27 + Returns: 28 + (mne.io.array.array.RawArray): loaded EEG 29 + """ 30 + 31 + raw = [] 32 + print(fnames) 33 + for fname in fnames: 34 + # read the file 35 + data = pd.read_csv(fname, index_col=0) 36 + 37 + data = data.dropna() 38 + 39 + # get estimation of sampling rate and use to determine sfreq 40 + # yes, this could probably be improved 41 + srate = 1000 / (data.index.values[1] - data.index.values[0]) 42 + if srate >= 200: 43 + sfreq = 256 44 + else: 45 + sfreq = 128 46 + 47 + # name of each channel 48 + ch_names = list(data.columns) 49 + 50 + # indices of each channel 51 + ch_ind = list(range(len(ch_names))) 52 + 53 + if replace_ch_names is not None: 54 + ch_names = [c if c not in replace_ch_names.keys() 55 + else replace_ch_names[c] for c in ch_names] 56 + 57 + # type of each channels 58 + ch_types = ['eeg'] * (len(ch_ind) - 1) + ['stim'] 59 + montage = read_montage('standard_1005') 60 + 61 + # get data and exclude Aux channel 62 + data = data.values[:, ch_ind].T 63 + 64 + # create MNE object 65 + info = create_info(ch_names=ch_names, ch_types=ch_types, 66 + sfreq=sfreq, montage=montage) 67 + raw.append(RawArray(data=data, info=info)) 68 + 69 + # concatenate all raw objects 70 + raws = concatenate_raws(raw) 71 + 72 + return raws 73 + 74 + 75 + def plot_topo(epochs, conditions=OrderedDict()): 76 + palette = sns.color_palette("hls", len(conditions) + 1) 77 + evokeds = [epochs[name].average() for name in (conditions)] 78 + 79 + evoked_topo = viz.plot_evoked_topo( 80 + evokeds, vline=None, color=palette[0:len(conditions)], show=False) 81 + evoked_topo.patch.set_alpha(0) 82 + evoked_topo.set_size_inches(10, 8) 83 + for axis in evoked_topo.axes: 84 + for line in axis.lines: 85 + line.set_linewidth(2) 86 + 87 + legend_loc = 0 88 + labels = [e.comment if e.comment else 'Unknown' for e in evokeds] 89 + legend = plt.legend(labels, loc=legend_loc, prop={'size': 20}) 90 + legend.get_frame().set_facecolor(axis.facecolor) 91 + txts = legend.get_texts() 92 + for txt, col in zip(txts, palette): 93 + txt.set_color(col) 94 + 95 + return evoked_topo 96 + 97 + 98 + def plot_conditions(epochs, ch_ind=0, conditions=OrderedDict(), ci=97.5, n_boot=1000, 99 + title='', palette=None, 100 + diff_waveform=(4, 3)): 101 + """Plot Averaged Epochs with ERP conditions. 102 + 103 + Args: 104 + epochs (mne.epochs): EEG epochs 105 + 106 + Keyword Args: 107 + conditions (OrderedDict): dictionary that contains the names of the 108 + conditions to plot as keys, and the list of corresponding marker 109 + numbers as value. E.g., 110 + 111 + conditions = {'Non-target': [0, 1], 112 + 'Target': [2, 3, 4]} 113 + 114 + ch_ind (int): index of channel to plot data from 115 + ci (float): confidence interval in range [0, 100] 116 + n_boot (int): number of bootstrap samples 117 + title (str): title of the figure 118 + palette (list): color palette to use for conditions 119 + ylim (tuple): (ymin, ymax) 120 + diff_waveform (tuple or None): tuple of ints indicating which 121 + conditions to subtract for producing the difference waveform. 122 + If None, do not plot a difference waveform 123 + 124 + Returns: 125 + (matplotlib.figure.Figure): figure object 126 + (list of matplotlib.axes._subplots.AxesSubplot): list of axes 127 + """ 128 + if isinstance(conditions, dict): 129 + conditions = OrderedDict(conditions) 130 + 131 + if palette is None: 132 + palette = sns.color_palette("hls", len(conditions) + 1) 133 + 134 + X = epochs.get_data() * 1e6 135 + times = epochs.times 136 + y = pd.Series(epochs.events[:, -1]) 137 + fig, ax = plt.subplots() 138 + 139 + for cond, color in zip(conditions.values(), palette): 140 + sns.tsplot(X[y.isin(cond), ch_ind], time=times, color=color, 141 + n_boot=n_boot, ci=ci) 142 + 143 + if diff_waveform: 144 + diff = (np.nanmean(X[y == diff_waveform[1], ch_ind], axis=0) - 145 + np.nanmean(X[y == diff_waveform[0], ch_ind], axis=0)) 146 + ax.plot(times, diff, color='k', lw=1) 147 + 148 + ax.set_title(epochs.ch_names[ch_ind]) 149 + ax.axvline(x=0, color='k', lw=1, label='_nolegend_') 150 + 151 + ax.set_xlabel('Time (s)') 152 + ax.set_ylabel('Amplitude (uV)') 153 + ax.set_xlabel('Time (s)') 154 + ax.set_ylabel('Amplitude (uV)') 155 + 156 + if diff_waveform: 157 + legend = (['{} - {}'.format(diff_waveform[1], diff_waveform[0])] + 158 + list(conditions.keys())) 159 + else: 160 + legend = conditions.keys() 161 + ax.legend(legend) 162 + sns.despine() 163 + plt.tight_layout() 164 + 165 + if title: 166 + fig.suptitle(title, fontsize=20) 167 + 168 + fig.set_size_inches(10, 8) 169 + 170 + return fig, ax 171 + 172 + 173 + def plot_highlight_regions(x, y, hue, hue_thresh=0, xlabel='', ylabel='', 174 + legend_str=()): 175 + """Plot a line with highlighted regions based on additional value. 176 + 177 + Plot a line and highlight ranges of x for which an additional value 178 + is lower than a threshold. For example, the additional value might be 179 + pvalues, and the threshold might be 0.05. 180 + 181 + Args: 182 + x (array_like): x coordinates 183 + y (array_like): y values of same shape as `x` 184 + 185 + Keyword Args: 186 + hue (array_like): values to be plotted as hue based on `hue_thresh`. 187 + Must be of the same shape as `x` and `y`. 188 + hue_thresh (float): threshold to be applied to `hue`. Regions for which 189 + `hue` is lower than `hue_thresh` will be highlighted. 190 + xlabel (str): x-axis label 191 + ylabel (str): y-axis label 192 + legend_str (tuple): legend for the line and the highlighted regions 193 + 194 + Returns: 195 + (matplotlib.figure.Figure): figure object 196 + (list of matplotlib.axes._subplots.AxesSubplot): list of axes 197 + """ 198 + fig, axes = plt.subplots(1, 1, figsize=(10, 5), sharey=True) 199 + 200 + axes.plot(x, y, lw=2, c='k') 201 + plt.xlabel(xlabel) 202 + plt.ylabel(ylabel) 203 + 204 + kk = 0 205 + a = [] 206 + while kk < len(hue): 207 + if hue[kk] < hue_thresh: 208 + b = kk 209 + kk += 1 210 + while kk < len(hue): 211 + if hue[kk] > hue_thresh: 212 + break 213 + else: 214 + kk += 1 215 + a.append([b, kk - 1]) 216 + else: 217 + kk += 1 218 + 219 + st = (x[1] - x[0]) / 2.0 220 + for p in a: 221 + axes.axvspan(x[p[0]]-st, x[p[1]]+st, facecolor='g', alpha=0.5) 222 + plt.legend(legend_str) 223 + sns.despine() 224 + 225 + return fig, axes 226 + 227 + 228 + def get_epochs_info(epochs): 229 + return [*[{x: len(epochs[x])} for x in epochs.event_id], {"Drop Percentage": round((1 - len(epochs.events)/len(epochs.drop_log)) * 100, 2)}, {"Total Epochs": len(epochs.events)}]