diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..afd1c95 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory" : "static/bower_components" +} diff --git a/eeg_test_thread.py b/eeg_test_thread.py index d6e5e6f..1f4d415 100644 --- a/eeg_test_thread.py +++ b/eeg_test_thread.py @@ -63,7 +63,7 @@ class EEGTestThread(Thread): self.question_files = [] self.question = [] - self.socketio.on_event('eeg_page_loaded', self.setPageLoaded, namespace='/main') + self.socketio.on_event('page_loaded', self.setPageLoaded, namespace='/main') self.socketio.on_event('submit_eeg_response', self.submitTestResponse, namespace='/main') self.socketio.on_event('finish_eeg_test', self.finishTestEarly, namespace='/main') # Percent speech inteligibility (estimated using behavioural measure) @@ -119,6 +119,15 @@ class EEGTestThread(Thread): self.unsetPageLoaded() self.socketio.emit('processing-complete', {'data': ''}, namespace='/main') self.waitForPageLoad() + self.fillTable() + + def fillTable(self): + ''' + ''' + symb = [[symb_dict[x], symb_dict[y]] for x, y in self.answers] + set_trace() + self.socketio.emit('eeg_test_fill_table', {'data': symb}, namespace='/main') + def setMatrix(self, questions): ''' @@ -141,11 +150,8 @@ class EEGTestThread(Thread): True: 10003, False: 10007 } - try: - self.answers[self.trial_ind, self.q_ind] = self.answer in self.response - symb = symb_dict[self.answers[self.trial_ind, self.q_ind]] - except: - set_trace() + self.answers[self.trial_ind, self.q_ind] = self.answer in self.response + symb = symb_dict[self.answers[self.trial_ind, self.q_ind]] self.socketio.emit('eeg_test_resp', {'q_ind': self.q_ind, 'trial_ind': self.trial_ind, "ans": symb}, namespace='/main') def finishTestEarly(self): @@ -310,7 +316,7 @@ class EEGTestThread(Thread): def saveState(self, out="eeg_test_state.pkl"): toSave = ['marker_files', 'clinPageLoaded', 'wav_files', 'participant', - 'response', 'pageLoaded', 'backupFilepath', 'noise_path', + 'response', 'backupFilepath', 'noise_path', 'question_files', 'partPageLoaded', 'si', 'question', 'answers'] saveDict = {k:self.__dict__[k] for k in toSave} with open(out, 'wb') as f: diff --git a/matrix_test/gen_noise.py b/matrix_test/gen_noise.py index bfaa6c8..b6943ae 100755 --- a/matrix_test/gen_noise.py +++ b/matrix_test/gen_noise.py @@ -190,13 +190,17 @@ def calc_spectrum(files, silences, fs=44100, plot=False): def gen_noise(OutDir, b, fs): print("Generating noise...") # Generate 10 minutes of white noise - x = np.random.randn(int(fs*60.*20.)) + x = np.random.randn(int(fs*60.*10.)) x /= x.max() noiseDir = os.path.join(OutDir, 'wav') + noiseDir = os.path.join(OutDir, 'rms') dir_must_exist(noiseDir) noiseDir = os.path.join(noiseDir, 'noise') dir_must_exist(noiseDir) y = block_lfilter_wav(b, [1.0], x, os.path.join(noiseDir, 'noise.wav'), 65538, 44100) + noise_rms_path = os.path.join(noiseDir, 'noise_rms.npy') + rms = np.mean(np.sqrt(y**2)) + np.save(noise_rms_path, rms) return y @@ -211,11 +215,11 @@ if __name__ == "__main__": help='Matrix test speech data location') parser.add_argument('--OutDir', type=PathType(exists=None, type='dir'), default='./stimulus', help='Output directory') - parser.add_argument('--SkipRMS', action='store_true') + parser.add_argument('--CalcRMS', action='store_true') args = {k:v for k,v in vars(parser.parse_args()).items() if v is not None} rmsDir = os.path.join(args['OutDir'], "rms") - if not args['SkipRMS']: + if args['CalcRMS']: indexes = gen_indexes() wavFiles = gen_audio_stim(args['MatrixDir'], args['OutDir'], indexes) rmsFiles = gen_rms(wavFiles, rmsDir) diff --git a/matrix_test/generate_matrix_stimulus.py b/matrix_test/generate_matrix_stimulus.py index d36f75d..f56a360 100755 --- a/matrix_test/generate_matrix_stimulus.py +++ b/matrix_test/generate_matrix_stimulus.py @@ -322,7 +322,7 @@ if __name__ == "__main__": gen2(args['MatrixDir'], args['OutDir'], y) generateAudioStimulus(**args) - #generateNoiseFromSentences(args['OutDir'], noiseDir) + generateNoiseFromSentences(args['OutDir'], noiseDir) #generateDecoderAudio(args['OutDir'], noiseDir, decoderDir) diff --git a/matrix_test_thread.py b/matrix_test_thread.py index 54961f0..e3f0092 100644 --- a/matrix_test_thread.py +++ b/matrix_test_thread.py @@ -10,7 +10,7 @@ from scipy.optimize import minimize import csv from shutil import copy2 -from pysndfile import sndio +from pysndfile import sndio, PySndfile from matrix_test.filesystem import globDir import sounddevice as sd import pdb @@ -18,7 +18,7 @@ import pdb from config import socketio -def run_matrix_thread(listN=None, sessionFilepath=None, participant=None): +def run_matrix_thread(listN=3, sessionFilepath=None, participant=None): global matThread if 'matThread' in globals(): if matThread.isAlive() and isinstance(matThread, MatTestThread): @@ -59,8 +59,11 @@ class MatTestThread(Thread): ''' Thread for running server side matrix test operations ''' - def __init__(self, listN=3, sessionFilepath=None, noiseFilepath="./matrix_test/stimulus/wav/noise/noise.wav", - listFolder="./matrix_test/stimulus/wav/sentence-lists/", socketio=None, participant=None): + def __init__(self, listN=3, sessionFilepath=None, + noiseFilepath="./matrix_test/stimulus/wav/noise/noise.wav", + noiseRMSFilepath="./matrix_test/stimulus/rms/noise/noise_rms.npy", + listFolder="./matrix_test/stimulus/wav/sentence-lists/", + socketio=None, participant=None): super(MatTestThread, self).__init__() self.participant=participant self.newResp = False @@ -72,14 +75,14 @@ class MatTestThread(Thread): self.socketio = socketio # Attach messages from gui to class methods self.socketio.on_event('submit_mat_response', self.submitMatResponse, namespace='/main') - self.socketio.on_event('mat_page_loaded', self.setPageLoaded, namespace='/main') + self.socketio.on_event('page_loaded', self.setPageLoaded, namespace='/main') self.socketio.on_event('save_file_dialog_resp', self.manualSave, namespace='/main') self.socketio.on_event('load_file_dialog_resp', self.loadStateSocketHandle, namespace='/main') self.socketio.on_event('repeat_stimulus', self.playStimulusSocketHandle, namespace='/main') self.socketio.on_event('finish_test', self.finishTestEarly, namespace='/main') self.socketio.on_event('finalise_results', self.finaliseResults, namespace='/main') - self.listN = listN + self.listN = int(listN) self.loadedLists = [] self.lists = [] self.listsRMS = [] @@ -128,10 +131,11 @@ class MatTestThread(Thread): # If loading session from file, load session variables from the file if sessionFilepath: self.loadState(sessionFilepath) + self.loadNoise(noiseFilepath, noiseRMSFilepath) else: # Preload audio at start of the test self.loadStimulus(listFolder, n=self.listN) - self.loadNoise(noiseFilepath) + self.loadNoise(noiseFilepath, noiseRMSFilepath) def testLoop(self): @@ -413,13 +417,12 @@ class MatTestThread(Thread): random.shuffle(self.availableSentenceInds) - def loadNoise(self, noiseFilepath): + def loadNoise(self, noiseFilepath, noiseRMSFilepath): ''' Read noise samples and calculate the RMS of the signal ''' - x, _, _ = sndio.read(noiseFilepath) - self.noise = x - self.noise_rms = np.sqrt(np.mean(self.noise**2)) + self.noise = PySndfile(noiseFilepath, 'r') + self.noise_rms = np.load(noiseRMSFilepath) def unsetPageLoaded(self): @@ -445,13 +448,12 @@ class MatTestThread(Thread): self.currentWords = self.listsString[0][currentSentenceInd] # Get noise data noiseLen = x.size + self.fs - start = random.randint(0, self.noise.size-noiseLen) + start = random.randint(0, self.noise.frames()-noiseLen) end = start + noiseLen - x_noise = self.noise[start:end] - # Calculate RMS of noise - noise_rms = np.sqrt(np.mean(x_noise**2)) + self.noise.seek(start) + x_noise = self.noise.read_frames(end-start) # Scale noise to match the RMS of the speech - x_noise = x_noise*(x_rms/noise_rms) + x_noise = x_noise*(x_rms/self.noise_rms) y = x_noise # Set speech to start 500ms after the noise, scaled to the desired SNR sigStart = round(self.fs/2.) @@ -470,9 +472,9 @@ class MatTestThread(Thread): def saveState(self, out="mat_state.pkl"): toSave = ['listsRMS', 'y', 'currentList', 'slope', 'snr', 'snrTrack', 'direction', 'noise_rms', 'i', 'currentWords', 'usedLists', - 'availableSentenceInds', 'trialN', 'listsString', 'noise', - 'fs', 'nCorrect', 'loadedLists', 'lists', 'listN', - 'wordsCorrect', 'responses', 'presentedWords', 'srt_50', 's_50'] + 'availableSentenceInds', 'trialN', 'listsString', 'fs', + 'nCorrect', 'loadedLists', 'lists', 'listN', 'wordsCorrect', + 'responses', 'presentedWords', 'srt_50', 's_50'] saveDict = {k:self.__dict__[k] for k in toSave} with open(out, 'wb') as f: dill.dump(saveDict, f) @@ -493,7 +495,8 @@ class MatTestThread(Thread): def loadState(self, filepath): with open(filepath, 'rb') as f: - self.__dict__.update(dill.load(f)) + backup_dict = dill.load(f) + self.__dict__.update(backup_dict) def run(self): diff --git a/socket_handlers.py b/socket_handlers.py index a880e43..0848748 100644 --- a/socket_handlers.py +++ b/socket_handlers.py @@ -30,6 +30,12 @@ from matrix_test_thread import run_matrix_thread from eeg_test_thread import run_eeg_test_thread from eeg_train_thread import run_eeg_train_thread +thread_runners = { + "eeg_test": run_eeg_test_thread, + "eeg_train": run_eeg_train_thread, + "mat": run_matrix_thread +} + ''' Generic socket handlers ''' @@ -160,6 +166,21 @@ def start_saved_eeg_train(msg): ''' EEG test socket handlers ''' + +@socketio.on('start_test', namespace='/main') +def start_test(msg): + test_name = msg.pop('test_name') + part_key = msg.pop('part_key') + thread_runner = thread_runners[test_name] + socketio.emit('participant_start_{}'.format(test_name), namespace='/main') + + if part_key != "--": + participant = participants[part_key] + else: + raise ValueError("Participant must be selected...") + + thread_runner(participant=participant, **msg) + @socketio.on('start_eeg_test', namespace='/main') def start_eeg_test(msg): ''' diff --git a/templates/eeg_setup.html b/templates/eeg_setup.html index dac37d8..72f7c8c 100644 --- a/templates/eeg_setup.html +++ b/templates/eeg_setup.html @@ -33,12 +33,12 @@ $('#load-backup').click(function(event) { // Send message to call stimulus generation function in Python - socket.emit('load_eeg_backup', {part_key: $("#participant").val()}); + socket.emit('load_eeg_test_backup', {part_key: $("#participant").val()}); return false; }) $('#load-saved').click(function(event) { // Send message to call stimulus generation function in Python - socket.emit('load_eeg_session', {part_key: $("#participant").val()}); + socket.emit('load_eeg_test_session', {part_key: $("#participant").val()}); return false; }) $('#start_eeg_train').click(function(event) { diff --git a/templates/eeg_test_clinician_end.html b/templates/eeg_test_clinician_end.html index a4fdb2d..4e77bc6 100644 --- a/templates/eeg_test_clinician_end.html +++ b/templates/eeg_test_clinician_end.html @@ -1,3 +1,73 @@ {% extends 'index.html' %} {% block content %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Trial #1234567
Q1-------
Q2-------
+
+ +
+
+
+ {% endblock %} diff --git a/templates/eeg_test_clinician_view.html b/templates/eeg_test_clinician_view.html index 8db1679..e52d663 100644 --- a/templates/eeg_test_clinician_view.html +++ b/templates/eeg_test_clinician_view.html @@ -63,10 +63,6 @@ waitingDialog.hide(); }); - socket.on('check-loaded', function(msg) { - socket.emit('eeg_page_loaded', {data: "clinician"}); - }); - $('#eeg_test_finish').click(function(event) { socket.emit("finish_eeg_test") }); diff --git a/templates/eeg_test_run.html b/templates/eeg_test_run.html index aac5a98..ef7d5be 100644 --- a/templates/eeg_test_run.html +++ b/templates/eeg_test_run.html @@ -94,10 +94,6 @@ return false; }); - socket.on('check-loaded', function(msg) { - socket.emit('eeg_page_loaded', {data: "participant"}); - }); - socket.on('eeg_stim_done', function(msg) { $('.eeg_submit').find('input, textarea, button, select').removeAttr('disabled'); off() diff --git a/templates/index.html b/templates/index.html index 722afc7..a67ec01 100644 --- a/templates/index.html +++ b/templates/index.html @@ -108,6 +108,10 @@ socket.on('main-notification', function(msg) { alert(msg.data) }); + + socket.on('check-loaded', function(msg) { + socket.emit('page_loaded', {data: "clinician"}); + }); }); diff --git a/templates/mat_test_clinician_end.html b/templates/mat_test_clinician_end.html index bd1e3e8..35edafd 100644 --- a/templates/mat_test_clinician_end.html +++ b/templates/mat_test_clinician_end.html @@ -21,10 +21,20 @@ $(document).ready(function(){ // Initialise socketio with a namespace called "main" var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main'); + var finalised=0; + window.onbeforeunload = confirmExit; + function confirmExit() { + if (formmodified == 0) { + return "New information not saved. Do you wish to leave the page?"; + } + } + $('#mat-save').click(function(event) { socket.emit("finalise_results") + finalised=1; window.location.href = '/home'; }); + $('#mat-repeat').click(function(event) { socket.emit("repeat_stimulus") }); @@ -45,10 +55,6 @@ $(document).ready(function(){ socket.on('load_file_dialog_resp', function(msg) { socket.emit("load_file_dialog_resp", msg) }); - - socket.on('check-loaded', function(msg) { - socket.emit('mat_page_loaded', {data: "clinician"}); - }); }); {% endblock %} diff --git a/templates/mat_test_clinician_view.html b/templates/mat_test_clinician_view.html index f2ccd4e..f3bdf37 100644 --- a/templates/mat_test_clinician_view.html +++ b/templates/mat_test_clinician_view.html @@ -38,10 +38,6 @@ $(document).ready(function(){ socket.emit("load_file_dialog_resp", msg) }); - socket.on('check-loaded', function(msg) { - socket.emit('mat_page_loaded', {data: "clinician"}); - }); - // Catch message when asynchronous process is complete socket.on('processing-complete', function(msg) { // Re-enable all inputs diff --git a/templates/mat_test_run.html b/templates/mat_test_run.html index ee1066e..5e5bab6 100644 --- a/templates/mat_test_run.html +++ b/templates/mat_test_run.html @@ -112,10 +112,6 @@ alert("Matrix stimulus processing complete!") window.location.href = '/matrix_test/complete'; }); - - socket.on('check-loaded', function(msg) { - socket.emit('mat_page_loaded', {data: "participant"}); - }); }); diff --git a/templates/matrix_test_setup.html b/templates/matrix_test_setup.html index d716978..7c6d407 100644 --- a/templates/matrix_test_setup.html +++ b/templates/matrix_test_setup.html @@ -44,7 +44,7 @@ return false; }) $('#submit').click(function(event) { - socket.emit('start_mat_test', {listN: $("#listN").val(), part_key: $("#participant").val()}); + socket.emit('start_test', {listN: $("#listN").val(), part_key: $("#participant").val(), test_name: "mat"}); return false; }) diff --git a/templates/participant_index.html b/templates/participant_index.html index 5f3e8b8..5842ae6 100644 --- a/templates/participant_index.html +++ b/templates/participant_index.html @@ -67,6 +67,10 @@ socket.on('participant_start_mat', function() { window.location.href = "/matrix_test/run"; }); + + socket.on('check-loaded', function(msg) { + socket.emit('page_loaded', {data: "participant"}); + }); });