Implemented /da/ and click stimulus presentation

This commit is contained in:
2018-12-16 22:48:14 +00:00
parent dee337550b
commit d7180d3227
22 changed files with 1957 additions and 24 deletions
-1
View File
@@ -24,7 +24,6 @@ class WavPlayer(Thread):
super(WavPlayer, self).__init__()
self.socketio = socketio
self._stopevent = Event()
self.socketio.on_event(stop_string, self.join, namespace='/main')
self.wav_file = wav_file
def join(self, timeout=None):
+929
View File
@@ -0,0 +1,929 @@
{
"patcher" : {
"fileversion" : 1,
"appversion" : {
"major" : 6,
"minor" : 1,
"revision" : 10,
"architecture" : "x64"
}
,
"rect" : [ 0.0, 44.0, 1223.0, 678.0 ],
"bglocked" : 0,
"openinpresentation" : 0,
"default_fontsize" : 12.0,
"default_fontface" : 0,
"default_fontname" : "Arial",
"gridonopen" : 0,
"gridsize" : [ 15.0, 15.0 ],
"gridsnaponopen" : 0,
"statusbarvisible" : 2,
"toolbarvisible" : 1,
"boxanimatetime" : 200,
"imprint" : 0,
"enablehscroll" : 1,
"enablevscroll" : 1,
"devicewidth" : 0.0,
"description" : "",
"digest" : "",
"tags" : "",
"boxes" : [ {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-51",
"maxclass" : "flonum",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "float", "bang" ],
"parameter_enable" : 0,
"patching_rect" : [ 53.25, 407.0, 88.0, 20.0 ],
"presentation_rect" : [ 376.0, 544.0, 0.0, 0.0 ]
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-52",
"maxclass" : "newobj",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 141.25, 447.0, 32.5, 20.0 ],
"presentation_rect" : [ 464.0, 584.0, 0.0, 0.0 ],
"text" : "*~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-53",
"maxclass" : "newobj",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 175.25, 447.0, 32.5, 20.0 ],
"presentation_rect" : [ 498.0, 584.0, 0.0, 0.0 ],
"text" : "*~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-54",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 141.25, 373.0, 33.0, 20.0 ],
"presentation_rect" : [ 464.0, 510.0, 0.0, 0.0 ],
"text" : "sig~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-55",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 173.75, 333.0, 32.5, 18.0 ],
"presentation_rect" : [ 496.5, 470.0, 0.0, 0.0 ],
"text" : "1"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-56",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 141.25, 333.0, 32.5, 18.0 ],
"presentation_rect" : [ 464.0, 470.0, 0.0, 0.0 ],
"text" : "0"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-57",
"maxclass" : "newobj",
"numinlets" : 3,
"numoutlets" : 2,
"outlettype" : [ "signal", "signal" ],
"patching_rect" : [ 141.25, 407.0, 110.0, 20.0 ],
"text" : "groove~ noise_buf"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-33",
"maxclass" : "flonum",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "float", "bang" ],
"parameter_enable" : 0,
"patching_rect" : [ 253.25, 407.0, 88.0, 20.0 ]
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-34",
"maxclass" : "newobj",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 341.25, 447.0, 32.5, 20.0 ],
"text" : "*~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-35",
"maxclass" : "newobj",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 375.25, 447.0, 32.5, 20.0 ],
"text" : "*~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-37",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 341.25, 373.0, 33.0, 20.0 ],
"text" : "sig~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-39",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 373.75, 333.0, 32.5, 18.0 ],
"text" : "1"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-48",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 341.25, 333.0, 32.5, 18.0 ],
"text" : "0"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-50",
"maxclass" : "newobj",
"numinlets" : 3,
"numoutlets" : 2,
"outlettype" : [ "signal", "signal" ],
"patching_rect" : [ 341.25, 407.0, 94.0, 20.0 ],
"text" : "groove~ da_buf"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-32",
"maxclass" : "flonum",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "float", "bang" ],
"parameter_enable" : 0,
"patching_rect" : [ 442.25, 407.0, 88.0, 20.0 ],
"presentation_rect" : [ 557.0, 512.0, 0.0, 0.0 ]
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-30",
"maxclass" : "newobj",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 530.25, 447.0, 32.5, 20.0 ],
"presentation_rect" : [ 503.0, 573.0, 0.0, 0.0 ],
"text" : "*~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-31",
"maxclass" : "newobj",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 564.25, 447.0, 32.5, 20.0 ],
"presentation_rect" : [ 537.0, 573.0, 0.0, 0.0 ],
"text" : "*~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-19",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "signal" ],
"patching_rect" : [ 530.25, 373.0, 33.0, 20.0 ],
"presentation_rect" : [ 732.0, 455.0, 0.0, 0.0 ],
"text" : "sig~"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-25",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 562.75, 333.0, 32.5, 18.0 ],
"presentation_rect" : [ 697.0, 411.0, 0.0, 0.0 ],
"text" : "1"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-26",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 530.25, 333.0, 32.5, 18.0 ],
"presentation_rect" : [ 622.0, 416.0, 0.0, 0.0 ],
"text" : "0"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-29",
"maxclass" : "newobj",
"numinlets" : 3,
"numoutlets" : 2,
"outlettype" : [ "signal", "signal" ],
"patching_rect" : [ 530.25, 407.0, 104.0, 20.0 ],
"text" : "groove~ click_buf"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-17",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 53.25, 682.0, 486.0, 20.0 ],
"text" : "loadmess append OSX:/Users/samuelperry/Work/SOTON/Initial_work/BPLabs/click_stim/"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-13",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 835.75, 204.0, 97.0, 20.0 ],
"presentation_rect" : [ 512.0, 408.0, 0.0, 0.0 ],
"text" : "prepend replace"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-14",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 835.75, 164.0, 79.0, 20.0 ],
"presentation_rect" : [ 512.0, 368.0, 0.0, 0.0 ],
"text" : "absolutepath"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-15",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 835.75, 132.0, 127.0, 18.0 ],
"presentation_rect" : [ 512.0, 299.0, 0.0, 0.0 ],
"text" : "click_3000_20Hz.wav"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-16",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "float", "bang" ],
"patching_rect" : [ 835.75, 235.0, 98.0, 20.0 ],
"text" : "buffer~ click_buf"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-28",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 53.25, 702.0, 476.0, 20.0 ],
"text" : "loadmess append OSX:/Users/samuelperry/Work/SOTON/Initial_work/BPLabs/da_stim/"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-27",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 53.25, 662.0, 596.0, 20.0 ],
"text" : "loadmess append OSX:/Users/samuelperry/Work/SOTON/Initial_work/BPLabs/matrix_test/stimulus/wav/noise/"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-23",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 650.0, 132.0, 64.0, 18.0 ],
"text" : "noise.wav"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-20",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 650.0, 204.0, 97.0, 20.0 ],
"text" : "prepend replace"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-21",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 650.0, 164.0, 79.0, 20.0 ],
"text" : "absolutepath"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-22",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "float", "bang" ],
"patching_rect" : [ 650.0, 235.0, 103.0, 20.0 ],
"text" : "buffer~ noise_buf"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-18",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 508.0, 204.0, 97.0, 20.0 ],
"text" : "prepend replace"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-2",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 53.25, 750.0, 49.0, 20.0 ],
"text" : "filepath"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-11",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 508.0, 164.0, 79.0, 20.0 ],
"text" : "absolutepath"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-10",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 508.0, 132.0, 82.0, 18.0 ],
"text" : "3000_da.wav"
}
}
, {
"box" : {
"fontname" : "Arial",
"fontsize" : 12.0,
"id" : "obj-8",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "float", "bang" ],
"patching_rect" : [ 508.0, 235.0, 88.0, 20.0 ],
"text" : "buffer~ da_buf"
}
}
, {
"box" : {
"id" : "obj-6",
"maxclass" : "ezdac~",
"numinlets" : 2,
"numoutlets" : 0,
"patching_rect" : [ 341.25, 537.0, 45.0, 45.0 ]
}
}
],
"lines" : [ {
"patchline" : {
"destination" : [ "obj-11", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-10", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-18", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-11", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-16", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-13", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-13", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-14", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-14", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-15", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-2", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-17", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-8", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-18", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-29", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-19", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-22", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-20", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-20", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-21", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-21", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-23", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-19", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-25", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-19", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-26", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-2", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-27", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-2", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-28", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-30", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-29", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-31", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-29", 1 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-6", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-30", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-6", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-31", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-30", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-32", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-31", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-32", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-34", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-33", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-35", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-33", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-6", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-34", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-6", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-35", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-50", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-37", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-37", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-39", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-37", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-48", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-34", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-50", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-35", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-50", 1 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-52", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-51", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-53", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-51", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-6", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-52", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-6", 1 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-53", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-57", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-54", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-54", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-55", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-54", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-56", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-52", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-57", 0 ]
}
}
, {
"patchline" : {
"destination" : [ "obj-53", 0 ],
"disabled" : 0,
"hidden" : 0,
"source" : [ "obj-57", 1 ]
}
}
],
"dependency_cache" : [ ]
}
}
+109
View File
@@ -0,0 +1,109 @@
from threading import Thread, Event
import os
import numpy as np
from matrix_test.filesystem import globDir
from pysndfile import PySndfile, sndio
from random import randint, shuffle
from shutil import copyfile
from natsort import natsorted
import numpy as np
import pandas as pd
from shutil import copy2
from test_base import BaseThread
from matrix_test.signalops import play_wav
from scipy.special import logit
from config import socketio
import csv
import pdb
import dill
symb_dict = {
True: 10003,
False: 10007
}
def set_trace():
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
log = logging.getLogger('engineio')
log.setLevel(logging.ERROR)
pdb.set_trace()
class ClickTestThread(BaseThread):
'''
Thread for running server side matrix test operations
'''
def __init__(self, sessionFilepath=None,
stimFolder='./click_stim/', nTrials=2,
socketio=None, participant=None, srt_50=None, s_50=None):
self.wav_file = os.path.join(stimFolder, 'click_3000_20Hz.wav')
self.test_name = 'click_test'
self.nTrials = nTrials
self.trial_ind = 0
self._stopevent = Event()
super(ClickTestThread, self).__init__(self.test_name,
sessionFilepath=sessionFilepath,
socketio=socketio,
participant=participant)
self.toSave = ['trial_ind', 'nTrials', 'wav_file', 'test_name']
self.socketio.on_event('finalise_results', self.finaliseResults, namespace='/main')
self.dev_mode = False
def testLoop(self):
'''
Main loop for iteratively finding the SRT
'''
self.waitForPageLoad()
self.socketio.emit('test_ready', namespace='/main')
for self.trial_ind in range(self.nTrials):
self.displayInstructions()
self.waitForPartReady()
if self._stopevent.isSet() or self.finishTest:
break
# Play concatenated matrix sentences at set SNR
self.playStimulus(self.wav_file)
self.saveState(out=self.backupFilepath)
if not self._stopevent.isSet():
self.unsetPageLoaded()
self.socketio.emit('processing-complete', namespace='/main')
def displayInstructions(self):
self.socketio.emit('display_instructions', namespace='/main')
def playStimulus(self, wav_file, replay=False):
self.newResp = False
self.socketio.emit("stim_playing", namespace="/main")
# if not replay:
# self.y = self.generateTrial(self.snr)
# Play audio
# sd.play(self.y, self.fs, blocking=True)
if not self.dev_mode:
self.play_wav(wav_file, 'finish_test')
else:
self.play_wav('./test.wav', 'finish_test')
self.socketio.emit("stim_done", namespace="/main")
def loadStimulus(self):
'''
'''
#audio, fs, enc, fmt = sndio.read(wav, return_format=True)
def saveState(self, out="test_state.pkl"):
saveDict = {k:self.__dict__[k] for k in self.toSave}
with open(out, 'wb') as f:
dill.dump(saveDict, f)
+109
View File
@@ -0,0 +1,109 @@
from threading import Thread, Event
import os
import numpy as np
from matrix_test.filesystem import globDir
from pysndfile import PySndfile, sndio
from random import randint, shuffle
from shutil import copyfile
from natsort import natsorted
import numpy as np
import pandas as pd
from shutil import copy2
from test_base import BaseThread
from matrix_test.signalops import play_wav
from scipy.special import logit
from config import socketio
import csv
import pdb
import dill
symb_dict = {
True: 10003,
False: 10007
}
def set_trace():
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
log = logging.getLogger('engineio')
log.setLevel(logging.ERROR)
pdb.set_trace()
class DaTestThread(BaseThread):
'''
Thread for running server side matrix test operations
'''
def __init__(self, sessionFilepath=None,
stimFolder='./da_stim/', nTrials=2,
socketio=None, participant=None, srt_50=None, s_50=None):
self.wav_file = os.path.join(stimFolder, '3000_da.wav')
self.test_name = 'da_test'
self.nTrials = nTrials
self.trial_ind = 0
self._stopevent = Event()
super(DaTestThread, self).__init__(self.test_name,
sessionFilepath=sessionFilepath,
socketio=socketio,
participant=participant)
self.toSave = ['trial_ind', 'nTrials', 'wav_file', 'test_name']
self.socketio.on_event('finalise_results', self.finaliseResults, namespace='/main')
self.dev_mode = True
def testLoop(self):
'''
Main loop for iteratively finding the SRT
'''
self.waitForPageLoad()
self.socketio.emit('test_ready', namespace='/main')
for self.trial_ind in range(self.nTrials):
self.displayInstructions()
self.waitForPartReady()
if self._stopevent.isSet() or self.finishTest:
break
# Play concatenated matrix sentences at set SNR
self.playStimulus(self.wav_file)
self.saveState(out=self.backupFilepath)
if not self._stopevent.isSet():
self.unsetPageLoaded()
self.socketio.emit('processing-complete', namespace='/main')
def displayInstructions(self):
self.socketio.emit('display_instructions', namespace='/main')
def playStimulus(self, wav_file, replay=False):
self.newResp = False
self.socketio.emit("stim_playing", namespace="/main")
# if not replay:
# self.y = self.generateTrial(self.snr)
# Play audio
# sd.play(self.y, self.fs, blocking=True)
if not self.dev_mode:
self.play_wav(wav_file, 'finish_test')
else:
self.play_wav('./test.wav', 'finish_test')
self.socketio.emit("stim_done", namespace="/main")
def loadStimulus(self):
'''
'''
#audio, fs, enc, fmt = sndio.read(wav, return_format=True)
def saveState(self, out="test_state.pkl"):
saveDict = {k:self.__dict__[k] for k in self.toSave}
with open(out, 'wb') as f:
dill.dump(saveDict, f)
+2 -9
View File
@@ -11,7 +11,6 @@ import pandas as pd
from shutil import copy2
from test_base import BaseThread, run_test_thread
from WavPlayer import play_wav
from scipy.special import logit
from config import socketio
import csv
@@ -144,12 +143,6 @@ class EEGTestThread(BaseThread):
self.answers[incomplete_responses] = np.nan
self.fillTable()
def finishTestEarly(self):
'''
'''
self.finishTest = True
def finaliseResults(self):
toSave = ['marker_files', 'clinPageLoaded', 'wav_files', 'participant',
'response', 'backupFilepath', 'noise_path', 'question_files',
@@ -180,9 +173,9 @@ class EEGTestThread(BaseThread):
# Play audio
# sd.play(self.y, self.fs, blocking=True)
if not self.dev_mode:
play_wav(wav_file, 'finish_test')
self.play_wav(wav_file, 'finish_test')
else:
play_wav('./test.wav', 'finish_test')
self.play_wav('./test.wav', 'finish_test')
self.socketio.emit("stim_done", namespace="/main")
+22 -5
View File
@@ -187,9 +187,26 @@ def matDecStim():
'''
Click stimulus routing
'''
@server.route('/click_stim')
def clickStim():
return render_template("click_stim.html")
@server.route('/click/setup')
def click_setup():
participants = find_participants()
return render_template("click_test_setup.html", part_keys=participants.keys())
@server.route('/click/clinician/run')
def click_clinician_run():
return render_template("click_test_clinician_view.html")
@server.route('/click/clinician/complete')
def click_clinician_complete():
return render_template("click_test_clinician_end.html")
@server.route('/click/run')
def click_run():
return render_template("click_test_run.html")
@server.route('/click/complete')
def click_complete():
return render_template("click_test_end.html")
'''
@@ -206,7 +223,7 @@ def da_clinician_run():
@server.route('/da/clinician/complete')
def da_clinician_complete():
return render_template("da_test_clinician_complete.html")
return render_template("da_test_clinician_end.html")
@server.route('/da/run')
def da_run():
@@ -214,7 +231,7 @@ def da_run():
@server.route('/da/complete')
def da_complete():
return render_template("da_test_run.html")
return render_template("da_test_end.html")
'''
Calibration routing
+2
View File
@@ -34,12 +34,14 @@ from config import server, socketio, participants
from da_test_thread import DaTestThread
from eeg_test_thread import EEGTestThread
from matrix_test_thread import MatTestThread
from click_test_thread import ClickTestThread
from test_base import run_test_thread
thread_types = {
'da_test': DaTestThread,
'eeg_test': EEGTestThread,
'mat_test': MatTestThread,
'click_test': ClickTestThread,
}
'''
+27
View File
@@ -0,0 +1,27 @@
{% extends 'index.html' %}
{% block content %}
<div class="card">
<div id="main-div" class="card-body">
<div class="d-flex justify-content-center mt-2" role="group">
<p>Use the controls below to play the calibration signal. The signal should be calibrated to 60dBa.</p>
</div>
<div class="d-flex justify-content-center mt-2" role="group">
<button type="button" id="play_calibrate" class="btn btn-primary mx-3">Play calibration signal</button>
<button type="button" id="stop_calibrate" class="btn btn-primary mx-3">Stop calibration signal</button>
</div>
</div>
</div>
<script>
$(document).ready(function(){
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
$('#play_calibrate').click(function(event) {
socket.emit("play_calibrate");
});
$('#stop_calibrate').click(function(event) {
socket.emit("stop_calibrate");
});
});
</script>
{% endblock %}
+22
View File
@@ -0,0 +1,22 @@
{% extends 'index.html' %}
{% block content %}
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class="card">
<div id="main-div" class="card-body">
<div class="d-flex justify-content-center mt-2" role="group">
<button type="button" id="mat-save" class="btn btn-primary mx-3">Save and finish test</button>
</div>
</div>
</div>
<script>
$(document).ready(function(){
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
$('#mat-save').click(function(event) {
socket.emit("finalise_results")
window.location.href = '/home';
});
});
</script>
{% endblock %}
+39
View File
@@ -0,0 +1,39 @@
{% extends 'index.html' %}
{% block content %}
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class="card">
<div id="main-div" class="card-body">
<div class="d-flex justify-content-center mt-2" role="group">
<button type="button" id="test_save" class="btn btn-primary mx-3">Save test state</button>
<button type="button" id="test_finish" class="btn btn-primary mx-3">Finish test</button>
</div>
</div>
</div>
<script>
$(document).ready(function(){
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
// Check if matrix stimulus is currently being processed
waitingDialog.show('Generating stimulus');
socket.on('test_ready', function(msg) {
waitingDialog.hide();
});
$('#test_save').click(function(event) {
socket.emit("open_save_file_dialog")
});
$('#test_finish').click(function(event) {
socket.emit("finish_test")
});
// Catch message when asynchronous process is complete
socket.on('processing-complete', function(msg) {
alert("Stimulus processing complete!")
window.location.href = '/click/clinician/complete';
});
});
</script>
{% endblock %}
+9
View File
@@ -0,0 +1,9 @@
{% extends 'participant_index.html' %}
{% block content %}
<div class="card">
<div class="card-body">
<h1>Test complete!</h1>
<p>Please wait for the experimenter to give further instructions...</p>
</div>
</div>
{% endblock %}
+92
View File
@@ -0,0 +1,92 @@
{% extends 'participant_index.html' %}
{% block content %}
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div id="main-div" class="outer">
<div id="overlay">
<span id="overlay_crosshair">&#10010;</span>
</div>
<div id="instructions">
<div class="card">
<div class="card-body">
<h2>Instructions</h2>
<p>
In this test, you will be presented with repeated click sounds.
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>
<script>
$(document).ready(function(){
jQuery.expr[':'].contains = function(a, i, m) {
return jQuery(a).text().toUpperCase()
.indexOf(m[3].toUpperCase()) >= 0;
};
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
var loading = true;
var part_ready = false;
waitingDialog.show('Loading... Please wait');
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('test_ready', function(msg) {
loading = false;
waitingDialog.hide();
});
socket.on('stim_playing', function(msg) {
on()
});
socket.on('stim_done', function(msg) {
off()
});
function on() {
$("#overlay").css("display", "table-cell");
$("#overlay_crosshair").css("display", "inline");
$("#overlay").fadeIn();
$("#overlay_crosshair").fadeIn();
}
function off() {
$("#overlay").fadeOut();
}
socket.on('processing-complete', function(msg) {
// Re-enable all inputs
$('#main-div').find('input, textarea, button, select').removeAttr('disabled');
alert("Click stimulus processing complete!")
window.location.href = '/click/complete';
});
});
</script>
{% endblock %}
+58
View File
@@ -0,0 +1,58 @@
{% extends 'index.html' %}
{% block content %}
<div class="card">
<div id="main-div" class="card-body">
<form action="{{ url_for('matDecStim') }}" method="POST">
<div class="form-group">
<select class="form-control" name="participant" id="participant">
<option>--</option>
{% for p in part_keys %}
<option>{{ p }}</option>
{% endfor %}
</select>
</div>
<div class="form-group d-flex justify-content-center">
<button type="button" id="start_test" class="btn btn-primary mx-3">Start data collection</button>
<button type="button" id="load-saved" class="btn btn-primary mx-3">Load saved session</button>
<button type="button" id="load-backup" class="btn btn-primary mx-3">Load previous automatic backup</button>
</div>
</form>
</div>
</div>
<script>
$(document).ready(function(){
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
$('#load-backup').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('load_backup_test', {part_key: $("#participant").val(), test_name: "click_test"});
return false;
})
$('#load-saved').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('load_session', {part_key: $("#participant").val(), test_name: "click_test"});
return false;
})
$('#start_test').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('start_test', {part_key: $("#participant").val(), test_name: "click_test"});
return false;
})
socket.on('participant_start_click_test', function(msg) {
window.location.href = '/click/clinician/run';
});
socket.on('save-dialog-resp', function(msg) {
// Set form test to filepath returned by dialog
document.getElementById("save-dir").value = msg.data
});
socket.on('click-dialog-resp', function(msg) {
// Set form test to filepath returned by dialog
document.getElementById("da-dir").value = msg.data
});
});
</script>
{% endblock %}
+22
View File
@@ -0,0 +1,22 @@
{% extends 'index.html' %}
{% block content %}
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class="card">
<div id="main-div" class="card-body">
<div class="d-flex justify-content-center mt-2" role="group">
<button type="button" id="mat-save" class="btn btn-primary mx-3">Save and finish test</button>
</div>
</div>
</div>
<script>
$(document).ready(function(){
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
$('#mat-save').click(function(event) {
socket.emit("finalise_results")
window.location.href = '/home';
});
});
</script>
{% endblock %}
+39
View File
@@ -0,0 +1,39 @@
{% extends 'index.html' %}
{% block content %}
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class="card">
<div id="main-div" class="card-body">
<div class="d-flex justify-content-center mt-2" role="group">
<button type="button" id="test_save" class="btn btn-primary mx-3">Save test state</button>
<button type="button" id="test_finish" class="btn btn-primary mx-3">Finish test</button>
</div>
</div>
</div>
<script>
$(document).ready(function(){
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
// Check if matrix stimulus is currently being processed
waitingDialog.show('Generating stimulus');
socket.on('test_ready', function(msg) {
waitingDialog.hide();
});
$('#test_save').click(function(event) {
socket.emit("open_save_file_dialog")
});
$('#test_finish').click(function(event) {
socket.emit("finish_test")
});
// Catch message when asynchronous process is complete
socket.on('processing-complete', function(msg) {
alert("Stimulus processing complete!")
window.location.href = '/da/clinician/complete';
});
});
</script>
{% endblock %}
+9
View File
@@ -0,0 +1,9 @@
{% extends 'participant_index.html' %}
{% block content %}
<div class="card">
<div class="card-body">
<h1>Test complete!</h1>
<p>Please wait for the experimenter to give further instructions...</p>
</div>
</div>
{% endblock %}
+93
View File
@@ -0,0 +1,93 @@
{% extends 'participant_index.html' %}
{% block content %}
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div id="main-div" class="outer">
<div id="overlay">
<span id="overlay_crosshair">&#10010;</span>
</div>
<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>
<script>
$(document).ready(function(){
jQuery.expr[':'].contains = function(a, i, m) {
return jQuery(a).text().toUpperCase()
.indexOf(m[3].toUpperCase()) >= 0;
};
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
var loading = true;
var part_ready = false;
waitingDialog.show('Loading... Please wait');
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('test_ready', function(msg) {
loading = false;
waitingDialog.hide();
});
socket.on('stim_playing', function(msg) {
on()
});
socket.on('stim_done', function(msg) {
off()
});
function on() {
$("#overlay").css("display", "table-cell");
$("#overlay_crosshair").css("display", "inline");
$("#overlay").fadeIn();
$("#overlay_crosshair").fadeIn();
}
function off() {
$("#overlay").fadeOut();
}
socket.on('processing-complete', function(msg) {
// Re-enable all inputs
$('#main-div').find('input, textarea, button, select').removeAttr('disabled');
alert("/da/ stimulus processing complete!")
window.location.href = '/da/complete';
});
});
</script>
{% endblock %}
+58
View File
@@ -0,0 +1,58 @@
{% extends 'index.html' %}
{% block content %}
<div class="card">
<div id="main-div" class="card-body">
<form action="{{ url_for('matDecStim') }}" method="POST">
<div class="form-group">
<select class="form-control" name="participant" id="participant">
<option>--</option>
{% for p in part_keys %}
<option>{{ p }}</option>
{% endfor %}
</select>
</div>
<div class="form-group d-flex justify-content-center">
<button type="button" id="start_da_test" class="btn btn-primary mx-3">Start data collection</button>
<button type="button" id="load-saved" class="btn btn-primary mx-3">Load saved session</button>
<button type="button" id="load-backup" class="btn btn-primary mx-3">Load previous automatic backup</button>
</div>
</form>
</div>
</div>
<script>
$(document).ready(function(){
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
$('#load-backup').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('load_backup_test', {part_key: $("#participant").val(), test_name: "da_test"});
return false;
})
$('#load-saved').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('load_session', {part_key: $("#participant").val(), test_name: "da_test"});
return false;
})
$('#start_da_test').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('start_test', {part_key: $("#participant").val(), test_name: "da_test"});
return false;
})
socket.on('participant_start_da_test', function(msg) {
window.location.href = '/da/clinician/run';
});
socket.on('save-dialog-resp', function(msg) {
// Set form test to filepath returned by dialog
document.getElementById("save-dir").value = msg.data
});
socket.on('da-dialog-resp', function(msg) {
// Set form test to filepath returned by dialog
document.getElementById("da-dir").value = msg.data
});
});
</script>
{% endblock %}
+38 -7
View File
@@ -17,11 +17,29 @@
<input type="text" id="behav_res" name='behav_res' value="./Matrix_test_results.pkl" style="width:85%"></input>
<button type="button" id="behav_res_button" class="btn btn-primary">Browse...</button>
</div>
<div class="form-group d-flex justify-content-center">
<button type="button" id="start_eeg_train" class="btn btn-primary mx-3">Start training data collection</button>
<button type="button" id="start_eeg_test" class="btn btn-primary mx-3">Start test data collection</button>
<button type="button" id="load-saved" class="btn btn-primary mx-3">Load saved session</button>
<button type="button" id="load-backup" class="btn btn-primary mx-3">Load previous automatic backup</button>
<div class="form-group container-fluid">
<div class="row">
<div class="col text-center mb-3">
<button type="button" id="start_eeg_train" class="btn btn-primary mx-3">Start training data collection</button>
</div>
<div class="col text-center mb-3">
<button type="button" id="load_train_saved" class="btn btn-primary mx-3">Load saved session</button>
</div>
<div class="col text-center mb-3">
<button type="button" id="load_train_backup" class="btn btn-primary mx-3">Load previous automatic backup</button>
</div>
</div>
<div class="row">
<div class="col text-center mb-3">
<button type="button" id="start_eeg_test" class="btn btn-primary mx-3">Start test data collection</button>
</div>
<div class="col text-center mb-3">
<button type="button" id="load_test_saved" class="btn btn-primary mx-3">Load saved session</button>
</div>
<div class="col text-center mb-3">
<button type="button" id="load_test_backup" class="btn btn-primary mx-3">Load previous automatic backup</button>
</div>
</div>
</div>
</form>
</div>
@@ -31,21 +49,34 @@
// Initialise socketio with a namespace called "main"
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + '/main');
$('#load-backup').click(function(event) {
$('#load_test_backup').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('load_backup_test', {part_key: $("#participant").val(), test_name: "eeg_test"});
return false;
})
$('#load-saved').click(function(event) {
$('#load_test_saved').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('load_session', {part_key: $("#participant").val(), test_name: "eeg_test"});
return false;
})
$('#load_train_backup').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('load_backup_test', {part_key: $("#participant").val(), test_name: "eeg_train"});
return false;
})
$('#load_train_saved').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('load_session', {part_key: $("#participant").val(), test_name: "eeg_train"});
return false;
})
$('#start_eeg_train').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('start_test', {part_key: $("#participant").val(), test_name: "eeg_train"});
return false;
})
$('#start_eeg_test').click(function(event) {
// Send message to call stimulus generation function in Python
socket.emit('start_test', {part_key: $("#participant").val(), test_name: "eeg_test"});
+2 -1
View File
@@ -87,7 +87,8 @@
<a class="dropdown-item" href="/tympanometry">Tympanometry</a>
<a class="dropdown-item" href="/matrix_test">Behavioral Matrix Test</a>
<a class="dropdown-item" href="/eeg">Matrix EEG recording</a>
<a class="dropdown-item" href="/da/setup">/da/ eeg recording</a>
<a class="dropdown-item" href="/da/setup">/da/ EEG recording</a>
<a class="dropdown-item" href="/click/setup">Click EEG recording</a>
</div>
</li>
</ul>
+3 -1
View File
@@ -66,7 +66,9 @@
var test_locs = {
'mat_test': "/matrix_test/run",
'eeg_test': "/eeg/test/run"
'eeg_test': "/eeg/test/run",
'da_test': "/da/run",
'click_test': "/click/run"
}
socket.on('participant_start', function(msg) {
+273
View File
@@ -0,0 +1,273 @@
import pdb
import os
import sounddevice as sd
import dill
from threading import Thread, Event
from config import socketio
from WavPlayer import WavPlayer
def run_test_thread(name, thread_type, sessionFilepath=None, participant=None, **kwargs):
thread_name = '{}TestThread'.format(name)
if thread_name in globals():
thread = globals()[thread_name]
if thread.isAlive() and isinstance(thread, thread_type):
daTestThread.join()
thread = thread_type(socketio=socketio, sessionFilepath=sessionFilepath,
participant=participant, **kwargs)
thread.start()
def set_trace():
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
log = logging.getLogger('engineio')
log.setLevel(logging.ERROR)
pdb.set_trace()
class BaseThread(Thread):
'''
Thread for running server side matrix test operations
'''
def __init__(self, test_name, sessionFilepath=None,
socketio=None, participant=None, **kwargs):
super(BaseThread, self).__init__()
self.participant=participant
self.socketio = socketio
self.test_name = test_name
self.pageLoaded = False
self.clinPageLoaded = False
self.partPageLoaded = False
self.finishTest = False
self.partReady = False
self.newResp = False
self.finalised = False
# Define variables to be saved in state files. Should be implemented in
# derived class
self.toSave = None
self.toFinalise = None
self.wavThread = None
# Attach handler methods to socketio messages
self.socketio.on_event('page_loaded', self.setPageLoaded, namespace='/main')
self.socketio.on_event('part_ready', self.setPartReady, namespace='/main')
self.socketio.on_event('finalise_results', self.finaliseResults, namespace='/main')
self.socketio.on_event('finish_test', self.finishTestEarly, 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._stopevent = Event()
# Attach messages from gui to class methods
folder = self.participant.data_paths[test_name]
self.backupFilepath=os.path.join(folder, '{}_state.pkl'.format(test_name))
# If loading session from file, load session variables from the file
if sessionFilepath:
self.loadState(sessionFilepath)
else:
# Preload audio at start of the test
self.loadStimulus()
self.dev_mode = False
def play_wav(self, wav_file, stop_string):
self.wavThread = WavPlayer(wav_file, socketio=socketio, stop_string=stop_string)
self.wavThread.run()
def testLoop(self):
'''
Main loop
'''
raise NotImplemented("Test loop code should not be called from the base "
"class. This should be implemented in the derived "
"test class")
def displayInstructions(self):
'''
Emit signal to display test instructions
'''
self.socketio.emit('display_instructions', data=self.test_name, namespace='/main')
def finishTestEarly(self):
'''
Set variables to finish the test as soon as possible and exit the
thread
'''
self.finishTest = True
self.wavThread._stopevent.set()
print(self.finishTest)
def join(self, timeout=None):
'''
Stop the thread.
'''
self._stopevent.set()
Thread.join(self, timeout)
def waitForResponse(self):
'''
Test waits for a response from the participant. To use his function
correctly, self.newResp must be set to True via a socketio handler in
order to continue the test.
'''
while not self.newResp and not self._stopevent.isSet() and not self.finishTest:
self._stopevent.wait(0.2)
return
def waitForPageLoad(self):
'''
Wait for page to load and poll for a socketio message to be sent
informing the thread of a successfully loaded page
'''
while not self.pageLoaded and not self._stopevent.isSet():
self.socketio.emit("check-loaded", namespace='/main')
self._stopevent.wait(0.5)
self.pageLoaded = False
def waitForPartReady(self):
'''
Test waits for the participant to finish reading instructions. To use
this function correctly, self.partReady must be set to True via a socketio
handler in order to continue the test.
'''
while not self.partReady and not self._stopevent.isSet() and not self.finishTest:
self._stopevent.wait(0.5)
self.partReady = False
return
def waitForFinalise(self):
'''
Wait for results to be finalised by socketio handler
'''
while not self.finalised and not self._stopevent.isSet() and not self.finishTest:
print(self.finishTest)
self._stopevent.wait(0.2)
return
def finaliseResults(self):
saveDict = {k:self.__dict__[k] for k in self.toSave}
self.participant[self.test_name].update(saveDict)
self.participant.save(self.test_name)
backup_path = os.path.join(self.participant.data_paths[self.test_name],
'finalised_backup.pkl')
copy2(self.backupFilepath, backup_path)
self.finalised = True
def playStimulusWav(self, wav_file, replay=False):
'''
output audio stimulus from wav file
'''
self.newResp = False
self.socketio.emit("{}_stim_playing".format(self.test_name), namespace="/main")
if not self.dev_mode:
play_wav(wav_file)
else:
play_wav('./test.wav')
self.socketio.emit("{}_stim_done".format(test_name), namespace="/main")
def playStimulus(self, y, fs):
'''
Output audio stimulus from numpy array
'''
self.newResp = False
self.socketio.emit("stim_playing", namespace="/main")
# Play audio
sd.play(y, fs, blocking=True)
self.socketio.emit("stim_done", namespace="/main")
def loadStimulus(self):
'''
Method for preloading stimulus before the start of the test. Should be
implemented in child class.
'''
raise NotImplemented("loadStimulus code should not be called from the base "
"class. This should be implemented in the derived "
"test class")
def unsetPageLoaded(self):
'''
For use in the main loop when a new page is loaded for
participant/clinician
'''
self.pageLoaded = False
self.partPageLoaded = False
self.clinPageLoaded = False
def setPartReady(self):
'''
Set variables indicating that the participant is ready to proceed with
the test
'''
self.partReady = True
def setPageLoaded(self, msg):
'''
Indicate that either the clinician or participant page has been loaded
'''
if msg['data'] == "clinician":
self.clinPageLoaded = True
else:
self.partPageLoaded = True
self.pageLoaded = self.clinPageLoaded and self.partPageLoaded
def saveState(self, out=None):
'''
Save the state of the thread to a pickle file
'''
if not out:
out = "{}_state.pkl".format(self.test_name)
saveDict = {k:self.__dict__[k] for k in self.toSave}
with open(out, 'wb') as f:
dill.dump(saveDict, f)
def manualSave(self, msg):
'''
Get and store participant response for current trial
'''
filepath = msg['data']
self.saveState(out=filepath)
def loadStateSocketHandle(self, msg):
'''
Catch messages indicating that the thread should be loaded from a
previously generated pickle file
'''
filepath = msg['data']
self.loadState(filepath)
def loadState(self, filepath):
'''
Restore thread state from a saved session filepath
'''
with open(filepath, 'rb') as f:
self.__dict__.update(dill.load(f))
def run(self):
'''
This function is called when the thread starts
'''
return self.testLoop()