Implemented random onset for behavioral test stimulus

This commit is contained in:
2019-01-24 11:23:22 +00:00
parent 1d3078f28b
commit cea97424ab
11 changed files with 134 additions and 49 deletions
+45 -28
View File
@@ -8,7 +8,7 @@
"architecture" : "x64"
}
,
"rect" : [ 0.0, 44.0, 1366.0, 678.0 ],
"rect" : [ 0.0, 45.0, 1280.0, 929.0 ],
"bglocked" : 0,
"openinpresentation" : 0,
"default_fontsize" : 12.0,
@@ -28,6 +28,35 @@
"digest" : "",
"tags" : "",
"boxes" : [ {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"frgb" : 0.0,
"id" : "obj-28",
"linecount" : 3,
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 1069.0, 608.5, 150.0, 47.0 ],
"text" : "Stim generated with guaranteed no clipping at +5dB"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"frgb" : 0.0,
"id" : "obj-22",
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 1069.0, 586.0, 185.0, 20.0 ],
"text" : "0.333, 1.23, 3.169231, 0.861538"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
@@ -37,8 +66,8 @@
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 1127.0, 559.0, 150.0, 33.0 ],
"text" : "RME Fireface Output:\n+4dB"
"patching_rect" : [ 1069.0, 553.0, 150.0, 33.0 ],
"text" : "RME Fireface Output:\n+1.7dB"
}
}
@@ -108,20 +137,6 @@
"patching_rect" : [ 99.0, 516.0, 20.0, 20.0 ]
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"frgb" : 0.0,
"id" : "obj-17",
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 889.0, 694.0, 150.0, 20.0 ],
"text" : "0.06574271"
}
}
, {
"box" : {
@@ -133,7 +148,7 @@
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 408.0, 215.0, 81.75, 20.0 ],
"text" : "80 dB SPL"
"text" : "70 dB SPL"
}
}
@@ -160,7 +175,7 @@
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 663.0, 694.0, 32.5, 20.0 ],
"patching_rect" : [ 663.0, 681.0, 32.5, 20.0 ],
"text" : "*~"
}
@@ -285,7 +300,7 @@
"numoutlets" : 1,
"outlettype" : [ "int" ],
"parameter_enable" : 0,
"patching_rect" : [ 835.0, 597.0, 20.0, 20.0 ]
"patching_rect" : [ 740.25, 565.5, 20.0, 20.0 ]
}
}
@@ -298,7 +313,7 @@
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 729.0, 617.0, 32.5, 20.0 ],
"patching_rect" : [ 663.0, 613.0, 32.5, 20.0 ],
"text" : "*~"
}
@@ -312,7 +327,7 @@
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 680.0, 563.0, 75.0, 20.0 ],
"patching_rect" : [ 663.0, 565.5, 75.0, 20.0 ],
"text" : "cycle~ 1000"
}
@@ -953,8 +968,8 @@
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 104.25, 780.0, 552.0, 20.0 ],
"text" : "loadmess append OSX:/Users/samuelperry/Work/SOTON/Initial_work/BPLabs/calibration/out/stimulus"
"patching_rect" : [ 104.25, 780.0, 509.0, 20.0 ],
"text" : "loadmess append OSX:/Users/sam/Work/SOTON/Initial_work/BPLabs/calibration/out/stimulus"
}
}
@@ -978,7 +993,7 @@
"maxclass" : "ezdac~",
"numinlets" : 2,
"numoutlets" : 0,
"patching_rect" : [ 511.25, 459.0, 45.0, 45.0 ]
"patching_rect" : [ 511.25, 476.0, 45.0, 45.0 ]
}
}
@@ -1130,6 +1145,7 @@
"destination" : [ "obj-6", 1 ],
"disabled" : 0,
"hidden" : 0,
"midpoints" : [ 672.5, 723.0, 579.625, 723.0, 579.625, 449.0, 546.75, 449.0 ],
"source" : [ "obj-16", 0 ]
}
@@ -1139,6 +1155,7 @@
"destination" : [ "obj-6", 0 ],
"disabled" : 0,
"hidden" : 0,
"midpoints" : [ 672.5, 723.0, 570.625, 723.0, 570.625, 449.0, 520.75, 449.0 ],
"source" : [ "obj-16", 0 ]
}
@@ -1345,7 +1362,7 @@
"destination" : [ "obj-88", 0 ],
"disabled" : 0,
"hidden" : 0,
"midpoints" : [ 389.75, 351.0, 349.0, 351.0, 349.0, 495.0, 389.75, 495.0 ],
"midpoints" : [ 389.75, 360.0, 349.0, 360.0, 349.0, 495.0, 389.75, 495.0 ],
"source" : [ "obj-56", 0 ]
}
@@ -1440,7 +1457,7 @@
"destination" : [ "obj-89", 0 ],
"disabled" : 0,
"hidden" : 0,
"midpoints" : [ 645.5, 351.0, 602.0, 351.0, 602.0, 495.0, 645.5, 495.0 ],
"midpoints" : [ 645.5, 358.0, 602.0, 358.0, 602.0, 495.0, 645.5, 495.0 ],
"source" : [ "obj-62", 0 ]
}
@@ -1535,7 +1552,7 @@
"destination" : [ "obj-90", 0 ],
"disabled" : 0,
"hidden" : 0,
"midpoints" : [ 898.5, 351.0, 850.0, 351.0, 850.0, 495.0, 898.5, 495.0 ],
"midpoints" : [ 898.5, 359.0, 850.0, 359.0, 850.0, 495.0, 898.5, 495.0 ],
"source" : [ "obj-68", 0 ]
}
+2 -3
View File
@@ -22,7 +22,7 @@ def calc_potential_max(wavs, noise_filepath, out_dir, out_name):
noise_rms = np.sqrt(np.mean(x**2))
max_noise_samp = max(np.abs(x))
snr = -15.
snr = 5.
snr_fs = 10**(-snr/20)
max_noise_samp *= max_wav_rms/noise_rms
max_sampl = max_wav_samp+(max_noise_samp*snr_fs)
@@ -48,8 +48,7 @@ def main():
dir_must_exist(out_dir)
dir_must_exist(out_red_dir)
dir_must_exist(out_stim_dir)
import pdb
pdb.set_trace()
story_coef = calc_potential_max(story_wavs, noise_file, out_red_dir, "story_red_coef")
mat_coef = calc_potential_max(mat_wavs, noise_file, out_red_dir, "mat_red_coef")
da_coef = calc_potential_max(da_files, da_noise_file, out_red_dir, "da_red_coef")
+11 -1
View File
@@ -21,7 +21,17 @@ def main():
y = np.sin(2*np.pi*f*n/fs)
coef = np.load('./out/calibration_coefficients/click_cal_coef.npy')
y *= coef
sndio.write("./out/stimulus/1k_tone.wav", y, fs, format='wav', enc='pcm16')
dir_must_exist('./out/calibrated_stim/')
sndio.write("./out/calibrated_stim/1k_tone.wav", y, fs, format='wav', enc='pcm16')
coef = np.load('./out/calibration_coefficients/da_cal_coef.npy')
y, fs, enc = sndio.read('./out/stimulus/da_cal_stim.wav')
sndio.write('./out/calibrated_stim/da_cal_stim.wav', y*coef, fs, format='wav', enc='pcm16')
coef = np.load('./out/calibration_coefficients/mat_cal_coef.npy')
y, fs, enc = sndio.read('./out/stimulus/mat_cal_stim.wav')
sndio.write('./out/calibrated_stim/mat_cal_stim.wav', y*coef, fs, format='wav', enc='pcm16')
coef = np.load('./out/calibration_coefficients/story_cal_coef.npy')
y, fs, enc = sndio.read('./out/stimulus/story_cal_stim.wav')
sndio.write('./out/calibrated_stim/story_cal_stim.wav', y*coef, fs, format='wav', enc='pcm16')
if __name__ == "__main__":
+4 -2
View File
@@ -59,8 +59,8 @@ class DaTestThread(BaseThread):
self.nTrials = nTrials
self.trial_ind = 0
self._stopevent = Event()
# (Completely clean stimulus added by default later)
self.si = np.array([19.0, 50.0, 81.0, 90.0])
# (Completely clean stimulus and stimulus at +10dB added by default later)
self.si = np.array([19.0, 50.0, 81.0])
super(DaTestThread, self).__init__(self.test_name,
sessionFilepath=sessionFilepath,
@@ -133,7 +133,9 @@ class DaTestThread(BaseThread):
x = logit(self.si * 0.01)
snrs = (x/(4*s_50))+srt_50
snrs = np.append(snrs, np.inf)
snrs = np.append(snrs, 10.0)
self.si = np.append(self.si, np.inf)
self.si = np.append(self.si, (10.0*(4*s_50))-srt_50)
self.snr_fs = 10**(-snrs/20)
self.snr_fs[self.snr_fs == np.inf] = 0.
+7 -5
View File
@@ -164,6 +164,7 @@ class EEGMatTrainThread(BaseThread):
)
# For each stimulus
trials = list(zip(self.stim_paths, self.question))[self.trial_ind:]
set_trace()
for (wav, q) in trials:
self.displayInstructions()
self.waitForPartReady()
@@ -173,6 +174,7 @@ class EEGMatTrainThread(BaseThread):
self.playStimulus(wav)
self.setMatrix(q)
self.saveState(out=self.backupFilepath)
self.finaliseResults()
if not self._stopevent.isSet():
self.unsetPageLoaded()
self.socketio.emit('processing-complete', namespace='/main')
@@ -225,10 +227,10 @@ class EEGMatTrainThread(BaseThread):
'response', 'backupFilepath', 'noise_path', 'question_files',
'si', 'question', 'answers', 'trial_ind']
saveDict = {k:self.__dict__[k] for k in toSave}
self.participant['eeg_test'].update(saveDict)
self.participant.save("eeg_test")
self.participant['eeg_mat_train'].update(saveDict)
self.participant.save("eeg_mat_train")
backup_path = os.path.join(self.participant.data_paths['eeg_test'],
backup_path = os.path.join(self.participant.data_paths['eeg_mat_train'],
'finalised_backup.pkl')
copy2(self.backupFilepath, backup_path)
self.finalised = True
@@ -256,10 +258,10 @@ class EEGMatTrainThread(BaseThread):
self.newResp = True
def saveState(self, out="eeg_test_state.pkl"):
def saveState(self, out="eeg_mat_train_state.pkl"):
toSave = ['marker_files', 'wav_files', 'participant', 'response',
'backupFilepath', 'noise_path', 'question_files', 'si',
'question', 'answers', 'trial_ind']
'question', 'stim_paths', 'answers', 'trial_ind']
saveDict = {k:self.__dict__[k] for k in toSave}
with open(out, 'wb') as f:
dill.dump(saveDict, f)
+3 -2
View File
@@ -65,7 +65,7 @@ class EEGStoryTrainThread(BaseThread):
self.socketio.on_event('finalise_results', self.finaliseResults, namespace='/main')
self.loadStimulus()
self.dev_mode = False
self.dev_mode = True
def setQuestion(self, q):
self.socketio.emit('set_question', data=q[0], namespace='/main')
@@ -93,6 +93,7 @@ class EEGStoryTrainThread(BaseThread):
self.processResponse()
self.trial_ind += 1
self.saveState(out=self.backupFilepath)
self.finaliseResults()
if not self._stopevent.isSet():
self.unsetPageLoaded()
self.socketio.emit('processing-complete', namespace='/main')
@@ -148,7 +149,7 @@ class EEGStoryTrainThread(BaseThread):
if not self.dev_mode:
self.play_wav(wav_file, 'finish_test')
else:
self.play_wav('./test.wav', 'finish_test')
self.play_wav('./da_stim/DA_170.wav', 'finish_test')
self.socketio.emit("stim_done", namespace="/main")
+2 -2
View File
@@ -128,8 +128,8 @@ def block_mix_wavs(wavpath_a, wavpath_b, out_wavpath, a_gain=1., b_gain=1., bloc
y[:, 0] = x1[:, 0] + x2
y[:, 1] = x1[:, 1] + x2
y[:, 2] = x1[:, 2]
elif mute_left:
y[:, 0] = 0.0
if mute_left:
y[:, 0] = 0.0
else:
y = x1 + x2
out_wav.write_frames(y)
+11 -5
View File
@@ -131,11 +131,17 @@ class MatTestThread(BaseThread):
self.loadNoise(noiseFilepath, noiseRMSFilepath)
def displayInstructions(self):
self.socketio.emit('display_instructions', namespace='/main')
def testLoop(self):
'''
Main loop for iteratively finding the SRT
'''
self.waitForPageLoad()
self.displayInstructions()
self.waitForPartReady()
while not self.finishTest and not self._stopevent.isSet() and len(self.availableSentenceInds):
self.plotSNR()
self.y = self.generateTrial(self.snr)
@@ -386,22 +392,22 @@ class MatTestThread(BaseThread):
def generateTrial(self, snr):
currentSentenceInd = self.availableSentenceInds.pop(0)
# Convert desired SNR to dB FS
snr_fs = 10**(snr/20)
snr_fs = 10**(snr/20.)
# Get speech data
x = self.lists[0][currentSentenceInd]
x_rms = self.listsRMS[0][currentSentenceInd]
self.currentWords = self.listsString[0][currentSentenceInd]
# Get noise data
noiseLen = x.size + self.fs
noiseLen = x.size + self.fs*2.5
start = random.randint(0, self.noise.frames()-noiseLen)
end = start + noiseLen
self.noise.seek(start)
x_noise = self.noise.read_frames(end-start)
# Scale speech to match the RMS of the noise
x = x*(self.noise_rms/x_rms)
# Scale noise to match the RMS of the speech
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.)
sigStart = random.randint(self.fs, round(2*self.fs))
y[sigStart:sigStart+x.size] += x*snr_fs
y *= self.reduction_coef
return y
+4 -1
View File
@@ -73,7 +73,10 @@ class Participant:
"notes": ''
},
"eeg_train": {
"eeg_story_train": {
"notes": ''
},
"eeg_mat_train": {
"notes": ''
},
+44
View File
@@ -1,6 +1,19 @@
{% extends 'participant_index.html' %}
{% block content %}
<div id="main-div" class="outer">
<div id="instructions">
<div class="card">
<div class="card-body">
<h2>Instructions</h2>
<p>
In this test, you will be presented with repeated "da"
utterances. Simply listen to the stimulus, you do not need to
respond.
</p>
<button type="button" href="#" id="instr_continue" class="Btn Btn-primary">Continue</button>
</div>
</div>
</div>
<div class="container-fluid mat_grid">
<div class="row">
<div class="col-md">
@@ -185,11 +198,15 @@
<div class="mat_submit">
<button type="button" href="#" disabled id="mat-submit" class="Btn Btn-primary">Submit</button>
</div>
<script>
$(document).ready(function(){
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
var part_ready = false;
loading = false;
waitingDialog.show('Loading... Please wait');
var choice = Array(5).fill('');
$('.mat-button').click(function(event) {
@@ -216,10 +233,37 @@
}
});
function displayInstructions() {
$("#instructions").css("display", "table-cell");
$("#instructions").fadeIn();
waitingDialog.hide();
}
function hideInstructions() {
$("#instructions").css("display", "none");
$("#instructions").fadeOut();
if(loading) {
waitingDialog.show('Loading... Please wait');
}
}
$('#instr_continue').click(function(event) {
hideInstructions();
part_ready = true;
socket.emit("part_ready")
on()
});
socket.on('display_instructions', function(msg) {
displayInstructions();
});
socket.on('stim_playing', function(msg) {
// Disable all inputs whilst processing
$('#main-div').find('input, textarea, button, select').attr('disabled','disabled');
});
socket.on('question', function(msg) {
// Disable all inputs whilst processing
$('#question').text(msg);
+1
View File
@@ -4,6 +4,7 @@ import os
import sounddevice as sd
import dill
from shutil import copy2
from threading import Thread, Event
from config import socketio