diff --git a/.gitignore b/.gitignore index 0b2774ca262faf2698bbeb5ef581810d019669cb..98eb93f7bcb2a5079aefd556a347716495a832a1 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ _region_.* # do not include the data that is generated in the tutorial data_*.csv + +# or the solution +signal_detection.solution.ipynb diff --git a/ENVIRONMENT.yml b/ENVIRONMENT.yml index 9db30640b73eaf8283ce6721f7967f6580b91e84..ebe709d30be5ffed1b28e4ed5a32360e28cedcaa 100644 --- a/ENVIRONMENT.yml +++ b/ENVIRONMENT.yml @@ -2,9 +2,10 @@ name: signal_detection channels: - conda-forge dependencies: -- python=3.6 +- python=3.9 - psychopy -- jupyter +- jupyterlab +- ipympl - pip - pip: - psychtoolbox @@ -12,3 +13,4 @@ dependencies: - pyo - pyparallel; platform_system != "Windows" - SoundFile; platform_system == "Windows" + - websocket_client diff --git a/README.md b/README.md index 70cfd5eebff2cea4641aaaee8318d66f58a747b8..aee178566cfba2c186795e36c444c35a100205a7 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ For this homework the most relevant sections are 1.1-1.3 (p. 3-15), 2.1-2.3 (p. ## Installation -You need python, psychopy and jupyter to run the tutorials. It's easiest if you install everything through `conda`. If you know what you're doing you can just follow the [official installation instructions for psychopy](https://www.psychopy.org/download.html). If you already have a working python and jupyter environment you might also be able to install things via `pip`. However, as psychopy seems to require python 3.6 and you don't want to mess with your existing (probably newer) python installation it's probably better to follow the `conda` instructions, which we have expanded a little bit on for your convenience. +You need python, psychopy and jupyter to run the tutorials. It's easiest if you install everything through `conda`. If you know what you're doing you can just follow the [official installation instructions for psychopy](https://www.psychopy.org/download.html). If you already have a working python and jupyter environment you might also be able to install things via `pip`. However, as psychopy seems to require python 3.8 and you don't want to mess with your existing (probably newer) python installation it's probably better to follow the `conda` instructions, which we have expanded a little bit on for your convenience. ### conda @@ -30,25 +30,32 @@ to install all the required packages. Then (still in the same directory) activat conda activate signal_detection ``` -start jupyter notebook +start jupyter lab ``` -jupyter notebook +jupyter lab ``` and click on `signal_detection_data_collection.ipynb` in the browser window that will open. Now you're ready to go. -Some people reported they had problems under Windows. They could install everything but apparently when running the code in the notebooks psychopy crashed. If that's your problem, try creating a new environment with the latest python version in conda (instead of 3.6 as described in the ENVIRONMENT.yml file) and install everything via `pip`. You can do this by +Some people reported they had problems under Windows. They could install everything but apparently when running the code in the notebooks psychopy crashed. If that's your problem, try creating a new environment with the latest python version in conda (instead of 3.8 as described in the ENVIRONMENT.yml file) and install everything via `pip`. You can do this by first deleting the old environment with ``` -conda env create -n signal_detection_new pip +conda env remove -n signal_detection + +``` + +(if that doesn't work it's probably because you need to deactivate the environment first with `conda deactivate`) and then installing everything from scratch with + +``` +conda env create -n signal_detection pip ``` which should also install all pip requirements if run in the right directory (otherwise run the pip-command from below in the directory). Then do ``` -conda activate signal_detection_new -jupyter notebook signal_detection_data_collection.ipynb +conda activate signal_detection +jupyter lab signal_detection_data_collection.ipynb ``` ### pip diff --git a/requirements.txt b/requirements.txt index 2afd195a1fa78d67967597b3d59e0d471993d4b1..2bfa2b024aa8c8c21a2a8047b6972f9b650ff585 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ imageio psychopy -jupyter +jupyterlab psychtoolbox pygame pyo pyparallel; platform_system != "Windows" SoundFile; platform_system == "Windows" +websocket_client diff --git a/signal_detection.py b/signal_detection.py index 197f921ad4ea39f76211a194c7f07c0f6bdbeb36..a8e2bb0d6bb8ceb093005b6a6df095fb8dd6aa73 100644 --- a/signal_detection.py +++ b/signal_detection.py @@ -1,12 +1,13 @@ from numpy import * import pandas as pd -pd.set_option('precision',2) +pd.set_option('display.precision',2) from os.path import isfile from imageio import imread from matplotlib.pyplot import * from scipy.optimize import minimize from scipy.stats import norm +from sys import argv import warnings warnings.filterwarnings('ignore') @@ -14,7 +15,6 @@ from psychopy import logging logging.console.setLevel(logging.CRITICAL) # set some global variables -DEBUG = False params = dict() params['num_of_trials'] = 50 # a block always consists of 50 trials params['fps'] = 60.0 # assume same frames per second for everyone @@ -73,7 +73,10 @@ def load_data(subject): data = pd.DataFrame(data) return data -def setup(intensity,task): +def setup(intensity,task,DEBUG=False): + from psychopy import prefs + # weirdly we need this on the mac so the screen doesn't freeze at the end: + prefs.general['audioLib'] = ['pyo'] from psychopy import core, visual, event, monitors if task == 'yes-no': text = 'Press y if you see an A and n otherwise.\nAny key to start (ESC to quit).' @@ -102,14 +105,14 @@ def setup(intensity,task): event.waitKeys() return win -def run_block(subject,intensity,p=0.5,num_of_blocks=1,task='yes-no'): - from psychopy import core, visual, event, monitors +def run_block(subject,intensity,p=0.5,num_of_blocks=1,task='yes-no',DEBUG=False): + from psychopy import core, visual, event, monitors, logging data = load_data(subject) if len(data)>0: block = data['block'].max()+1 else: block = 1 - win = setup(intensity,task) + win = setup(intensity,task,DEBUG) v = ((0,-5), (0,5), (0,0), (5,0), (-5,0)) fix = visual.ShapeStim(win, vertices=v, lineWidth=2, closeShape=False, lineColor="white") @@ -219,6 +222,7 @@ def run_block(subject,intensity,p=0.5,num_of_blocks=1,task='yes-no'): win.flip() event.waitKeys() # and quit + logging.flush() win.setMouseVisible(True) win.close() try: @@ -383,3 +387,21 @@ def interactive_sdt(): lam=widgets.FloatSlider(min=-3, max=7, step=0.1, value=1)) def update(d_prime, lam): plot_sdt(d_prime, lam) + +def main(subject,intensity): + data = run_block(subject=subject,intensity=intensity,DEBUG=False) + print(summarize(data,group='block').T) + +if __name__ == "__main__": + if len(argv) == 1: + subject = 'test' + intensity = 100 + main(subject,intensity) + elif len(argv) == 3: + subject = argv[1] + intensity = float(argv[2]) + main(subject,intensity) + else: + print("CALL WITH: python signal_detection.py SUBJECT INTENSITY") + print("SUBJECT: name for data files, data_SUBJECT.csv") + print("INTENSITY: number btw. -127 an 128, strength of A") diff --git a/signal_detection_data_collection.ipynb b/signal_detection_data_collection.ipynb index 60437020c1729b04e562caa7163bd838ae1c266b..dd8c2592e75bd12a3becb5b98ee503915d1c982d 100644 --- a/signal_detection_data_collection.ipynb +++ b/signal_detection_data_collection.ipynb @@ -67,7 +67,11 @@ "metadata": {}, "outputs": [], "source": [ - "data = run_block(subject='test',intensity=100) # run this cell by shift+enter" + "data = run_block(subject='test',intensity=100,DEBUG=True) # run this cell by shift+enter\n", + "# on some systems you might not be able to close the window after the experiment\n", + "# therefore try the first test run with DEBUG=True and then restart the kernel if necessary\n", + "# Note: you can also run this from the command line if your window doesn't close at the end:\n", + "# python signal_detection test 100" ] }, { @@ -310,7 +314,7 @@ "source": [ "First check the proportion correct column *pc*. This is your average performance over all the blocks you did. It's likely that even for the condition $p=0.5$ your performance was better than 75%. At least that was the case for me. Perhaps that's because I have become better at the task due to learning. Or perhaps it's just random variaton. Doesn't really matter. In any case we'll assume that for this experiment your sensitivity stayed relatively constant -- since the stimuli stayed constant. We'll get back to the question how to interpret the *pc* column in a second.\n", "\n", - "Before that, check that with an increase in $p$ your false alarm rate $f$ and your hit rate $h$ go up. As they should! You have adopted your response bias to the prior probability of there being a signal. A good plot for inspecting the raw data of this experiment is the so-called *receicer operating characteristic* or *ROC*, for short. It's a plot of the hit rate as a function of the false alarm rate." + "Before that, check that with an increase in $p$ your false alarm rate $f$ and your hit rate $h$ go up. As they should! You have adopted your response bias to the prior probability of there being a signal. A good plot for inspecting the raw data of this experiment is the so-called *receiver operating characteristic* or *ROC*, for short. It's a plot of the hit rate as a function of the false alarm rate." ] }, { @@ -392,7 +396,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -406,7 +410,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.9.12" } }, "nbformat": 4,